mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-16 11:39:18 +00:00
Merge branch 'develop' into fix-voucher-types
This commit is contained in:
11
.github/helper/install.sh
vendored
11
.github/helper/install.sh
vendored
@@ -12,9 +12,16 @@ pip install frappe-bench
|
|||||||
|
|
||||||
githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}}
|
githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}}
|
||||||
frappeuser=${FRAPPE_USER:-"frappe"}
|
frappeuser=${FRAPPE_USER:-"frappe"}
|
||||||
frappebranch=${FRAPPE_BRANCH:-$githubbranch}
|
frappecommitish=${FRAPPE_BRANCH:-$githubbranch}
|
||||||
|
|
||||||
|
mkdir frappe
|
||||||
|
pushd frappe
|
||||||
|
git init
|
||||||
|
git remote add origin "https://github.com/${frappeuser}/frappe"
|
||||||
|
git fetch origin "${frappecommitish}" --depth 1
|
||||||
|
git checkout FETCH_HEAD
|
||||||
|
popd
|
||||||
|
|
||||||
git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
|
|
||||||
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
|
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
|
||||||
|
|
||||||
mkdir ~/frappe-bench/sites/test_site
|
mkdir ~/frappe-bench/sites/test_site
|
||||||
|
|||||||
3
.github/workflows/patch.yml
vendored
3
.github/workflows/patch.yml
vendored
@@ -137,7 +137,8 @@ jobs:
|
|||||||
update_to_version 15
|
update_to_version 15
|
||||||
|
|
||||||
echo "Updating to latest version"
|
echo "Updating to latest version"
|
||||||
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
git -C "apps/frappe" fetch --depth 1 upstream "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
||||||
|
git -C "apps/frappe" checkout -q -f FETCH_HEAD
|
||||||
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
|
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
|
||||||
|
|
||||||
pgrep honcho | xargs kill
|
pgrep honcho | xargs kill
|
||||||
|
|||||||
130
.github/workflows/run-indinvidual-tests.yml
vendored
Normal file
130
.github/workflows/run-indinvidual-tests.yml
vendored
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
name: Individual
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: server-individual-tests-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
discover:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||||
|
steps:
|
||||||
|
- name: Clone
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- id: set-matrix
|
||||||
|
run: |
|
||||||
|
# Use grep and find to get the list of test files
|
||||||
|
matrix=$(find . -path '*/doctype/*/test_*.py' | xargs grep -l 'def test_' | awk '{
|
||||||
|
# Remove ./ prefix, file extension, and replace / with .
|
||||||
|
gsub(/^\.\//, "", $0)
|
||||||
|
gsub(/\.py$/, "", $0)
|
||||||
|
gsub(/\//, ".", $0)
|
||||||
|
# Add to array
|
||||||
|
tests[NR] = $0
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
# Start JSON array
|
||||||
|
printf "{\n \"include\": [\n"
|
||||||
|
# Loop through array and create JSON objects
|
||||||
|
for (i=1; i<=NR; i++) {
|
||||||
|
printf " {\"test\": \"%s\"}", tests[i]
|
||||||
|
if (i < NR) printf ","
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
|
# Close JSON array
|
||||||
|
printf " ]\n}"
|
||||||
|
}')
|
||||||
|
|
||||||
|
# Output the matrix
|
||||||
|
echo "matrix=$(echo "$matrix" | jq -c)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# For debugging (optional)
|
||||||
|
echo "Generated matrix:"
|
||||||
|
echo "$matrix"
|
||||||
|
test:
|
||||||
|
needs: discover
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 60
|
||||||
|
env:
|
||||||
|
NODE_ENV: "production"
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix: ${{fromJson(needs.discover.outputs.matrix)}}
|
||||||
|
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mariadb:10.6
|
||||||
|
env:
|
||||||
|
MARIADB_ROOT_PASSWORD: 'root'
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
|
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Add to Hosts
|
||||||
|
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
|
- name: Cache pip
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v4
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||||
|
${{ runner.os }}-build-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Get yarn cache directory path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
id: yarn-cache
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
|
env:
|
||||||
|
DB: mariadb
|
||||||
|
TYPE: server
|
||||||
|
FRAPPE_USER: ${{ github.event.inputs.user }}
|
||||||
|
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: 'cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --module ${{ matrix.test }}'
|
||||||
6
.github/workflows/server-tests-mariadb.yml
vendored
6
.github/workflows/server-tests-mariadb.yml
vendored
@@ -1,6 +1,8 @@
|
|||||||
name: Server (Mariadb)
|
name: Server (Mariadb)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
repository_dispatch:
|
||||||
|
types: [frappe-framework-change]
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.js'
|
- '**.js'
|
||||||
@@ -117,10 +119,10 @@ jobs:
|
|||||||
DB: mariadb
|
DB: mariadb
|
||||||
TYPE: server
|
TYPE: server
|
||||||
FRAPPE_USER: ${{ github.event.inputs.user }}
|
FRAPPE_USER: ${{ github.event.inputs.user }}
|
||||||
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
|
FRAPPE_BRANCH: ${{ github.event.client_payload.sha || github.event.inputs.branch }}
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 4 --build-number ${{ matrix.container }}'
|
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds ${{ strategy.job-total }} --build-number ${{ matrix.container }}'
|
||||||
env:
|
env:
|
||||||
TYPE: server
|
TYPE: server
|
||||||
CAPTURE_COVERAGE: ${{ github.event_name != 'pull_request' }}
|
CAPTURE_COVERAGE: ${{ github.event_name != 'pull_request' }}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ def build_conditions(process_type, account, company):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if account:
|
if account:
|
||||||
conditions += f"AND {deferred_account}='{account}'"
|
conditions += f"AND {deferred_account}={frappe.db.escape(account)}"
|
||||||
elif company:
|
elif company:
|
||||||
conditions += f"AND p.company = {frappe.db.escape(company)}"
|
conditions += f"AND p.company = {frappe.db.escape(company)}"
|
||||||
|
|
||||||
|
|||||||
@@ -102,14 +102,12 @@ class Account(NestedSet):
|
|||||||
self.name = get_autoname_with_number(self.account_number, self.account_name, self.company)
|
self.name = get_autoname_with_number(self.account_number, self.account_name, self.company)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
from erpnext.accounts.utils import validate_field_number
|
|
||||||
|
|
||||||
if frappe.local.flags.allow_unverified_charts:
|
if frappe.local.flags.allow_unverified_charts:
|
||||||
return
|
return
|
||||||
self.validate_parent()
|
self.validate_parent()
|
||||||
self.validate_parent_child_account_type()
|
self.validate_parent_child_account_type()
|
||||||
self.validate_root_details()
|
self.validate_root_details()
|
||||||
validate_field_number("Account", self.name, self.account_number, self.company, "account_number")
|
self.validate_account_number()
|
||||||
self.validate_group_or_ledger()
|
self.validate_group_or_ledger()
|
||||||
self.set_root_and_report_type()
|
self.set_root_and_report_type()
|
||||||
self.validate_mandatory()
|
self.validate_mandatory()
|
||||||
@@ -310,6 +308,22 @@ class Account(NestedSet):
|
|||||||
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
||||||
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
|
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
|
||||||
|
|
||||||
|
def validate_account_number(self, account_number=None):
|
||||||
|
if not account_number:
|
||||||
|
account_number = self.account_number
|
||||||
|
|
||||||
|
if account_number:
|
||||||
|
account_with_same_number = frappe.db.get_value(
|
||||||
|
"Account",
|
||||||
|
{"account_number": account_number, "company": self.company, "name": ["!=", self.name]},
|
||||||
|
)
|
||||||
|
if account_with_same_number:
|
||||||
|
frappe.throw(
|
||||||
|
_("Account Number {0} already used in account {1}").format(
|
||||||
|
account_number, account_with_same_number
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
|
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
|
||||||
for company in descendants:
|
for company in descendants:
|
||||||
company_bold = frappe.bold(company)
|
company_bold = frappe.bold(company)
|
||||||
@@ -463,19 +477,6 @@ def get_account_autoname(account_number, account_name, company):
|
|||||||
return " - ".join(parts)
|
return " - ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
def validate_account_number(name, account_number, company):
|
|
||||||
if account_number:
|
|
||||||
account_with_same_number = frappe.db.get_value(
|
|
||||||
"Account", {"account_number": account_number, "company": company, "name": ["!=", name]}
|
|
||||||
)
|
|
||||||
if account_with_same_number:
|
|
||||||
frappe.throw(
|
|
||||||
_("Account Number {0} already used in account {1}").format(
|
|
||||||
account_number, account_with_same_number
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_account_number(name, account_name, account_number=None, from_descendant=False):
|
def update_account_number(name, account_name, account_number=None, from_descendant=False):
|
||||||
account = frappe.get_cached_doc("Account", name)
|
account = frappe.get_cached_doc("Account", name)
|
||||||
@@ -516,7 +517,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
|
|||||||
|
|
||||||
frappe.throw(message, title=_("Rename Not Allowed"))
|
frappe.throw(message, title=_("Rename Not Allowed"))
|
||||||
|
|
||||||
validate_account_number(name, account_number, account.company)
|
account.validate_account_number(account_number)
|
||||||
if account_number:
|
if account_number:
|
||||||
frappe.db.set_value("Account", name, "account_number", account_number.strip())
|
frappe.db.set_value("Account", name, "account_number", account_number.strip())
|
||||||
else:
|
else:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,9 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.test_runner import make_test_records
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.account import (
|
from erpnext.accounts.doctype.account.account import (
|
||||||
@@ -15,10 +13,10 @@ from erpnext.accounts.doctype.account.account import (
|
|||||||
)
|
)
|
||||||
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account
|
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account
|
||||||
|
|
||||||
test_dependencies = ["Company"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Company"]
|
||||||
|
|
||||||
|
|
||||||
class TestAccount(unittest.TestCase):
|
class TestAccount(IntegrationTestCase):
|
||||||
def test_rename_account(self):
|
def test_rename_account(self):
|
||||||
if not frappe.db.exists("Account", "1210 - Debtors - _TC"):
|
if not frappe.db.exists("Account", "1210 - Debtors - _TC"):
|
||||||
acc = frappe.new_doc("Account")
|
acc = frappe.new_doc("Account")
|
||||||
@@ -203,8 +201,6 @@ class TestAccount(unittest.TestCase):
|
|||||||
In a parent->child company setup, child should inherit parent account currency if explicitly specified.
|
In a parent->child company setup, child should inherit parent account currency if explicitly specified.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
make_test_records("Company")
|
|
||||||
|
|
||||||
frappe.local.flags.pop("ignore_root_company_validation", None)
|
frappe.local.flags.pop("ignore_root_company_validation", None)
|
||||||
|
|
||||||
def create_bank_account():
|
def create_bank_account():
|
||||||
@@ -328,7 +324,7 @@ class TestAccount(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def _make_test_records(verbose=None):
|
def _make_test_records(verbose=None):
|
||||||
from frappe.test_runner import make_test_objects
|
from frappe.tests.utils import make_test_objects
|
||||||
|
|
||||||
accounts = [
|
accounts = [
|
||||||
# [account_name, parent_account, is_group]
|
# [account_name, parent_account, is_group]
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"doctype": "Account",
|
|
||||||
"name": "_Test Account 1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
3
erpnext/accounts/doctype/account/test_records.toml
Normal file
3
erpnext/accounts/doctype/account/test_records.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[[Account]]
|
||||||
|
name = "_Test Account 1"
|
||||||
|
|
||||||
@@ -113,9 +113,9 @@ def get_previous_closing_entries(company, closing_date, accounting_dimensions):
|
|||||||
entries = []
|
entries = []
|
||||||
last_period_closing_voucher = frappe.db.get_all(
|
last_period_closing_voucher = frappe.db.get_all(
|
||||||
"Period Closing Voucher",
|
"Period Closing Voucher",
|
||||||
filters={"docstatus": 1, "company": company, "posting_date": ("<", closing_date)},
|
filters={"docstatus": 1, "company": company, "period_end_date": ("<", closing_date)},
|
||||||
fields=["name"],
|
fields=["name"],
|
||||||
order_by="posting_date desc",
|
order_by="period_end_date desc",
|
||||||
limit=1,
|
limit=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,17 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestAccountClosingBalance(FrappeTestCase):
|
class UnitTestAccountClosingBalance(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for AccountClosingBalance.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestAccountClosingBalance(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ frappe.ui.form.on("Accounting Dimension", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
label: function (frm) {
|
label: function (frm) {
|
||||||
frm.set_value("fieldname", frappe.model.scrub(frm.doc.label));
|
frm.set_value("fieldname", frm.doc.label.replace(/ /g, "_").replace(/-/g, "_").toLowerCase());
|
||||||
},
|
},
|
||||||
|
|
||||||
document_type: function (frm) {
|
document_type: function (frm) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import json
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, scrub
|
from frappe import _, scrub
|
||||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||||
|
from frappe.database.schema import validate_column_name
|
||||||
from frappe.model import core_doctypes_list
|
from frappe.model import core_doctypes_list
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cstr
|
from frappe.utils import cstr
|
||||||
@@ -60,6 +61,7 @@ class AccountingDimension(Document):
|
|||||||
if not self.is_new():
|
if not self.is_new():
|
||||||
self.validate_document_type_change()
|
self.validate_document_type_change()
|
||||||
|
|
||||||
|
validate_column_name(self.fieldname)
|
||||||
self.validate_dimension_defaults()
|
self.validate_dimension_defaults()
|
||||||
|
|
||||||
def validate_document_type_change(self):
|
def validate_document_type_change(self):
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
test_dependencies = ["Cost Center", "Location", "Warehouse", "Department"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Cost Center", "Location", "Warehouse", "Department"]
|
||||||
|
|
||||||
|
|
||||||
class TestAccountingDimension(unittest.TestCase):
|
class TestAccountingDimension(IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
create_dimension()
|
create_dimension()
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension imp
|
|||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||||
|
|
||||||
test_dependencies = ["Location", "Cost Center", "Department"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Location", "Cost Center", "Department"]
|
||||||
|
|
||||||
|
|
||||||
class TestAccountingDimensionFilter(unittest.TestCase):
|
class TestAccountingDimensionFilter(unittest.TestCase):
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ def validate_accounting_period_on_doc_save(doc, method=None):
|
|||||||
date = doc.available_for_use_date
|
date = doc.available_for_use_date
|
||||||
elif doc.doctype == "Asset Repair":
|
elif doc.doctype == "Asset Repair":
|
||||||
date = doc.completion_date
|
date = doc.completion_date
|
||||||
|
elif doc.doctype == "Period Closing Voucher":
|
||||||
|
date = doc.period_end_date
|
||||||
else:
|
else:
|
||||||
date = doc.posting_date
|
date = doc.posting_date
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import add_months, nowdate
|
from frappe.utils import add_months, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_period.accounting_period import (
|
from erpnext.accounts.doctype.accounting_period.accounting_period import (
|
||||||
@@ -12,10 +12,10 @@ from erpnext.accounts.doctype.accounting_period.accounting_period import (
|
|||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"]
|
||||||
|
|
||||||
|
|
||||||
class TestAccountingPeriod(unittest.TestCase):
|
class TestAccountingPeriod(IntegrationTestCase):
|
||||||
def test_overlap(self):
|
def test_overlap(self):
|
||||||
ap1 = create_accounting_period(
|
ap1 = create_accounting_period(
|
||||||
start_date="2018-04-01", end_date="2018-06-30", company="Wind Power LLC"
|
start_date="2018-04-01", end_date="2018-06-30", company="Wind Power LLC"
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestAccountsSettings(unittest.TestCase):
|
class TestAccountsSettings(IntegrationTestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# Just in case `save` method succeeds, we need to take things back to default so that other tests
|
# Just in case `save` method succeeds, we need to take things back to default so that other tests
|
||||||
# don't break
|
# don't break
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Advance Payment Ledger Entry", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2024-10-16 16:57:12.085072",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"company",
|
||||||
|
"voucher_type",
|
||||||
|
"voucher_no",
|
||||||
|
"against_voucher_type",
|
||||||
|
"against_voucher_no",
|
||||||
|
"amount",
|
||||||
|
"currency",
|
||||||
|
"event"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Voucher Type",
|
||||||
|
"options": "DocType",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Voucher No",
|
||||||
|
"options": "voucher_type",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_voucher_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Against Voucher Type",
|
||||||
|
"options": "DocType",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Against Voucher No",
|
||||||
|
"options": "against_voucher_type",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "event",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Event",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"in_create": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2024-11-05 10:31:28.736671",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Advance Payment Ledger Entry",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts User",
|
||||||
|
"share": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts Manager",
|
||||||
|
"share": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Auditor",
|
||||||
|
"share": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "creation",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class AdvancePaymentLedgerEntry(Document):
|
||||||
|
# begin: auto-generated types
|
||||||
|
# This code is auto-generated. Do not modify anything in this block.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from frappe.types import DF
|
||||||
|
|
||||||
|
against_voucher_no: DF.DynamicLink | None
|
||||||
|
against_voucher_type: DF.Link | None
|
||||||
|
amount: DF.Currency
|
||||||
|
company: DF.Link | None
|
||||||
|
currency: DF.Link | None
|
||||||
|
event: DF.Data | None
|
||||||
|
voucher_no: DF.DynamicLink | None
|
||||||
|
voucher_type: DF.Link | None
|
||||||
|
# end: auto-generated types
|
||||||
|
|
||||||
|
pass
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
|
from frappe.utils import nowdate, today
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||||
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
|
||||||
|
# On IntegrationTestCase, the doctype test records and all
|
||||||
|
# link-field test record depdendencies are recursively loaded
|
||||||
|
# Use these module variables to add/remove to/from that list
|
||||||
|
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||||
|
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||||
|
|
||||||
|
|
||||||
|
class TestAdvancePaymentLedgerEntry(AccountsTestMixin, IntegrationTestCase):
|
||||||
|
"""
|
||||||
|
Integration tests for AdvancePaymentLedgerEntry.
|
||||||
|
Use this class for testing interactions between multiple components.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.create_company()
|
||||||
|
self.create_usd_receivable_account()
|
||||||
|
self.create_usd_payable_account()
|
||||||
|
self.create_item()
|
||||||
|
self.clear_old_entries()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def create_sales_order(self, qty=1, rate=100, currency="INR", do_not_submit=False):
|
||||||
|
"""
|
||||||
|
Helper method
|
||||||
|
"""
|
||||||
|
so = make_sales_order(
|
||||||
|
company=self.company,
|
||||||
|
customer=self.customer,
|
||||||
|
currency=currency,
|
||||||
|
item=self.item,
|
||||||
|
qty=qty,
|
||||||
|
rate=rate,
|
||||||
|
transaction_date=today(),
|
||||||
|
do_not_submit=do_not_submit,
|
||||||
|
)
|
||||||
|
return so
|
||||||
|
|
||||||
|
def create_purchase_order(self, qty=1, rate=100, currency="INR", do_not_submit=False):
|
||||||
|
"""
|
||||||
|
Helper method
|
||||||
|
"""
|
||||||
|
po = create_purchase_order(
|
||||||
|
company=self.company,
|
||||||
|
customer=self.supplier,
|
||||||
|
currency=currency,
|
||||||
|
item=self.item,
|
||||||
|
qty=qty,
|
||||||
|
rate=rate,
|
||||||
|
transaction_date=today(),
|
||||||
|
do_not_submit=do_not_submit,
|
||||||
|
)
|
||||||
|
return po
|
||||||
|
|
||||||
|
def test_so_advance_paid_and_currency_with_payment(self):
|
||||||
|
self.create_customer("_Test USD Customer", "USD")
|
||||||
|
|
||||||
|
so = self.create_sales_order(currency="USD", do_not_submit=True)
|
||||||
|
so.conversion_rate = 80
|
||||||
|
so.submit()
|
||||||
|
|
||||||
|
pe_exchange_rate = 85
|
||||||
|
pe = get_payment_entry(so.doctype, so.name, bank_account=self.cash)
|
||||||
|
pe.reference_no = "1"
|
||||||
|
pe.reference_date = nowdate()
|
||||||
|
pe.paid_from = self.debtors_usd
|
||||||
|
pe.paid_from_account_currency = "USD"
|
||||||
|
pe.source_exchange_rate = pe_exchange_rate
|
||||||
|
pe.paid_amount = so.grand_total
|
||||||
|
pe.received_amount = pe_exchange_rate * pe.paid_amount
|
||||||
|
pe.references[0].outstanding_amount = 100
|
||||||
|
pe.references[0].total_amount = 100
|
||||||
|
pe.references[0].allocated_amount = 100
|
||||||
|
pe.save().submit()
|
||||||
|
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(so.advance_paid, 100)
|
||||||
|
self.assertEqual(so.party_account_currency, "USD")
|
||||||
|
|
||||||
|
# cancel advance payment
|
||||||
|
pe.reload()
|
||||||
|
pe.cancel()
|
||||||
|
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(so.advance_paid, 0)
|
||||||
|
self.assertEqual(so.party_account_currency, "USD")
|
||||||
|
|
||||||
|
def test_so_advance_paid_and_currency_with_journal(self):
|
||||||
|
self.create_customer("_Test USD Customer", "USD")
|
||||||
|
|
||||||
|
so = self.create_sales_order(currency="USD", do_not_submit=True)
|
||||||
|
so.conversion_rate = 80
|
||||||
|
so.submit()
|
||||||
|
|
||||||
|
je_exchange_rate = 85
|
||||||
|
je = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Journal Entry",
|
||||||
|
"company": self.company,
|
||||||
|
"voucher_type": "Journal Entry",
|
||||||
|
"posting_date": so.transaction_date,
|
||||||
|
"multi_currency": True,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"account": self.debtors_usd,
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": so.customer,
|
||||||
|
"credit": 8500,
|
||||||
|
"credit_in_account_currency": 100,
|
||||||
|
"is_advance": "Yes",
|
||||||
|
"reference_type": so.doctype,
|
||||||
|
"reference_name": so.name,
|
||||||
|
"exchange_rate": je_exchange_rate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account": self.cash,
|
||||||
|
"debit": 8500,
|
||||||
|
"debit_in_account_currency": 8500,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
je.save().submit()
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(so.advance_paid, 100)
|
||||||
|
self.assertEqual(so.party_account_currency, "USD")
|
||||||
|
|
||||||
|
# cancel advance payment
|
||||||
|
je.reload()
|
||||||
|
je.cancel()
|
||||||
|
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(so.advance_paid, 0)
|
||||||
|
self.assertEqual(so.party_account_currency, "USD")
|
||||||
|
|
||||||
|
def test_po_advance_paid_and_currency_with_payment(self):
|
||||||
|
self.create_supplier("_Test USD Supplier", "USD")
|
||||||
|
|
||||||
|
po = self.create_purchase_order(currency="USD", do_not_submit=True)
|
||||||
|
po.conversion_rate = 80
|
||||||
|
po.submit()
|
||||||
|
|
||||||
|
pe_exchange_rate = 85
|
||||||
|
pe = get_payment_entry(po.doctype, po.name, bank_account=self.cash)
|
||||||
|
pe.reference_no = "1"
|
||||||
|
pe.reference_date = nowdate()
|
||||||
|
pe.paid_to = self.creditors_usd
|
||||||
|
pe.paid_to_account_currency = "USD"
|
||||||
|
pe.target_exchange_rate = pe_exchange_rate
|
||||||
|
pe.received_amount = po.grand_total
|
||||||
|
pe.paid_amount = pe_exchange_rate * pe.received_amount
|
||||||
|
pe.references[0].outstanding_amount = 100
|
||||||
|
pe.references[0].total_amount = 100
|
||||||
|
pe.references[0].allocated_amount = 100
|
||||||
|
pe.save().submit()
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.advance_paid, 100)
|
||||||
|
self.assertEqual(po.party_account_currency, "USD")
|
||||||
|
|
||||||
|
# cancel advance payment
|
||||||
|
pe.reload()
|
||||||
|
pe.cancel()
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.advance_paid, 0)
|
||||||
|
self.assertEqual(po.party_account_currency, "USD")
|
||||||
|
|
||||||
|
def test_po_advance_paid_and_currency_with_journal(self):
|
||||||
|
self.create_supplier("_Test USD Supplier", "USD")
|
||||||
|
|
||||||
|
po = self.create_purchase_order(currency="USD", do_not_submit=True)
|
||||||
|
po.conversion_rate = 80
|
||||||
|
po.submit()
|
||||||
|
|
||||||
|
je_exchange_rate = 85
|
||||||
|
je = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Journal Entry",
|
||||||
|
"company": self.company,
|
||||||
|
"voucher_type": "Journal Entry",
|
||||||
|
"posting_date": po.transaction_date,
|
||||||
|
"multi_currency": True,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"account": self.creditors_usd,
|
||||||
|
"party_type": "Supplier",
|
||||||
|
"party": po.supplier,
|
||||||
|
"debit": 8500,
|
||||||
|
"debit_in_account_currency": 100,
|
||||||
|
"is_advance": "Yes",
|
||||||
|
"reference_type": po.doctype,
|
||||||
|
"reference_name": po.name,
|
||||||
|
"exchange_rate": je_exchange_rate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account": self.cash,
|
||||||
|
"credit": 8500,
|
||||||
|
"credit_in_account_currency": 8500,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
je.save().submit()
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.advance_paid, 100)
|
||||||
|
self.assertEqual(po.party_account_currency, "USD")
|
||||||
|
|
||||||
|
# cancel advance payment
|
||||||
|
je.reload()
|
||||||
|
je.cancel()
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.advance_paid, 0)
|
||||||
|
self.assertEqual(po.party_account_currency, "USD")
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestBank(unittest.TestCase):
|
|
||||||
|
class TestBank(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -208,8 +208,49 @@
|
|||||||
"label": "Disabled"
|
"label": "Disabled"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [
|
||||||
"modified": "2024-03-27 13:06:37.049542",
|
{
|
||||||
|
"group": "Transactions",
|
||||||
|
"link_doctype": "Payment Request",
|
||||||
|
"link_fieldname": "bank_account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Transactions",
|
||||||
|
"link_doctype": "Payment Order",
|
||||||
|
"link_fieldname": "bank_account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Transactions",
|
||||||
|
"link_doctype": "Bank Guarantee",
|
||||||
|
"link_fieldname": "bank_account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Transactions",
|
||||||
|
"link_doctype": "Bank Transaction",
|
||||||
|
"link_fieldname": "bank_account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Accounting",
|
||||||
|
"link_doctype": "Payment Entry",
|
||||||
|
"link_fieldname": "bank_account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Accounting",
|
||||||
|
"link_doctype": "Journal Entry",
|
||||||
|
"link_fieldname": "bank_account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Party",
|
||||||
|
"link_doctype": "Customer",
|
||||||
|
"link_fieldname": "default_bank_account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Party",
|
||||||
|
"link_doctype": "Supplier",
|
||||||
|
"link_fieldname": "default_bank_account"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"modified": "2024-10-30 09:41:14.113414",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Bank Account",
|
"name": "Bank Account",
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
from frappe import _
|
|
||||||
|
|
||||||
|
|
||||||
def get_data():
|
|
||||||
return {
|
|
||||||
"fieldname": "bank_account",
|
|
||||||
"non_standard_fieldnames": {
|
|
||||||
"Customer": "default_bank_account",
|
|
||||||
"Supplier": "default_bank_account",
|
|
||||||
},
|
|
||||||
"transactions": [
|
|
||||||
{
|
|
||||||
"label": _("Payments"),
|
|
||||||
"items": ["Payment Entry", "Payment Request", "Payment Order", "Payroll Entry"],
|
|
||||||
},
|
|
||||||
{"label": _("Party"), "items": ["Customer", "Supplier"]},
|
|
||||||
{"items": ["Bank Guarantee"]},
|
|
||||||
{"items": ["Journal Entry"]},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import ValidationError
|
from frappe import ValidationError
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
# test_records = frappe.get_test_records('Bank Account')
|
|
||||||
|
|
||||||
|
|
||||||
class TestBankAccount(unittest.TestCase):
|
class TestBankAccount(IntegrationTestCase):
|
||||||
def test_validate_iban(self):
|
def test_validate_iban(self):
|
||||||
valid_ibans = [
|
valid_ibans = [
|
||||||
"GB82 WEST 1234 5698 7654 32",
|
"GB82 WEST 1234 5698 7654 32",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestBankAccountSubtype(unittest.TestCase):
|
|
||||||
|
class TestBankAccountSubtype(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestBankAccountType(unittest.TestCase):
|
|
||||||
|
class TestBankAccountType(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -108,8 +108,18 @@ class BankClearance(Document):
|
|||||||
if not d.clearance_date:
|
if not d.clearance_date:
|
||||||
d.clearance_date = None
|
d.clearance_date = None
|
||||||
|
|
||||||
payment_entry = frappe.get_doc(d.payment_document, d.payment_entry)
|
if d.payment_document == "Sales Invoice":
|
||||||
payment_entry.db_set("clearance_date", d.clearance_date)
|
frappe.db.set_value(
|
||||||
|
"Sales Invoice Payment",
|
||||||
|
{"parent": d.payment_entry, "account": self.get("account"), "amount": [">", 0]},
|
||||||
|
"clearance_date",
|
||||||
|
d.clearance_date,
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
frappe.db.set_value(
|
||||||
|
d.payment_document, d.payment_entry, "clearance_date", d.clearance_date
|
||||||
|
)
|
||||||
|
|
||||||
clearance_date_updated = True
|
clearance_date_updated = True
|
||||||
|
|
||||||
@@ -158,7 +168,7 @@ def get_payment_entries_for_bank_clearance(
|
|||||||
"Payment Entry" as payment_document, name as payment_entry,
|
"Payment Entry" as payment_document, name as payment_entry,
|
||||||
reference_no as cheque_number, reference_date as cheque_date,
|
reference_no as cheque_number, reference_date as cheque_date,
|
||||||
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
|
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
|
||||||
if(paid_from=%(account)s, 0, received_amount) as debit,
|
if(paid_from=%(account)s, 0, received_amount + total_taxes_and_charges) as debit,
|
||||||
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
||||||
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
||||||
from `tabPayment Entry`
|
from `tabPayment Entry`
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import add_months, getdate
|
from frappe.utils import add_months, getdate
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
from erpnext.tests.utils import if_lending_app_installed, if_lending_app_not_installed
|
from erpnext.tests.utils import if_lending_app_installed, if_lending_app_not_installed
|
||||||
|
|
||||||
|
|
||||||
class TestBankClearance(unittest.TestCase):
|
class TestBankClearance(IntegrationTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
clear_payment_entries()
|
super().setUpClass()
|
||||||
clear_loan_transactions()
|
create_warehouse(
|
||||||
|
warehouse_name="_Test Warehouse",
|
||||||
|
properties={"parent_warehouse": "All Warehouses - _TC"},
|
||||||
|
company="_Test Company",
|
||||||
|
)
|
||||||
|
create_item("_Test Item")
|
||||||
|
create_cost_center(cost_center_name="_Test Cost Center", company="_Test Company")
|
||||||
|
|
||||||
make_bank_account()
|
make_bank_account()
|
||||||
add_transactions()
|
add_transactions()
|
||||||
|
|
||||||
@@ -83,18 +94,31 @@ class TestBankClearance(unittest.TestCase):
|
|||||||
bank_clearance.get_payment_entries()
|
bank_clearance.get_payment_entries()
|
||||||
self.assertEqual(len(bank_clearance.payment_entries), 3)
|
self.assertEqual(len(bank_clearance.payment_entries), 3)
|
||||||
|
|
||||||
|
def test_update_clearance_date_on_si(self):
|
||||||
|
sales_invoice = make_pos_sales_invoice()
|
||||||
|
|
||||||
def clear_payment_entries():
|
date = getdate()
|
||||||
frappe.db.delete("Payment Entry")
|
bank_clearance = frappe.get_doc("Bank Clearance")
|
||||||
|
bank_clearance.account = "_Test Bank Clearance - _TC"
|
||||||
|
bank_clearance.from_date = add_months(date, -1)
|
||||||
|
bank_clearance.to_date = date
|
||||||
|
bank_clearance.include_pos_transactions = 1
|
||||||
|
bank_clearance.get_payment_entries()
|
||||||
|
|
||||||
|
self.assertNotEqual(len(bank_clearance.payment_entries), 0)
|
||||||
|
for payment in bank_clearance.payment_entries:
|
||||||
|
if payment.payment_entry == sales_invoice.name:
|
||||||
|
payment.clearance_date = date
|
||||||
|
|
||||||
@if_lending_app_installed
|
bank_clearance.update_clearance_date()
|
||||||
def clear_loan_transactions():
|
|
||||||
for dt in [
|
si_clearance_date = frappe.db.get_value(
|
||||||
"Loan Disbursement",
|
"Sales Invoice Payment",
|
||||||
"Loan Repayment",
|
{"parent": sales_invoice.name, "account": bank_clearance.account},
|
||||||
]:
|
"clearance_date",
|
||||||
frappe.db.delete(dt)
|
)
|
||||||
|
|
||||||
|
self.assertEqual(si_clearance_date, date)
|
||||||
|
|
||||||
|
|
||||||
def make_bank_account():
|
def make_bank_account():
|
||||||
@@ -115,9 +139,45 @@ def add_transactions():
|
|||||||
|
|
||||||
|
|
||||||
def make_payment_entry():
|
def make_payment_entry():
|
||||||
pi = make_purchase_invoice(supplier="_Test Supplier", qty=1, rate=690)
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
|
|
||||||
|
supplier = create_supplier(supplier_name="_Test Supplier")
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
supplier=supplier,
|
||||||
|
supplier_warehouse="_Test Warehouse - _TC",
|
||||||
|
expense_account="Cost of Goods Sold - _TC",
|
||||||
|
uom="Nos",
|
||||||
|
qty=1,
|
||||||
|
rate=690,
|
||||||
|
)
|
||||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC")
|
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC")
|
||||||
pe.reference_no = "Conrad Oct 18"
|
pe.reference_no = "Conrad Oct 18"
|
||||||
pe.reference_date = "2018-10-24"
|
pe.reference_date = "2018-10-24"
|
||||||
pe.insert()
|
pe.insert()
|
||||||
pe.submit()
|
pe.submit()
|
||||||
|
|
||||||
|
|
||||||
|
def make_pos_sales_invoice():
|
||||||
|
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
|
||||||
|
make_customer,
|
||||||
|
)
|
||||||
|
|
||||||
|
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
|
||||||
|
|
||||||
|
if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}):
|
||||||
|
mode_of_payment.append(
|
||||||
|
"accounts", {"company": "_Test Company", "default_account": "_Test Bank Clearance - _TC"}
|
||||||
|
)
|
||||||
|
mode_of_payment.save()
|
||||||
|
|
||||||
|
customer = make_customer(customer="_Test Customer")
|
||||||
|
|
||||||
|
si = create_sales_invoice(customer=customer, item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1)
|
||||||
|
si.set("payments", [])
|
||||||
|
si.append(
|
||||||
|
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank Clearance - _TC", "amount": 1000}
|
||||||
|
)
|
||||||
|
si.insert()
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
return si
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestBankGuarantee(unittest.TestCase):
|
|
||||||
|
class TestBankGuarantee(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import add_days, today
|
from frappe.utils import add_days, today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
||||||
@@ -15,7 +15,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
|
|||||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
|
||||||
|
|
||||||
class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
|
class TestBankReconciliationTool(AccountsTestMixin, IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.create_company()
|
self.create_company()
|
||||||
self.create_customer()
|
self.create_customer()
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestBankStatementImport(unittest.TestCase):
|
|
||||||
|
class TestBankStatementImport(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -2,13 +2,22 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
|
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
|
||||||
|
|
||||||
|
|
||||||
class TestAutoMatchParty(FrappeTestCase):
|
class UnitTestBankTransaction(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for BankTransaction.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestAutoMatchParty(IntegrationTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
create_bank_account()
|
create_bank_account()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import json
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import utils
|
from frappe import utils
|
||||||
from frappe.model.docstatus import DocStatus
|
from frappe.model.docstatus import DocStatus
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
|
|
||||||
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
||||||
get_linked_payments,
|
get_linked_payments,
|
||||||
@@ -18,19 +18,20 @@ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make
|
|||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.tests.utils import if_lending_app_installed
|
from erpnext.tests.utils import if_lending_app_installed
|
||||||
|
|
||||||
test_dependencies = ["Item", "Cost Center"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "Cost Center"]
|
||||||
|
|
||||||
|
|
||||||
class TestBankTransaction(FrappeTestCase):
|
class UnitTestBankTransaction(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for BankTransaction.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestBankTransaction(IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
for dt in [
|
|
||||||
"Bank Transaction",
|
|
||||||
"Payment Entry",
|
|
||||||
"Payment Entry Reference",
|
|
||||||
"POS Profile",
|
|
||||||
]:
|
|
||||||
frappe.db.delete(dt)
|
|
||||||
clear_loan_transactions()
|
|
||||||
make_pos_profile()
|
make_pos_profile()
|
||||||
|
|
||||||
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
|
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
|
||||||
@@ -222,11 +223,6 @@ class TestBankTransaction(FrappeTestCase):
|
|||||||
self.assertEqual(linked_payments[0]["name"], repayment_entry.name)
|
self.assertEqual(linked_payments[0]["name"], repayment_entry.name)
|
||||||
|
|
||||||
|
|
||||||
@if_lending_app_installed
|
|
||||||
def clear_loan_transactions():
|
|
||||||
frappe.db.delete("Loan Repayment")
|
|
||||||
|
|
||||||
|
|
||||||
def create_bank_account(
|
def create_bank_account(
|
||||||
bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
|
bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -2,8 +2,17 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestBisectAccountingStatements(FrappeTestCase):
|
class UnitTestBisectAccountingStatements(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for BisectAccountingStatements.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestBisectAccountingStatements(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -2,8 +2,17 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestBisectNodes(FrappeTestCase):
|
class UnitTestBisectNodes(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for BisectNodes.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestBisectNodes(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import now_datetime, nowdate
|
from frappe.utils import now_datetime, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.budget.budget import BudgetError, get_actual_expense
|
from erpnext.accounts.doctype.budget.budget import BudgetError, get_actual_expense
|
||||||
@@ -11,10 +11,10 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
|
|||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
|
|
||||||
test_dependencies = ["Monthly Distribution"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Monthly Distribution"]
|
||||||
|
|
||||||
|
|
||||||
class TestBudget(unittest.TestCase):
|
class TestBudget(IntegrationTestCase):
|
||||||
def test_monthly_budget_crossed_ignore(self):
|
def test_monthly_budget_crossed_ignore(self):
|
||||||
set_total_expense_zero(nowdate(), "cost_center")
|
set_total_expense_zero(nowdate(), "cost_center")
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestCashierClosing(unittest.TestCase):
|
|
||||||
|
class TestCashierClosing(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestChartofAccountsImporter(unittest.TestCase):
|
|
||||||
|
class TestChartofAccountsImporter(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
# test_records = frappe.get_test_records('Cheque Print Template')
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestChequePrintTemplate(unittest.TestCase):
|
class TestChequePrintTemplate(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
test_records = frappe.get_test_records("Cost Center")
|
|
||||||
|
|
||||||
|
|
||||||
class TestCostCenter(unittest.TestCase):
|
class TestCostCenter(IntegrationTestCase):
|
||||||
def test_cost_center_creation_against_child_node(self):
|
def test_cost_center_creation_against_child_node(self):
|
||||||
if not frappe.db.get_value("Cost Center", {"name": "_Test Cost Center 2 - _TC"}):
|
|
||||||
frappe.get_doc(test_records[1]).insert()
|
|
||||||
|
|
||||||
cost_center = frappe.get_doc(
|
cost_center = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Cost Center",
|
"doctype": "Cost Center",
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"company": "_Test Company",
|
|
||||||
"cost_center_name": "_Test Cost Center",
|
|
||||||
"doctype": "Cost Center",
|
|
||||||
"is_group": 0,
|
|
||||||
"parent_cost_center": "_Test Company - _TC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"company": "_Test Company",
|
|
||||||
"cost_center_name": "_Test Cost Center 2",
|
|
||||||
"doctype": "Cost Center",
|
|
||||||
"is_group": 0,
|
|
||||||
"parent_cost_center": "_Test Company - _TC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"company": "_Test Company",
|
|
||||||
"cost_center_name": "_Test Write Off Cost Center",
|
|
||||||
"doctype": "Cost Center",
|
|
||||||
"is_group": 0,
|
|
||||||
"parent_cost_center": "_Test Company - _TC"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
18
erpnext/accounts/doctype/cost_center/test_records.toml
Normal file
18
erpnext/accounts/doctype/cost_center/test_records.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[["Cost Center"]]
|
||||||
|
company = "_Test Company"
|
||||||
|
cost_center_name = "_Test Cost Center"
|
||||||
|
is_group = 0
|
||||||
|
parent_cost_center = "_Test Company - _TC"
|
||||||
|
|
||||||
|
[["Cost Center"]]
|
||||||
|
company = "_Test Company"
|
||||||
|
cost_center_name = "_Test Cost Center 2"
|
||||||
|
is_group = 0
|
||||||
|
parent_cost_center = "_Test Company - _TC"
|
||||||
|
|
||||||
|
[["Cost Center"]]
|
||||||
|
company = "_Test Company"
|
||||||
|
cost_center_name = "_Test Write Off Cost Center"
|
||||||
|
is_group = 0
|
||||||
|
parent_cost_center = "_Test Company - _TC"
|
||||||
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import add_days, today
|
from frappe.utils import add_days, today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||||
@@ -17,7 +17,7 @@ from erpnext.accounts.doctype.cost_center_allocation.cost_center_allocation impo
|
|||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
|
|
||||||
|
|
||||||
class TestCostCenterAllocation(unittest.TestCase):
|
class TestCostCenterAllocation(IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
cost_centers = [
|
cost_centers = [
|
||||||
"Main Cost Center 1",
|
"Main Cost Center 1",
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"]
|
||||||
|
|
||||||
|
|
||||||
def test_create_test_data():
|
def test_create_test_data():
|
||||||
@@ -110,7 +110,7 @@ def test_create_test_data():
|
|||||||
coupon_code.insert()
|
coupon_code.insert()
|
||||||
|
|
||||||
|
|
||||||
class TestCouponCode(unittest.TestCase):
|
class TestCouponCode(IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
test_create_test_data()
|
test_create_test_data()
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ def get_api_endpoint(service_provider: str | None = None, use_http: bool = False
|
|||||||
if service_provider == "exchangerate.host":
|
if service_provider == "exchangerate.host":
|
||||||
api = "api.exchangerate.host/convert"
|
api = "api.exchangerate.host/convert"
|
||||||
elif service_provider == "frankfurter.app":
|
elif service_provider == "frankfurter.app":
|
||||||
api = "frankfurter.app/{transaction_date}"
|
api = "api.frankfurter.app/{transaction_date}"
|
||||||
|
|
||||||
protocol = "https://"
|
protocol = "https://"
|
||||||
if use_http:
|
if use_http:
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestCurrencyExchangeSettings(unittest.TestCase):
|
|
||||||
|
class TestCurrencyExchangeSettings(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -220,19 +220,31 @@ def get_linked_dunnings_as_per_state(sales_invoice, state):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_dunning_letter_text(dunning_type, doc, language=None):
|
def get_dunning_letter_text(dunning_type: str, doc: str | dict, language: str | None = None) -> dict:
|
||||||
|
DOCTYPE = "Dunning Letter Text"
|
||||||
|
FIELDS = ["body_text", "closing_text", "language"]
|
||||||
|
|
||||||
if isinstance(doc, str):
|
if isinstance(doc, str):
|
||||||
doc = json.loads(doc)
|
doc = json.loads(doc)
|
||||||
|
|
||||||
|
if not language:
|
||||||
|
language = doc.get("language")
|
||||||
|
|
||||||
if language:
|
if language:
|
||||||
filters = {"parent": dunning_type, "language": language}
|
letter_text = frappe.db.get_value(
|
||||||
else:
|
DOCTYPE, {"parent": dunning_type, "language": language}, FIELDS, as_dict=1
|
||||||
filters = {"parent": dunning_type, "is_default_language": 1}
|
)
|
||||||
letter_text = frappe.db.get_value(
|
|
||||||
"Dunning Letter Text", filters, ["body_text", "closing_text", "language"], as_dict=1
|
if not letter_text:
|
||||||
)
|
letter_text = frappe.db.get_value(
|
||||||
if letter_text:
|
DOCTYPE, {"parent": dunning_type, "is_default_language": 1}, FIELDS, as_dict=1
|
||||||
return {
|
)
|
||||||
"body_text": frappe.render_template(letter_text.body_text, doc),
|
|
||||||
"closing_text": frappe.render_template(letter_text.closing_text, doc),
|
if not letter_text:
|
||||||
"language": letter_text.language,
|
return {}
|
||||||
}
|
|
||||||
|
return {
|
||||||
|
"body_text": frappe.render_template(letter_text.body_text, doc),
|
||||||
|
"closing_text": frappe.render_template(letter_text.closing_text, doc),
|
||||||
|
"language": letter_text.language,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
from frappe.utils import add_days, nowdate, today
|
from frappe.utils import add_days, nowdate, today
|
||||||
|
|
||||||
from erpnext import get_default_cost_center
|
from erpnext import get_default_cost_center
|
||||||
@@ -16,10 +16,19 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
|
|||||||
create_sales_invoice_against_cost_center,
|
create_sales_invoice_against_cost_center,
|
||||||
)
|
)
|
||||||
|
|
||||||
test_dependencies = ["Company", "Cost Center"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Company", "Cost Center"]
|
||||||
|
|
||||||
|
|
||||||
class TestDunning(FrappeTestCase):
|
class UnitTestDunning(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for Dunning.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestDunning(IntegrationTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestDunningType(unittest.TestCase):
|
|
||||||
|
class TestDunningType(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"doctype": "Dunning Type",
|
|
||||||
"dunning_type": "_Test First Notice",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"is_default": 1,
|
|
||||||
"dunning_fee": 0.0,
|
|
||||||
"rate_of_interest": 0.0,
|
|
||||||
"dunning_letter_text": [
|
|
||||||
{
|
|
||||||
"language": "en",
|
|
||||||
"body_text": "We have still not received payment for our invoice",
|
|
||||||
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"income_account": "Sales - _TC",
|
|
||||||
"cost_center": "_Test Cost Center - _TC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Dunning Type",
|
|
||||||
"dunning_type": "_Test Second Notice",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"is_default": 0,
|
|
||||||
"dunning_fee": 10.0,
|
|
||||||
"rate_of_interest": 10.0,
|
|
||||||
"dunning_letter_text": [
|
|
||||||
{
|
|
||||||
"language": "en",
|
|
||||||
"body_text": "We have still not received payment for our invoice",
|
|
||||||
"closing_text": "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"income_account": "Sales - _TC",
|
|
||||||
"cost_center": "_Test Cost Center - _TC"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
28
erpnext/accounts/doctype/dunning_type/test_records.toml
Normal file
28
erpnext/accounts/doctype/dunning_type/test_records.toml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[["Dunning Type"]]
|
||||||
|
dunning_type = "_Test First Notice"
|
||||||
|
company = "_Test Company"
|
||||||
|
is_default = 1
|
||||||
|
dunning_fee = 0.0
|
||||||
|
rate_of_interest = 0.0
|
||||||
|
income_account = "Sales - _TC"
|
||||||
|
cost_center = "_Test Cost Center - _TC"
|
||||||
|
[["Dunning Type".dunning_letter_text]]
|
||||||
|
language = "en"
|
||||||
|
body_text = "We have still not received payment for our invoice"
|
||||||
|
closing_text = "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
|
||||||
|
|
||||||
|
|
||||||
|
[["Dunning Type"]]
|
||||||
|
dunning_type = "_Test Second Notice"
|
||||||
|
company = "_Test Company"
|
||||||
|
is_default = 0
|
||||||
|
dunning_fee = 10.0
|
||||||
|
rate_of_interest = 10.0
|
||||||
|
income_account = "Sales - _TC"
|
||||||
|
cost_center = "_Test Cost Center - _TC"
|
||||||
|
[["Dunning Type".dunning_letter_text]]
|
||||||
|
language = "en"
|
||||||
|
body_text = "We have still not received payment for our invoice"
|
||||||
|
closing_text = "We kindly request that you pay the outstanding amount immediately, including interest and late fees."
|
||||||
|
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import add_days, flt, today
|
from frappe.utils import add_days, flt, today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
@@ -11,7 +11,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
|||||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
|
||||||
|
|
||||||
class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
class TestExchangeRateRevaluation(AccountsTestMixin, IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.create_company()
|
self.create_company()
|
||||||
self.create_usd_receivable_account()
|
self.create_usd_receivable_account()
|
||||||
@@ -35,7 +35,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
|||||||
company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account
|
company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account
|
||||||
company_doc.save()
|
company_doc.save()
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
||||||
)
|
)
|
||||||
@@ -88,7 +88,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
|||||||
)[0]
|
)[0]
|
||||||
self.assertEqual(acc_balance.balance, 8500.0)
|
self.assertEqual(acc_balance.balance, 8500.0)
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
||||||
)
|
)
|
||||||
@@ -158,7 +158,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
|||||||
self.assertEqual(acc_balance.balance, 0.0)
|
self.assertEqual(acc_balance.balance, 0.0)
|
||||||
self.assertEqual(acc_balance.balance_in_account_currency, 0.0)
|
self.assertEqual(acc_balance.balance_in_account_currency, 0.0)
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
||||||
)
|
)
|
||||||
@@ -247,7 +247,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
|||||||
self.assertEqual(flt(acc_balance.balance, precision), 0.0)
|
self.assertEqual(flt(acc_balance.balance, precision), 0.0)
|
||||||
self.assertEqual(flt(acc_balance.balance_in_account_currency, precision), 0.0)
|
self.assertEqual(flt(acc_balance.balance_in_account_currency, precision), 0.0)
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
{"allow_multi_currency_invoices_against_single_party_account": 1, "allow_stale": 0},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
|
|
||||||
|
|
||||||
class TestFinanceBook(unittest.TestCase):
|
class TestFinanceBook(IntegrationTestCase):
|
||||||
def test_finance_book(self):
|
def test_finance_book(self):
|
||||||
finance_book = create_finance_book()
|
finance_book = create_finance_book()
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
frappe.ui.form.on("Fiscal Year", {
|
frappe.ui.form.on("Fiscal Year", {
|
||||||
onload: function (frm) {
|
onload: function (frm) {
|
||||||
if (frm.doc.__islocal) {
|
if (frm.doc.__islocal) {
|
||||||
frm.set_value(
|
frm.set_value("year_start_date", frappe.datetime.year_start());
|
||||||
"year_start_date",
|
|
||||||
frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
year_start_date: function (frm) {
|
year_start_date: function (frm) {
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import now_datetime
|
from frappe.utils import now_datetime
|
||||||
|
|
||||||
test_ignore = ["Company"]
|
IGNORE_TEST_RECORD_DEPENDENCIES = ["Company"]
|
||||||
|
|
||||||
|
|
||||||
class TestFiscalYear(unittest.TestCase):
|
class TestFiscalYear(IntegrationTestCase):
|
||||||
def test_extra_year(self):
|
def test_extra_year(self):
|
||||||
if frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2000"):
|
if frappe.db.exists("Fiscal Year", "_Test Fiscal Year 2000"):
|
||||||
frappe.delete_doc("Fiscal Year", "_Test Fiscal Year 2000")
|
frappe.delete_doc("Fiscal Year", "_Test Fiscal Year 2000")
|
||||||
@@ -39,8 +38,21 @@ def test_record_generator():
|
|||||||
]
|
]
|
||||||
|
|
||||||
start = 2012
|
start = 2012
|
||||||
|
this_year = now_datetime().year
|
||||||
end = now_datetime().year + 25
|
end = now_datetime().year + 25
|
||||||
for year in range(start, end):
|
# The current year fails to load with the following error:
|
||||||
|
# Year start date or end date is overlapping with 2024. To avoid please set company
|
||||||
|
# This is a quick-fix: if current FY is needed, please refactor test data properly
|
||||||
|
for year in range(start, this_year):
|
||||||
|
test_records.append(
|
||||||
|
{
|
||||||
|
"doctype": "Fiscal Year",
|
||||||
|
"year": f"_Test Fiscal Year {year}",
|
||||||
|
"year_start_date": f"{year}-01-01",
|
||||||
|
"year_end_date": f"{year}-12-31",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for year in range(this_year + 1, end):
|
||||||
test_records.append(
|
test_records.append(
|
||||||
{
|
{
|
||||||
"doctype": "Fiscal Year",
|
"doctype": "Fiscal Year",
|
||||||
|
|||||||
@@ -6,38 +6,50 @@
|
|||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"dates_section",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"transaction_date",
|
"transaction_date",
|
||||||
|
"column_break_avko",
|
||||||
|
"fiscal_year",
|
||||||
|
"due_date",
|
||||||
|
"account_details_section",
|
||||||
"account",
|
"account",
|
||||||
|
"account_currency",
|
||||||
|
"column_break_ifvf",
|
||||||
|
"against",
|
||||||
"party_type",
|
"party_type",
|
||||||
"party",
|
"party",
|
||||||
"cost_center",
|
"transaction_details_section",
|
||||||
"debit",
|
"voucher_type",
|
||||||
"credit",
|
"voucher_no",
|
||||||
"account_currency",
|
"voucher_subtype",
|
||||||
"debit_in_account_currency",
|
"transaction_currency",
|
||||||
"credit_in_account_currency",
|
"column_break_dpsx",
|
||||||
"against",
|
|
||||||
"against_voucher_type",
|
"against_voucher_type",
|
||||||
"against_voucher",
|
"against_voucher",
|
||||||
"voucher_type",
|
|
||||||
"voucher_subtype",
|
|
||||||
"voucher_no",
|
|
||||||
"voucher_detail_no",
|
"voucher_detail_no",
|
||||||
|
"transaction_exchange_rate",
|
||||||
|
"amounts_section",
|
||||||
|
"debit_in_account_currency",
|
||||||
|
"debit",
|
||||||
|
"debit_in_transaction_currency",
|
||||||
|
"column_break_bm1w",
|
||||||
|
"credit_in_account_currency",
|
||||||
|
"credit",
|
||||||
|
"credit_in_transaction_currency",
|
||||||
|
"dimensions_section",
|
||||||
|
"cost_center",
|
||||||
|
"column_break_lmnm",
|
||||||
"project",
|
"project",
|
||||||
"remarks",
|
"more_info_section",
|
||||||
|
"finance_book",
|
||||||
|
"company",
|
||||||
"is_opening",
|
"is_opening",
|
||||||
"is_advance",
|
"is_advance",
|
||||||
"fiscal_year",
|
"column_break_8abq",
|
||||||
"company",
|
|
||||||
"finance_book",
|
|
||||||
"to_rename",
|
"to_rename",
|
||||||
"due_date",
|
|
||||||
"is_cancelled",
|
"is_cancelled",
|
||||||
"transaction_currency",
|
"remarks"
|
||||||
"debit_in_transaction_currency",
|
|
||||||
"credit_in_transaction_currency",
|
|
||||||
"transaction_exchange_rate"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -285,13 +297,67 @@
|
|||||||
"fieldname": "voucher_subtype",
|
"fieldname": "voucher_subtype",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Voucher Subtype"
|
"label": "Voucher Subtype"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dates_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Dates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_avko",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "account_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Account Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_ifvf",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "transaction_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Transaction Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amounts_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Amounts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_dpsx",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "more_info_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "More Info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_bm1w",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimensions_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Dimensions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_lmnm",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_8abq",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-list",
|
"icon": "fa fa-list",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:09:45.205364",
|
"modified": "2024-08-22 13:03:39.997475",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "GL Entry",
|
"name": "GL Entry",
|
||||||
|
|||||||
@@ -430,8 +430,9 @@ def update_against_account(voucher_type, voucher_no):
|
|||||||
|
|
||||||
|
|
||||||
def on_doctype_update():
|
def on_doctype_update():
|
||||||
frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"])
|
|
||||||
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
|
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
|
||||||
|
frappe.db.add_index("GL Entry", ["posting_date", "company"])
|
||||||
|
frappe.db.add_index("GL Entry", ["party_type", "party"])
|
||||||
|
|
||||||
|
|
||||||
def rename_gle_sle_docs():
|
def rename_gle_sle_docs():
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.naming import parse_naming_series
|
from frappe.model.naming import parse_naming_series
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
from erpnext.accounts.doctype.gl_entry.gl_entry import rename_gle_sle_docs
|
from erpnext.accounts.doctype.gl_entry.gl_entry import rename_gle_sle_docs
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
|
|
||||||
|
|
||||||
class TestGLEntry(unittest.TestCase):
|
class TestGLEntry(IntegrationTestCase):
|
||||||
def test_round_off_entry(self):
|
def test_round_off_entry(self):
|
||||||
frappe.db.set_value("Company", "_Test Company", "round_off_account", "_Test Write Off - _TC")
|
frappe.db.set_value("Company", "_Test Company", "round_off_account", "_Test Write Off - _TC")
|
||||||
frappe.db.set_value("Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC")
|
frappe.db.set_value("Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC")
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import add_days, flt, nowdate
|
from frappe.utils import add_days, flt, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import create_account
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
@@ -12,7 +12,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
|||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
|
||||||
|
|
||||||
|
|
||||||
class TestInvoiceDiscounting(unittest.TestCase):
|
class TestInvoiceDiscounting(IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.ar_credit = create_account(
|
self.ar_credit = create_account(
|
||||||
account_name="_Test Accounts Receivable Credit",
|
account_name="_Test Accounts Receivable Credit",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestItemTaxTemplate(unittest.TestCase):
|
|
||||||
|
class TestItemTaxTemplate(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template",
|
|
||||||
"title": "_Test Account Excise Duty @ 10",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"taxes": [
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template Detail",
|
|
||||||
"parentfield": "taxes",
|
|
||||||
"tax_rate": 10,
|
|
||||||
"tax_type": "_Test Account Excise Duty - _TC"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template",
|
|
||||||
"title": "_Test Account Excise Duty @ 12",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"taxes": [
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template Detail",
|
|
||||||
"parentfield": "taxes",
|
|
||||||
"tax_rate": 12,
|
|
||||||
"tax_type": "_Test Account Excise Duty - _TC"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template",
|
|
||||||
"title": "_Test Account Excise Duty @ 15",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"taxes": [
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template Detail",
|
|
||||||
"parentfield": "taxes",
|
|
||||||
"tax_rate": 15,
|
|
||||||
"tax_type": "_Test Account Excise Duty - _TC"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template",
|
|
||||||
"title": "_Test Account Excise Duty @ 20",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"taxes": [
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template Detail",
|
|
||||||
"parentfield": "taxes",
|
|
||||||
"tax_rate": 20,
|
|
||||||
"tax_type": "_Test Account Excise Duty - _TC"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template",
|
|
||||||
"title": "_Test Item Tax Template 1",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"taxes": [
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template Detail",
|
|
||||||
"parentfield": "taxes",
|
|
||||||
"tax_rate": 5,
|
|
||||||
"tax_type": "_Test Account Excise Duty - _TC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template Detail",
|
|
||||||
"parentfield": "taxes",
|
|
||||||
"tax_rate": 10,
|
|
||||||
"tax_type": "_Test Account Education Cess - _TC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Item Tax Template Detail",
|
|
||||||
"parentfield": "taxes",
|
|
||||||
"tax_rate": 15,
|
|
||||||
"tax_type": "_Test Account S&H Education Cess - _TC"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
62
erpnext/accounts/doctype/item_tax_template/test_records.toml
Normal file
62
erpnext/accounts/doctype/item_tax_template/test_records.toml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
[["Item Tax Template"]]
|
||||||
|
title = "_Test Account Excise Duty @ 10"
|
||||||
|
company = "_Test Company"
|
||||||
|
[["Item Tax Template".taxes]]
|
||||||
|
doctype = "Item Tax Template Detail"
|
||||||
|
parentfield = "taxes"
|
||||||
|
tax_rate = 10
|
||||||
|
tax_type = "_Test Account Excise Duty - _TC"
|
||||||
|
|
||||||
|
|
||||||
|
[["Item Tax Template"]]
|
||||||
|
title = "_Test Account Excise Duty @ 12"
|
||||||
|
company = "_Test Company"
|
||||||
|
[["Item Tax Template".taxes]]
|
||||||
|
doctype = "Item Tax Template Detail"
|
||||||
|
parentfield = "taxes"
|
||||||
|
tax_rate = 12
|
||||||
|
tax_type = "_Test Account Excise Duty - _TC"
|
||||||
|
|
||||||
|
|
||||||
|
[["Item Tax Template"]]
|
||||||
|
title = "_Test Account Excise Duty @ 15"
|
||||||
|
company = "_Test Company"
|
||||||
|
[["Item Tax Template".taxes]]
|
||||||
|
doctype = "Item Tax Template Detail"
|
||||||
|
parentfield = "taxes"
|
||||||
|
tax_rate = 15
|
||||||
|
tax_type = "_Test Account Excise Duty - _TC"
|
||||||
|
|
||||||
|
|
||||||
|
[["Item Tax Template"]]
|
||||||
|
title = "_Test Account Excise Duty @ 20"
|
||||||
|
company = "_Test Company"
|
||||||
|
[["Item Tax Template".taxes]]
|
||||||
|
doctype = "Item Tax Template Detail"
|
||||||
|
parentfield = "taxes"
|
||||||
|
tax_rate = 20
|
||||||
|
tax_type = "_Test Account Excise Duty - _TC"
|
||||||
|
|
||||||
|
|
||||||
|
[["Item Tax Template"]]
|
||||||
|
title = "_Test Item Tax Template 1"
|
||||||
|
company = "_Test Company"
|
||||||
|
[["Item Tax Template".taxes]]
|
||||||
|
doctype = "Item Tax Template Detail"
|
||||||
|
parentfield = "taxes"
|
||||||
|
tax_rate = 5
|
||||||
|
tax_type = "_Test Account Excise Duty - _TC"
|
||||||
|
|
||||||
|
[["Item Tax Template".taxes]]
|
||||||
|
doctype = "Item Tax Template Detail"
|
||||||
|
parentfield = "taxes"
|
||||||
|
tax_rate = 10
|
||||||
|
tax_type = "_Test Account Education Cess - _TC"
|
||||||
|
|
||||||
|
[["Item Tax Template".taxes]]
|
||||||
|
doctype = "Item Tax Template Detail"
|
||||||
|
parentfield = "taxes"
|
||||||
|
tax_rate = 15
|
||||||
|
tax_type = "_Test Account S&H Education Cess - _TC"
|
||||||
|
|
||||||
|
|
||||||
@@ -188,6 +188,7 @@ class JournalEntry(AccountsController):
|
|||||||
self.validate_cheque_info()
|
self.validate_cheque_info()
|
||||||
self.check_credit_limit()
|
self.check_credit_limit()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
self.make_advance_payment_ledger_entries()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.update_asset_value()
|
self.update_asset_value()
|
||||||
self.update_inter_company_jv()
|
self.update_inter_company_jv()
|
||||||
@@ -218,8 +219,10 @@ class JournalEntry(AccountsController):
|
|||||||
"Repost Accounting Ledger Items",
|
"Repost Accounting Ledger Items",
|
||||||
"Unreconcile Payment",
|
"Unreconcile Payment",
|
||||||
"Unreconcile Payment Entries",
|
"Unreconcile Payment Entries",
|
||||||
|
"Advance Payment Ledger Entry",
|
||||||
)
|
)
|
||||||
self.make_gl_entries(1)
|
self.make_gl_entries(1)
|
||||||
|
self.make_advance_payment_ledger_entries()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
self.unlink_advance_entry_reference()
|
self.unlink_advance_entry_reference()
|
||||||
self.unlink_asset_reference()
|
self.unlink_asset_reference()
|
||||||
@@ -262,7 +265,7 @@ class JournalEntry(AccountsController):
|
|||||||
frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation"))
|
frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation"))
|
||||||
|
|
||||||
def validate_stock_accounts(self):
|
def validate_stock_accounts(self):
|
||||||
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
|
stock_accounts = get_stock_accounts(self.company, accounts=self.accounts)
|
||||||
for account in stock_accounts:
|
for account in stock_accounts:
|
||||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
||||||
account, self.posting_date, self.company
|
account, self.posting_date, self.company
|
||||||
@@ -1673,6 +1676,8 @@ def make_reverse_journal_entry(source_name, target_doc=None):
|
|||||||
"debit": "credit",
|
"debit": "credit",
|
||||||
"credit_in_account_currency": "debit_in_account_currency",
|
"credit_in_account_currency": "debit_in_account_currency",
|
||||||
"credit": "debit",
|
"credit": "debit",
|
||||||
|
"reference_type": "reference_type",
|
||||||
|
"reference_name": "reference_name",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import change_settings
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
@@ -13,25 +10,36 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInv
|
|||||||
from erpnext.exceptions import InvalidAccountCurrency
|
from erpnext.exceptions import InvalidAccountCurrency
|
||||||
|
|
||||||
|
|
||||||
class TestJournalEntry(unittest.TestCase):
|
class UnitTestJournalEntry(UnitTestCase):
|
||||||
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
"""
|
||||||
|
Unit tests for JournalEntry.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestJournalEntry(IntegrationTestCase):
|
||||||
|
@IntegrationTestCase.change_settings(
|
||||||
|
"Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}
|
||||||
|
)
|
||||||
def test_journal_entry_with_against_jv(self):
|
def test_journal_entry_with_against_jv(self):
|
||||||
jv_invoice = frappe.copy_doc(test_records[2])
|
jv_invoice = frappe.copy_doc(self.globalTestRecords["Journal Entry"][2])
|
||||||
base_jv = frappe.copy_doc(test_records[0])
|
base_jv = frappe.copy_doc(self.globalTestRecords["Journal Entry"][0])
|
||||||
self.jv_against_voucher_testcase(base_jv, jv_invoice)
|
self.jv_against_voucher_testcase(base_jv, jv_invoice)
|
||||||
|
|
||||||
def test_jv_against_sales_order(self):
|
def test_jv_against_sales_order(self):
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
|
||||||
sales_order = make_sales_order(do_not_save=True)
|
sales_order = make_sales_order(do_not_save=True)
|
||||||
base_jv = frappe.copy_doc(test_records[0])
|
base_jv = frappe.copy_doc(self.globalTestRecords["Journal Entry"][0])
|
||||||
self.jv_against_voucher_testcase(base_jv, sales_order)
|
self.jv_against_voucher_testcase(base_jv, sales_order)
|
||||||
|
|
||||||
def test_jv_against_purchase_order(self):
|
def test_jv_against_purchase_order(self):
|
||||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
|
|
||||||
purchase_order = create_purchase_order(do_not_save=True)
|
purchase_order = create_purchase_order(do_not_save=True)
|
||||||
base_jv = frappe.copy_doc(test_records[1])
|
base_jv = frappe.copy_doc(self.globalTestRecords["Journal Entry"][1])
|
||||||
self.jv_against_voucher_testcase(base_jv, purchase_order)
|
self.jv_against_voucher_testcase(base_jv, purchase_order)
|
||||||
|
|
||||||
def jv_against_voucher_testcase(self, base_jv, test_voucher):
|
def jv_against_voucher_testcase(self, base_jv, test_voucher):
|
||||||
@@ -515,6 +523,23 @@ class TestJournalEntry(unittest.TestCase):
|
|||||||
self.assertEqual(row.debit_in_account_currency, 100)
|
self.assertEqual(row.debit_in_account_currency, 100)
|
||||||
self.assertEqual(row.credit_in_account_currency, 100)
|
self.assertEqual(row.credit_in_account_currency, 100)
|
||||||
|
|
||||||
|
def test_transaction_exchange_rate_on_journals(self):
|
||||||
|
jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable USD - _TC", 100, save=False)
|
||||||
|
jv.accounts[0].update({"debit_in_account_currency": 8500, "exchange_rate": 1})
|
||||||
|
jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD", "exchange_rate": 85})
|
||||||
|
jv.submit()
|
||||||
|
actual = frappe.db.get_all(
|
||||||
|
"GL Entry",
|
||||||
|
filters={"voucher_no": jv.name, "is_cancelled": 0},
|
||||||
|
fields=["account", "transaction_exchange_rate"],
|
||||||
|
order_by="account",
|
||||||
|
)
|
||||||
|
expected = [
|
||||||
|
{"account": "_Test Bank - _TC", "transaction_exchange_rate": 1.0},
|
||||||
|
{"account": "_Test Receivable USD - _TC", "transaction_exchange_rate": 85.0},
|
||||||
|
]
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
|
||||||
def make_journal_entry(
|
def make_journal_entry(
|
||||||
account1,
|
account1,
|
||||||
@@ -563,6 +588,3 @@ def make_journal_entry(
|
|||||||
jv.submit()
|
jv.submit()
|
||||||
|
|
||||||
return jv
|
return jv
|
||||||
|
|
||||||
|
|
||||||
test_records = frappe.get_test_records("Journal Entry")
|
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"cheque_date": "2013-03-14",
|
|
||||||
"cheque_no": "33",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"doctype": "Journal Entry",
|
|
||||||
"accounts": [
|
|
||||||
{
|
|
||||||
"account": "Debtors - _TC",
|
|
||||||
"party_type": "Customer",
|
|
||||||
"party": "_Test Customer",
|
|
||||||
"credit_in_account_currency": 400.0,
|
|
||||||
"debit_in_account_currency": 0.0,
|
|
||||||
"doctype": "Journal Entry Account",
|
|
||||||
"parentfield": "accounts",
|
|
||||||
"cost_center": "_Test Cost Center - _TC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"account": "_Test Bank - _TC",
|
|
||||||
"credit_in_account_currency": 0.0,
|
|
||||||
"debit_in_account_currency": 400.0,
|
|
||||||
"doctype": "Journal Entry Account",
|
|
||||||
"parentfield": "accounts",
|
|
||||||
"cost_center": "_Test Cost Center - _TC"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"naming_series": "_T-Journal Entry-",
|
|
||||||
"posting_date": "2013-02-14",
|
|
||||||
"user_remark": "test",
|
|
||||||
"voucher_type": "Bank Entry"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"cheque_date": "2013-02-14",
|
|
||||||
"cheque_no": "33",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"doctype": "Journal Entry",
|
|
||||||
"accounts": [
|
|
||||||
{
|
|
||||||
"account": "_Test Payable - _TC",
|
|
||||||
"party_type": "Supplier",
|
|
||||||
"party": "_Test Supplier",
|
|
||||||
"credit_in_account_currency": 0.0,
|
|
||||||
"debit_in_account_currency": 400.0,
|
|
||||||
"doctype": "Journal Entry Account",
|
|
||||||
"parentfield": "accounts",
|
|
||||||
"cost_center": "_Test Cost Center - _TC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"account": "_Test Bank - _TC",
|
|
||||||
"credit_in_account_currency": 400.0,
|
|
||||||
"debit_in_account_currency": 0.0,
|
|
||||||
"doctype": "Journal Entry Account",
|
|
||||||
"parentfield": "accounts",
|
|
||||||
"cost_center": "_Test Cost Center - _TC"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"naming_series": "_T-Journal Entry-",
|
|
||||||
"posting_date": "2013-02-14",
|
|
||||||
"user_remark": "test",
|
|
||||||
"voucher_type": "Bank Entry"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"cheque_date": "2013-02-14",
|
|
||||||
"cheque_no": "33",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"doctype": "Journal Entry",
|
|
||||||
"accounts": [
|
|
||||||
{
|
|
||||||
"account": "Debtors - _TC",
|
|
||||||
"party_type": "Customer",
|
|
||||||
"party": "_Test Customer",
|
|
||||||
"credit_in_account_currency": 0.0,
|
|
||||||
"debit_in_account_currency": 400.0,
|
|
||||||
"doctype": "Journal Entry Account",
|
|
||||||
"parentfield": "accounts",
|
|
||||||
"cost_center": "_Test Cost Center - _TC"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"account": "Sales - _TC",
|
|
||||||
"credit_in_account_currency": 400.0,
|
|
||||||
"debit_in_account_currency": 0.0,
|
|
||||||
"doctype": "Journal Entry Account",
|
|
||||||
"parentfield": "accounts",
|
|
||||||
"cost_center": "_Test Cost Center - _TC"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"naming_series": "_T-Journal Entry-",
|
|
||||||
"posting_date": "2013-02-14",
|
|
||||||
"user_remark": "test",
|
|
||||||
"voucher_type": "Bank Entry"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
81
erpnext/accounts/doctype/journal_entry/test_records.toml
Normal file
81
erpnext/accounts/doctype/journal_entry/test_records.toml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
[["Journal Entry"]]
|
||||||
|
cheque_date = "2013-03-14"
|
||||||
|
cheque_no = "33"
|
||||||
|
company = "_Test Company"
|
||||||
|
naming_series = "_T-Journal Entry-"
|
||||||
|
posting_date = "2013-02-14"
|
||||||
|
user_remark = "test"
|
||||||
|
voucher_type = "Bank Entry"
|
||||||
|
[["Journal Entry".accounts]]
|
||||||
|
account = "Debtors - _TC"
|
||||||
|
party_type = "Customer"
|
||||||
|
party = "_Test Customer"
|
||||||
|
credit_in_account_currency = 400.0
|
||||||
|
debit_in_account_currency = 0.0
|
||||||
|
doctype = "Journal Entry Account"
|
||||||
|
parentfield = "accounts"
|
||||||
|
cost_center = "_Test Cost Center - _TC"
|
||||||
|
|
||||||
|
[["Journal Entry".accounts]]
|
||||||
|
account = "_Test Bank - _TC"
|
||||||
|
credit_in_account_currency = 0.0
|
||||||
|
debit_in_account_currency = 400.0
|
||||||
|
doctype = "Journal Entry Account"
|
||||||
|
parentfield = "accounts"
|
||||||
|
cost_center = "_Test Cost Center - _TC"
|
||||||
|
|
||||||
|
|
||||||
|
[["Journal Entry"]]
|
||||||
|
cheque_date = "2013-02-14"
|
||||||
|
cheque_no = "33"
|
||||||
|
company = "_Test Company"
|
||||||
|
naming_series = "_T-Journal Entry-"
|
||||||
|
posting_date = "2013-02-14"
|
||||||
|
user_remark = "test"
|
||||||
|
voucher_type = "Bank Entry"
|
||||||
|
[["Journal Entry".accounts]]
|
||||||
|
account = "_Test Payable - _TC"
|
||||||
|
party_type = "Supplier"
|
||||||
|
party = "_Test Supplier"
|
||||||
|
credit_in_account_currency = 0.0
|
||||||
|
debit_in_account_currency = 400.0
|
||||||
|
doctype = "Journal Entry Account"
|
||||||
|
parentfield = "accounts"
|
||||||
|
cost_center = "_Test Cost Center - _TC"
|
||||||
|
|
||||||
|
[["Journal Entry".accounts]]
|
||||||
|
account = "_Test Bank - _TC"
|
||||||
|
credit_in_account_currency = 400.0
|
||||||
|
debit_in_account_currency = 0.0
|
||||||
|
doctype = "Journal Entry Account"
|
||||||
|
parentfield = "accounts"
|
||||||
|
cost_center = "_Test Cost Center - _TC"
|
||||||
|
|
||||||
|
|
||||||
|
[["Journal Entry"]]
|
||||||
|
cheque_date = "2013-02-14"
|
||||||
|
cheque_no = "33"
|
||||||
|
company = "_Test Company"
|
||||||
|
naming_series = "_T-Journal Entry-"
|
||||||
|
posting_date = "2013-02-14"
|
||||||
|
user_remark = "test"
|
||||||
|
voucher_type = "Bank Entry"
|
||||||
|
[["Journal Entry".accounts]]
|
||||||
|
account = "Debtors - _TC"
|
||||||
|
party_type = "Customer"
|
||||||
|
party = "_Test Customer"
|
||||||
|
credit_in_account_currency = 0.0
|
||||||
|
debit_in_account_currency = 400.0
|
||||||
|
doctype = "Journal Entry Account"
|
||||||
|
parentfield = "accounts"
|
||||||
|
cost_center = "_Test Cost Center - _TC"
|
||||||
|
|
||||||
|
[["Journal Entry".accounts]]
|
||||||
|
account = "Sales - _TC"
|
||||||
|
credit_in_account_currency = 400.0
|
||||||
|
debit_in_account_currency = 0.0
|
||||||
|
doctype = "Journal Entry Account"
|
||||||
|
parentfield = "accounts"
|
||||||
|
cost_center = "_Test Cost Center - _TC"
|
||||||
|
|
||||||
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestJournalEntryTemplate(unittest.TestCase):
|
|
||||||
|
class TestJournalEntryTemplate(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -3,14 +3,14 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
from erpnext.accounts.utils import run_ledger_health_checks
|
from erpnext.accounts.utils import run_ledger_health_checks
|
||||||
|
|
||||||
|
|
||||||
class TestLedgerHealth(AccountsTestMixin, FrappeTestCase):
|
class TestLedgerHealth(AccountsTestMixin, IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.create_company()
|
self.create_company()
|
||||||
self.create_customer()
|
self.create_customer()
|
||||||
|
|||||||
@@ -2,8 +2,17 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestLedgerHealthMonitor(FrappeTestCase):
|
class UnitTestLedgerHealthMonitor(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for LedgerHealthMonitor.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestLedgerHealthMonitor(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
from erpnext.accounts.doctype.ledger_merge.ledger_merge import start_merge
|
from erpnext.accounts.doctype.ledger_merge.ledger_merge import start_merge
|
||||||
|
|
||||||
|
|
||||||
class TestLedgerMerge(unittest.TestCase):
|
class TestLedgerMerge(IntegrationTestCase):
|
||||||
def test_merge_success(self):
|
def test_merge_success(self):
|
||||||
if not frappe.db.exists("Account", "Indirect Expenses - _TC"):
|
if not frappe.db.exists("Account", "Indirect Expenses - _TC"):
|
||||||
acc = frappe.new_doc("Account")
|
acc = frappe.new_doc("Account")
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import today
|
from frappe.utils import today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
|
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
|
|
||||||
class TestLoyaltyPointEntry(unittest.TestCase):
|
class TestLoyaltyPointEntry(IntegrationTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
# Create test records
|
# Create test records
|
||||||
create_records()
|
create_records()
|
||||||
cls.loyalty_program_name = "Test Single Loyalty"
|
cls.loyalty_program_name = "Test Single Loyalty"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import cint, flt, getdate, today
|
from frappe.utils import cint, flt, getdate, today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||||
@@ -13,9 +13,10 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
|||||||
from erpnext.accounts.party import get_dashboard_info
|
from erpnext.accounts.party import get_dashboard_info
|
||||||
|
|
||||||
|
|
||||||
class TestLoyaltyProgram(unittest.TestCase):
|
class TestLoyaltyProgram(IntegrationTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
# create relevant item, customer, loyalty program, etc
|
# create relevant item, customer, loyalty program, etc
|
||||||
create_records()
|
create_records()
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
# test_records = frappe.get_test_records('Mode of Payment')
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestModeofPayment(unittest.TestCase):
|
class TestModeofPayment(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
test_records = frappe.get_test_records("Monthly Distribution")
|
|
||||||
|
|
||||||
|
|
||||||
class TestMonthlyDistribution(unittest.TestCase):
|
class TestMonthlyDistribution(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
[{
|
|
||||||
"doctype": "Monthly Distribution",
|
|
||||||
"distribution_id": "_Test Distribution",
|
|
||||||
"fiscal_year": "_Test Fiscal Year 2013",
|
|
||||||
"percentages": [
|
|
||||||
{
|
|
||||||
"month": "January",
|
|
||||||
"percentage_allocation": "8"
|
|
||||||
}, {
|
|
||||||
"month": "February",
|
|
||||||
"percentage_allocation": "8"
|
|
||||||
}, {
|
|
||||||
"month": "March",
|
|
||||||
"percentage_allocation": "8"
|
|
||||||
}, {
|
|
||||||
"month": "April",
|
|
||||||
"percentage_allocation": "8"
|
|
||||||
}, {
|
|
||||||
"month": "May",
|
|
||||||
"percentage_allocation": "8"
|
|
||||||
}, {
|
|
||||||
"month": "June",
|
|
||||||
"percentage_allocation": "8"
|
|
||||||
}, {
|
|
||||||
"month": "July",
|
|
||||||
"percentage_allocation": "8"
|
|
||||||
}, {
|
|
||||||
"month": "August",
|
|
||||||
"percentage_allocation": "8"
|
|
||||||
}, {
|
|
||||||
"month": "September",
|
|
||||||
"percentage_allocation": "8"
|
|
||||||
}, {
|
|
||||||
"month": "October",
|
|
||||||
"percentage_allocation": "8"
|
|
||||||
}, {
|
|
||||||
"month": "November",
|
|
||||||
"percentage_allocation": "10"
|
|
||||||
}, {
|
|
||||||
"month": "December",
|
|
||||||
"percentage_allocation": "10"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
[["Monthly Distribution"]]
|
||||||
|
distribution_id = "_Test Distribution"
|
||||||
|
fiscal_year = "_Test Fiscal Year 2013"
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "January"
|
||||||
|
percentage_allocation = "8"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "February"
|
||||||
|
percentage_allocation = "8"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "March"
|
||||||
|
percentage_allocation = "8"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "April"
|
||||||
|
percentage_allocation = "8"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "May"
|
||||||
|
percentage_allocation = "8"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "June"
|
||||||
|
percentage_allocation = "8"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "July"
|
||||||
|
percentage_allocation = "8"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "August"
|
||||||
|
percentage_allocation = "8"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "September"
|
||||||
|
percentage_allocation = "8"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "October"
|
||||||
|
percentage_allocation = "8"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "November"
|
||||||
|
percentage_allocation = "10"
|
||||||
|
|
||||||
|
[["Monthly Distribution".percentages]]
|
||||||
|
month = "December"
|
||||||
|
percentage_allocation = "10"
|
||||||
|
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
|
||||||
create_dimension,
|
create_dimension,
|
||||||
@@ -12,12 +12,21 @@ from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_crea
|
|||||||
get_temporary_opening_account,
|
get_temporary_opening_account,
|
||||||
)
|
)
|
||||||
|
|
||||||
test_dependencies = ["Customer", "Supplier", "Accounting Dimension"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Customer", "Supplier", "Accounting Dimension"]
|
||||||
|
|
||||||
|
|
||||||
class TestOpeningInvoiceCreationTool(FrappeTestCase):
|
class UnitTestOpeningInvoiceCreationTool(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for OpeningInvoiceCreationTool.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestOpeningInvoiceCreationTool(IntegrationTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(cls):
|
||||||
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||||
make_company()
|
make_company()
|
||||||
create_dimension()
|
create_dimension()
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestPartyLink(unittest.TestCase):
|
|
||||||
|
class TestPartyLink(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -174,6 +174,17 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("payment_request", "references", function (doc, cdt, cdn) {
|
||||||
|
const row = frappe.get_doc(cdt, cdn);
|
||||||
|
return {
|
||||||
|
query: "erpnext.accounts.doctype.payment_request.payment_request.get_open_payment_requests_query",
|
||||||
|
filters: {
|
||||||
|
reference_doctype: row.reference_doctype,
|
||||||
|
reference_name: row.reference_name,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_query("sales_taxes_and_charges_template", function () {
|
frm.set_query("sales_taxes_and_charges_template", function () {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
@@ -191,7 +202,15 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.add_fetch(
|
||||||
|
"payment_request",
|
||||||
|
"outstanding_amount",
|
||||||
|
"payment_request_outstanding",
|
||||||
|
"Payment Entry Reference"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
erpnext.hide_company(frm);
|
erpnext.hide_company(frm);
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
@@ -216,6 +235,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
|
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
|
||||||
|
frappe.flags.allocate_payment_amount = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
validate_company: (frm) => {
|
validate_company: (frm) => {
|
||||||
@@ -797,7 +817,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (frm.doc.payment_type == "Pay")
|
if (frm.doc.payment_type == "Pay")
|
||||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1);
|
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, true);
|
||||||
else frm.events.set_unallocated_amount(frm);
|
else frm.events.set_unallocated_amount(frm);
|
||||||
|
|
||||||
frm.set_paid_amount_based_on_received_amount = false;
|
frm.set_paid_amount_based_on_received_amount = false;
|
||||||
@@ -818,7 +838,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.payment_type == "Receive")
|
if (frm.doc.payment_type == "Receive")
|
||||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, 1);
|
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, true);
|
||||||
else frm.events.set_unallocated_amount(frm);
|
else frm.events.set_unallocated_amount(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -989,6 +1009,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
c.outstanding_amount = d.outstanding_amount;
|
c.outstanding_amount = d.outstanding_amount;
|
||||||
c.bill_no = d.bill_no;
|
c.bill_no = d.bill_no;
|
||||||
c.payment_term = d.payment_term;
|
c.payment_term = d.payment_term;
|
||||||
|
c.payment_term_outstanding = d.payment_term_outstanding;
|
||||||
c.allocated_amount = d.allocated_amount;
|
c.allocated_amount = d.allocated_amount;
|
||||||
c.account = d.account;
|
c.account = d.account;
|
||||||
|
|
||||||
@@ -1038,7 +1059,8 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
|
|
||||||
frm.events.allocate_party_amount_against_ref_docs(
|
frm.events.allocate_party_amount_against_ref_docs(
|
||||||
frm,
|
frm,
|
||||||
frm.doc.payment_type == "Receive" ? frm.doc.paid_amount : frm.doc.received_amount
|
frm.doc.payment_type == "Receive" ? frm.doc.paid_amount : frm.doc.received_amount,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1052,93 +1074,13 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
return ["Sales Invoice", "Purchase Invoice"];
|
return ["Sales Invoice", "Purchase Invoice"];
|
||||||
},
|
},
|
||||||
|
|
||||||
allocate_party_amount_against_ref_docs: function (frm, paid_amount, paid_amount_change) {
|
allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
|
||||||
var total_positive_outstanding_including_order = 0;
|
await frm.call("allocate_amount_to_references", {
|
||||||
var total_negative_outstanding = 0;
|
paid_amount: paid_amount,
|
||||||
var total_deductions = frappe.utils.sum(
|
paid_amount_change: paid_amount_change,
|
||||||
$.map(frm.doc.deductions || [], function (d) {
|
allocate_payment_amount: frappe.flags.allocate_payment_amount ?? false,
|
||||||
return flt(d.amount);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
paid_amount -= total_deductions;
|
|
||||||
|
|
||||||
$.each(frm.doc.references || [], function (i, row) {
|
|
||||||
if (flt(row.outstanding_amount) > 0)
|
|
||||||
total_positive_outstanding_including_order += flt(row.outstanding_amount);
|
|
||||||
else total_negative_outstanding += Math.abs(flt(row.outstanding_amount));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var allocated_negative_outstanding = 0;
|
|
||||||
if (
|
|
||||||
(frm.doc.payment_type == "Receive" && frm.doc.party_type == "Customer") ||
|
|
||||||
(frm.doc.payment_type == "Pay" && frm.doc.party_type == "Supplier") ||
|
|
||||||
(frm.doc.payment_type == "Pay" && frm.doc.party_type == "Employee")
|
|
||||||
) {
|
|
||||||
if (total_positive_outstanding_including_order > paid_amount) {
|
|
||||||
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
|
|
||||||
allocated_negative_outstanding =
|
|
||||||
total_negative_outstanding < remaining_outstanding
|
|
||||||
? total_negative_outstanding
|
|
||||||
: remaining_outstanding;
|
|
||||||
}
|
|
||||||
|
|
||||||
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
|
|
||||||
} else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
|
|
||||||
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"));
|
|
||||||
if (paid_amount > total_negative_outstanding) {
|
|
||||||
if (total_negative_outstanding == 0) {
|
|
||||||
frappe.msgprint(
|
|
||||||
__("Cannot {0} {1} {2} without any negative outstanding invoice", [
|
|
||||||
frm.doc.payment_type,
|
|
||||||
frm.doc.party_type == "Customer" ? "to" : "from",
|
|
||||||
frm.doc.party_type,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
frappe.msgprint(
|
|
||||||
__("Paid Amount cannot be greater than total negative outstanding amount {0}", [
|
|
||||||
total_negative_outstanding,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
allocated_positive_outstanding = total_negative_outstanding - paid_amount;
|
|
||||||
allocated_negative_outstanding =
|
|
||||||
paid_amount +
|
|
||||||
(total_positive_outstanding_including_order < allocated_positive_outstanding
|
|
||||||
? total_positive_outstanding_including_order
|
|
||||||
: allocated_positive_outstanding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$.each(frm.doc.references || [], function (i, row) {
|
|
||||||
if (frappe.flags.allocate_payment_amount == 0) {
|
|
||||||
//If allocate payment amount checkbox is unchecked, set zero to allocate amount
|
|
||||||
row.allocated_amount = 0;
|
|
||||||
} else if (
|
|
||||||
frappe.flags.allocate_payment_amount != 0 &&
|
|
||||||
(!row.allocated_amount || paid_amount_change)
|
|
||||||
) {
|
|
||||||
if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) {
|
|
||||||
row.allocated_amount =
|
|
||||||
row.outstanding_amount >= allocated_positive_outstanding
|
|
||||||
? allocated_positive_outstanding
|
|
||||||
: row.outstanding_amount;
|
|
||||||
allocated_positive_outstanding -= flt(row.allocated_amount);
|
|
||||||
} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
|
|
||||||
row.allocated_amount =
|
|
||||||
Math.abs(row.outstanding_amount) >= allocated_negative_outstanding
|
|
||||||
? -1 * allocated_negative_outstanding
|
|
||||||
: row.outstanding_amount;
|
|
||||||
allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.refresh_fields();
|
|
||||||
frm.events.set_total_allocated_amount(frm);
|
frm.events.set_total_allocated_amount(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1686,6 +1628,62 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
|
|
||||||
return current_tax_amount;
|
return current_tax_amount;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
cost_center: function (frm) {
|
||||||
|
if (frm.doc.posting_date && (frm.doc.paid_from || frm.doc.paid_to)) {
|
||||||
|
return frappe.call({
|
||||||
|
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_and_account_balance",
|
||||||
|
args: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
date: frm.doc.posting_date,
|
||||||
|
paid_from: frm.doc.paid_from,
|
||||||
|
paid_to: frm.doc.paid_to,
|
||||||
|
ptype: frm.doc.party_type,
|
||||||
|
pty: frm.doc.party,
|
||||||
|
cost_center: frm.doc.cost_center,
|
||||||
|
},
|
||||||
|
callback: function (r, rt) {
|
||||||
|
if (r.message) {
|
||||||
|
frappe.run_serially([
|
||||||
|
() => {
|
||||||
|
frm.set_value(
|
||||||
|
"paid_from_account_balance",
|
||||||
|
r.message.paid_from_account_balance
|
||||||
|
);
|
||||||
|
frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance);
|
||||||
|
frm.set_value("party_balance", r.message.party_balance);
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
after_save: function (frm) {
|
||||||
|
const { matched_payment_requests } = frappe.last_response;
|
||||||
|
if (!matched_payment_requests) return;
|
||||||
|
|
||||||
|
const COLUMN_LABEL = [
|
||||||
|
[__("Reference DocType"), __("Reference Name"), __("Allocated Amount"), __("Payment Request")],
|
||||||
|
];
|
||||||
|
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __("Unset Matched Payment Request"),
|
||||||
|
message: COLUMN_LABEL.concat(matched_payment_requests),
|
||||||
|
as_table: true,
|
||||||
|
wide: true,
|
||||||
|
primary_action: {
|
||||||
|
label: __("Allocate Payment Request"),
|
||||||
|
action() {
|
||||||
|
frappe.hide_msgprint();
|
||||||
|
frm.call("set_matched_payment_requests", { matched_payment_requests }, () => {
|
||||||
|
frm.dirty();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on("Payment Entry Reference", {
|
frappe.ui.form.on("Payment Entry Reference", {
|
||||||
@@ -1778,35 +1776,3 @@ frappe.ui.form.on("Payment Entry Deduction", {
|
|||||||
frm.events.set_unallocated_amount(frm);
|
frm.events.set_unallocated_amount(frm);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
frappe.ui.form.on("Payment Entry", {
|
|
||||||
cost_center: function (frm) {
|
|
||||||
if (frm.doc.posting_date && (frm.doc.paid_from || frm.doc.paid_to)) {
|
|
||||||
return frappe.call({
|
|
||||||
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_and_account_balance",
|
|
||||||
args: {
|
|
||||||
company: frm.doc.company,
|
|
||||||
date: frm.doc.posting_date,
|
|
||||||
paid_from: frm.doc.paid_from,
|
|
||||||
paid_to: frm.doc.paid_to,
|
|
||||||
ptype: frm.doc.party_type,
|
|
||||||
pty: frm.doc.party,
|
|
||||||
cost_center: frm.doc.cost_center,
|
|
||||||
},
|
|
||||||
callback: function (r, rt) {
|
|
||||||
if (r.message) {
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
frm.set_value(
|
|
||||||
"paid_from_account_balance",
|
|
||||||
r.message.paid_from_account_balance
|
|
||||||
);
|
|
||||||
frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance);
|
|
||||||
frm.set_value("party_balance", r.message.party_balance);
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ from functools import reduce
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import ValidationError, _, qb, scrub, throw
|
from frappe import ValidationError, _, qb, scrub, throw
|
||||||
|
from frappe.query_builder import Tuple
|
||||||
|
from frappe.query_builder.functions import Count
|
||||||
from frappe.utils import cint, comma_or, flt, getdate, nowdate
|
from frappe.utils import cint, comma_or, flt, getdate, nowdate
|
||||||
from frappe.utils.data import comma_and, fmt_money
|
from frappe.utils.data import comma_and, fmt_money, get_link_to_form
|
||||||
from pypika import Case
|
from pypika import Case
|
||||||
from pypika.functions import Coalesce, Sum
|
from pypika.functions import Coalesce, Sum
|
||||||
|
|
||||||
@@ -180,14 +182,18 @@ class PaymentEntry(AccountsController):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
self.set_total_in_words()
|
self.set_total_in_words()
|
||||||
|
|
||||||
|
def before_save(self):
|
||||||
|
self.set_matched_unset_payment_requests_to_response()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if self.difference_amount:
|
if self.difference_amount:
|
||||||
frappe.throw(_("Difference Amount must be zero"))
|
frappe.throw(_("Difference Amount must be zero"))
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
|
||||||
self.update_payment_schedule()
|
self.update_payment_schedule()
|
||||||
self.set_payment_req_status()
|
self.update_payment_requests()
|
||||||
|
self.make_advance_payment_ledger_entries()
|
||||||
|
self.update_advance_paid() # advance_paid_status depends on the payment request amount
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def set_liability_account(self):
|
def set_liability_account(self):
|
||||||
@@ -228,9 +234,21 @@ class PaymentEntry(AccountsController):
|
|||||||
self.is_opening = "No"
|
self.is_opening = "No"
|
||||||
return
|
return
|
||||||
|
|
||||||
liability_account = get_party_account(
|
accounts = get_party_account(self.party_type, self.party, self.company, include_advance=True)
|
||||||
self.party_type, self.party, self.company, include_advance=True
|
|
||||||
)[1]
|
liability_account = accounts[1] if len(accounts) > 1 else None
|
||||||
|
fieldname = (
|
||||||
|
"default_advance_received_account"
|
||||||
|
if self.party_type == "Customer"
|
||||||
|
else "default_advance_paid_account"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not liability_account:
|
||||||
|
throw(
|
||||||
|
_("Please set default {0} in Company {1}").format(
|
||||||
|
frappe.bold(frappe.get_meta("Company").get_label(fieldname)), frappe.bold(self.company)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.set(self.party_account_field, liability_account)
|
self.set(self.party_account_field, liability_account)
|
||||||
|
|
||||||
@@ -255,34 +273,40 @@ class PaymentEntry(AccountsController):
|
|||||||
"Repost Accounting Ledger Items",
|
"Repost Accounting Ledger Items",
|
||||||
"Unreconcile Payment",
|
"Unreconcile Payment",
|
||||||
"Unreconcile Payment Entries",
|
"Unreconcile Payment Entries",
|
||||||
|
"Advance Payment Ledger Entry",
|
||||||
)
|
)
|
||||||
super().on_cancel()
|
super().on_cancel()
|
||||||
self.make_gl_entries(cancel=1)
|
self.make_gl_entries(cancel=1)
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
|
||||||
self.delink_advance_entry_references()
|
self.delink_advance_entry_references()
|
||||||
self.update_payment_schedule(cancel=1)
|
self.update_payment_schedule(cancel=1)
|
||||||
self.set_payment_req_status()
|
self.update_payment_requests(cancel=True)
|
||||||
|
self.make_advance_payment_ledger_entries()
|
||||||
|
self.update_advance_paid() # advance_paid_status depends on the payment request amount
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def set_payment_req_status(self):
|
def update_payment_requests(self, cancel=False):
|
||||||
from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status
|
from erpnext.accounts.doctype.payment_request.payment_request import (
|
||||||
|
update_payment_requests_as_per_pe_references,
|
||||||
|
)
|
||||||
|
|
||||||
update_payment_req_status(self, None)
|
update_payment_requests_as_per_pe_references(self.references, cancel=cancel)
|
||||||
|
|
||||||
def update_outstanding_amounts(self):
|
def update_outstanding_amounts(self):
|
||||||
self.set_missing_ref_details(force=True)
|
self.set_missing_ref_details(force=True)
|
||||||
|
|
||||||
def validate_duplicate_entry(self):
|
def validate_duplicate_entry(self):
|
||||||
reference_names = []
|
reference_names = set()
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
if (d.reference_doctype, d.reference_name, d.payment_term) in reference_names:
|
key = (d.reference_doctype, d.reference_name, d.payment_term, d.payment_request)
|
||||||
|
if key in reference_names:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{0}: Duplicate entry in References {1} {2}").format(
|
_("Row #{0}: Duplicate entry in References {1} {2}").format(
|
||||||
d.idx, d.reference_doctype, d.reference_name
|
d.idx, d.reference_doctype, d.reference_name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
reference_names.append((d.reference_doctype, d.reference_name, d.payment_term))
|
|
||||||
|
reference_names.add(key)
|
||||||
|
|
||||||
def set_bank_account_data(self):
|
def set_bank_account_data(self):
|
||||||
if self.bank_account:
|
if self.bank_account:
|
||||||
@@ -308,6 +332,8 @@ class PaymentEntry(AccountsController):
|
|||||||
if self.payment_type == "Internal Transfer":
|
if self.payment_type == "Internal Transfer":
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.validate_allocated_amount_as_per_payment_request()
|
||||||
|
|
||||||
if self.party_type in ("Customer", "Supplier"):
|
if self.party_type in ("Customer", "Supplier"):
|
||||||
self.validate_allocated_amount_with_latest_data()
|
self.validate_allocated_amount_with_latest_data()
|
||||||
else:
|
else:
|
||||||
@@ -320,6 +346,27 @@ class PaymentEntry(AccountsController):
|
|||||||
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
|
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||||
frappe.throw(fail_message.format(d.idx))
|
frappe.throw(fail_message.format(d.idx))
|
||||||
|
|
||||||
|
def validate_allocated_amount_as_per_payment_request(self):
|
||||||
|
"""
|
||||||
|
Allocated amount should not be greater than the outstanding amount of the Payment Request.
|
||||||
|
"""
|
||||||
|
if not self.references:
|
||||||
|
return
|
||||||
|
|
||||||
|
pr_outstanding_amounts = get_payment_request_outstanding_set_in_references(self.references)
|
||||||
|
|
||||||
|
if not pr_outstanding_amounts:
|
||||||
|
return
|
||||||
|
|
||||||
|
for ref in self.references:
|
||||||
|
if ref.payment_request and ref.allocated_amount > pr_outstanding_amounts[ref.payment_request]:
|
||||||
|
frappe.throw(
|
||||||
|
msg=_(
|
||||||
|
"Row #{0}: Allocated Amount cannot be greater than Outstanding Amount of Payment Request {1}"
|
||||||
|
).format(ref.idx, get_link_to_form("Payment Request", ref.payment_request)),
|
||||||
|
title=_("Invalid Allocated Amount"),
|
||||||
|
)
|
||||||
|
|
||||||
def term_based_allocation_enabled_for_reference(
|
def term_based_allocation_enabled_for_reference(
|
||||||
self, reference_doctype: str, reference_name: str
|
self, reference_doctype: str, reference_name: str
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@@ -524,7 +571,10 @@ class PaymentEntry(AccountsController):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if field == "exchange_rate" or not d.get(field) or force:
|
if field == "exchange_rate" or not d.get(field) or force:
|
||||||
d.db_set(field, value)
|
if self.get("_action") in ("submit", "cancel"):
|
||||||
|
d.db_set(field, value)
|
||||||
|
else:
|
||||||
|
d.set(field, value)
|
||||||
|
|
||||||
def validate_payment_type(self):
|
def validate_payment_type(self):
|
||||||
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
|
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
|
||||||
@@ -1216,6 +1266,10 @@ class PaymentEntry(AccountsController):
|
|||||||
if not self.party_account:
|
if not self.party_account:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
|
||||||
|
"advance_payment_payable_doctypes"
|
||||||
|
)
|
||||||
|
|
||||||
if self.payment_type == "Receive":
|
if self.payment_type == "Receive":
|
||||||
against_account = self.paid_to
|
against_account = self.paid_to
|
||||||
else:
|
else:
|
||||||
@@ -1261,11 +1315,30 @@ class PaymentEntry(AccountsController):
|
|||||||
{
|
{
|
||||||
dr_or_cr: allocated_amount_in_company_currency,
|
dr_or_cr: allocated_amount_in_company_currency,
|
||||||
dr_or_cr + "_in_account_currency": d.allocated_amount,
|
dr_or_cr + "_in_account_currency": d.allocated_amount,
|
||||||
"against_voucher_type": d.reference_doctype,
|
|
||||||
"against_voucher": d.reference_name,
|
|
||||||
"cost_center": cost_center,
|
"cost_center": cost_center,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.book_advance_payments_in_separate_party_account:
|
||||||
|
if d.reference_doctype in advance_payment_doctypes:
|
||||||
|
# Upon reconciliation, whole ledger will be reposted. So, reference to SO/PO is fine
|
||||||
|
gle.update(
|
||||||
|
{
|
||||||
|
"against_voucher_type": d.reference_doctype,
|
||||||
|
"against_voucher": d.reference_name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Do not reference Invoices while Advance is in separate party account
|
||||||
|
gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name})
|
||||||
|
else:
|
||||||
|
gle.update(
|
||||||
|
{
|
||||||
|
"against_voucher_type": d.reference_doctype,
|
||||||
|
"against_voucher": d.reference_name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
gl_entries.append(gle)
|
gl_entries.append(gle)
|
||||||
|
|
||||||
if self.unallocated_amount:
|
if self.unallocated_amount:
|
||||||
@@ -1692,6 +1765,380 @@ class PaymentEntry(AccountsController):
|
|||||||
|
|
||||||
return current_tax_fraction
|
return current_tax_fraction
|
||||||
|
|
||||||
|
def set_matched_unset_payment_requests_to_response(self):
|
||||||
|
"""
|
||||||
|
Find matched Payment Requests for those references which have no Payment Request set.\n
|
||||||
|
And set to `frappe.response` to show in the frontend for allocation.
|
||||||
|
"""
|
||||||
|
if not self.references:
|
||||||
|
return
|
||||||
|
|
||||||
|
matched_payment_requests = get_matched_payment_request_of_references(
|
||||||
|
[row for row in self.references if not row.payment_request]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not matched_payment_requests:
|
||||||
|
return
|
||||||
|
|
||||||
|
frappe.response["matched_payment_requests"] = matched_payment_requests
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def allocate_amount_to_references(self, paid_amount, paid_amount_change, allocate_payment_amount):
|
||||||
|
"""
|
||||||
|
Allocate `Allocated Amount` and `Payment Request` against `Reference` based on `Paid Amount` and `Outstanding Amount`.\n
|
||||||
|
:param paid_amount: Paid Amount / Received Amount.
|
||||||
|
:param paid_amount_change: Flag to check if `Paid Amount` is changed or not.
|
||||||
|
:param allocate_payment_amount: Flag to allocate amount or not. (Payment Request is also dependent on this flag)
|
||||||
|
"""
|
||||||
|
if not self.references:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not allocate_payment_amount:
|
||||||
|
for ref in self.references:
|
||||||
|
ref.allocated_amount = 0
|
||||||
|
return
|
||||||
|
|
||||||
|
# calculating outstanding amounts
|
||||||
|
precision = self.precision("paid_amount")
|
||||||
|
total_positive_outstanding_including_order = 0
|
||||||
|
total_negative_outstanding = 0
|
||||||
|
paid_amount -= sum(flt(d.amount, precision) for d in self.deductions)
|
||||||
|
|
||||||
|
for ref in self.references:
|
||||||
|
reference_outstanding_amount = ref.outstanding_amount
|
||||||
|
abs_outstanding_amount = abs(reference_outstanding_amount)
|
||||||
|
|
||||||
|
if reference_outstanding_amount > 0:
|
||||||
|
total_positive_outstanding_including_order += abs_outstanding_amount
|
||||||
|
else:
|
||||||
|
total_negative_outstanding += abs_outstanding_amount
|
||||||
|
|
||||||
|
# calculating allocated outstanding amounts
|
||||||
|
allocated_negative_outstanding = 0
|
||||||
|
allocated_positive_outstanding = 0
|
||||||
|
|
||||||
|
# checking party type and payment type
|
||||||
|
if (self.payment_type == "Receive" and self.party_type == "Customer") or (
|
||||||
|
self.payment_type == "Pay" and self.party_type in ("Supplier", "Employee")
|
||||||
|
):
|
||||||
|
if total_positive_outstanding_including_order > paid_amount:
|
||||||
|
remaining_outstanding = flt(
|
||||||
|
total_positive_outstanding_including_order - paid_amount, precision
|
||||||
|
)
|
||||||
|
allocated_negative_outstanding = min(remaining_outstanding, total_negative_outstanding)
|
||||||
|
|
||||||
|
allocated_positive_outstanding = paid_amount + allocated_negative_outstanding
|
||||||
|
|
||||||
|
elif self.party_type in ("Supplier", "Employee"):
|
||||||
|
if paid_amount > total_negative_outstanding:
|
||||||
|
if total_negative_outstanding == 0:
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Cannot {0} from {2} without any negative outstanding invoice").format(
|
||||||
|
self.payment_type,
|
||||||
|
self.party_type,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
frappe.msgprint(
|
||||||
|
_("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
|
||||||
|
total_negative_outstanding
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
else:
|
||||||
|
allocated_positive_outstanding = flt(total_negative_outstanding - paid_amount, precision)
|
||||||
|
allocated_negative_outstanding = paid_amount + min(
|
||||||
|
total_positive_outstanding_including_order, allocated_positive_outstanding
|
||||||
|
)
|
||||||
|
|
||||||
|
# inner function to set `allocated_amount` to those row which have no PR
|
||||||
|
def _allocation_to_unset_pr_row(
|
||||||
|
row, outstanding_amount, allocated_positive_outstanding, allocated_negative_outstanding
|
||||||
|
):
|
||||||
|
if outstanding_amount > 0 and allocated_positive_outstanding >= 0:
|
||||||
|
row.allocated_amount = min(allocated_positive_outstanding, outstanding_amount)
|
||||||
|
allocated_positive_outstanding = flt(
|
||||||
|
allocated_positive_outstanding - row.allocated_amount, precision
|
||||||
|
)
|
||||||
|
elif outstanding_amount < 0 and allocated_negative_outstanding:
|
||||||
|
row.allocated_amount = min(allocated_negative_outstanding, abs(outstanding_amount)) * -1
|
||||||
|
allocated_negative_outstanding = flt(
|
||||||
|
allocated_negative_outstanding - abs(row.allocated_amount), precision
|
||||||
|
)
|
||||||
|
return allocated_positive_outstanding, allocated_negative_outstanding
|
||||||
|
|
||||||
|
# allocate amount based on `paid_amount` is changed or not
|
||||||
|
if not paid_amount_change:
|
||||||
|
for ref in self.references:
|
||||||
|
allocated_positive_outstanding, allocated_negative_outstanding = _allocation_to_unset_pr_row(
|
||||||
|
ref,
|
||||||
|
ref.outstanding_amount,
|
||||||
|
allocated_positive_outstanding,
|
||||||
|
allocated_negative_outstanding,
|
||||||
|
)
|
||||||
|
|
||||||
|
allocate_open_payment_requests_to_references(self.references, self.precision("paid_amount"))
|
||||||
|
|
||||||
|
else:
|
||||||
|
payment_request_outstanding_amounts = (
|
||||||
|
get_payment_request_outstanding_set_in_references(self.references) or {}
|
||||||
|
)
|
||||||
|
references_outstanding_amounts = get_references_outstanding_amount(self.references) or {}
|
||||||
|
remaining_references_allocated_amounts = references_outstanding_amounts.copy()
|
||||||
|
|
||||||
|
# Re allocate amount to those references which have PR set (Higher priority)
|
||||||
|
for ref in self.references:
|
||||||
|
if not ref.payment_request:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# fetch outstanding_amount of `Reference` (Payment Term) and `Payment Request` to allocate new amount
|
||||||
|
key = (ref.reference_doctype, ref.reference_name, ref.get("payment_term"))
|
||||||
|
reference_outstanding_amount = references_outstanding_amounts[key]
|
||||||
|
pr_outstanding_amount = payment_request_outstanding_amounts[ref.payment_request]
|
||||||
|
|
||||||
|
if reference_outstanding_amount > 0 and allocated_positive_outstanding >= 0:
|
||||||
|
# allocate amount according to outstanding amounts
|
||||||
|
outstanding_amounts = (
|
||||||
|
allocated_positive_outstanding,
|
||||||
|
reference_outstanding_amount,
|
||||||
|
pr_outstanding_amount,
|
||||||
|
)
|
||||||
|
|
||||||
|
ref.allocated_amount = min(outstanding_amounts)
|
||||||
|
|
||||||
|
# update amounts to track allocation
|
||||||
|
allocated_amount = ref.allocated_amount
|
||||||
|
allocated_positive_outstanding = flt(
|
||||||
|
allocated_positive_outstanding - allocated_amount, precision
|
||||||
|
)
|
||||||
|
remaining_references_allocated_amounts[key] = flt(
|
||||||
|
remaining_references_allocated_amounts[key] - allocated_amount, precision
|
||||||
|
)
|
||||||
|
payment_request_outstanding_amounts[ref.payment_request] = flt(
|
||||||
|
payment_request_outstanding_amounts[ref.payment_request] - allocated_amount, precision
|
||||||
|
)
|
||||||
|
|
||||||
|
elif reference_outstanding_amount < 0 and allocated_negative_outstanding:
|
||||||
|
# allocate amount according to outstanding amounts
|
||||||
|
outstanding_amounts = (
|
||||||
|
allocated_negative_outstanding,
|
||||||
|
abs(reference_outstanding_amount),
|
||||||
|
pr_outstanding_amount,
|
||||||
|
)
|
||||||
|
|
||||||
|
ref.allocated_amount = min(outstanding_amounts) * -1
|
||||||
|
|
||||||
|
# update amounts to track allocation
|
||||||
|
allocated_amount = abs(ref.allocated_amount)
|
||||||
|
allocated_negative_outstanding = flt(
|
||||||
|
allocated_negative_outstanding - allocated_amount, precision
|
||||||
|
)
|
||||||
|
remaining_references_allocated_amounts[key] += allocated_amount # negative amount
|
||||||
|
payment_request_outstanding_amounts[ref.payment_request] = flt(
|
||||||
|
payment_request_outstanding_amounts[ref.payment_request] - allocated_amount, precision
|
||||||
|
)
|
||||||
|
# Re allocate amount to those references which have no PR (Lower priority)
|
||||||
|
for ref in self.references:
|
||||||
|
if ref.payment_request:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (ref.reference_doctype, ref.reference_name, ref.get("payment_term"))
|
||||||
|
reference_outstanding_amount = remaining_references_allocated_amounts[key]
|
||||||
|
|
||||||
|
allocated_positive_outstanding, allocated_negative_outstanding = _allocation_to_unset_pr_row(
|
||||||
|
ref,
|
||||||
|
reference_outstanding_amount,
|
||||||
|
allocated_positive_outstanding,
|
||||||
|
allocated_negative_outstanding,
|
||||||
|
)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def set_matched_payment_requests(self, matched_payment_requests):
|
||||||
|
"""
|
||||||
|
Set `Payment Request` against `Reference` based on `matched_payment_requests`.\n
|
||||||
|
:param matched_payment_requests: List of tuple of matched Payment Requests.
|
||||||
|
|
||||||
|
---
|
||||||
|
Example: [(reference_doctype, reference_name, allocated_amount, payment_request), ...]
|
||||||
|
"""
|
||||||
|
if not self.references or not matched_payment_requests:
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(matched_payment_requests, str):
|
||||||
|
matched_payment_requests = json.loads(matched_payment_requests)
|
||||||
|
|
||||||
|
# modify matched_payment_requests
|
||||||
|
# like (reference_doctype, reference_name, allocated_amount): payment_request
|
||||||
|
payment_requests = {}
|
||||||
|
|
||||||
|
for row in matched_payment_requests:
|
||||||
|
key = tuple(row[:3])
|
||||||
|
payment_requests[key] = row[3]
|
||||||
|
|
||||||
|
for ref in self.references:
|
||||||
|
if ref.payment_request:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (ref.reference_doctype, ref.reference_name, ref.allocated_amount)
|
||||||
|
|
||||||
|
if key in payment_requests:
|
||||||
|
ref.payment_request = payment_requests[key]
|
||||||
|
del payment_requests[key] # to avoid duplicate allocation
|
||||||
|
|
||||||
|
|
||||||
|
def get_matched_payment_request_of_references(references=None):
|
||||||
|
"""
|
||||||
|
Get those `Payment Requests` which are matched with `References`.\n
|
||||||
|
- Amount must be same.
|
||||||
|
- Only single `Payment Request` available for this amount.
|
||||||
|
|
||||||
|
Example: [(reference_doctype, reference_name, allocated_amount, payment_request), ...]
|
||||||
|
"""
|
||||||
|
if not references:
|
||||||
|
return
|
||||||
|
|
||||||
|
# to fetch matched rows
|
||||||
|
refs = {
|
||||||
|
(row.reference_doctype, row.reference_name, row.allocated_amount)
|
||||||
|
for row in references
|
||||||
|
if row.reference_doctype and row.reference_name and row.allocated_amount
|
||||||
|
}
|
||||||
|
|
||||||
|
if not refs:
|
||||||
|
return
|
||||||
|
|
||||||
|
PR = frappe.qb.DocType("Payment Request")
|
||||||
|
|
||||||
|
# query to group by reference_doctype, reference_name, outstanding_amount
|
||||||
|
subquery = (
|
||||||
|
frappe.qb.from_(PR)
|
||||||
|
.select(
|
||||||
|
PR.reference_doctype,
|
||||||
|
PR.reference_name,
|
||||||
|
PR.outstanding_amount.as_("allocated_amount"),
|
||||||
|
PR.name.as_("payment_request"),
|
||||||
|
Count("*").as_("count"),
|
||||||
|
)
|
||||||
|
.where(Tuple(PR.reference_doctype, PR.reference_name, PR.outstanding_amount).isin(refs))
|
||||||
|
.where(PR.status != "Paid")
|
||||||
|
.where(PR.docstatus == 1)
|
||||||
|
.groupby(PR.reference_doctype, PR.reference_name, PR.outstanding_amount)
|
||||||
|
)
|
||||||
|
|
||||||
|
# query to fetch matched rows which are single
|
||||||
|
matched_prs = (
|
||||||
|
frappe.qb.from_(subquery)
|
||||||
|
.select(
|
||||||
|
subquery.reference_doctype,
|
||||||
|
subquery.reference_name,
|
||||||
|
subquery.allocated_amount,
|
||||||
|
subquery.payment_request,
|
||||||
|
)
|
||||||
|
.where(subquery.count == 1)
|
||||||
|
.run()
|
||||||
|
)
|
||||||
|
|
||||||
|
return matched_prs if matched_prs else None
|
||||||
|
|
||||||
|
|
||||||
|
def get_references_outstanding_amount(references=None):
|
||||||
|
"""
|
||||||
|
Fetch accurate outstanding amount of `References`.\n
|
||||||
|
- If `Payment Term` is set, then fetch outstanding amount from `Payment Schedule`.
|
||||||
|
- If `Payment Term` is not set, then fetch outstanding amount from `References` it self.
|
||||||
|
|
||||||
|
Example: {(reference_doctype, reference_name, payment_term): outstanding_amount, ...}
|
||||||
|
"""
|
||||||
|
if not references:
|
||||||
|
return
|
||||||
|
|
||||||
|
refs_with_payment_term = get_outstanding_of_references_with_payment_term(references) or {}
|
||||||
|
refs_without_payment_term = get_outstanding_of_references_with_no_payment_term(references) or {}
|
||||||
|
|
||||||
|
return {**refs_with_payment_term, **refs_without_payment_term}
|
||||||
|
|
||||||
|
|
||||||
|
def get_outstanding_of_references_with_payment_term(references=None):
|
||||||
|
"""
|
||||||
|
Fetch outstanding amount of `References` which have `Payment Term` set.\n
|
||||||
|
Example: {(reference_doctype, reference_name, payment_term): outstanding_amount, ...}
|
||||||
|
"""
|
||||||
|
if not references:
|
||||||
|
return
|
||||||
|
|
||||||
|
refs = {
|
||||||
|
(row.reference_doctype, row.reference_name, row.payment_term)
|
||||||
|
for row in references
|
||||||
|
if row.reference_doctype and row.reference_name and row.payment_term
|
||||||
|
}
|
||||||
|
|
||||||
|
if not refs:
|
||||||
|
return
|
||||||
|
|
||||||
|
PS = frappe.qb.DocType("Payment Schedule")
|
||||||
|
|
||||||
|
response = (
|
||||||
|
frappe.qb.from_(PS)
|
||||||
|
.select(PS.parenttype, PS.parent, PS.payment_term, PS.outstanding)
|
||||||
|
.where(Tuple(PS.parenttype, PS.parent, PS.payment_term).isin(refs))
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
if not response:
|
||||||
|
return
|
||||||
|
|
||||||
|
return {(row.parenttype, row.parent, row.payment_term): row.outstanding for row in response}
|
||||||
|
|
||||||
|
|
||||||
|
def get_outstanding_of_references_with_no_payment_term(references):
|
||||||
|
"""
|
||||||
|
Fetch outstanding amount of `References` which have no `Payment Term` set.\n
|
||||||
|
- Fetch outstanding amount from `References` it self.
|
||||||
|
|
||||||
|
Note: `None` is used for allocation of `Payment Request`
|
||||||
|
Example: {(reference_doctype, reference_name, None): outstanding_amount, ...}
|
||||||
|
"""
|
||||||
|
if not references:
|
||||||
|
return
|
||||||
|
|
||||||
|
outstanding_amounts = {}
|
||||||
|
|
||||||
|
for ref in references:
|
||||||
|
if ref.payment_term:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (ref.reference_doctype, ref.reference_name, None)
|
||||||
|
|
||||||
|
if key not in outstanding_amounts:
|
||||||
|
outstanding_amounts[key] = ref.outstanding_amount
|
||||||
|
|
||||||
|
return outstanding_amounts
|
||||||
|
|
||||||
|
|
||||||
|
def get_payment_request_outstanding_set_in_references(references=None):
|
||||||
|
"""
|
||||||
|
Fetch outstanding amount of `Payment Request` which are set in `References`.\n
|
||||||
|
Example: {payment_request: outstanding_amount, ...}
|
||||||
|
"""
|
||||||
|
if not references:
|
||||||
|
return
|
||||||
|
|
||||||
|
referenced_payment_requests = {row.payment_request for row in references if row.payment_request}
|
||||||
|
|
||||||
|
if not referenced_payment_requests:
|
||||||
|
return
|
||||||
|
|
||||||
|
PR = frappe.qb.DocType("Payment Request")
|
||||||
|
|
||||||
|
response = (
|
||||||
|
frappe.qb.from_(PR)
|
||||||
|
.select(PR.name, PR.outstanding_amount)
|
||||||
|
.where(PR.name.isin(referenced_payment_requests))
|
||||||
|
).run()
|
||||||
|
|
||||||
|
return dict(response) if response else None
|
||||||
|
|
||||||
|
|
||||||
def validate_inclusive_tax(tax, doc):
|
def validate_inclusive_tax(tax, doc):
|
||||||
def _on_previous_row_error(row_range):
|
def _on_previous_row_error(row_range):
|
||||||
@@ -2144,7 +2591,9 @@ def get_party_details(company, party_type, party, date, cost_center=None):
|
|||||||
account_balance = get_balance_on(party_account, date, cost_center=cost_center)
|
account_balance = get_balance_on(party_account, date, cost_center=cost_center)
|
||||||
_party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name"
|
_party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name"
|
||||||
party_name = frappe.db.get_value(party_type, party, _party_name)
|
party_name = frappe.db.get_value(party_type, party, _party_name)
|
||||||
party_balance = get_balance_on(party_type=party_type, party=party, cost_center=cost_center)
|
party_balance = get_balance_on(
|
||||||
|
party_type=party_type, party=party, company=company, cost_center=cost_center
|
||||||
|
)
|
||||||
if party_type in ["Customer", "Supplier"]:
|
if party_type in ["Customer", "Supplier"]:
|
||||||
party_bank_account = get_party_bank_account(party_type, party)
|
party_bank_account = get_party_bank_account(party_type, party)
|
||||||
bank_account = get_default_company_bank_account(company, party_type, party)
|
bank_account = get_default_company_bank_account(company, party_type, party)
|
||||||
@@ -2323,6 +2772,7 @@ def get_payment_entry(
|
|||||||
payment_type=None,
|
payment_type=None,
|
||||||
reference_date=None,
|
reference_date=None,
|
||||||
ignore_permissions=False,
|
ignore_permissions=False,
|
||||||
|
created_from_payment_request=False,
|
||||||
):
|
):
|
||||||
doc = frappe.get_doc(dt, dn)
|
doc = frappe.get_doc(dt, dn)
|
||||||
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
|
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
|
||||||
@@ -2472,9 +2922,179 @@ def get_payment_entry(
|
|||||||
|
|
||||||
pe.set_difference_amount()
|
pe.set_difference_amount()
|
||||||
|
|
||||||
|
# If PE is created from PR directly, then no need to find open PRs for the references
|
||||||
|
if not created_from_payment_request:
|
||||||
|
allocate_open_payment_requests_to_references(pe.references, pe.precision("paid_amount"))
|
||||||
|
|
||||||
return pe
|
return pe
|
||||||
|
|
||||||
|
|
||||||
|
def get_open_payment_requests_for_references(references=None):
|
||||||
|
"""
|
||||||
|
Fetch all unpaid Payment Requests for the references. \n
|
||||||
|
- Each reference can have multiple Payment Requests. \n
|
||||||
|
|
||||||
|
Example: {("Sales Invoice", "SINV-00001"): {"PREQ-00001": 1000, "PREQ-00002": 2000}}
|
||||||
|
"""
|
||||||
|
if not references:
|
||||||
|
return
|
||||||
|
|
||||||
|
refs = {
|
||||||
|
(row.reference_doctype, row.reference_name)
|
||||||
|
for row in references
|
||||||
|
if row.reference_doctype and row.reference_name and row.allocated_amount
|
||||||
|
}
|
||||||
|
|
||||||
|
if not refs:
|
||||||
|
return
|
||||||
|
|
||||||
|
PR = frappe.qb.DocType("Payment Request")
|
||||||
|
|
||||||
|
response = (
|
||||||
|
frappe.qb.from_(PR)
|
||||||
|
.select(PR.name, PR.reference_doctype, PR.reference_name, PR.outstanding_amount)
|
||||||
|
.where(Tuple(PR.reference_doctype, PR.reference_name).isin(list(refs)))
|
||||||
|
.where(PR.status != "Paid")
|
||||||
|
.where(PR.docstatus == 1)
|
||||||
|
.orderby(Coalesce(PR.transaction_date, PR.creation), order=frappe.qb.asc)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
if not response:
|
||||||
|
return
|
||||||
|
|
||||||
|
reference_payment_requests = {}
|
||||||
|
|
||||||
|
for row in response:
|
||||||
|
key = (row.reference_doctype, row.reference_name)
|
||||||
|
|
||||||
|
if key not in reference_payment_requests:
|
||||||
|
reference_payment_requests[key] = {row.name: row.outstanding_amount}
|
||||||
|
else:
|
||||||
|
reference_payment_requests[key][row.name] = row.outstanding_amount
|
||||||
|
|
||||||
|
return reference_payment_requests
|
||||||
|
|
||||||
|
|
||||||
|
def allocate_open_payment_requests_to_references(references=None, precision=None):
|
||||||
|
"""
|
||||||
|
Allocate unpaid Payment Requests to the references. \n
|
||||||
|
---
|
||||||
|
- Allocation based on below factors
|
||||||
|
- Reference Allocated Amount
|
||||||
|
- Reference Outstanding Amount (With Payment Terms or without Payment Terms)
|
||||||
|
- Reference Payment Request's outstanding amount
|
||||||
|
---
|
||||||
|
- Allocation based on below scenarios
|
||||||
|
- Reference's Allocated Amount == Payment Request's Outstanding Amount
|
||||||
|
- Allocate the Payment Request to the reference
|
||||||
|
- This PR will not be allocated further
|
||||||
|
- Reference's Allocated Amount < Payment Request's Outstanding Amount
|
||||||
|
- Allocate the Payment Request to the reference
|
||||||
|
- Reduce the PR's outstanding amount by the allocated amount
|
||||||
|
- This PR can be allocated further
|
||||||
|
- Reference's Allocated Amount > Payment Request's Outstanding Amount
|
||||||
|
- Allocate the Payment Request to the reference
|
||||||
|
- Reduce Allocated Amount of the reference by the PR's outstanding amount
|
||||||
|
- Create a new row for the remaining amount until the Allocated Amount is 0
|
||||||
|
- Allocate PR if available
|
||||||
|
---
|
||||||
|
- Note:
|
||||||
|
- Priority is given to the first Payment Request of respective references.
|
||||||
|
- Single Reference can have multiple rows.
|
||||||
|
- With Payment Terms or without Payment Terms
|
||||||
|
- With Payment Request or without Payment Request
|
||||||
|
"""
|
||||||
|
if not references:
|
||||||
|
return
|
||||||
|
|
||||||
|
# get all unpaid payment requests for the references
|
||||||
|
references_open_payment_requests = get_open_payment_requests_for_references(references)
|
||||||
|
|
||||||
|
if not references_open_payment_requests:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not precision:
|
||||||
|
precision = references[0].precision("allocated_amount")
|
||||||
|
|
||||||
|
# to manage new rows
|
||||||
|
row_number = 1
|
||||||
|
MOVE_TO_NEXT_ROW = 1
|
||||||
|
TO_SKIP_NEW_ROW = 2
|
||||||
|
|
||||||
|
while row_number <= len(references):
|
||||||
|
row = references[row_number - 1]
|
||||||
|
reference_key = (row.reference_doctype, row.reference_name)
|
||||||
|
|
||||||
|
# update the idx to maintain the order
|
||||||
|
row.idx = row_number
|
||||||
|
|
||||||
|
# unpaid payment requests for the reference
|
||||||
|
reference_payment_requests = references_open_payment_requests.get(reference_key)
|
||||||
|
|
||||||
|
if not reference_payment_requests:
|
||||||
|
row_number += MOVE_TO_NEXT_ROW # to move to next reference row
|
||||||
|
continue
|
||||||
|
|
||||||
|
# get the first payment request and its outstanding amount
|
||||||
|
payment_request, pr_outstanding_amount = next(iter(reference_payment_requests.items()))
|
||||||
|
allocated_amount = row.allocated_amount
|
||||||
|
|
||||||
|
# allocate the payment request to the reference and PR's outstanding amount
|
||||||
|
row.payment_request = payment_request
|
||||||
|
|
||||||
|
if pr_outstanding_amount == allocated_amount:
|
||||||
|
del reference_payment_requests[payment_request]
|
||||||
|
row_number += MOVE_TO_NEXT_ROW
|
||||||
|
|
||||||
|
elif pr_outstanding_amount > allocated_amount:
|
||||||
|
# reduce the outstanding amount of the payment request
|
||||||
|
reference_payment_requests[payment_request] -= allocated_amount
|
||||||
|
row_number += MOVE_TO_NEXT_ROW
|
||||||
|
|
||||||
|
else:
|
||||||
|
# split the reference row to allocate the remaining amount
|
||||||
|
del reference_payment_requests[payment_request]
|
||||||
|
row.allocated_amount = pr_outstanding_amount
|
||||||
|
allocated_amount = flt(allocated_amount - pr_outstanding_amount, precision)
|
||||||
|
|
||||||
|
# set the remaining amount to the next row
|
||||||
|
while allocated_amount:
|
||||||
|
# create a new row for the remaining amount
|
||||||
|
new_row = frappe.copy_doc(row)
|
||||||
|
references.insert(row_number, new_row)
|
||||||
|
|
||||||
|
# get the first payment request and its outstanding amount
|
||||||
|
payment_request, pr_outstanding_amount = next(
|
||||||
|
iter(reference_payment_requests.items()), (None, None)
|
||||||
|
)
|
||||||
|
|
||||||
|
# update new row
|
||||||
|
new_row.idx = row_number + 1
|
||||||
|
new_row.payment_request = payment_request
|
||||||
|
new_row.allocated_amount = min(
|
||||||
|
pr_outstanding_amount if pr_outstanding_amount else allocated_amount, allocated_amount
|
||||||
|
)
|
||||||
|
|
||||||
|
if not payment_request or not pr_outstanding_amount:
|
||||||
|
row_number += TO_SKIP_NEW_ROW
|
||||||
|
break
|
||||||
|
|
||||||
|
elif pr_outstanding_amount == allocated_amount:
|
||||||
|
del reference_payment_requests[payment_request]
|
||||||
|
row_number += TO_SKIP_NEW_ROW
|
||||||
|
break
|
||||||
|
|
||||||
|
elif pr_outstanding_amount > allocated_amount:
|
||||||
|
reference_payment_requests[payment_request] -= allocated_amount
|
||||||
|
row_number += TO_SKIP_NEW_ROW
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
allocated_amount = flt(allocated_amount - pr_outstanding_amount, precision)
|
||||||
|
del reference_payment_requests[payment_request]
|
||||||
|
row_number += MOVE_TO_NEXT_ROW
|
||||||
|
|
||||||
|
|
||||||
def update_accounting_dimensions(pe, doc):
|
def update_accounting_dimensions(pe, doc):
|
||||||
"""
|
"""
|
||||||
Updates accounting dimensions in Payment Entry based on the accounting dimensions in the reference document
|
Updates accounting dimensions in Payment Entry based on the accounting dimensions in the reference document
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
from frappe.utils import add_days, flt, nowdate
|
from frappe.utils import add_days, flt, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import create_account
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
@@ -25,10 +25,19 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
|
|||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
from erpnext.setup.doctype.employee.test_employee import make_employee
|
from erpnext.setup.doctype.employee.test_employee import make_employee
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item", "Currency Exchange"]
|
||||||
|
|
||||||
|
|
||||||
class TestPaymentEntry(FrappeTestCase):
|
class UnitTestPaymentEntry(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for PaymentEntry.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestPaymentEntry(IntegrationTestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
@@ -383,7 +392,7 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
self.assertEqual(si.payment_schedule[0].outstanding, 0)
|
self.assertEqual(si.payment_schedule[0].outstanding, 0)
|
||||||
self.assertEqual(si.payment_schedule[0].discounted_amount, 50)
|
self.assertEqual(si.payment_schedule[0].discounted_amount, 50)
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{
|
{
|
||||||
"allow_multi_currency_invoices_against_single_party_account": 1,
|
"allow_multi_currency_invoices_against_single_party_account": 1,
|
||||||
@@ -610,12 +619,9 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
self.assertEqual(flt(pe.references[0].exchange_gain_loss, 2), -94.74)
|
self.assertEqual(flt(pe.references[0].exchange_gain_loss, 2), -94.74)
|
||||||
|
|
||||||
def test_payment_entry_retrieves_last_exchange_rate(self):
|
def test_payment_entry_retrieves_last_exchange_rate(self):
|
||||||
from erpnext.setup.doctype.currency_exchange.test_currency_exchange import (
|
from erpnext.setup.doctype.currency_exchange.test_currency_exchange import save_new_records
|
||||||
save_new_records,
|
|
||||||
test_records,
|
|
||||||
)
|
|
||||||
|
|
||||||
save_new_records(test_records)
|
save_new_records(self.globalTestRecords["Currency Exchange"])
|
||||||
|
|
||||||
pe = frappe.new_doc("Payment Entry")
|
pe = frappe.new_doc("Payment Entry")
|
||||||
pe.payment_type = "Pay"
|
pe.payment_type = "Pay"
|
||||||
@@ -1090,7 +1096,7 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
}
|
}
|
||||||
self.assertDictEqual(ref_details, expected_response)
|
self.assertDictEqual(ref_details, expected_response)
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{
|
{
|
||||||
"unlink_payment_on_cancellation_of_invoice": 1,
|
"unlink_payment_on_cancellation_of_invoice": 1,
|
||||||
@@ -1185,7 +1191,7 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
si3.cancel()
|
si3.cancel()
|
||||||
si3.delete()
|
si3.delete()
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{
|
{
|
||||||
"unlink_payment_on_cancellation_of_invoice": 1,
|
"unlink_payment_on_cancellation_of_invoice": 1,
|
||||||
@@ -1791,7 +1797,7 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
# 'Is Opening' should always be 'No' for normal advance payments
|
# 'Is Opening' should always be 'No' for normal advance payments
|
||||||
self.assertEqual(gl_with_opening_set, [])
|
self.assertEqual(gl_with_opening_set, [])
|
||||||
|
|
||||||
@change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1})
|
@IntegrationTestCase.change_settings("Accounts Settings", {"delete_linked_ledger_entries": 1})
|
||||||
def test_delete_linked_exchange_gain_loss_journal(self):
|
def test_delete_linked_exchange_gain_loss_journal(self):
|
||||||
from erpnext.accounts.doctype.account.test_account import create_account
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
|
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"due_date",
|
"due_date",
|
||||||
"bill_no",
|
"bill_no",
|
||||||
"payment_term",
|
"payment_term",
|
||||||
|
"payment_term_outstanding",
|
||||||
"account_type",
|
"account_type",
|
||||||
"payment_type",
|
"payment_type",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
@@ -18,7 +19,9 @@
|
|||||||
"allocated_amount",
|
"allocated_amount",
|
||||||
"exchange_rate",
|
"exchange_rate",
|
||||||
"exchange_gain_loss",
|
"exchange_gain_loss",
|
||||||
"account"
|
"account",
|
||||||
|
"payment_request",
|
||||||
|
"payment_request_outstanding"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -120,12 +123,33 @@
|
|||||||
"fieldname": "payment_type",
|
"fieldname": "payment_type",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Payment Type"
|
"label": "Payment Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_request",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Payment Request",
|
||||||
|
"options": "Payment Request"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.payment_term",
|
||||||
|
"fieldname": "payment_term_outstanding",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Payment Term Outstanding",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.payment_request && doc.payment_request_outstanding",
|
||||||
|
"fieldname": "payment_request_outstanding",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"is_virtual": 1,
|
||||||
|
"label": "Payment Request Outstanding",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-04-05 09:44:08.310593",
|
"modified": "2024-09-16 18:11:50.019343",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry Reference",
|
"name": "Payment Entry Reference",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
@@ -25,11 +25,19 @@ class PaymentEntryReference(Document):
|
|||||||
parent: DF.Data
|
parent: DF.Data
|
||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
|
payment_request: DF.Link | None
|
||||||
|
payment_request_outstanding: DF.Float
|
||||||
payment_term: DF.Link | None
|
payment_term: DF.Link | None
|
||||||
|
payment_term_outstanding: DF.Float
|
||||||
payment_type: DF.Data | None
|
payment_type: DF.Data | None
|
||||||
reference_doctype: DF.Link
|
reference_doctype: DF.Link
|
||||||
reference_name: DF.DynamicLink
|
reference_name: DF.DynamicLink
|
||||||
total_amount: DF.Float
|
total_amount: DF.Float
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
pass
|
@property
|
||||||
|
def payment_request_outstanding(self):
|
||||||
|
if not self.payment_request:
|
||||||
|
return
|
||||||
|
|
||||||
|
return frappe.db.get_value("Payment Request", self.payment_request, "outstanding_amount")
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
# test_records = frappe.get_test_records('Payment Gateway Account')
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
test_ignore = ["Payment Gateway"]
|
IGNORE_TEST_RECORD_DEPENDENCIES = ["Payment Gateway"]
|
||||||
|
|
||||||
|
|
||||||
class TestPaymentGatewayAccount(unittest.TestCase):
|
class TestPaymentGatewayAccount(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
@@ -13,7 +13,16 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
|
|||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
|
|
||||||
class TestPaymentLedgerEntry(FrappeTestCase):
|
class UnitTestPaymentLedgerEntry(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for PaymentLedgerEntry.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestPaymentLedgerEntry(IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.ple = qb.DocType("Payment Ledger Entry")
|
self.ple = qb.DocType("Payment Ledger Entry")
|
||||||
self.create_company()
|
self.create_company()
|
||||||
@@ -445,7 +454,7 @@ class TestPaymentLedgerEntry(FrappeTestCase):
|
|||||||
self.assertEqual(pl_entries_for_crnote[0], expected_values[0])
|
self.assertEqual(pl_entries_for_crnote[0], expected_values[0])
|
||||||
self.assertEqual(pl_entries_for_crnote[1], expected_values[1])
|
self.assertEqual(pl_entries_for_crnote[1], expected_values[1])
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1},
|
{"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1},
|
||||||
)
|
)
|
||||||
@@ -474,7 +483,7 @@ class TestPaymentLedgerEntry(FrappeTestCase):
|
|||||||
si.delete()
|
si.delete()
|
||||||
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, si.doctype, si.name)
|
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, si.doctype, si.name)
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1},
|
{"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1},
|
||||||
)
|
)
|
||||||
@@ -507,7 +516,7 @@ class TestPaymentLedgerEntry(FrappeTestCase):
|
|||||||
si.delete()
|
si.delete()
|
||||||
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, si.doctype, si.name)
|
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, si.doctype, si.name)
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{
|
{
|
||||||
"unlink_payment_on_cancellation_of_invoice": 1,
|
"unlink_payment_on_cancellation_of_invoice": 1,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
|
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
|
||||||
@@ -17,7 +17,16 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
|||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
|
||||||
|
|
||||||
class TestPaymentOrder(FrappeTestCase):
|
class UnitTestPaymentOrder(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for PaymentOrder.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestPaymentOrder(IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
|
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
|
||||||
uniq_identifier = frappe.generate_hash(length=10)
|
uniq_identifier = frappe.generate_hash(length=10)
|
||||||
|
|||||||
@@ -323,6 +323,7 @@ class PaymentReconciliation(Document):
|
|||||||
"posting_date": inv.posting_date,
|
"posting_date": inv.posting_date,
|
||||||
"currency": inv.currency,
|
"currency": inv.currency,
|
||||||
"cost_center": inv.cost_center,
|
"cost_center": inv.cost_center,
|
||||||
|
"remarks": inv.remarks,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
from frappe.utils import add_days, add_years, flt, getdate, nowdate, today
|
from frappe.utils import add_days, add_years, flt, getdate, nowdate, today
|
||||||
|
|
||||||
from erpnext import get_default_cost_center
|
from erpnext import get_default_cost_center
|
||||||
@@ -17,10 +17,19 @@ from erpnext.accounts.utils import get_fiscal_year
|
|||||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Item"]
|
||||||
|
|
||||||
|
|
||||||
class TestPaymentReconciliation(FrappeTestCase):
|
class UnitTestPaymentReconciliation(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for PaymentReconciliation.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestPaymentReconciliation(IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.create_company()
|
self.create_company()
|
||||||
self.create_item()
|
self.create_item()
|
||||||
@@ -1104,7 +1113,7 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
|
payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
|
||||||
self.assertCountEqual(payment_vouchers, [je2.name, pe2.name])
|
self.assertCountEqual(payment_vouchers, [je2.name, pe2.name])
|
||||||
|
|
||||||
@change_settings(
|
@IntegrationTestCase.change_settings(
|
||||||
"Accounts Settings",
|
"Accounts Settings",
|
||||||
{
|
{
|
||||||
"allow_multi_currency_invoices_against_single_party_account": 1,
|
"allow_multi_currency_invoices_against_single_party_account": 1,
|
||||||
@@ -1986,13 +1995,15 @@ def make_period_closing_voucher(company, cost_center, posting_date=None, submit=
|
|||||||
parent_account=parent_account,
|
parent_account=parent_account,
|
||||||
doctype="Account",
|
doctype="Account",
|
||||||
)
|
)
|
||||||
|
fy = get_fiscal_year(posting_date, company=company)
|
||||||
pcv = frappe.get_doc(
|
pcv = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Period Closing Voucher",
|
"doctype": "Period Closing Voucher",
|
||||||
"transaction_date": posting_date or today(),
|
"transaction_date": posting_date or today(),
|
||||||
"posting_date": posting_date or today(),
|
"period_start_date": fy[1],
|
||||||
|
"period_end_date": fy[2],
|
||||||
"company": company,
|
"company": company,
|
||||||
"fiscal_year": get_fiscal_year(posting_date or today(), company=company)[0],
|
"fiscal_year": fy[0],
|
||||||
"cost_center": cost_center,
|
"cost_center": cost_center,
|
||||||
"closing_account_head": surplus_account,
|
"closing_account_head": surplus_account,
|
||||||
"remarks": "test",
|
"remarks": "test",
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"amount",
|
"amount",
|
||||||
"difference_amount",
|
"difference_amount",
|
||||||
"sec_break1",
|
"sec_break1",
|
||||||
"remark",
|
"remarks",
|
||||||
"currency",
|
"currency",
|
||||||
"exchange_rate",
|
"exchange_rate",
|
||||||
"cost_center"
|
"cost_center"
|
||||||
@@ -74,12 +74,6 @@
|
|||||||
"fieldname": "sec_break1",
|
"fieldname": "sec_break1",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "remark",
|
|
||||||
"fieldtype": "Small Text",
|
|
||||||
"label": "Remark",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -105,12 +99,18 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Cost Center",
|
"label": "Cost Center",
|
||||||
"options": "Cost Center"
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "remarks",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Remarks",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_virtual": 1,
|
"is_virtual": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:10:10.980445",
|
"modified": "2024-10-29 16:24:43.021230",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Reconciliation Payment",
|
"name": "Payment Reconciliation Payment",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class PaymentReconciliationPayment(Document):
|
|||||||
reference_name: DF.DynamicLink | None
|
reference_name: DF.DynamicLink | None
|
||||||
reference_row: DF.Data | None
|
reference_row: DF.Data | None
|
||||||
reference_type: DF.Link | None
|
reference_type: DF.Link | None
|
||||||
remark: DF.SmallText | None
|
remarks: DF.SmallText | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ frappe.ui.form.on("Payment Request", "refresh", function (frm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(!frm.doc.payment_gateway_account || frm.doc.payment_request_type == "Outward") &&
|
frm.doc.payment_request_type == "Outward" &&
|
||||||
frm.doc.status == "Initiated"
|
["Initiated", "Partially Paid"].includes(frm.doc.status)
|
||||||
) {
|
) {
|
||||||
frm.add_custom_button(__("Create Payment Entry"), function () {
|
frm.add_custom_button(__("Create Payment Entry"), function () {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
|||||||
@@ -15,14 +15,17 @@
|
|||||||
"party_details",
|
"party_details",
|
||||||
"party_type",
|
"party_type",
|
||||||
"party",
|
"party",
|
||||||
|
"party_name",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"reference_doctype",
|
"reference_doctype",
|
||||||
"reference_name",
|
"reference_name",
|
||||||
"transaction_details",
|
"transaction_details",
|
||||||
"grand_total",
|
"grand_total",
|
||||||
|
"currency",
|
||||||
"is_a_subscription",
|
"is_a_subscription",
|
||||||
"column_break_18",
|
"column_break_18",
|
||||||
"currency",
|
"outstanding_amount",
|
||||||
|
"party_account_currency",
|
||||||
"subscription_section",
|
"subscription_section",
|
||||||
"subscription_plans",
|
"subscription_plans",
|
||||||
"bank_account_details",
|
"bank_account_details",
|
||||||
@@ -71,6 +74,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "transaction_date",
|
"fieldname": "transaction_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
|
"in_preview": 1,
|
||||||
"label": "Transaction Date"
|
"label": "Transaction Date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -135,7 +139,8 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "reference_doctype",
|
"options": "reference_doctype",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "transaction_details",
|
"fieldname": "transaction_details",
|
||||||
@@ -143,12 +148,14 @@
|
|||||||
"label": "Transaction Details"
|
"label": "Transaction Details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Amount in customer's currency",
|
"description": "Amount in transaction currency",
|
||||||
"fieldname": "grand_total",
|
"fieldname": "grand_total",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
|
"in_preview": 1,
|
||||||
"label": "Amount",
|
"label": "Amount",
|
||||||
"non_negative": 1,
|
"non_negative": 1,
|
||||||
"options": "currency"
|
"options": "currency",
|
||||||
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -403,6 +410,17 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.docstatus === 1",
|
||||||
|
"description": "Amount in party's bank account currency",
|
||||||
|
"fieldname": "outstanding_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_preview": 1,
|
||||||
|
"label": "Outstanding Amount",
|
||||||
|
"non_negative": 1,
|
||||||
|
"options": "party_account_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -413,13 +431,26 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_pnyv",
|
"fieldname": "column_break_pnyv",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "party_account_currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Party Account Currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "party_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Party Name",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-08-07 16:39:54.288002",
|
"modified": "2024-10-23 12:23:40.117336",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Request",
|
"name": "Payment Request",
|
||||||
@@ -454,6 +485,7 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"show_preview_popup": 1,
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from frappe.query_builder.functions import Sum
|
|||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
from frappe.utils.background_jobs import enqueue
|
from frappe.utils.background_jobs import enqueue
|
||||||
|
|
||||||
|
from erpnext import get_company_currency
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
)
|
)
|
||||||
@@ -19,6 +20,15 @@ from erpnext.accounts.party import get_party_account, get_party_bank_account
|
|||||||
from erpnext.accounts.utils import get_account_currency, get_currency_precision
|
from erpnext.accounts.utils import get_account_currency, get_currency_precision
|
||||||
from erpnext.utilities import payment_app_import_guard
|
from erpnext.utilities import payment_app_import_guard
|
||||||
|
|
||||||
|
ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST = [
|
||||||
|
"Sales Order",
|
||||||
|
"Purchase Order",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Purchase Invoice",
|
||||||
|
"POS Invoice",
|
||||||
|
"Fees",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _get_payment_gateway_controller(*args, **kwargs):
|
def _get_payment_gateway_controller(*args, **kwargs):
|
||||||
with payment_app_import_guard():
|
with payment_app_import_guard():
|
||||||
@@ -46,9 +56,11 @@ class PaymentRequest(Document):
|
|||||||
bank_account: DF.Link | None
|
bank_account: DF.Link | None
|
||||||
bank_account_no: DF.ReadOnly | None
|
bank_account_no: DF.ReadOnly | None
|
||||||
branch_code: DF.ReadOnly | None
|
branch_code: DF.ReadOnly | None
|
||||||
|
company: DF.Link | None
|
||||||
cost_center: DF.Link | None
|
cost_center: DF.Link | None
|
||||||
currency: DF.Link | None
|
currency: DF.Link | None
|
||||||
email_to: DF.Data | None
|
email_to: DF.Data | None
|
||||||
|
failed_reason: DF.Data | None
|
||||||
grand_total: DF.Currency
|
grand_total: DF.Currency
|
||||||
iban: DF.ReadOnly | None
|
iban: DF.ReadOnly | None
|
||||||
is_a_subscription: DF.Check
|
is_a_subscription: DF.Check
|
||||||
@@ -57,16 +69,19 @@ class PaymentRequest(Document):
|
|||||||
mode_of_payment: DF.Link | None
|
mode_of_payment: DF.Link | None
|
||||||
mute_email: DF.Check
|
mute_email: DF.Check
|
||||||
naming_series: DF.Literal["ACC-PRQ-.YYYY.-"]
|
naming_series: DF.Literal["ACC-PRQ-.YYYY.-"]
|
||||||
|
outstanding_amount: DF.Currency
|
||||||
party: DF.DynamicLink | None
|
party: DF.DynamicLink | None
|
||||||
|
party_account_currency: DF.Link | None
|
||||||
|
party_name: DF.Data | None
|
||||||
party_type: DF.Link | None
|
party_type: DF.Link | None
|
||||||
payment_account: DF.ReadOnly | None
|
payment_account: DF.ReadOnly | None
|
||||||
payment_channel: DF.Literal["", "Email", "Phone"]
|
payment_channel: DF.Literal["", "Email", "Phone", "Other"]
|
||||||
payment_gateway: DF.ReadOnly | None
|
payment_gateway: DF.ReadOnly | None
|
||||||
payment_gateway_account: DF.Link | None
|
payment_gateway_account: DF.Link | None
|
||||||
payment_order: DF.Link | None
|
payment_order: DF.Link | None
|
||||||
payment_request_type: DF.Literal["Outward", "Inward"]
|
payment_request_type: DF.Literal["Outward", "Inward"]
|
||||||
payment_url: DF.Data | None
|
payment_url: DF.Data | None
|
||||||
print_format: DF.Literal
|
print_format: DF.Literal[None]
|
||||||
project: DF.Link | None
|
project: DF.Link | None
|
||||||
reference_doctype: DF.Link | None
|
reference_doctype: DF.Link | None
|
||||||
reference_name: DF.DynamicLink | None
|
reference_name: DF.DynamicLink | None
|
||||||
@@ -85,7 +100,6 @@ class PaymentRequest(Document):
|
|||||||
subscription_plans: DF.Table[SubscriptionPlanDetail]
|
subscription_plans: DF.Table[SubscriptionPlanDetail]
|
||||||
swift_number: DF.ReadOnly | None
|
swift_number: DF.ReadOnly | None
|
||||||
transaction_date: DF.Date | None
|
transaction_date: DF.Date | None
|
||||||
company: DF.Link | None
|
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@@ -101,6 +115,12 @@ class PaymentRequest(Document):
|
|||||||
frappe.throw(_("To create a Payment Request reference document is required"))
|
frappe.throw(_("To create a Payment Request reference document is required"))
|
||||||
|
|
||||||
def validate_payment_request_amount(self):
|
def validate_payment_request_amount(self):
|
||||||
|
if self.grand_total == 0:
|
||||||
|
frappe.throw(
|
||||||
|
_("{0} cannot be zero").format(self.get_label_from_fieldname("grand_total")),
|
||||||
|
title=_("Invalid Amount"),
|
||||||
|
)
|
||||||
|
|
||||||
existing_payment_request_amount = flt(
|
existing_payment_request_amount = flt(
|
||||||
get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
|
get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
|
||||||
)
|
)
|
||||||
@@ -150,16 +170,28 @@ class PaymentRequest(Document):
|
|||||||
).format(self.grand_total, amount)
|
).format(self.grand_total, amount)
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_change(self):
|
|
||||||
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
|
||||||
advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
|
|
||||||
"advance_payment_payable_doctypes"
|
|
||||||
)
|
|
||||||
if self.reference_doctype in advance_payment_doctypes:
|
|
||||||
# set advance payment status
|
|
||||||
ref_doc.set_advance_payment_status()
|
|
||||||
|
|
||||||
def before_submit(self):
|
def before_submit(self):
|
||||||
|
if (
|
||||||
|
self.currency != self.party_account_currency
|
||||||
|
and self.party_account_currency == get_company_currency(self.company)
|
||||||
|
):
|
||||||
|
# set outstanding amount in party account currency
|
||||||
|
invoice = frappe.get_value(
|
||||||
|
self.reference_doctype,
|
||||||
|
self.reference_name,
|
||||||
|
["rounded_total", "grand_total", "base_rounded_total", "base_grand_total"],
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
grand_total = invoice.get("rounded_total") or invoice.get("grand_total")
|
||||||
|
base_grand_total = invoice.get("base_rounded_total") or invoice.get("base_grand_total")
|
||||||
|
self.outstanding_amount = flt(
|
||||||
|
self.grand_total / grand_total * base_grand_total,
|
||||||
|
self.precision("outstanding_amount"),
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.outstanding_amount = self.grand_total
|
||||||
|
|
||||||
if self.payment_request_type == "Outward":
|
if self.payment_request_type == "Outward":
|
||||||
self.status = "Initiated"
|
self.status = "Initiated"
|
||||||
elif self.payment_request_type == "Inward":
|
elif self.payment_request_type == "Inward":
|
||||||
@@ -174,6 +206,9 @@ class PaymentRequest(Document):
|
|||||||
self.send_email()
|
self.send_email()
|
||||||
self.make_communication_entry()
|
self.make_communication_entry()
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
self.update_reference_advance_payment_status()
|
||||||
|
|
||||||
def request_phone_payment(self):
|
def request_phone_payment(self):
|
||||||
controller = _get_payment_gateway_controller(self.payment_gateway)
|
controller = _get_payment_gateway_controller(self.payment_gateway)
|
||||||
request_amount = self.get_request_amount()
|
request_amount = self.get_request_amount()
|
||||||
@@ -211,6 +246,7 @@ class PaymentRequest(Document):
|
|||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.check_if_payment_entry_exists()
|
self.check_if_payment_entry_exists()
|
||||||
self.set_as_cancelled()
|
self.set_as_cancelled()
|
||||||
|
self.update_reference_advance_payment_status()
|
||||||
|
|
||||||
def make_invoice(self):
|
def make_invoice(self):
|
||||||
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||||
@@ -254,12 +290,12 @@ class PaymentRequest(Document):
|
|||||||
return controller.get_payment_url(
|
return controller.get_payment_url(
|
||||||
**{
|
**{
|
||||||
"amount": flt(self.grand_total, self.precision("grand_total")),
|
"amount": flt(self.grand_total, self.precision("grand_total")),
|
||||||
"title": data.company.encode("utf-8"),
|
"title": data.company,
|
||||||
"description": self.subject.encode("utf-8"),
|
"description": self.subject,
|
||||||
"reference_doctype": "Payment Request",
|
"reference_doctype": "Payment Request",
|
||||||
"reference_docname": self.name,
|
"reference_docname": self.name,
|
||||||
"payer_email": self.email_to or frappe.session.user,
|
"payer_email": self.email_to or frappe.session.user,
|
||||||
"payer_name": frappe.safe_encode(data.customer_name),
|
"payer_name": data.customer_name,
|
||||||
"order_id": self.name,
|
"order_id": self.name,
|
||||||
"currency": self.currency,
|
"currency": self.currency,
|
||||||
}
|
}
|
||||||
@@ -267,7 +303,7 @@ class PaymentRequest(Document):
|
|||||||
|
|
||||||
def set_as_paid(self):
|
def set_as_paid(self):
|
||||||
if self.payment_channel == "Phone":
|
if self.payment_channel == "Phone":
|
||||||
self.db_set("status", "Paid")
|
self.db_set({"status": "Paid", "outstanding_amount": 0})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
payment_entry = self.create_payment_entry()
|
payment_entry = self.create_payment_entry()
|
||||||
@@ -289,26 +325,32 @@ class PaymentRequest(Document):
|
|||||||
else:
|
else:
|
||||||
party_account = get_party_account("Customer", ref_doc.get("customer"), ref_doc.company)
|
party_account = get_party_account("Customer", ref_doc.get("customer"), ref_doc.company)
|
||||||
|
|
||||||
party_account_currency = ref_doc.get("party_account_currency") or get_account_currency(party_account)
|
party_account_currency = (
|
||||||
|
self.get("party_account_currency")
|
||||||
|
or ref_doc.get("party_account_currency")
|
||||||
|
or get_account_currency(party_account)
|
||||||
|
)
|
||||||
|
|
||||||
|
party_amount = bank_amount = self.outstanding_amount
|
||||||
|
|
||||||
bank_amount = self.grand_total
|
|
||||||
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
|
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
|
||||||
party_amount = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
|
exchange_rate = ref_doc.get("conversion_rate")
|
||||||
else:
|
bank_amount = flt(self.outstanding_amount / exchange_rate, self.precision("grand_total"))
|
||||||
party_amount = self.grand_total
|
|
||||||
|
|
||||||
|
# outstanding amount is already in Part's account currency
|
||||||
payment_entry = get_payment_entry(
|
payment_entry = get_payment_entry(
|
||||||
self.reference_doctype,
|
self.reference_doctype,
|
||||||
self.reference_name,
|
self.reference_name,
|
||||||
party_amount=party_amount,
|
party_amount=party_amount,
|
||||||
bank_account=self.payment_account,
|
bank_account=self.payment_account,
|
||||||
bank_amount=bank_amount,
|
bank_amount=bank_amount,
|
||||||
|
created_from_payment_request=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
payment_entry.update(
|
payment_entry.update(
|
||||||
{
|
{
|
||||||
"mode_of_payment": self.mode_of_payment,
|
"mode_of_payment": self.mode_of_payment,
|
||||||
"reference_no": self.name,
|
"reference_no": self.name, # to prevent validation error
|
||||||
"reference_date": nowdate(),
|
"reference_date": nowdate(),
|
||||||
"remarks": "Payment Entry against {} {} via Payment Request {}".format(
|
"remarks": "Payment Entry against {} {} via Payment Request {}".format(
|
||||||
self.reference_doctype, self.reference_name, self.name
|
self.reference_doctype, self.reference_name, self.name
|
||||||
@@ -316,6 +358,9 @@ class PaymentRequest(Document):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Allocate payment_request for each reference in payment_entry (Payment Term can splits the row)
|
||||||
|
self._allocate_payment_request_to_pe_references(references=payment_entry.references)
|
||||||
|
|
||||||
# Update dimensions
|
# Update dimensions
|
||||||
payment_entry.update(
|
payment_entry.update(
|
||||||
{
|
{
|
||||||
@@ -324,14 +369,6 @@ class PaymentRequest(Document):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if party_account_currency == ref_doc.company_currency and party_account_currency != self.currency:
|
|
||||||
amount = payment_entry.base_paid_amount
|
|
||||||
else:
|
|
||||||
amount = self.grand_total
|
|
||||||
|
|
||||||
payment_entry.received_amount = amount
|
|
||||||
payment_entry.get("references")[0].allocated_amount = amount
|
|
||||||
|
|
||||||
# Update 'Paid Amount' on Forex transactions
|
# Update 'Paid Amount' on Forex transactions
|
||||||
if self.currency != ref_doc.company_currency:
|
if self.currency != ref_doc.company_currency:
|
||||||
if (
|
if (
|
||||||
@@ -426,25 +463,80 @@ class PaymentRequest(Document):
|
|||||||
|
|
||||||
return create_stripe_subscription(gateway_controller, data)
|
return create_stripe_subscription(gateway_controller, data)
|
||||||
|
|
||||||
|
def update_reference_advance_payment_status(self):
|
||||||
|
advance_payment_doctypes = frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
|
||||||
|
"advance_payment_payable_doctypes"
|
||||||
|
)
|
||||||
|
if self.reference_doctype in advance_payment_doctypes:
|
||||||
|
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
|
||||||
|
ref_doc.set_advance_payment_status()
|
||||||
|
|
||||||
|
def _allocate_payment_request_to_pe_references(self, references):
|
||||||
|
"""
|
||||||
|
Allocate the Payment Request to the Payment Entry references based on\n
|
||||||
|
- Allocated Amount.
|
||||||
|
- Outstanding Amount of Payment Request.\n
|
||||||
|
Payment Request is doc itself and references are the rows of Payment Entry.
|
||||||
|
"""
|
||||||
|
if len(references) == 1:
|
||||||
|
references[0].payment_request = self.name
|
||||||
|
return
|
||||||
|
|
||||||
|
precision = references[0].precision("allocated_amount")
|
||||||
|
outstanding_amount = self.outstanding_amount
|
||||||
|
|
||||||
|
# to manage rows
|
||||||
|
row_number = 1
|
||||||
|
MOVE_TO_NEXT_ROW = 1
|
||||||
|
TO_SKIP_NEW_ROW = 2
|
||||||
|
NEW_ROW_ADDED = False
|
||||||
|
|
||||||
|
while row_number <= len(references):
|
||||||
|
row = references[row_number - 1]
|
||||||
|
|
||||||
|
# update the idx to maintain the order
|
||||||
|
row.idx = row_number
|
||||||
|
|
||||||
|
if outstanding_amount == 0:
|
||||||
|
if not NEW_ROW_ADDED:
|
||||||
|
break
|
||||||
|
|
||||||
|
row_number += MOVE_TO_NEXT_ROW
|
||||||
|
continue
|
||||||
|
|
||||||
|
# allocate the payment request to the row
|
||||||
|
row.payment_request = self.name
|
||||||
|
|
||||||
|
if row.allocated_amount <= outstanding_amount:
|
||||||
|
outstanding_amount = flt(outstanding_amount - row.allocated_amount, precision)
|
||||||
|
row_number += MOVE_TO_NEXT_ROW
|
||||||
|
else:
|
||||||
|
remaining_allocated_amount = flt(row.allocated_amount - outstanding_amount, precision)
|
||||||
|
row.allocated_amount = outstanding_amount
|
||||||
|
outstanding_amount = 0
|
||||||
|
|
||||||
|
# create a new row without PR for remaining unallocated amount
|
||||||
|
new_row = frappe.copy_doc(row)
|
||||||
|
references.insert(row_number, new_row)
|
||||||
|
|
||||||
|
# update new row
|
||||||
|
new_row.idx = row_number + 1
|
||||||
|
new_row.payment_request = None
|
||||||
|
new_row.allocated_amount = remaining_allocated_amount
|
||||||
|
|
||||||
|
NEW_ROW_ADDED = True
|
||||||
|
row_number += TO_SKIP_NEW_ROW
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def make_payment_request(**args):
|
def make_payment_request(**args):
|
||||||
"""Make payment request"""
|
"""Make payment request"""
|
||||||
|
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
|
if args.dt not in ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST:
|
||||||
|
frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt)))
|
||||||
|
|
||||||
if ref_doc.doctype not in [
|
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
|
||||||
"Sales Order",
|
|
||||||
"Purchase Order",
|
|
||||||
"Sales Invoice",
|
|
||||||
"Purchase Invoice",
|
|
||||||
"POS Invoice",
|
|
||||||
"Fees",
|
|
||||||
]:
|
|
||||||
frappe.throw(
|
|
||||||
_("Payment Requests cannot be created against: {0}").format(frappe.bold(ref_doc.doctype))
|
|
||||||
)
|
|
||||||
|
|
||||||
gateway_account = get_gateway_details(args) or frappe._dict()
|
gateway_account = get_gateway_details(args) or frappe._dict()
|
||||||
|
|
||||||
@@ -468,11 +560,15 @@ def make_payment_request(**args):
|
|||||||
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
|
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# fetches existing payment request `grand_total` amount
|
||||||
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc.doctype, ref_doc.name)
|
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc.doctype, ref_doc.name)
|
||||||
|
|
||||||
if existing_payment_request_amount:
|
if existing_payment_request_amount:
|
||||||
grand_total -= existing_payment_request_amount
|
grand_total -= existing_payment_request_amount
|
||||||
|
|
||||||
|
if not grand_total:
|
||||||
|
frappe.throw(_("Payment Request is already created"))
|
||||||
|
|
||||||
if draft_payment_request:
|
if draft_payment_request:
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
|
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
|
||||||
@@ -486,6 +582,13 @@ def make_payment_request(**args):
|
|||||||
"Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward"
|
"Outward" if args.get("dt") in ["Purchase Order", "Purchase Invoice"] else "Inward"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
party_type = args.get("party_type") or "Customer"
|
||||||
|
party_account_currency = ref_doc.get("party_account_currency")
|
||||||
|
|
||||||
|
if not party_account_currency:
|
||||||
|
party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company)
|
||||||
|
party_account_currency = get_account_currency(party_account)
|
||||||
|
|
||||||
pr.update(
|
pr.update(
|
||||||
{
|
{
|
||||||
"payment_gateway_account": gateway_account.get("name"),
|
"payment_gateway_account": gateway_account.get("name"),
|
||||||
@@ -494,6 +597,7 @@ def make_payment_request(**args):
|
|||||||
"payment_channel": gateway_account.get("payment_channel"),
|
"payment_channel": gateway_account.get("payment_channel"),
|
||||||
"payment_request_type": args.get("payment_request_type"),
|
"payment_request_type": args.get("payment_request_type"),
|
||||||
"currency": ref_doc.currency,
|
"currency": ref_doc.currency,
|
||||||
|
"party_account_currency": party_account_currency,
|
||||||
"grand_total": grand_total,
|
"grand_total": grand_total,
|
||||||
"mode_of_payment": args.mode_of_payment,
|
"mode_of_payment": args.mode_of_payment,
|
||||||
"email_to": args.recipient_id or ref_doc.owner,
|
"email_to": args.recipient_id or ref_doc.owner,
|
||||||
@@ -502,9 +606,10 @@ def make_payment_request(**args):
|
|||||||
"reference_doctype": ref_doc.doctype,
|
"reference_doctype": ref_doc.doctype,
|
||||||
"reference_name": ref_doc.name,
|
"reference_name": ref_doc.name,
|
||||||
"company": ref_doc.get("company"),
|
"company": ref_doc.get("company"),
|
||||||
"party_type": args.get("party_type") or "Customer",
|
"party_type": party_type,
|
||||||
"party": args.get("party") or ref_doc.get("customer"),
|
"party": args.get("party") or ref_doc.get("customer"),
|
||||||
"bank_account": bank_account,
|
"bank_account": bank_account,
|
||||||
|
"party_name": args.get("party_name") or ref_doc.get("customer_name"),
|
||||||
"make_sales_invoice": (
|
"make_sales_invoice": (
|
||||||
args.make_sales_invoice # new standard
|
args.make_sales_invoice # new standard
|
||||||
or args.order_type == "Shopping Cart" # compat for webshop app
|
or args.order_type == "Shopping Cart" # compat for webshop app
|
||||||
@@ -551,13 +656,14 @@ def get_amount(ref_doc, payment_account=None):
|
|||||||
dt = ref_doc.doctype
|
dt = ref_doc.doctype
|
||||||
if dt in ["Sales Order", "Purchase Order"]:
|
if dt in ["Sales Order", "Purchase Order"]:
|
||||||
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
|
grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
|
||||||
grand_total -= get_paid_amount_against_order(dt, ref_doc.name)
|
|
||||||
elif dt in ["Sales Invoice", "Purchase Invoice"]:
|
elif dt in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
if not ref_doc.get("is_pos"):
|
if not ref_doc.get("is_pos"):
|
||||||
if ref_doc.party_account_currency == ref_doc.currency:
|
if ref_doc.party_account_currency == ref_doc.currency:
|
||||||
grand_total = flt(ref_doc.grand_total)
|
grand_total = flt(ref_doc.rounded_total or ref_doc.grand_total)
|
||||||
else:
|
else:
|
||||||
grand_total = flt(ref_doc.base_grand_total) / ref_doc.conversion_rate
|
grand_total = flt(
|
||||||
|
flt(ref_doc.base_rounded_total or ref_doc.base_grand_total) / ref_doc.conversion_rate
|
||||||
|
)
|
||||||
elif dt == "Sales Invoice":
|
elif dt == "Sales Invoice":
|
||||||
for pay in ref_doc.payments:
|
for pay in ref_doc.payments:
|
||||||
if pay.type == "Phone" and pay.account == payment_account:
|
if pay.type == "Phone" and pay.account == payment_account:
|
||||||
@@ -579,24 +685,20 @@ def get_amount(ref_doc, payment_account=None):
|
|||||||
|
|
||||||
def get_existing_payment_request_amount(ref_dt, ref_dn):
|
def get_existing_payment_request_amount(ref_dt, ref_dn):
|
||||||
"""
|
"""
|
||||||
Get the existing payment request which are unpaid or partially paid for payment channel other than Phone
|
Return the total amount of Payment Requests against a reference document.
|
||||||
and get the summation of existing paid payment request for Phone payment channel.
|
|
||||||
"""
|
"""
|
||||||
existing_payment_request_amount = frappe.db.sql(
|
PR = frappe.qb.DocType("Payment Request")
|
||||||
"""
|
|
||||||
select sum(grand_total)
|
response = (
|
||||||
from `tabPayment Request`
|
frappe.qb.from_(PR)
|
||||||
where
|
.select(Sum(PR.grand_total))
|
||||||
reference_doctype = %s
|
.where(PR.reference_doctype == ref_dt)
|
||||||
and reference_name = %s
|
.where(PR.reference_name == ref_dn)
|
||||||
and docstatus = 1
|
.where(PR.docstatus == 1)
|
||||||
and (status != 'Paid'
|
.run()
|
||||||
or (payment_channel = 'Phone'
|
|
||||||
and status = 'Paid'))
|
|
||||||
""",
|
|
||||||
(ref_dt, ref_dn),
|
|
||||||
)
|
)
|
||||||
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
|
|
||||||
|
return response[0][0] if response[0] else 0
|
||||||
|
|
||||||
|
|
||||||
def get_gateway_details(args): # nosemgrep
|
def get_gateway_details(args): # nosemgrep
|
||||||
@@ -638,41 +740,66 @@ def make_payment_entry(docname):
|
|||||||
return doc.create_payment_entry(submit=False).as_dict()
|
return doc.create_payment_entry(submit=False).as_dict()
|
||||||
|
|
||||||
|
|
||||||
def update_payment_req_status(doc, method):
|
def update_payment_requests_as_per_pe_references(references=None, cancel=False):
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_reference_details
|
"""
|
||||||
|
Update Payment Request's `Status` and `Outstanding Amount` based on Payment Entry Reference's `Allocated Amount`.
|
||||||
|
"""
|
||||||
|
if not references:
|
||||||
|
return
|
||||||
|
|
||||||
for ref in doc.references:
|
precision = references[0].precision("allocated_amount")
|
||||||
payment_request_name = frappe.db.get_value(
|
|
||||||
"Payment Request",
|
referenced_payment_requests = frappe.get_all(
|
||||||
{
|
"Payment Request",
|
||||||
"reference_doctype": ref.reference_doctype,
|
filters={"name": ["in", {row.payment_request for row in references if row.payment_request}]},
|
||||||
"reference_name": ref.reference_name,
|
fields=[
|
||||||
"docstatus": 1,
|
"name",
|
||||||
},
|
"grand_total",
|
||||||
|
"outstanding_amount",
|
||||||
|
"payment_request_type",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
referenced_payment_requests = {pr.name: pr for pr in referenced_payment_requests}
|
||||||
|
|
||||||
|
for ref in references:
|
||||||
|
if not ref.payment_request:
|
||||||
|
continue
|
||||||
|
|
||||||
|
payment_request = referenced_payment_requests[ref.payment_request]
|
||||||
|
pr_outstanding = payment_request["outstanding_amount"]
|
||||||
|
|
||||||
|
# update outstanding amount
|
||||||
|
new_outstanding_amount = flt(
|
||||||
|
pr_outstanding + ref.allocated_amount if cancel else pr_outstanding - ref.allocated_amount,
|
||||||
|
precision,
|
||||||
)
|
)
|
||||||
|
|
||||||
if payment_request_name:
|
# to handle same payment request for the multiple allocations
|
||||||
ref_details = get_reference_details(
|
payment_request["outstanding_amount"] = new_outstanding_amount
|
||||||
ref.reference_doctype,
|
|
||||||
ref.reference_name,
|
if not cancel and new_outstanding_amount < 0:
|
||||||
doc.party_account_currency,
|
frappe.throw(
|
||||||
doc.party_type,
|
msg=_(
|
||||||
doc.party,
|
"The allocated amount is greater than the outstanding amount of Payment Request {0}"
|
||||||
|
).format(ref.payment_request),
|
||||||
|
title=_("Invalid Allocated Amount"),
|
||||||
)
|
)
|
||||||
pay_req_doc = frappe.get_doc("Payment Request", payment_request_name)
|
|
||||||
status = pay_req_doc.status
|
|
||||||
|
|
||||||
if status != "Paid" and not ref_details.outstanding_amount:
|
# update status
|
||||||
status = "Paid"
|
if new_outstanding_amount == payment_request["grand_total"]:
|
||||||
elif status != "Partially Paid" and ref_details.outstanding_amount != ref_details.total_amount:
|
status = "Initiated" if payment_request["payment_request_type"] == "Outward" else "Requested"
|
||||||
status = "Partially Paid"
|
elif new_outstanding_amount == 0:
|
||||||
elif ref_details.outstanding_amount == ref_details.total_amount:
|
status = "Paid"
|
||||||
if pay_req_doc.payment_request_type == "Outward":
|
elif new_outstanding_amount > 0:
|
||||||
status = "Initiated"
|
status = "Partially Paid"
|
||||||
elif pay_req_doc.payment_request_type == "Inward":
|
|
||||||
status = "Requested"
|
|
||||||
|
|
||||||
pay_req_doc.db_set("status", status)
|
# update database
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Payment Request",
|
||||||
|
ref.payment_request,
|
||||||
|
{"outstanding_amount": new_outstanding_amount, "status": status},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_dummy_message(doc):
|
def get_dummy_message(doc):
|
||||||
@@ -758,28 +885,33 @@ def validate_payment(doc, method=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_paid_amount_against_order(dt, dn):
|
@frappe.whitelist()
|
||||||
pe_ref = frappe.qb.DocType("Payment Entry Reference")
|
def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, filters):
|
||||||
if dt == "Sales Order":
|
# permission checks in `get_list()`
|
||||||
inv_dt, inv_field = "Sales Invoice Item", "sales_order"
|
reference_doctype = filters.get("reference_doctype")
|
||||||
else:
|
reference_name = filters.get("reference_doctype")
|
||||||
inv_dt, inv_field = "Purchase Invoice Item", "purchase_order"
|
|
||||||
inv_item = frappe.qb.DocType(inv_dt)
|
if not reference_doctype or not reference_name:
|
||||||
return (
|
return []
|
||||||
frappe.qb.from_(pe_ref)
|
|
||||||
.select(
|
open_payment_requests = frappe.get_list(
|
||||||
Sum(pe_ref.allocated_amount),
|
"Payment Request",
|
||||||
|
filters={
|
||||||
|
"reference_doctype": filters["reference_doctype"],
|
||||||
|
"reference_name": filters["reference_name"],
|
||||||
|
"status": ["!=", "Paid"],
|
||||||
|
"outstanding_amount": ["!=", 0], # for compatibility with old data
|
||||||
|
"docstatus": 1,
|
||||||
|
},
|
||||||
|
fields=["name", "grand_total", "outstanding_amount"],
|
||||||
|
order_by="transaction_date ASC,creation ASC",
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
(
|
||||||
|
pr.name,
|
||||||
|
_("<strong>Grand Total:</strong> {0}").format(pr.grand_total),
|
||||||
|
_("<strong>Outstanding Amount:</strong> {0}").format(pr.outstanding_amount),
|
||||||
)
|
)
|
||||||
.where(
|
for pr in open_payment_requests
|
||||||
(pe_ref.docstatus == 1)
|
]
|
||||||
& (
|
|
||||||
(pe_ref.reference_name == dn)
|
|
||||||
| pe_ref.reference_name.isin(
|
|
||||||
frappe.qb.from_(inv_item)
|
|
||||||
.select(inv_item.parent)
|
|
||||||
.where(inv_item[inv_field] == dn)
|
|
||||||
.distinct()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
).run()[0][0] or 0
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
|
import re
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests import IntegrationTestCase, UnitTestCase
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
|
||||||
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
@@ -14,7 +16,7 @@ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_pur
|
|||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
from erpnext.setup.utils import get_exchange_rate
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
|
|
||||||
test_dependencies = ["Currency Exchange", "Journal Entry", "Contact", "Address"]
|
EXTRA_TEST_RECORD_DEPENDENCIES = ["Currency Exchange", "Journal Entry", "Contact", "Address"]
|
||||||
|
|
||||||
PAYMENT_URL = "https://example.com/payment"
|
PAYMENT_URL = "https://example.com/payment"
|
||||||
|
|
||||||
@@ -55,7 +57,16 @@ payment_method = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TestPaymentRequest(FrappeTestCase):
|
class UnitTestPaymentRequest(UnitTestCase):
|
||||||
|
"""
|
||||||
|
Unit tests for PaymentRequest.
|
||||||
|
Use this class for testing individual functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestPaymentRequest(IntegrationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
for payment_gateway in payment_gateways:
|
for payment_gateway in payment_gateways:
|
||||||
if not frappe.db.get_value("Payment Gateway", payment_gateway["gateway"], "name"):
|
if not frappe.db.get_value("Payment Gateway", payment_gateway["gateway"], "name"):
|
||||||
@@ -216,7 +227,7 @@ class TestPaymentRequest(FrappeTestCase):
|
|||||||
|
|
||||||
def test_payment_entry_against_purchase_invoice(self):
|
def test_payment_entry_against_purchase_invoice(self):
|
||||||
si_usd = make_purchase_invoice(
|
si_usd = make_purchase_invoice(
|
||||||
customer="_Test Supplier USD",
|
supplier="_Test Supplier USD",
|
||||||
debit_to="_Test Payable USD - _TC",
|
debit_to="_Test Payable USD - _TC",
|
||||||
currency="USD",
|
currency="USD",
|
||||||
conversion_rate=50,
|
conversion_rate=50,
|
||||||
@@ -241,7 +252,7 @@ class TestPaymentRequest(FrappeTestCase):
|
|||||||
|
|
||||||
def test_multiple_payment_entry_against_purchase_invoice(self):
|
def test_multiple_payment_entry_against_purchase_invoice(self):
|
||||||
purchase_invoice = make_purchase_invoice(
|
purchase_invoice = make_purchase_invoice(
|
||||||
customer="_Test Supplier USD",
|
supplier="_Test Supplier USD",
|
||||||
debit_to="_Test Payable USD - _TC",
|
debit_to="_Test Payable USD - _TC",
|
||||||
currency="USD",
|
currency="USD",
|
||||||
conversion_rate=50,
|
conversion_rate=50,
|
||||||
@@ -415,3 +426,266 @@ class TestPaymentRequest(FrappeTestCase):
|
|||||||
self.assertEqual(pe.paid_amount, 800)
|
self.assertEqual(pe.paid_amount, 800)
|
||||||
self.assertEqual(pe.base_received_amount, 800)
|
self.assertEqual(pe.base_received_amount, 800)
|
||||||
self.assertEqual(pe.received_amount, 10)
|
self.assertEqual(pe.received_amount, 10)
|
||||||
|
|
||||||
|
def test_multiple_payment_if_partially_paid_for_same_currency(self):
|
||||||
|
so = make_sales_order(currency="INR", qty=1, rate=1000)
|
||||||
|
|
||||||
|
self.assertEqual(so.advance_payment_status, "Not Requested")
|
||||||
|
|
||||||
|
pr = make_payment_request(
|
||||||
|
dt="Sales Order",
|
||||||
|
dn=so.name,
|
||||||
|
mute_email=1,
|
||||||
|
submit_doc=1,
|
||||||
|
return_doc=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pr.grand_total, 1000)
|
||||||
|
self.assertEqual(pr.outstanding_amount, pr.grand_total)
|
||||||
|
self.assertEqual(pr.party_account_currency, pr.currency) # INR
|
||||||
|
self.assertEqual(pr.status, "Requested")
|
||||||
|
|
||||||
|
so.load_from_db()
|
||||||
|
self.assertEqual(so.advance_payment_status, "Requested")
|
||||||
|
|
||||||
|
# to make partial payment
|
||||||
|
pe = pr.create_payment_entry(submit=False)
|
||||||
|
pe.paid_amount = 200
|
||||||
|
pe.references[0].allocated_amount = 200
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
self.assertEqual(pe.references[0].payment_request, pr.name)
|
||||||
|
|
||||||
|
so.load_from_db()
|
||||||
|
self.assertEqual(so.advance_payment_status, "Partially Paid")
|
||||||
|
|
||||||
|
pr.load_from_db()
|
||||||
|
self.assertEqual(pr.status, "Partially Paid")
|
||||||
|
self.assertEqual(pr.outstanding_amount, 800)
|
||||||
|
self.assertEqual(pr.grand_total, 1000)
|
||||||
|
|
||||||
|
# complete payment
|
||||||
|
pe = pr.create_payment_entry()
|
||||||
|
|
||||||
|
self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount
|
||||||
|
self.assertEqual(pe.references[0].allocated_amount, 800)
|
||||||
|
self.assertEqual(pe.references[0].outstanding_amount, 800) # for Orders it is not zero
|
||||||
|
self.assertEqual(pe.references[0].payment_request, pr.name)
|
||||||
|
|
||||||
|
so.load_from_db()
|
||||||
|
self.assertEqual(so.advance_payment_status, "Fully Paid")
|
||||||
|
|
||||||
|
pr.load_from_db()
|
||||||
|
self.assertEqual(pr.status, "Paid")
|
||||||
|
self.assertEqual(pr.outstanding_amount, 0)
|
||||||
|
self.assertEqual(pr.grand_total, 1000)
|
||||||
|
|
||||||
|
# creating a more payment Request must not allowed
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
frappe.exceptions.ValidationError,
|
||||||
|
re.compile(r"Payment Request is already created"),
|
||||||
|
make_payment_request,
|
||||||
|
dt="Sales Order",
|
||||||
|
dn=so.name,
|
||||||
|
mute_email=1,
|
||||||
|
submit_doc=1,
|
||||||
|
return_doc=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
@IntegrationTestCase.change_settings(
|
||||||
|
"Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1}
|
||||||
|
)
|
||||||
|
def test_multiple_payment_if_partially_paid_for_multi_currency(self):
|
||||||
|
pi = make_purchase_invoice(currency="USD", conversion_rate=50, qty=1, rate=100, do_not_save=1)
|
||||||
|
pi.credit_to = "Creditors - _TC"
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
pr = make_payment_request(
|
||||||
|
dt="Purchase Invoice",
|
||||||
|
dn=pi.name,
|
||||||
|
mute_email=1,
|
||||||
|
submit_doc=1,
|
||||||
|
return_doc=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 100 USD -> 5000 INR
|
||||||
|
self.assertEqual(pr.grand_total, 100)
|
||||||
|
self.assertEqual(pr.outstanding_amount, 5000)
|
||||||
|
self.assertEqual(pr.currency, "USD")
|
||||||
|
self.assertEqual(pr.party_account_currency, "INR")
|
||||||
|
self.assertEqual(pr.status, "Initiated")
|
||||||
|
|
||||||
|
# to make partial payment
|
||||||
|
pe = pr.create_payment_entry(submit=False)
|
||||||
|
pe.paid_amount = 2000
|
||||||
|
pe.references[0].allocated_amount = 2000
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
self.assertEqual(pe.references[0].payment_request, pr.name)
|
||||||
|
|
||||||
|
pr.load_from_db()
|
||||||
|
self.assertEqual(pr.status, "Partially Paid")
|
||||||
|
self.assertEqual(pr.outstanding_amount, 3000)
|
||||||
|
self.assertEqual(pr.grand_total, 100)
|
||||||
|
|
||||||
|
# complete payment
|
||||||
|
pe = pr.create_payment_entry()
|
||||||
|
self.assertEqual(pe.paid_amount, 3000) # paid amount set from pr's outstanding amount
|
||||||
|
self.assertEqual(pe.references[0].allocated_amount, 3000)
|
||||||
|
self.assertEqual(pe.references[0].outstanding_amount, 0) # for Invoices it will zero
|
||||||
|
self.assertEqual(pe.references[0].payment_request, pr.name)
|
||||||
|
|
||||||
|
pr.load_from_db()
|
||||||
|
self.assertEqual(pr.status, "Paid")
|
||||||
|
self.assertEqual(pr.outstanding_amount, 0)
|
||||||
|
self.assertEqual(pr.grand_total, 100)
|
||||||
|
|
||||||
|
# creating a more payment Request must not allowed
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
frappe.exceptions.ValidationError,
|
||||||
|
re.compile(r"Payment Request is already created"),
|
||||||
|
make_payment_request,
|
||||||
|
dt="Purchase Invoice",
|
||||||
|
dn=pi.name,
|
||||||
|
mute_email=1,
|
||||||
|
submit_doc=1,
|
||||||
|
return_doc=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_single_payment_with_payment_term_for_same_currency(self):
|
||||||
|
create_payment_terms_template()
|
||||||
|
|
||||||
|
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=20000)
|
||||||
|
po.payment_terms_template = "Test Receivable Template" # 84.746 and 15.254
|
||||||
|
po.save()
|
||||||
|
po.submit()
|
||||||
|
|
||||||
|
self.assertEqual(po.advance_payment_status, "Not Initiated")
|
||||||
|
|
||||||
|
pr = make_payment_request(
|
||||||
|
dt="Purchase Order",
|
||||||
|
dn=po.name,
|
||||||
|
mute_email=1,
|
||||||
|
submit_doc=1,
|
||||||
|
return_doc=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pr.grand_total, 20000)
|
||||||
|
self.assertEqual(pr.outstanding_amount, pr.grand_total)
|
||||||
|
self.assertEqual(pr.party_account_currency, pr.currency) # INR
|
||||||
|
self.assertEqual(pr.status, "Initiated")
|
||||||
|
|
||||||
|
po.load_from_db()
|
||||||
|
self.assertEqual(po.advance_payment_status, "Initiated")
|
||||||
|
|
||||||
|
pe = pr.create_payment_entry()
|
||||||
|
|
||||||
|
self.assertEqual(len(pe.references), 2)
|
||||||
|
self.assertEqual(pe.paid_amount, 20000)
|
||||||
|
|
||||||
|
# check 1st payment term
|
||||||
|
self.assertEqual(pe.references[0].allocated_amount, 16949.2)
|
||||||
|
self.assertEqual(pe.references[0].payment_request, pr.name)
|
||||||
|
|
||||||
|
# check 2nd payment term
|
||||||
|
self.assertEqual(pe.references[1].allocated_amount, 3050.8)
|
||||||
|
self.assertEqual(pe.references[1].payment_request, pr.name)
|
||||||
|
|
||||||
|
po.load_from_db()
|
||||||
|
self.assertEqual(po.advance_payment_status, "Fully Paid")
|
||||||
|
|
||||||
|
pr.load_from_db()
|
||||||
|
self.assertEqual(pr.status, "Paid")
|
||||||
|
self.assertEqual(pr.outstanding_amount, 0)
|
||||||
|
self.assertEqual(pr.grand_total, 20000)
|
||||||
|
|
||||||
|
@IntegrationTestCase.change_settings(
|
||||||
|
"Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1}
|
||||||
|
)
|
||||||
|
def test_single_payment_with_payment_term_for_multi_currency(self):
|
||||||
|
create_payment_terms_template()
|
||||||
|
|
||||||
|
si = create_sales_invoice(
|
||||||
|
do_not_save=1, currency="USD", debit_to="Debtors - _TC", qty=1, rate=200, conversion_rate=50
|
||||||
|
)
|
||||||
|
si.payment_terms_template = "Test Receivable Template" # 84.746 and 15.254
|
||||||
|
si.save()
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
pr = make_payment_request(
|
||||||
|
dt="Sales Invoice",
|
||||||
|
dn=si.name,
|
||||||
|
mute_email=1,
|
||||||
|
submit_doc=1,
|
||||||
|
return_doc=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 200 USD -> 10000 INR
|
||||||
|
self.assertEqual(pr.grand_total, 200)
|
||||||
|
self.assertEqual(pr.outstanding_amount, 10000)
|
||||||
|
self.assertEqual(pr.currency, "USD")
|
||||||
|
self.assertEqual(pr.party_account_currency, "INR")
|
||||||
|
self.assertEqual(pr.status, "Requested")
|
||||||
|
|
||||||
|
pe = pr.create_payment_entry()
|
||||||
|
self.assertEqual(len(pe.references), 2)
|
||||||
|
self.assertEqual(pe.paid_amount, 10000)
|
||||||
|
|
||||||
|
# check 1st payment term
|
||||||
|
# convert it via dollar and conversion_rate
|
||||||
|
self.assertEqual(pe.references[0].allocated_amount, 8474.5) # multi currency conversion
|
||||||
|
self.assertEqual(pe.references[0].payment_request, pr.name)
|
||||||
|
|
||||||
|
# check 2nd payment term
|
||||||
|
self.assertEqual(pe.references[1].allocated_amount, 1525.5) # multi currency conversion
|
||||||
|
self.assertEqual(pe.references[1].payment_request, pr.name)
|
||||||
|
|
||||||
|
pr.load_from_db()
|
||||||
|
self.assertEqual(pr.status, "Paid")
|
||||||
|
self.assertEqual(pr.outstanding_amount, 0)
|
||||||
|
self.assertEqual(pr.grand_total, 200)
|
||||||
|
|
||||||
|
def test_payment_cancel_process(self):
|
||||||
|
so = make_sales_order(currency="INR", qty=1, rate=1000)
|
||||||
|
self.assertEqual(so.advance_payment_status, "Not Requested")
|
||||||
|
|
||||||
|
pr = make_payment_request(
|
||||||
|
dt="Sales Order",
|
||||||
|
dn=so.name,
|
||||||
|
mute_email=1,
|
||||||
|
submit_doc=1,
|
||||||
|
return_doc=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pr.status, "Requested")
|
||||||
|
self.assertEqual(pr.grand_total, 1000)
|
||||||
|
self.assertEqual(pr.outstanding_amount, pr.grand_total)
|
||||||
|
|
||||||
|
so.load_from_db()
|
||||||
|
self.assertEqual(so.advance_payment_status, "Requested")
|
||||||
|
|
||||||
|
pe = pr.create_payment_entry(submit=False)
|
||||||
|
pe.paid_amount = 800
|
||||||
|
pe.references[0].allocated_amount = 800
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
self.assertEqual(pe.references[0].payment_request, pr.name)
|
||||||
|
|
||||||
|
so.load_from_db()
|
||||||
|
self.assertEqual(so.advance_payment_status, "Partially Paid")
|
||||||
|
|
||||||
|
pr.load_from_db()
|
||||||
|
self.assertEqual(pr.status, "Partially Paid")
|
||||||
|
self.assertEqual(pr.outstanding_amount, 200)
|
||||||
|
self.assertEqual(pr.grand_total, 1000)
|
||||||
|
|
||||||
|
# cancelling PE
|
||||||
|
pe.cancel()
|
||||||
|
|
||||||
|
pr.load_from_db()
|
||||||
|
self.assertEqual(pr.status, "Requested")
|
||||||
|
self.assertEqual(pr.outstanding_amount, 1000)
|
||||||
|
self.assertEqual(pr.grand_total, 1000)
|
||||||
|
|
||||||
|
so.load_from_db()
|
||||||
|
self.assertEqual(so.advance_payment_status, "Requested")
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
class TestPaymentTerm(unittest.TestCase):
|
|
||||||
|
class TestPaymentTerm(IntegrationTestCase):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"doctype":"Payment Term",
|
|
||||||
"due_date_based_on":"Day(s) after invoice date",
|
|
||||||
"payment_term_name":"_Test N30",
|
|
||||||
"description":"_Test Net 30 Days",
|
|
||||||
"invoice_portion":50,
|
|
||||||
"credit_days":30
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype":"Payment Term",
|
|
||||||
"due_date_based_on":"Day(s) after invoice date",
|
|
||||||
"payment_term_name":"_Test COD",
|
|
||||||
"description":"_Test Cash on Delivery",
|
|
||||||
"invoice_portion":50,
|
|
||||||
"credit_days":0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype":"Payment Term",
|
|
||||||
"due_date_based_on":"Month(s) after the end of the invoice month",
|
|
||||||
"payment_term_name":"_Test EONM",
|
|
||||||
"description":"_Test End of Next Month",
|
|
||||||
"invoice_portion":100,
|
|
||||||
"credit_months":1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype":"Payment Term",
|
|
||||||
"due_date_based_on":"Day(s) after invoice date",
|
|
||||||
"payment_term_name":"_Test N30 1",
|
|
||||||
"description":"_Test Net 30 Days",
|
|
||||||
"invoice_portion":100,
|
|
||||||
"credit_days":30
|
|
||||||
}
|
|
||||||
]
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user