Merge branch 'develop' into make-against-field-dynamic
This commit is contained in:
commit
6280031722
16
.github/helper/install.sh
vendored
16
.github/helper/install.sh
vendored
@ -4,7 +4,9 @@ set -e
|
|||||||
|
|
||||||
cd ~ || exit
|
cd ~ || exit
|
||||||
|
|
||||||
sudo apt update && sudo apt install redis-server libcups2-dev
|
sudo apt update
|
||||||
|
sudo apt remove mysql-server mysql-client
|
||||||
|
sudo apt install libcups2-dev redis-server mariadb-client-10.6
|
||||||
|
|
||||||
pip install frappe-bench
|
pip install frappe-bench
|
||||||
|
|
||||||
@ -25,14 +27,14 @@ fi
|
|||||||
|
|
||||||
|
|
||||||
if [ "$DB" == "mariadb" ];then
|
if [ "$DB" == "mariadb" ];then
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
||||||
|
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
|
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
||||||
|
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
|
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$DB" == "postgres" ];then
|
if [ "$DB" == "postgres" ];then
|
||||||
|
22
.github/workflows/initiate_release.yml
vendored
22
.github/workflows/initiate_release.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
version: ["13", "14"]
|
version: ["13", "14", "15"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: octokit/request-action@v2.x
|
- uses: octokit/request-action@v2.x
|
||||||
@ -30,23 +30,3 @@ jobs:
|
|||||||
head: version-${{ matrix.version }}-hotfix
|
head: version-${{ matrix.version }}-hotfix
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
|
||||||
beta-release:
|
|
||||||
name: Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: octokit/request-action@v2.x
|
|
||||||
with:
|
|
||||||
route: POST /repos/{owner}/{repo}/pulls
|
|
||||||
owner: frappe
|
|
||||||
repo: erpnext
|
|
||||||
title: |-
|
|
||||||
"chore: release v15 beta"
|
|
||||||
body: "Automated beta release."
|
|
||||||
base: version-15-beta
|
|
||||||
head: develop
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
||||||
|
3
.github/workflows/patch.yml
vendored
3
.github/workflows/patch.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
MARIADB_ROOT_PASSWORD: 'root'
|
MARIADB_ROOT_PASSWORD: 'root'
|
||||||
ports:
|
ports:
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
@ -134,6 +134,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
update_to_version 14
|
update_to_version 14
|
||||||
|
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" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
||||||
|
2
.github/workflows/server-tests-mariadb.yml
vendored
2
.github/workflows/server-tests-mariadb.yml
vendored
@ -47,7 +47,7 @@ jobs:
|
|||||||
MARIADB_ROOT_PASSWORD: 'root'
|
MARIADB_ROOT_PASSWORD: 'root'
|
||||||
ports:
|
ports:
|
||||||
- 3306:3306
|
- 3306:3306
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone
|
- name: Clone
|
||||||
|
46
.mergify.yml
46
.mergify.yml
@ -17,6 +17,7 @@ pull_request_rules:
|
|||||||
- base=version-12
|
- base=version-12
|
||||||
- base=version-14
|
- base=version-14
|
||||||
- base=version-15
|
- base=version-15
|
||||||
|
- base=version-16
|
||||||
actions:
|
actions:
|
||||||
close:
|
close:
|
||||||
comment:
|
comment:
|
||||||
@ -24,16 +25,6 @@ pull_request_rules:
|
|||||||
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
|
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
|
||||||
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
|
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
|
||||||
|
|
||||||
- name: Auto-close PRs on pre-release branch
|
|
||||||
conditions:
|
|
||||||
- base=version-13-pre-release
|
|
||||||
actions:
|
|
||||||
close:
|
|
||||||
comment:
|
|
||||||
message: |
|
|
||||||
@{{author}}, pre-release branch is not maintained anymore. Releases are directly done by merging hotfix branch to stable branches.
|
|
||||||
|
|
||||||
|
|
||||||
- name: backport to develop
|
- name: backport to develop
|
||||||
conditions:
|
conditions:
|
||||||
- label="backport develop"
|
- label="backport develop"
|
||||||
@ -54,13 +45,13 @@ pull_request_rules:
|
|||||||
assignees:
|
assignees:
|
||||||
- "{{ author }}"
|
- "{{ author }}"
|
||||||
|
|
||||||
- name: backport to version-14-pre-release
|
- name: backport to version-15-hotfix
|
||||||
conditions:
|
conditions:
|
||||||
- label="backport version-14-pre-release"
|
- label="backport version-15-hotfix"
|
||||||
actions:
|
actions:
|
||||||
backport:
|
backport:
|
||||||
branches:
|
branches:
|
||||||
- version-14-pre-release
|
- version-15-hotfix
|
||||||
assignees:
|
assignees:
|
||||||
- "{{ author }}"
|
- "{{ author }}"
|
||||||
|
|
||||||
@ -74,35 +65,6 @@ pull_request_rules:
|
|||||||
assignees:
|
assignees:
|
||||||
- "{{ author }}"
|
- "{{ author }}"
|
||||||
|
|
||||||
- name: backport to version-13-pre-release
|
|
||||||
conditions:
|
|
||||||
- label="backport version-13-pre-release"
|
|
||||||
actions:
|
|
||||||
backport:
|
|
||||||
branches:
|
|
||||||
- version-13-pre-release
|
|
||||||
assignees:
|
|
||||||
- "{{ author }}"
|
|
||||||
|
|
||||||
- name: backport to version-12-hotfix
|
|
||||||
conditions:
|
|
||||||
- label="backport version-12-hotfix"
|
|
||||||
actions:
|
|
||||||
backport:
|
|
||||||
branches:
|
|
||||||
- version-12-hotfix
|
|
||||||
assignees:
|
|
||||||
- "{{ author }}"
|
|
||||||
|
|
||||||
- name: backport to version-12-pre-release
|
|
||||||
conditions:
|
|
||||||
- label="backport version-12-pre-release"
|
|
||||||
actions:
|
|
||||||
backport:
|
|
||||||
branches:
|
|
||||||
- version-12-pre-release
|
|
||||||
assignees:
|
|
||||||
- "{{ author }}"
|
|
||||||
|
|
||||||
- name: Automatic merge on CI success and review
|
- name: Automatic merge on CI success and review
|
||||||
conditions:
|
conditions:
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"country_code": "ni",
|
"country_code": "ni",
|
||||||
"name": "Nicaragua - Catalogo de Cuentas",
|
"name": "Nicaragua - Catálogo de Cuentas",
|
||||||
"tree": {
|
"tree": {
|
||||||
"Activo": {
|
"Activo": {
|
||||||
"Activo Corriente": {
|
"Activo Corriente": {
|
||||||
|
@ -37,6 +37,7 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
cle.flags.ignore_permissions = True
|
cle.flags.ignore_permissions = True
|
||||||
|
cle.flags.ignore_links = True
|
||||||
cle.submit()
|
cle.submit()
|
||||||
|
|
||||||
|
|
||||||
|
@ -302,3 +302,30 @@ def get_dimensions(with_cost_center_and_project=False):
|
|||||||
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
|
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
|
||||||
|
|
||||||
return dimension_filters, default_dimensions_map
|
return dimension_filters, default_dimensions_map
|
||||||
|
|
||||||
|
|
||||||
|
def create_accounting_dimensions_for_doctype(doctype):
|
||||||
|
accounting_dimensions = frappe.db.get_all(
|
||||||
|
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not accounting_dimensions:
|
||||||
|
return
|
||||||
|
|
||||||
|
for d in accounting_dimensions:
|
||||||
|
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
|
||||||
|
|
||||||
|
if field:
|
||||||
|
continue
|
||||||
|
|
||||||
|
df = {
|
||||||
|
"fieldname": d.fieldname,
|
||||||
|
"label": d.label,
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": d.document_type,
|
||||||
|
"insert_after": "accounting_dimensions_section",
|
||||||
|
}
|
||||||
|
|
||||||
|
create_custom_field(doctype, df, ignore_validate=True)
|
||||||
|
|
||||||
|
frappe.clear_cache(doctype=doctype)
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
"column_break_19",
|
"column_break_19",
|
||||||
"add_taxes_from_item_tax_template",
|
"add_taxes_from_item_tax_template",
|
||||||
"book_tax_discount_loss",
|
"book_tax_discount_loss",
|
||||||
|
"round_row_wise_tax",
|
||||||
"print_settings",
|
"print_settings",
|
||||||
"show_inclusive_tax_in_print",
|
"show_inclusive_tax_in_print",
|
||||||
"show_taxes_as_table_in_print",
|
"show_taxes_as_table_in_print",
|
||||||
@ -414,6 +415,13 @@
|
|||||||
"fieldname": "ignore_account_closing_balance",
|
"fieldname": "ignore_account_closing_balance",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Ignore Account Closing Balance"
|
"label": "Ignore Account Closing Balance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Tax Amount will be rounded on a row(items) level",
|
||||||
|
"fieldname": "round_row_wise_tax",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Round Tax Amount Row-wise"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@ -421,7 +429,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-27 15:05:34.000264",
|
"modified": "2023-08-28 00:12:02.740633",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"account_type",
|
"account_type",
|
||||||
"account_subtype",
|
"account_subtype",
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
|
"disabled",
|
||||||
"is_default",
|
"is_default",
|
||||||
"is_company_account",
|
"is_company_account",
|
||||||
"company",
|
"company",
|
||||||
@ -199,10 +200,16 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
"label": "Branch Code"
|
"label": "Branch Code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "disabled",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Disabled"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-05-04 15:49:42.620630",
|
"modified": "2023-09-22 21:31:34.763977",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Bank Account",
|
"name": "Bank Account",
|
||||||
|
@ -35,13 +35,14 @@ class TestBankClearance(unittest.TestCase):
|
|||||||
from lending.loan_management.doctype.loan.test_loan import (
|
from lending.loan_management.doctype.loan.test_loan import (
|
||||||
create_loan,
|
create_loan,
|
||||||
create_loan_accounts,
|
create_loan_accounts,
|
||||||
create_loan_type,
|
create_loan_product,
|
||||||
create_repayment_entry,
|
create_repayment_entry,
|
||||||
make_loan_disbursement_entry,
|
make_loan_disbursement_entry,
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_loan_masters():
|
def create_loan_masters():
|
||||||
create_loan_type(
|
create_loan_product(
|
||||||
|
"Clearance Loan",
|
||||||
"Clearance Loan",
|
"Clearance Loan",
|
||||||
2000000,
|
2000000,
|
||||||
13.5,
|
13.5,
|
||||||
|
@ -18,6 +18,7 @@ from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_s
|
|||||||
get_entries,
|
get_entries,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.utils import get_account_currency, get_balance_on
|
from erpnext.accounts.utils import get_account_currency, get_balance_on
|
||||||
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
|
|
||||||
|
|
||||||
class BankReconciliationTool(Document):
|
class BankReconciliationTool(Document):
|
||||||
@ -130,7 +131,7 @@ def create_journal_entry_bts(
|
|||||||
bank_transaction = frappe.db.get_values(
|
bank_transaction = frappe.db.get_values(
|
||||||
"Bank Transaction",
|
"Bank Transaction",
|
||||||
bank_transaction_name,
|
bank_transaction_name,
|
||||||
fieldname=["name", "deposit", "withdrawal", "bank_account"],
|
fieldname=["name", "deposit", "withdrawal", "bank_account", "currency"],
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)[0]
|
)[0]
|
||||||
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
|
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
|
||||||
@ -144,29 +145,94 @@ def create_journal_entry_bts(
|
|||||||
)
|
)
|
||||||
|
|
||||||
company = frappe.get_value("Account", company_account, "company")
|
company = frappe.get_value("Account", company_account, "company")
|
||||||
|
company_default_currency = frappe.get_cached_value("Company", company, "default_currency")
|
||||||
|
company_account_currency = frappe.get_cached_value("Account", company_account, "account_currency")
|
||||||
|
second_account_currency = frappe.get_cached_value("Account", second_account, "account_currency")
|
||||||
|
|
||||||
|
# determine if multi-currency Journal or not
|
||||||
|
is_multi_currency = (
|
||||||
|
True
|
||||||
|
if company_default_currency != company_account_currency
|
||||||
|
or company_default_currency != second_account_currency
|
||||||
|
or company_default_currency != bank_transaction.currency
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
accounts = []
|
accounts = []
|
||||||
# Multi Currency?
|
second_account_dict = {
|
||||||
accounts.append(
|
|
||||||
{
|
|
||||||
"account": second_account,
|
"account": second_account,
|
||||||
|
"account_currency": second_account_currency,
|
||||||
"credit_in_account_currency": bank_transaction.deposit,
|
"credit_in_account_currency": bank_transaction.deposit,
|
||||||
"debit_in_account_currency": bank_transaction.withdrawal,
|
"debit_in_account_currency": bank_transaction.withdrawal,
|
||||||
"party_type": party_type,
|
"party_type": party_type,
|
||||||
"party": party,
|
"party": party,
|
||||||
"cost_center": get_default_cost_center(company),
|
"cost_center": get_default_cost_center(company),
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
accounts.append(
|
company_account_dict = {
|
||||||
{
|
|
||||||
"account": company_account,
|
"account": company_account,
|
||||||
|
"account_currency": company_account_currency,
|
||||||
"bank_account": bank_transaction.bank_account,
|
"bank_account": bank_transaction.bank_account,
|
||||||
"credit_in_account_currency": bank_transaction.withdrawal,
|
"credit_in_account_currency": bank_transaction.withdrawal,
|
||||||
"debit_in_account_currency": bank_transaction.deposit,
|
"debit_in_account_currency": bank_transaction.deposit,
|
||||||
"cost_center": get_default_cost_center(company),
|
"cost_center": get_default_cost_center(company),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# convert transaction amount to company currency
|
||||||
|
if is_multi_currency:
|
||||||
|
exc_rate = get_exchange_rate(bank_transaction.currency, company_default_currency, posting_date)
|
||||||
|
withdrawal_in_company_currency = flt(exc_rate * abs(bank_transaction.withdrawal))
|
||||||
|
deposit_in_company_currency = flt(exc_rate * abs(bank_transaction.deposit))
|
||||||
|
else:
|
||||||
|
withdrawal_in_company_currency = bank_transaction.withdrawal
|
||||||
|
deposit_in_company_currency = bank_transaction.deposit
|
||||||
|
|
||||||
|
# if second account is of foreign currency, convert and set debit and credit fields.
|
||||||
|
if second_account_currency != company_default_currency:
|
||||||
|
exc_rate = get_exchange_rate(second_account_currency, company_default_currency, posting_date)
|
||||||
|
second_account_dict.update(
|
||||||
|
{
|
||||||
|
"exchange_rate": exc_rate,
|
||||||
|
"credit": deposit_in_company_currency,
|
||||||
|
"debit": withdrawal_in_company_currency,
|
||||||
|
"credit_in_account_currency": flt(deposit_in_company_currency / exc_rate) or 0,
|
||||||
|
"debit_in_account_currency": flt(withdrawal_in_company_currency / exc_rate) or 0,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
second_account_dict.update(
|
||||||
|
{
|
||||||
|
"exchange_rate": 1,
|
||||||
|
"credit": deposit_in_company_currency,
|
||||||
|
"debit": withdrawal_in_company_currency,
|
||||||
|
"credit_in_account_currency": deposit_in_company_currency,
|
||||||
|
"debit_in_account_currency": withdrawal_in_company_currency,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# if company account is of foreign currency, convert and set debit and credit fields.
|
||||||
|
if company_account_currency != company_default_currency:
|
||||||
|
exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date)
|
||||||
|
company_account_dict.update(
|
||||||
|
{
|
||||||
|
"exchange_rate": exc_rate,
|
||||||
|
"credit": withdrawal_in_company_currency,
|
||||||
|
"debit": deposit_in_company_currency,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
company_account_dict.update(
|
||||||
|
{
|
||||||
|
"exchange_rate": 1,
|
||||||
|
"credit": withdrawal_in_company_currency,
|
||||||
|
"debit": deposit_in_company_currency,
|
||||||
|
"credit_in_account_currency": withdrawal_in_company_currency,
|
||||||
|
"debit_in_account_currency": deposit_in_company_currency,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
accounts.append(second_account_dict)
|
||||||
|
accounts.append(company_account_dict)
|
||||||
|
|
||||||
journal_entry_dict = {
|
journal_entry_dict = {
|
||||||
"voucher_type": entry_type,
|
"voucher_type": entry_type,
|
||||||
@ -176,6 +242,9 @@ def create_journal_entry_bts(
|
|||||||
"cheque_no": reference_number,
|
"cheque_no": reference_number,
|
||||||
"mode_of_payment": mode_of_payment,
|
"mode_of_payment": mode_of_payment,
|
||||||
}
|
}
|
||||||
|
if is_multi_currency:
|
||||||
|
journal_entry_dict.update({"multi_currency": True})
|
||||||
|
|
||||||
journal_entry = frappe.new_doc("Journal Entry")
|
journal_entry = frappe.new_doc("Journal Entry")
|
||||||
journal_entry.update(journal_entry_dict)
|
journal_entry.update(journal_entry_dict)
|
||||||
journal_entry.set("accounts", accounts)
|
journal_entry.set("accounts", accounts)
|
||||||
|
@ -2,6 +2,16 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("Bank Statement Import", {
|
frappe.ui.form.on("Bank Statement Import", {
|
||||||
|
onload(frm) {
|
||||||
|
frm.set_query("bank_account", function (doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
setup(frm) {
|
setup(frm) {
|
||||||
frappe.realtime.on("data_import_refresh", ({ data_import }) => {
|
frappe.realtime.on("data_import_refresh", ({ data_import }) => {
|
||||||
frm.import_in_progress = false;
|
frm.import_in_progress = false;
|
||||||
@ -352,10 +362,11 @@ frappe.ui.form.on("Bank Statement Import", {
|
|||||||
|
|
||||||
export_errored_rows(frm) {
|
export_errored_rows(frm) {
|
||||||
open_url_post(
|
open_url_post(
|
||||||
"/api/method/frappe.core.doctype.data_import.data_import.download_errored_template",
|
"/api/method/erpnext.accounts.doctype.bank_statement_import.bank_statement_import.download_errored_template",
|
||||||
{
|
{
|
||||||
data_import_name: frm.doc.name,
|
data_import_name: frm.doc.name,
|
||||||
}
|
},
|
||||||
|
true
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -112,7 +112,8 @@ class AutoMatchbyPartyNameDescription:
|
|||||||
|
|
||||||
for party in parties:
|
for party in parties:
|
||||||
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
|
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
|
||||||
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
|
field = party.lower() + "_name"
|
||||||
|
names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
|
||||||
|
|
||||||
for field in ["bank_party_name", "description"]:
|
for field in ["bank_party_name", "description"]:
|
||||||
if not self.get(field):
|
if not self.get(field):
|
||||||
@ -131,7 +132,11 @@ class AutoMatchbyPartyNameDescription:
|
|||||||
|
|
||||||
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
|
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
|
||||||
skip = False
|
skip = False
|
||||||
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
|
result = process.extract(
|
||||||
|
query=self.get(field),
|
||||||
|
choices={row.get("name"): row.get("party_name") for row in names},
|
||||||
|
scorer=fuzz.token_set_ratio,
|
||||||
|
)
|
||||||
party_name, skip = self.process_fuzzy_result(result)
|
party_name, skip = self.process_fuzzy_result(result)
|
||||||
|
|
||||||
if not party_name:
|
if not party_name:
|
||||||
@ -149,14 +154,14 @@ class AutoMatchbyPartyNameDescription:
|
|||||||
|
|
||||||
Returns: Result, Skip (whether or not to discontinue matching)
|
Returns: Result, Skip (whether or not to discontinue matching)
|
||||||
"""
|
"""
|
||||||
PARTY, SCORE, CUTOFF = 0, 1, 80
|
SCORE, PARTY_ID, CUTOFF = 1, 2, 80
|
||||||
|
|
||||||
if not result or not len(result):
|
if not result or not len(result):
|
||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
first_result = result[0]
|
first_result = result[0]
|
||||||
if len(result) == 1:
|
if len(result) == 1:
|
||||||
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
|
return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True
|
||||||
|
|
||||||
second_result = result[1]
|
second_result = result[1]
|
||||||
if first_result[SCORE] > CUTOFF:
|
if first_result[SCORE] > CUTOFF:
|
||||||
@ -165,7 +170,7 @@ class AutoMatchbyPartyNameDescription:
|
|||||||
if first_result[SCORE] == second_result[SCORE]:
|
if first_result[SCORE] == second_result[SCORE]:
|
||||||
return None, True
|
return None, True
|
||||||
|
|
||||||
return first_result[PARTY], True
|
return first_result[PARTY_ID], True
|
||||||
else:
|
else:
|
||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
|
@ -89,7 +89,6 @@ class BankTransaction(StatusUpdater):
|
|||||||
- 0 > a: Error: already over-allocated
|
- 0 > a: Error: already over-allocated
|
||||||
- clear means: set the latest transaction date as clearance date
|
- clear means: set the latest transaction date as clearance date
|
||||||
"""
|
"""
|
||||||
gl_bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
|
|
||||||
remaining_amount = self.unallocated_amount
|
remaining_amount = self.unallocated_amount
|
||||||
for payment_entry in self.payment_entries:
|
for payment_entry in self.payment_entries:
|
||||||
if payment_entry.allocated_amount == 0.0:
|
if payment_entry.allocated_amount == 0.0:
|
||||||
|
@ -410,7 +410,7 @@ def add_vouchers():
|
|||||||
def create_loan_and_repayment():
|
def create_loan_and_repayment():
|
||||||
from lending.loan_management.doctype.loan.test_loan import (
|
from lending.loan_management.doctype.loan.test_loan import (
|
||||||
create_loan,
|
create_loan,
|
||||||
create_loan_type,
|
create_loan_product,
|
||||||
create_repayment_entry,
|
create_repayment_entry,
|
||||||
make_loan_disbursement_entry,
|
make_loan_disbursement_entry,
|
||||||
)
|
)
|
||||||
@ -420,7 +420,8 @@ def create_loan_and_repayment():
|
|||||||
|
|
||||||
from erpnext.setup.doctype.employee.test_employee import make_employee
|
from erpnext.setup.doctype.employee.test_employee import make_employee
|
||||||
|
|
||||||
create_loan_type(
|
create_loan_product(
|
||||||
|
"Personal Loan",
|
||||||
"Personal Loan",
|
"Personal Loan",
|
||||||
500000,
|
500000,
|
||||||
8.4,
|
8.4,
|
||||||
@ -441,7 +442,7 @@ def create_loan_and_repayment():
|
|||||||
"applicant_type": "Employee",
|
"applicant_type": "Employee",
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"applicant": applicant,
|
"applicant": applicant,
|
||||||
"loan_type": "Personal Loan",
|
"loan_product": "Personal Loan",
|
||||||
"loan_amount": 5000,
|
"loan_amount": 5000,
|
||||||
"repayment_method": "Repay Fixed Amount per Period",
|
"repayment_method": "Repay Fixed Amount per Period",
|
||||||
"monthly_repayment_amount": 500,
|
"monthly_repayment_amount": 500,
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"disabled",
|
"disabled",
|
||||||
"service_provider",
|
"service_provider",
|
||||||
"api_endpoint",
|
"api_endpoint",
|
||||||
|
"access_key",
|
||||||
"url",
|
"url",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"help",
|
"help",
|
||||||
@ -84,12 +85,18 @@
|
|||||||
"fieldname": "disabled",
|
"fieldname": "disabled",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Disabled"
|
"label": "Disabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.service_provider == 'exchangerate.host';",
|
||||||
|
"fieldname": "access_key",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Access Key"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-01-09 12:19:03.955906",
|
"modified": "2023-10-04 15:30:25.333860",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Currency Exchange Settings",
|
"name": "Currency Exchange Settings",
|
||||||
|
@ -18,11 +18,21 @@ class CurrencyExchangeSettings(Document):
|
|||||||
|
|
||||||
def set_parameters_and_result(self):
|
def set_parameters_and_result(self):
|
||||||
if self.service_provider == "exchangerate.host":
|
if self.service_provider == "exchangerate.host":
|
||||||
|
|
||||||
|
if not self.access_key:
|
||||||
|
frappe.throw(
|
||||||
|
_("Access Key is required for Service Provider: {0}").format(
|
||||||
|
frappe.bold(self.service_provider)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.set("result_key", [])
|
self.set("result_key", [])
|
||||||
self.set("req_params", [])
|
self.set("req_params", [])
|
||||||
|
|
||||||
self.api_endpoint = "https://api.exchangerate.host/convert"
|
self.api_endpoint = "https://api.exchangerate.host/convert"
|
||||||
self.append("result_key", {"key": "result"})
|
self.append("result_key", {"key": "result"})
|
||||||
|
self.append("req_params", {"key": "access_key", "value": self.access_key})
|
||||||
|
self.append("req_params", {"key": "amount", "value": "1"})
|
||||||
self.append("req_params", {"key": "date", "value": "{transaction_date}"})
|
self.append("req_params", {"key": "date", "value": "{transaction_date}"})
|
||||||
self.append("req_params", {"key": "from", "value": "{from_currency}"})
|
self.append("req_params", {"key": "from", "value": "{from_currency}"})
|
||||||
self.append("req_params", {"key": "to", "value": "{to_currency}"})
|
self.append("req_params", {"key": "to", "value": "{to_currency}"})
|
||||||
|
@ -53,7 +53,15 @@ frappe.ui.form.on("Journal Entry", {
|
|||||||
|
|
||||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
||||||
},
|
},
|
||||||
|
before_save: function(frm) {
|
||||||
|
if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) {
|
||||||
|
let payment_entry_references = frm.doc.accounts.filter(elem => (elem.reference_type == "Payment Entry"));
|
||||||
|
if (payment_entry_references.length > 0) {
|
||||||
|
let rows = payment_entry_references.map(x => "#"+x.idx);
|
||||||
|
frappe.throw(__("Rows: {0} have 'Payment Entry' as reference_type. This should not be set manually.", [frappe.utils.comma_and(rows)]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
make_inter_company_journal_entry: function(frm) {
|
make_inter_company_journal_entry: function(frm) {
|
||||||
var d = new frappe.ui.Dialog({
|
var d = new frappe.ui.Dialog({
|
||||||
title: __("Select Company"),
|
title: __("Select Company"),
|
||||||
|
@ -218,6 +218,7 @@ def make_customer(customer=None):
|
|||||||
"territory": "All Territories",
|
"territory": "All Territories",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if not frappe.db.exists("Customer", customer_name):
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
customer.insert(ignore_permissions=True)
|
customer.insert(ignore_permissions=True)
|
||||||
return customer.name
|
return customer.name
|
||||||
|
@ -271,16 +271,18 @@ class PaymentEntry(AccountsController):
|
|||||||
|
|
||||||
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
|
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
|
||||||
latest = latest.get(d.payment_term) or latest.get(None)
|
latest = latest.get(d.payment_term) or latest.get(None)
|
||||||
|
|
||||||
# The reference has already been fully paid
|
# The reference has already been fully paid
|
||||||
if not latest:
|
if not latest:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
|
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
|
||||||
)
|
)
|
||||||
# The reference has already been partly paid
|
# The reference has already been partly paid
|
||||||
elif latest.outstanding_amount < latest.invoice_amount and flt(
|
elif (
|
||||||
d.outstanding_amount, d.precision("outstanding_amount")
|
latest.outstanding_amount < latest.invoice_amount
|
||||||
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
|
and flt(d.outstanding_amount, d.precision("outstanding_amount"))
|
||||||
|
!= flt(latest.outstanding_amount, d.precision("outstanding_amount"))
|
||||||
|
and d.payment_term == ""
|
||||||
|
):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
|
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
|
||||||
@ -1758,11 +1760,10 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
|||||||
"voucher_type": d.voucher_type,
|
"voucher_type": d.voucher_type,
|
||||||
"posting_date": d.posting_date,
|
"posting_date": d.posting_date,
|
||||||
"invoice_amount": flt(d.invoice_amount),
|
"invoice_amount": flt(d.invoice_amount),
|
||||||
"outstanding_amount": flt(d.outstanding_amount),
|
"outstanding_amount": payment_term_outstanding
|
||||||
"payment_term_outstanding": payment_term_outstanding,
|
|
||||||
"allocated_amount": payment_term_outstanding
|
|
||||||
if payment_term_outstanding
|
if payment_term_outstanding
|
||||||
else d.outstanding_amount,
|
else d.outstanding_amount,
|
||||||
|
"payment_term_outstanding": payment_term_outstanding,
|
||||||
"payment_amount": payment_term.payment_amount,
|
"payment_amount": payment_term.payment_amount,
|
||||||
"payment_term": payment_term.payment_term,
|
"payment_term": payment_term.payment_term,
|
||||||
"account": d.account,
|
"account": d.account,
|
||||||
|
@ -30,7 +30,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "posting_date",
|
"fieldname": "posting_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Posting Date"
|
"label": "Posting Date",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "account_type",
|
"fieldname": "account_type",
|
||||||
@ -64,7 +65,8 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Voucher Type",
|
"label": "Voucher Type",
|
||||||
"options": "DocType"
|
"options": "DocType",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "voucher_no",
|
"fieldname": "voucher_no",
|
||||||
@ -72,14 +74,16 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Voucher No",
|
"label": "Voucher No",
|
||||||
"options": "voucher_type"
|
"options": "voucher_type",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "against_voucher_type",
|
"fieldname": "against_voucher_type",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Against Voucher Type",
|
"label": "Against Voucher Type",
|
||||||
"options": "DocType"
|
"options": "DocType",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "against_voucher_no",
|
"fieldname": "against_voucher_no",
|
||||||
@ -87,7 +91,8 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Against Voucher No",
|
"label": "Against Voucher No",
|
||||||
"options": "against_voucher_type"
|
"options": "against_voucher_type",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "amount",
|
"fieldname": "amount",
|
||||||
@ -147,13 +152,14 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "voucher_detail_no",
|
"fieldname": "voucher_detail_no",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Voucher Detail No"
|
"label": "Voucher Detail No",
|
||||||
|
"search_index": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-29 12:24:20.500632",
|
"modified": "2023-11-03 16:39:58.904113",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Ledger Entry",
|
"name": "Payment Ledger Entry",
|
||||||
|
@ -229,6 +229,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
|||||||
this.data = [];
|
this.data = [];
|
||||||
const dialog = new frappe.ui.Dialog({
|
const dialog = new frappe.ui.Dialog({
|
||||||
title: __("Select Difference Account"),
|
title: __("Select Difference Account"),
|
||||||
|
size: 'extra-large',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
fieldname: "allocation",
|
fieldname: "allocation",
|
||||||
@ -252,6 +253,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
|||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
read_only: 1
|
read_only: 1
|
||||||
}, {
|
}, {
|
||||||
|
fieldtype:'Date',
|
||||||
|
fieldname:"gain_loss_posting_date",
|
||||||
|
label: __("Posting Date"),
|
||||||
|
in_list_view: 1,
|
||||||
|
reqd: 1,
|
||||||
|
}, {
|
||||||
|
|
||||||
fieldtype:'Link',
|
fieldtype:'Link',
|
||||||
options: 'Account',
|
options: 'Account',
|
||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
@ -285,6 +293,9 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
|||||||
args.forEach(d => {
|
args.forEach(d => {
|
||||||
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
|
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
|
||||||
"difference_account", d.difference_account);
|
"difference_account", d.difference_account);
|
||||||
|
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
|
||||||
|
"gain_loss_posting_date", d.gain_loss_posting_date);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.reconcile_payment_entries();
|
this.reconcile_payment_entries();
|
||||||
@ -300,6 +311,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
|||||||
'reference_name': d.reference_name,
|
'reference_name': d.reference_name,
|
||||||
'difference_amount': d.difference_amount,
|
'difference_amount': d.difference_amount,
|
||||||
'difference_account': d.difference_account,
|
'difference_account': d.difference_account,
|
||||||
|
'gain_loss_posting_date': d.gain_loss_posting_date
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -109,6 +109,8 @@ class PaymentReconciliation(Document):
|
|||||||
"t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1"
|
"t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
limit = f"limit {self.payment_limit}" if self.payment_limit else " "
|
||||||
|
|
||||||
# nosemgrep
|
# nosemgrep
|
||||||
journal_entries = frappe.db.sql(
|
journal_entries = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
@ -132,11 +134,13 @@ class PaymentReconciliation(Document):
|
|||||||
ELSE {bank_account_condition}
|
ELSE {bank_account_condition}
|
||||||
END)
|
END)
|
||||||
order by t1.posting_date
|
order by t1.posting_date
|
||||||
|
{limit}
|
||||||
""".format(
|
""".format(
|
||||||
**{
|
**{
|
||||||
"dr_or_cr": dr_or_cr,
|
"dr_or_cr": dr_or_cr,
|
||||||
"bank_account_condition": bank_account_condition,
|
"bank_account_condition": bank_account_condition,
|
||||||
"condition": condition,
|
"condition": condition,
|
||||||
|
"limit": limit,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
@ -162,7 +166,7 @@ class PaymentReconciliation(Document):
|
|||||||
if self.payment_name:
|
if self.payment_name:
|
||||||
conditions.append(doc.name.like(f"%{self.payment_name}%"))
|
conditions.append(doc.name.like(f"%{self.payment_name}%"))
|
||||||
|
|
||||||
self.return_invoices = (
|
self.return_invoices_query = (
|
||||||
qb.from_(doc)
|
qb.from_(doc)
|
||||||
.select(
|
.select(
|
||||||
ConstantColumn(voucher_type).as_("voucher_type"),
|
ConstantColumn(voucher_type).as_("voucher_type"),
|
||||||
@ -170,8 +174,11 @@ class PaymentReconciliation(Document):
|
|||||||
doc.return_against,
|
doc.return_against,
|
||||||
)
|
)
|
||||||
.where(Criterion.all(conditions))
|
.where(Criterion.all(conditions))
|
||||||
.run(as_dict=True)
|
|
||||||
)
|
)
|
||||||
|
if self.payment_limit:
|
||||||
|
self.return_invoices_query = self.return_invoices_query.limit(self.payment_limit)
|
||||||
|
|
||||||
|
self.return_invoices = self.return_invoices_query.run(as_dict=True)
|
||||||
|
|
||||||
def get_dr_or_cr_notes(self):
|
def get_dr_or_cr_notes(self):
|
||||||
|
|
||||||
@ -328,6 +335,7 @@ class PaymentReconciliation(Document):
|
|||||||
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
|
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
|
||||||
res.difference_account = default_exchange_gain_loss_account
|
res.difference_account = default_exchange_gain_loss_account
|
||||||
res.exchange_rate = inv.get("exchange_rate")
|
res.exchange_rate = inv.get("exchange_rate")
|
||||||
|
res.update({"gain_loss_posting_date": pay.get("posting_date")})
|
||||||
|
|
||||||
if pay.get("amount") == 0:
|
if pay.get("amount") == 0:
|
||||||
entries.append(res)
|
entries.append(res)
|
||||||
@ -434,6 +442,7 @@ class PaymentReconciliation(Document):
|
|||||||
"allocated_amount": flt(row.get("allocated_amount")),
|
"allocated_amount": flt(row.get("allocated_amount")),
|
||||||
"difference_amount": flt(row.get("difference_amount")),
|
"difference_amount": flt(row.get("difference_amount")),
|
||||||
"difference_account": row.get("difference_account"),
|
"difference_account": row.get("difference_account"),
|
||||||
|
"difference_posting_date": row.get("gain_loss_posting_date"),
|
||||||
"cost_center": row.get("cost_center"),
|
"cost_center": row.get("cost_center"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
|
|||||||
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
|
||||||
from erpnext.accounts.party import get_party_account
|
from erpnext.accounts.party import get_party_account
|
||||||
|
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"]
|
test_dependencies = ["Item"]
|
||||||
@ -85,26 +86,44 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
self.customer5 = make_customer("_Test PR Customer 5", "EUR")
|
self.customer5 = make_customer("_Test PR Customer 5", "EUR")
|
||||||
|
|
||||||
def create_account(self):
|
def create_account(self):
|
||||||
account_name = "Debtors EUR"
|
accounts = [
|
||||||
|
{
|
||||||
|
"attribute": "debtors_eur",
|
||||||
|
"account_name": "Debtors EUR",
|
||||||
|
"parent_account": "Accounts Receivable - _PR",
|
||||||
|
"account_currency": "EUR",
|
||||||
|
"account_type": "Receivable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attribute": "creditors_usd",
|
||||||
|
"account_name": "Payable USD",
|
||||||
|
"parent_account": "Accounts Payable - _PR",
|
||||||
|
"account_currency": "USD",
|
||||||
|
"account_type": "Payable",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for x in accounts:
|
||||||
|
x = frappe._dict(x)
|
||||||
if not frappe.db.get_value(
|
if not frappe.db.get_value(
|
||||||
"Account", filters={"account_name": account_name, "company": self.company}
|
"Account", filters={"account_name": x.account_name, "company": self.company}
|
||||||
):
|
):
|
||||||
acc = frappe.new_doc("Account")
|
acc = frappe.new_doc("Account")
|
||||||
acc.account_name = account_name
|
acc.account_name = x.account_name
|
||||||
acc.parent_account = "Accounts Receivable - _PR"
|
acc.parent_account = x.parent_account
|
||||||
acc.company = self.company
|
acc.company = self.company
|
||||||
acc.account_currency = "EUR"
|
acc.account_currency = x.account_currency
|
||||||
acc.account_type = "Receivable"
|
acc.account_type = x.account_type
|
||||||
acc.insert()
|
acc.insert()
|
||||||
else:
|
else:
|
||||||
name = frappe.db.get_value(
|
name = frappe.db.get_value(
|
||||||
"Account",
|
"Account",
|
||||||
filters={"account_name": account_name, "company": self.company},
|
filters={"account_name": x.account_name, "company": self.company},
|
||||||
fieldname="name",
|
fieldname="name",
|
||||||
pluck=True,
|
pluck=True,
|
||||||
)
|
)
|
||||||
acc = frappe.get_doc("Account", name)
|
acc = frappe.get_doc("Account", name)
|
||||||
self.debtors_eur = acc.name
|
setattr(self, x.attribute, acc.name)
|
||||||
|
|
||||||
def create_sales_invoice(
|
def create_sales_invoice(
|
||||||
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
||||||
@ -151,6 +170,64 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
payment.posting_date = posting_date
|
payment.posting_date = posting_date
|
||||||
return payment
|
return payment
|
||||||
|
|
||||||
|
def create_purchase_invoice(
|
||||||
|
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Helper function to populate default values in sales invoice
|
||||||
|
"""
|
||||||
|
pinv = make_purchase_invoice(
|
||||||
|
qty=qty,
|
||||||
|
rate=rate,
|
||||||
|
company=self.company,
|
||||||
|
customer=self.supplier,
|
||||||
|
item_code=self.item,
|
||||||
|
item_name=self.item,
|
||||||
|
cost_center=self.cost_center,
|
||||||
|
warehouse=self.warehouse,
|
||||||
|
debit_to=self.debit_to,
|
||||||
|
parent_cost_center=self.cost_center,
|
||||||
|
update_stock=0,
|
||||||
|
currency="INR",
|
||||||
|
is_pos=0,
|
||||||
|
is_return=0,
|
||||||
|
return_against=None,
|
||||||
|
income_account=self.income_account,
|
||||||
|
expense_account=self.expense_account,
|
||||||
|
do_not_save=do_not_save,
|
||||||
|
do_not_submit=do_not_submit,
|
||||||
|
)
|
||||||
|
return pinv
|
||||||
|
|
||||||
|
def create_purchase_order(
|
||||||
|
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Helper function to populate default values in sales invoice
|
||||||
|
"""
|
||||||
|
pord = create_purchase_order(
|
||||||
|
qty=qty,
|
||||||
|
rate=rate,
|
||||||
|
company=self.company,
|
||||||
|
customer=self.supplier,
|
||||||
|
item_code=self.item,
|
||||||
|
item_name=self.item,
|
||||||
|
cost_center=self.cost_center,
|
||||||
|
warehouse=self.warehouse,
|
||||||
|
debit_to=self.debit_to,
|
||||||
|
parent_cost_center=self.cost_center,
|
||||||
|
update_stock=0,
|
||||||
|
currency="INR",
|
||||||
|
is_pos=0,
|
||||||
|
is_return=0,
|
||||||
|
return_against=None,
|
||||||
|
income_account=self.income_account,
|
||||||
|
expense_account=self.expense_account,
|
||||||
|
do_not_save=do_not_save,
|
||||||
|
do_not_submit=do_not_submit,
|
||||||
|
)
|
||||||
|
return pord
|
||||||
|
|
||||||
def clear_old_entries(self):
|
def clear_old_entries(self):
|
||||||
doctype_list = [
|
doctype_list = [
|
||||||
"GL Entry",
|
"GL Entry",
|
||||||
@ -163,13 +240,11 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
for doctype in doctype_list:
|
for doctype in doctype_list:
|
||||||
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
||||||
|
|
||||||
def create_payment_reconciliation(self):
|
def create_payment_reconciliation(self, party_is_customer=True):
|
||||||
pr = frappe.new_doc("Payment Reconciliation")
|
pr = frappe.new_doc("Payment Reconciliation")
|
||||||
pr.company = self.company
|
pr.company = self.company
|
||||||
pr.party_type = (
|
pr.party_type = "Customer" if party_is_customer else "Supplier"
|
||||||
self.party_type if hasattr(self, "party_type") and self.party_type else "Customer"
|
pr.party = self.customer if party_is_customer else self.supplier
|
||||||
)
|
|
||||||
pr.party = self.customer
|
|
||||||
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
|
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
|
||||||
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||||
return pr
|
return pr
|
||||||
@ -906,9 +981,13 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
self.assertEqual(pr.allocation[0].difference_amount, 0)
|
self.assertEqual(pr.allocation[0].difference_amount, 0)
|
||||||
|
|
||||||
def test_reconciliation_purchase_invoice_against_return(self):
|
def test_reconciliation_purchase_invoice_against_return(self):
|
||||||
pi = make_purchase_invoice(
|
self.supplier = "_Test Supplier USD"
|
||||||
supplier="_Test Supplier USD", currency="USD", conversion_rate=50
|
pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
|
||||||
).submit()
|
pi.supplier = self.supplier
|
||||||
|
pi.currency = "USD"
|
||||||
|
pi.conversion_rate = 50
|
||||||
|
pi.credit_to = self.creditors_usd
|
||||||
|
pi.save().submit()
|
||||||
|
|
||||||
pi_return = frappe.get_doc(pi.as_dict())
|
pi_return = frappe.get_doc(pi.as_dict())
|
||||||
pi_return.name = None
|
pi_return.name = None
|
||||||
@ -918,11 +997,12 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
pi_return.items[0].qty = -pi_return.items[0].qty
|
pi_return.items[0].qty = -pi_return.items[0].qty
|
||||||
pi_return.submit()
|
pi_return.submit()
|
||||||
|
|
||||||
self.company = "_Test Company"
|
pr = frappe.get_doc("Payment Reconciliation")
|
||||||
self.party_type = "Supplier"
|
pr.company = self.company
|
||||||
self.customer = "_Test Supplier USD"
|
pr.party_type = "Supplier"
|
||||||
|
pr.party = self.supplier
|
||||||
pr = self.create_payment_reconciliation()
|
pr.receivable_payable_account = self.creditors_usd
|
||||||
|
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||||
pr.get_unreconciled_entries()
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
invoices = []
|
invoices = []
|
||||||
@ -931,6 +1011,7 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
if invoice.invoice_number == pi.name:
|
if invoice.invoice_number == pi.name:
|
||||||
invoices.append(invoice.as_dict())
|
invoices.append(invoice.as_dict())
|
||||||
break
|
break
|
||||||
|
|
||||||
for payment in pr.payments:
|
for payment in pr.payments:
|
||||||
if payment.reference_name == pi_return.name:
|
if payment.reference_name == pi_return.name:
|
||||||
payments.append(payment.as_dict())
|
payments.append(payment.as_dict())
|
||||||
@ -941,6 +1022,121 @@ class TestPaymentReconciliation(FrappeTestCase):
|
|||||||
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
|
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
|
||||||
pr.reconcile()
|
pr.reconcile()
|
||||||
|
|
||||||
|
def test_reconciliation_from_purchase_order_to_multiple_invoices(self):
|
||||||
|
"""
|
||||||
|
Reconciling advance payment from PO/SO to multiple invoices should not cause overallocation
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.supplier = "_Test Supplier"
|
||||||
|
|
||||||
|
pi1 = self.create_purchase_invoice(qty=10, rate=100)
|
||||||
|
pi2 = self.create_purchase_invoice(qty=10, rate=100)
|
||||||
|
po = self.create_purchase_order(qty=20, rate=100)
|
||||||
|
pay = get_payment_entry(po.doctype, po.name)
|
||||||
|
# Overpay Puchase Order
|
||||||
|
pay.paid_amount = 3000
|
||||||
|
pay.save().submit()
|
||||||
|
# assert total allocated and unallocated before reconciliation
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[0].reference_doctype,
|
||||||
|
pay.references[0].reference_name,
|
||||||
|
pay.references[0].allocated_amount,
|
||||||
|
),
|
||||||
|
(po.doctype, po.name, 2000),
|
||||||
|
)
|
||||||
|
self.assertEqual(pay.total_allocated_amount, 2000)
|
||||||
|
self.assertEqual(pay.unallocated_amount, 1000)
|
||||||
|
self.assertEqual(pay.difference_amount, 0)
|
||||||
|
|
||||||
|
pr = self.create_payment_reconciliation(party_is_customer=False)
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
|
self.assertEqual(len(pr.invoices), 2)
|
||||||
|
self.assertEqual(len(pr.payments), 2)
|
||||||
|
|
||||||
|
for x in pr.payments:
|
||||||
|
self.assertEqual((x.reference_type, x.reference_name), (pay.doctype, pay.name))
|
||||||
|
|
||||||
|
invoices = [x.as_dict() for x in pr.invoices]
|
||||||
|
payments = [x.as_dict() for x in pr.payments]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
# partial allocation on pi1 and full allocate on pi2
|
||||||
|
pr.allocation[0].allocated_amount = 100
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
# assert references and total allocated and unallocated amount
|
||||||
|
pay.reload()
|
||||||
|
self.assertEqual(len(pay.references), 3)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[0].reference_doctype,
|
||||||
|
pay.references[0].reference_name,
|
||||||
|
pay.references[0].allocated_amount,
|
||||||
|
),
|
||||||
|
(po.doctype, po.name, 900),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[1].reference_doctype,
|
||||||
|
pay.references[1].reference_name,
|
||||||
|
pay.references[1].allocated_amount,
|
||||||
|
),
|
||||||
|
(pi1.doctype, pi1.name, 100),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[2].reference_doctype,
|
||||||
|
pay.references[2].reference_name,
|
||||||
|
pay.references[2].allocated_amount,
|
||||||
|
),
|
||||||
|
(pi2.doctype, pi2.name, 1000),
|
||||||
|
)
|
||||||
|
self.assertEqual(pay.total_allocated_amount, 2000)
|
||||||
|
self.assertEqual(pay.unallocated_amount, 1000)
|
||||||
|
self.assertEqual(pay.difference_amount, 0)
|
||||||
|
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
self.assertEqual(len(pr.invoices), 1)
|
||||||
|
self.assertEqual(len(pr.payments), 2)
|
||||||
|
|
||||||
|
invoices = [x.as_dict() for x in pr.invoices]
|
||||||
|
payments = [x.as_dict() for x in pr.payments]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
# assert references and total allocated and unallocated amount
|
||||||
|
pay.reload()
|
||||||
|
self.assertEqual(len(pay.references), 3)
|
||||||
|
# PO references should be removed now
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[0].reference_doctype,
|
||||||
|
pay.references[0].reference_name,
|
||||||
|
pay.references[0].allocated_amount,
|
||||||
|
),
|
||||||
|
(pi1.doctype, pi1.name, 100),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[1].reference_doctype,
|
||||||
|
pay.references[1].reference_name,
|
||||||
|
pay.references[1].allocated_amount,
|
||||||
|
),
|
||||||
|
(pi2.doctype, pi2.name, 1000),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
(
|
||||||
|
pay.references[2].reference_doctype,
|
||||||
|
pay.references[2].reference_name,
|
||||||
|
pay.references[2].allocated_amount,
|
||||||
|
),
|
||||||
|
(pi1.doctype, pi1.name, 900),
|
||||||
|
)
|
||||||
|
self.assertEqual(pay.total_allocated_amount, 2000)
|
||||||
|
self.assertEqual(pay.unallocated_amount, 1000)
|
||||||
|
self.assertEqual(pay.difference_amount, 0)
|
||||||
|
|
||||||
|
|
||||||
def make_customer(customer_name, currency=None):
|
def make_customer(customer_name, currency=None):
|
||||||
if not frappe.db.exists("Customer", customer_name):
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"is_advance",
|
"is_advance",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
"difference_amount",
|
"difference_amount",
|
||||||
|
"gain_loss_posting_date",
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
"difference_account",
|
"difference_account",
|
||||||
"exchange_rate",
|
"exchange_rate",
|
||||||
@ -151,11 +152,16 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Cost Center",
|
"label": "Cost Center",
|
||||||
"options": "Cost Center"
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "gain_loss_posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Difference Posting Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-03 07:52:33.684217",
|
"modified": "2023-10-23 10:44:56.066303",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Reconciliation Allocation",
|
"name": "Payment Reconciliation Allocation",
|
||||||
|
@ -268,8 +268,7 @@
|
|||||||
"fieldname": "email_to",
|
"fieldname": "email_to",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
"label": "To",
|
"label": "To"
|
||||||
"options": "Email"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
"depends_on": "eval: doc.payment_channel != \"Phone\"",
|
||||||
@ -340,8 +339,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "payment_url",
|
"fieldname": "payment_url",
|
||||||
"hidden": 1,
|
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
"length": 500,
|
"length": 500,
|
||||||
"options": "URL",
|
"options": "URL",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
@ -396,7 +395,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-16 14:15:02.510890",
|
"modified": "2023-09-27 09:51:42.277638",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Request",
|
"name": "Payment Request",
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
|
||||||
# For license information, please see license.txt
|
|
||||||
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import flt, get_url, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
from frappe.utils.background_jobs import enqueue
|
from frappe.utils.background_jobs import enqueue
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
@ -20,7 +16,6 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
|||||||
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
||||||
from erpnext.accounts.party import get_party_account, get_party_bank_account
|
from erpnext.accounts.party import get_party_account, get_party_bank_account
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription
|
|
||||||
from erpnext.utilities import payment_app_import_guard
|
from erpnext.utilities import payment_app_import_guard
|
||||||
|
|
||||||
|
|
||||||
@ -249,7 +244,7 @@ class PaymentRequest(Document):
|
|||||||
if (
|
if (
|
||||||
party_account_currency == ref_doc.company_currency and party_account_currency != self.currency
|
party_account_currency == ref_doc.company_currency and party_account_currency != self.currency
|
||||||
):
|
):
|
||||||
party_amount = ref_doc.base_grand_total
|
party_amount = ref_doc.get("base_rounded_total") or ref_doc.get("base_grand_total")
|
||||||
else:
|
else:
|
||||||
party_amount = self.grand_total
|
party_amount = self.grand_total
|
||||||
|
|
||||||
@ -364,35 +359,11 @@ class PaymentRequest(Document):
|
|||||||
def get_payment_success_url(self):
|
def get_payment_success_url(self):
|
||||||
return self.payment_success_url
|
return self.payment_success_url
|
||||||
|
|
||||||
def on_payment_authorized(self, status=None):
|
|
||||||
if not status:
|
|
||||||
return
|
|
||||||
|
|
||||||
shopping_cart_settings = frappe.get_doc("E Commerce Settings")
|
|
||||||
|
|
||||||
if status in ["Authorized", "Completed"]:
|
|
||||||
redirect_to = None
|
|
||||||
self.set_as_paid()
|
|
||||||
|
|
||||||
# if shopping cart enabled and in session
|
|
||||||
if (
|
|
||||||
shopping_cart_settings.enabled
|
|
||||||
and hasattr(frappe.local, "session")
|
|
||||||
and frappe.local.session.user != "Guest"
|
|
||||||
) and self.payment_channel != "Phone":
|
|
||||||
|
|
||||||
success_url = shopping_cart_settings.payment_success_url
|
|
||||||
if success_url:
|
|
||||||
redirect_to = ({"Orders": "/orders", "Invoices": "/invoices", "My Account": "/me"}).get(
|
|
||||||
success_url, "/me"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
redirect_to = get_url("/orders/{0}".format(self.reference_name))
|
|
||||||
|
|
||||||
return redirect_to
|
|
||||||
|
|
||||||
def create_subscription(self, payment_provider, gateway_controller, data):
|
def create_subscription(self, payment_provider, gateway_controller, data):
|
||||||
if payment_provider == "stripe":
|
if payment_provider == "stripe":
|
||||||
|
with payment_app_import_guard():
|
||||||
|
from payments.payment_gateways.stripe_integration import create_stripe_subscription
|
||||||
|
|
||||||
return create_stripe_subscription(gateway_controller, data)
|
return create_stripe_subscription(gateway_controller, data)
|
||||||
|
|
||||||
|
|
||||||
@ -544,13 +515,12 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
|
|||||||
|
|
||||||
|
|
||||||
def get_gateway_details(args): # nosemgrep
|
def get_gateway_details(args): # nosemgrep
|
||||||
"""return gateway and payment account of default payment gateway"""
|
"""
|
||||||
if args.get("payment_gateway_account"):
|
Return gateway and payment account of default payment gateway
|
||||||
return get_payment_gateway_account(args.get("payment_gateway_account"))
|
"""
|
||||||
|
gateway_account = args.get("payment_gateway_account", {"is_default": 1})
|
||||||
if args.order_type == "Shopping Cart":
|
if gateway_account:
|
||||||
payment_gateway_account = frappe.get_doc("E Commerce Settings").payment_gateway_account
|
return get_payment_gateway_account(gateway_account)
|
||||||
return get_payment_gateway_account(payment_gateway_account)
|
|
||||||
|
|
||||||
gateway_account = get_payment_gateway_account({"is_default": 1})
|
gateway_account = get_payment_gateway_account({"is_default": 1})
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"transaction_date",
|
"transaction_date",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"fiscal_year",
|
"fiscal_year",
|
||||||
|
"year_start_date",
|
||||||
"amended_from",
|
"amended_from",
|
||||||
"company",
|
"company",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
@ -100,16 +101,22 @@
|
|||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"label": "Error Message",
|
"label": "Error Message",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "year_start_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Year Start Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-07-20 14:51:04.714154",
|
"modified": "2023-09-11 20:19:11.810533",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Period Closing Voucher",
|
"name": "Period Closing Voucher",
|
||||||
|
"naming_rule": "Expression (old style)",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@ -144,5 +151,6 @@
|
|||||||
"search_fields": "posting_date, fiscal_year",
|
"search_fields": "posting_date, fiscal_year",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"title_field": "closing_account_head"
|
"title_field": "closing_account_head"
|
||||||
}
|
}
|
@ -95,15 +95,23 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
|
|
||||||
self.check_if_previous_year_closed()
|
self.check_if_previous_year_closed()
|
||||||
|
|
||||||
pce = frappe.db.sql(
|
pcv = frappe.qb.DocType("Period Closing Voucher")
|
||||||
"""select name from `tabPeriod Closing Voucher`
|
existing_entry = (
|
||||||
where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""",
|
frappe.qb.from_(pcv)
|
||||||
(self.posting_date, self.fiscal_year, self.company),
|
.select(pcv.name)
|
||||||
|
.where(
|
||||||
|
(pcv.posting_date >= self.posting_date)
|
||||||
|
& (pcv.fiscal_year == self.fiscal_year)
|
||||||
|
& (pcv.docstatus == 1)
|
||||||
|
& (pcv.company == self.company)
|
||||||
)
|
)
|
||||||
if pce and pce[0][0]:
|
.run()
|
||||||
|
)
|
||||||
|
|
||||||
|
if existing_entry and existing_entry[0][0]:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Another Period Closing Entry {0} has been made after {1}").format(
|
_("Another Period Closing Entry {0} has been made after {1}").format(
|
||||||
pce[0][0], self.posting_date
|
existing_entry[0][0], self.posting_date
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -130,18 +138,27 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
process_gl_entries,
|
process_gl_entries,
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
|
voucher_name=self.name,
|
||||||
|
timeout=3000,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.enqueue(
|
||||||
|
process_closing_entries,
|
||||||
|
gl_entries=gl_entries,
|
||||||
closing_entries=closing_entries,
|
closing_entries=closing_entries,
|
||||||
voucher_name=self.name,
|
voucher_name=self.name,
|
||||||
company=self.company,
|
company=self.company,
|
||||||
closing_date=self.posting_date,
|
closing_date=self.posting_date,
|
||||||
queue="long",
|
timeout=3000,
|
||||||
)
|
)
|
||||||
|
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
||||||
alert=True,
|
alert=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
|
process_gl_entries(gl_entries, self.name)
|
||||||
|
process_closing_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
|
||||||
|
|
||||||
def get_grouped_gl_entries(self, get_opening_entries=False):
|
def get_grouped_gl_entries(self, get_opening_entries=False):
|
||||||
closing_entries = []
|
closing_entries = []
|
||||||
@ -322,17 +339,12 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
return query.run(as_dict=1)
|
return query.run(as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
def process_gl_entries(gl_entries, voucher_name):
|
||||||
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
|
||||||
make_closing_entries,
|
|
||||||
)
|
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if gl_entries:
|
if gl_entries:
|
||||||
make_gl_entries(gl_entries, merge_entries=False)
|
make_gl_entries(gl_entries, merge_entries=False)
|
||||||
|
|
||||||
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
|
||||||
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
|
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
@ -340,6 +352,19 @@ def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closi
|
|||||||
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
|
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
|
||||||
|
|
||||||
|
|
||||||
|
def process_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
||||||
|
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
||||||
|
make_closing_entries,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if gl_entries + closing_entries:
|
||||||
|
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(e)
|
||||||
|
|
||||||
|
|
||||||
def make_reverse_gl_entries(voucher_type, voucher_no):
|
def make_reverse_gl_entries(voucher_type, voucher_no):
|
||||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from frappe.utils import today
|
|||||||
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
|
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
|
||||||
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
|
||||||
from erpnext.accounts.utils import get_fiscal_year, now
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
|
|
||||||
class TestPeriodClosingVoucher(unittest.TestCase):
|
class TestPeriodClosingVoucher(unittest.TestCase):
|
||||||
|
@ -6,6 +6,7 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.utils import add_days, nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
@ -125,70 +126,64 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(inv.grand_total, 5474.0)
|
self.assertEqual(inv.grand_total, 5474.0)
|
||||||
|
|
||||||
def test_tax_calculation_with_item_tax_template(self):
|
def test_tax_calculation_with_item_tax_template(self):
|
||||||
inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=1)
|
import json
|
||||||
item_row = inv.get("items")[0]
|
|
||||||
|
|
||||||
add_items = [
|
from erpnext.stock.get_item_details import get_item_details
|
||||||
(54, "_Test Account Excise Duty @ 12 - _TC"),
|
|
||||||
(288, "_Test Account Excise Duty @ 15 - _TC"),
|
# set tax template in item
|
||||||
(144, "_Test Account Excise Duty @ 20 - _TC"),
|
item = frappe.get_cached_doc("Item", "_Test Item")
|
||||||
(430, "_Test Item Tax Template 1 - _TC"),
|
item.set(
|
||||||
|
"taxes",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"item_tax_template": "_Test Account Excise Duty @ 15 - _TC",
|
||||||
|
"valid_from": add_days(nowdate(), -5),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
# create POS invoice with item
|
||||||
|
pos_inv = create_pos_invoice(qty=84, rate=4.6, do_not_save=True)
|
||||||
|
item_details = get_item_details(
|
||||||
|
doc=pos_inv,
|
||||||
|
args={
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"company": pos_inv.company,
|
||||||
|
"doctype": "POS Invoice",
|
||||||
|
"conversion_rate": 1.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
tax_map = json.loads(item_details.item_tax_rate)
|
||||||
|
for tax in tax_map:
|
||||||
|
pos_inv.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": tax,
|
||||||
|
"rate": tax_map[tax],
|
||||||
|
"description": "Test",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pos_inv.submit()
|
||||||
|
pos_inv.load_from_db()
|
||||||
|
|
||||||
|
# check if correct tax values are applied from tax template
|
||||||
|
self.assertEqual(pos_inv.net_total, 386.4)
|
||||||
|
|
||||||
|
expected_taxes = [
|
||||||
|
{
|
||||||
|
"tax_amount": 57.96,
|
||||||
|
"total": 444.36,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
for qty, item_tax_template in add_items:
|
|
||||||
item_row_copy = copy.deepcopy(item_row)
|
|
||||||
item_row_copy.qty = qty
|
|
||||||
item_row_copy.item_tax_template = item_tax_template
|
|
||||||
inv.append("items", item_row_copy)
|
|
||||||
|
|
||||||
inv.append(
|
for i in range(len(expected_taxes)):
|
||||||
"taxes",
|
for key in expected_taxes[i]:
|
||||||
{
|
self.assertEqual(expected_taxes[i][key], pos_inv.get("taxes")[i].get(key))
|
||||||
"account_head": "_Test Account Excise Duty - _TC",
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
"description": "Excise Duty",
|
|
||||||
"doctype": "Sales Taxes and Charges",
|
|
||||||
"rate": 11,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
inv.append(
|
|
||||||
"taxes",
|
|
||||||
{
|
|
||||||
"account_head": "_Test Account Education Cess - _TC",
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
"description": "Education Cess",
|
|
||||||
"doctype": "Sales Taxes and Charges",
|
|
||||||
"rate": 0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
inv.append(
|
|
||||||
"taxes",
|
|
||||||
{
|
|
||||||
"account_head": "_Test Account S&H Education Cess - _TC",
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
"description": "S&H Education Cess",
|
|
||||||
"doctype": "Sales Taxes and Charges",
|
|
||||||
"rate": 3,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
inv.insert()
|
|
||||||
|
|
||||||
self.assertEqual(inv.net_total, 4600)
|
self.assertEqual(pos_inv.get("base_total_taxes_and_charges"), 57.96)
|
||||||
|
|
||||||
self.assertEqual(inv.get("taxes")[0].tax_amount, 502.41)
|
|
||||||
self.assertEqual(inv.get("taxes")[0].total, 5102.41)
|
|
||||||
|
|
||||||
self.assertEqual(inv.get("taxes")[1].tax_amount, 197.80)
|
|
||||||
self.assertEqual(inv.get("taxes")[1].total, 5300.21)
|
|
||||||
|
|
||||||
self.assertEqual(inv.get("taxes")[2].tax_amount, 375.36)
|
|
||||||
self.assertEqual(inv.get("taxes")[2].total, 5675.57)
|
|
||||||
|
|
||||||
self.assertEqual(inv.grand_total, 5675.57)
|
|
||||||
self.assertEqual(inv.rounding_adjustment, 0.43)
|
|
||||||
self.assertEqual(inv.rounded_total, 5676.0)
|
|
||||||
|
|
||||||
def test_tax_calculation_with_multiple_items_and_discount(self):
|
def test_tax_calculation_with_multiple_items_and_discount(self):
|
||||||
inv = create_pos_invoice(qty=1, rate=75, do_not_save=True)
|
inv = create_pos_invoice(qty=1, rate=75, do_not_save=True)
|
||||||
@ -776,19 +771,28 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
create_batch_item_with_batch("_BATCH ITEM Test For Reserve", "TestBatch-RS 02")
|
create_batch_item_with_batch("_BATCH ITEM Test For Reserve", "TestBatch-RS 02")
|
||||||
make_stock_entry(
|
se = make_stock_entry(
|
||||||
target="_Test Warehouse - _TC",
|
target="_Test Warehouse - _TC",
|
||||||
item_code="_BATCH ITEM Test For Reserve",
|
item_code="_BATCH ITEM Test For Reserve",
|
||||||
qty=20,
|
qty=30,
|
||||||
basic_rate=100,
|
basic_rate=100,
|
||||||
batch_no="TestBatch-RS 02",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
se.reload()
|
||||||
|
|
||||||
|
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
# POS Invoice 1, for the batch without bundle
|
||||||
pos_inv1 = create_pos_invoice(
|
pos_inv1 = create_pos_invoice(
|
||||||
item="_BATCH ITEM Test For Reserve", rate=300, qty=15, batch_no="TestBatch-RS 02"
|
item="_BATCH ITEM Test For Reserve", rate=300, qty=15, do_not_save=1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
pos_inv1.items[0].batch_no = batch_no
|
||||||
pos_inv1.save()
|
pos_inv1.save()
|
||||||
pos_inv1.submit()
|
pos_inv1.submit()
|
||||||
|
pos_inv1.reload()
|
||||||
|
|
||||||
|
self.assertFalse(pos_inv1.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
batches = get_auto_batch_nos(
|
batches = get_auto_batch_nos(
|
||||||
frappe._dict(
|
frappe._dict(
|
||||||
@ -797,7 +801,24 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for batch in batches:
|
for batch in batches:
|
||||||
if batch.batch_no == "TestBatch-RS 02" and batch.warehouse == "_Test Warehouse - _TC":
|
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
|
||||||
|
self.assertEqual(batch.qty, 15)
|
||||||
|
|
||||||
|
# POS Invoice 2, for the batch with bundle
|
||||||
|
pos_inv2 = create_pos_invoice(
|
||||||
|
item="_BATCH ITEM Test For Reserve", rate=300, qty=10, batch_no=batch_no
|
||||||
|
)
|
||||||
|
pos_inv2.reload()
|
||||||
|
self.assertTrue(pos_inv2.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
batches = get_auto_batch_nos(
|
||||||
|
frappe._dict(
|
||||||
|
{"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for batch in batches:
|
||||||
|
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
|
||||||
self.assertEqual(batch.qty, 5)
|
self.assertEqual(batch.qty, 5)
|
||||||
|
|
||||||
def test_pos_batch_item_qty_validation(self):
|
def test_pos_batch_item_qty_validation(self):
|
||||||
|
@ -454,7 +454,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
||||||
error_message = safe_load_json(message_log)
|
error_message = get_error_message(message_log)
|
||||||
|
|
||||||
if closing_entry:
|
if closing_entry:
|
||||||
closing_entry.set_status(update=True, status="Failed")
|
closing_entry.set_status(update=True, status="Failed")
|
||||||
@ -483,7 +483,7 @@ def cancel_merge_logs(merge_logs, closing_entry=None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
||||||
error_message = safe_load_json(message_log)
|
error_message = get_error_message(message_log)
|
||||||
|
|
||||||
if closing_entry:
|
if closing_entry:
|
||||||
closing_entry.set_status(update=True, status="Submitted")
|
closing_entry.set_status(update=True, status="Submitted")
|
||||||
@ -525,10 +525,8 @@ def check_scheduler_status():
|
|||||||
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
|
frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
|
|
||||||
def safe_load_json(message):
|
def get_error_message(message) -> str:
|
||||||
try:
|
try:
|
||||||
json_message = json.loads(message).get("message")
|
return message["message"]
|
||||||
except Exception:
|
except Exception:
|
||||||
json_message = message
|
return str(message)
|
||||||
|
|
||||||
return json_message
|
|
||||||
|
@ -110,7 +110,7 @@
|
|||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-21 17:36:26.642617",
|
"modified": "2023-11-02 11:32:12.254018",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Process Payment Reconciliation Log",
|
"name": "Process Payment Reconciliation Log",
|
||||||
@ -125,7 +125,19 @@
|
|||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "Accounts Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts User",
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,20 @@ class ProcessStatementOfAccounts(Document):
|
|||||||
|
|
||||||
|
|
||||||
def get_report_pdf(doc, consolidated=True):
|
def get_report_pdf(doc, consolidated=True):
|
||||||
|
statement_dict = get_statement_dict(doc)
|
||||||
|
if not bool(statement_dict):
|
||||||
|
return False
|
||||||
|
elif consolidated:
|
||||||
|
delimiter = '<div style="page-break-before: always;"></div>' if doc.include_break else ""
|
||||||
|
result = delimiter.join(list(statement_dict.values()))
|
||||||
|
return get_pdf(result, {"orientation": doc.orientation})
|
||||||
|
else:
|
||||||
|
for customer, statement_html in statement_dict.items():
|
||||||
|
statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation})
|
||||||
|
return statement_dict
|
||||||
|
|
||||||
|
|
||||||
|
def get_statement_dict(doc, get_statement_dict=False):
|
||||||
statement_dict = {}
|
statement_dict = {}
|
||||||
ageing = ""
|
ageing = ""
|
||||||
|
|
||||||
@ -78,17 +92,10 @@ def get_report_pdf(doc, consolidated=True):
|
|||||||
if not res:
|
if not res:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing)
|
statement_dict[entry.customer] = (
|
||||||
|
[res, ageing] if get_statement_dict else get_html(doc, filters, entry, col, res, ageing)
|
||||||
|
)
|
||||||
|
|
||||||
if not bool(statement_dict):
|
|
||||||
return False
|
|
||||||
elif consolidated:
|
|
||||||
delimiter = '<div style="page-break-before: always;"></div>' if doc.include_break else ""
|
|
||||||
result = delimiter.join(list(statement_dict.values()))
|
|
||||||
return get_pdf(result, {"orientation": doc.orientation})
|
|
||||||
else:
|
|
||||||
for customer, statement_html in statement_dict.items():
|
|
||||||
statement_dict[customer] = get_pdf(statement_html, {"orientation": doc.orientation})
|
|
||||||
return statement_dict
|
return statement_dict
|
||||||
|
|
||||||
|
|
||||||
@ -102,7 +109,8 @@ def set_ageing(doc, entry):
|
|||||||
"range2": 60,
|
"range2": 60,
|
||||||
"range3": 90,
|
"range3": 90,
|
||||||
"range4": 120,
|
"range4": 120,
|
||||||
"customer": entry.customer,
|
"party_type": "Customer",
|
||||||
|
"party": [entry.customer],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
col1, ageing = get_ageing(ageing_filters)
|
col1, ageing = get_ageing(ageing_filters)
|
||||||
@ -145,7 +153,8 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency):
|
|||||||
def get_ar_filters(doc, entry):
|
def get_ar_filters(doc, entry):
|
||||||
return {
|
return {
|
||||||
"report_date": doc.posting_date if doc.posting_date else None,
|
"report_date": doc.posting_date if doc.posting_date else None,
|
||||||
"customer": entry.customer,
|
"party_type": "Customer",
|
||||||
|
"party": [entry.customer],
|
||||||
"customer_name": entry.customer_name if entry.customer_name else None,
|
"customer_name": entry.customer_name if entry.customer_name else None,
|
||||||
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
||||||
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
||||||
|
@ -4,39 +4,107 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_days, getdate, today
|
from frappe.utils import add_days, getdate, today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import (
|
from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import (
|
||||||
|
get_statement_dict,
|
||||||
send_emails,
|
send_emails,
|
||||||
)
|
)
|
||||||
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.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
|
||||||
|
|
||||||
class TestProcessStatementOfAccounts(unittest.TestCase):
|
class TestProcessStatementOfAccounts(AccountsTestMixin, FrappeTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.create_company()
|
||||||
|
self.create_customer()
|
||||||
|
self.create_customer(customer_name="Other Customer")
|
||||||
|
self.clear_old_entries()
|
||||||
self.si = create_sales_invoice()
|
self.si = create_sales_invoice()
|
||||||
self.process_soa = create_process_soa()
|
create_sales_invoice(customer="Other Customer")
|
||||||
|
|
||||||
|
def test_process_soa_for_gl(self):
|
||||||
|
"""Tests the utils for Statement of Accounts(General Ledger)"""
|
||||||
|
process_soa = create_process_soa(
|
||||||
|
name="_Test Process SOA for GL",
|
||||||
|
customers=[{"customer": "_Test Customer"}, {"customer": "Other Customer"}],
|
||||||
|
)
|
||||||
|
statement_dict = get_statement_dict(process_soa, get_statement_dict=True)
|
||||||
|
|
||||||
|
# Checks if the statements are filtered based on the Customer
|
||||||
|
self.assertIn("Other Customer", statement_dict)
|
||||||
|
self.assertIn("_Test Customer", statement_dict)
|
||||||
|
|
||||||
|
# Checks if the correct number of receivable entries exist
|
||||||
|
# 3 rows for opening and closing and 1 row for SI
|
||||||
|
receivable_entries = statement_dict["_Test Customer"][0]
|
||||||
|
self.assertEqual(len(receivable_entries), 4)
|
||||||
|
|
||||||
|
# Checks the amount for the receivable entry
|
||||||
|
self.assertEqual(receivable_entries[1].voucher_no, self.si.name)
|
||||||
|
self.assertEqual(receivable_entries[1].balance, 100)
|
||||||
|
|
||||||
|
def test_process_soa_for_ar(self):
|
||||||
|
"""Tests the utils for Statement of Accounts(Accounts Receivable)"""
|
||||||
|
process_soa = create_process_soa(name="_Test Process SOA for AR", report="Accounts Receivable")
|
||||||
|
statement_dict = get_statement_dict(process_soa, get_statement_dict=True)
|
||||||
|
|
||||||
|
# Checks if the statements are filtered based on the Customer
|
||||||
|
self.assertNotIn("Other Customer", statement_dict)
|
||||||
|
self.assertIn("_Test Customer", statement_dict)
|
||||||
|
|
||||||
|
# Checks if the correct number of receivable entries exist
|
||||||
|
receivable_entries = statement_dict["_Test Customer"][0]
|
||||||
|
self.assertEqual(len(receivable_entries), 1)
|
||||||
|
|
||||||
|
# Checks the amount for the receivable entry
|
||||||
|
self.assertEqual(receivable_entries[0].voucher_no, self.si.name)
|
||||||
|
self.assertEqual(receivable_entries[0].total_due, 100)
|
||||||
|
|
||||||
|
# Checks the ageing summary for AR
|
||||||
|
ageing_summary = statement_dict["_Test Customer"][1][0]
|
||||||
|
expected_summary = frappe._dict(
|
||||||
|
range1=100,
|
||||||
|
range2=0,
|
||||||
|
range3=0,
|
||||||
|
range4=0,
|
||||||
|
range5=0,
|
||||||
|
)
|
||||||
|
self.check_ageing_summary(ageing_summary, expected_summary)
|
||||||
|
|
||||||
def test_auto_email_for_process_soa_ar(self):
|
def test_auto_email_for_process_soa_ar(self):
|
||||||
send_emails(self.process_soa.name, from_scheduler=True)
|
process_soa = create_process_soa(
|
||||||
self.process_soa.load_from_db()
|
name="_Test Process SOA", enable_auto_email=1, report="Accounts Receivable"
|
||||||
self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7)))
|
)
|
||||||
|
send_emails(process_soa.name, from_scheduler=True)
|
||||||
|
process_soa.load_from_db()
|
||||||
|
self.assertEqual(process_soa.posting_date, getdate(add_days(today(), 7)))
|
||||||
|
|
||||||
|
def check_ageing_summary(self, ageing, expected_ageing):
|
||||||
|
for age_range in expected_ageing:
|
||||||
|
self.assertEqual(expected_ageing[age_range], ageing.get(age_range))
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA")
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
|
||||||
def create_process_soa():
|
def create_process_soa(**args):
|
||||||
frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA")
|
args = frappe._dict(args)
|
||||||
|
frappe.delete_doc_if_exists("Process Statement Of Accounts", args.name)
|
||||||
process_soa = frappe.new_doc("Process Statement Of Accounts")
|
process_soa = frappe.new_doc("Process Statement Of Accounts")
|
||||||
soa_dict = {
|
soa_dict = frappe._dict(
|
||||||
"name": "Test Process SOA",
|
name=args.name,
|
||||||
"company": "_Test Company",
|
company=args.company or "_Test Company",
|
||||||
}
|
customers=args.customers or [{"customer": "_Test Customer"}],
|
||||||
|
enable_auto_email=1 if args.enable_auto_email else 0,
|
||||||
|
frequency=args.frequency or "Weekly",
|
||||||
|
report=args.report or "General Ledger",
|
||||||
|
from_date=args.from_date or getdate(today()),
|
||||||
|
to_date=args.to_date or getdate(today()),
|
||||||
|
posting_date=args.posting_date or getdate(today()),
|
||||||
|
include_ageing=1,
|
||||||
|
)
|
||||||
process_soa.update(soa_dict)
|
process_soa.update(soa_dict)
|
||||||
process_soa.set("customers", [{"customer": "_Test Customer"}])
|
|
||||||
process_soa.enable_auto_email = 1
|
|
||||||
process_soa.frequency = "Weekly"
|
|
||||||
process_soa.report = "Accounts Receivable"
|
|
||||||
process_soa.save()
|
process_soa.save()
|
||||||
return process_soa
|
return process_soa
|
||||||
|
@ -65,6 +65,25 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm);
|
erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.frm.doc.repost_required && this.frm.doc.docstatus===1) {
|
||||||
|
this.frm.set_intro(__("Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update."));
|
||||||
|
this.frm.add_custom_button(__('Repost Accounting Entries'),
|
||||||
|
() => {
|
||||||
|
this.frm.call({
|
||||||
|
doc: this.frm.doc,
|
||||||
|
method: 'repost_accounting_entries',
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Reposting...'),
|
||||||
|
callback: (r) => {
|
||||||
|
if (!r.exc) {
|
||||||
|
frappe.msgprint(__('Accounting Entries are reposted.'));
|
||||||
|
me.frm.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).removeClass('btn-default').addClass('btn-warning');
|
||||||
|
}
|
||||||
|
|
||||||
if(!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0){
|
if(!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0){
|
||||||
if(doc.on_hold) {
|
if(doc.on_hold) {
|
||||||
this.frm.add_custom_button(
|
this.frm.add_custom_button(
|
||||||
@ -460,6 +479,12 @@ cur_frm.set_query("expense_account", "items", function(doc) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cur_frm.set_query("wip_composite_asset", "items", function() {
|
||||||
|
return {
|
||||||
|
filters: {'is_composite_asset': 1, 'docstatus': 0 }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
cur_frm.cscript.expense_account = function(doc, cdt, cdn){
|
cur_frm.cscript.expense_account = function(doc, cdt, cdn){
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
if(d.idx == 1 && d.expense_account){
|
if(d.idx == 1 && d.expense_account){
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
"currency_and_price_list",
|
"currency_and_price_list",
|
||||||
"currency",
|
"currency",
|
||||||
"conversion_rate",
|
"conversion_rate",
|
||||||
|
"use_transaction_date_exchange_rate",
|
||||||
"column_break2",
|
"column_break2",
|
||||||
"buying_price_list",
|
"buying_price_list",
|
||||||
"price_list_currency",
|
"price_list_currency",
|
||||||
@ -166,6 +167,7 @@
|
|||||||
"against_expense_account",
|
"against_expense_account",
|
||||||
"column_break_63",
|
"column_break_63",
|
||||||
"unrealized_profit_loss_account",
|
"unrealized_profit_loss_account",
|
||||||
|
"repost_required",
|
||||||
"subscription_section",
|
"subscription_section",
|
||||||
"subscription",
|
"subscription",
|
||||||
"auto_repeat",
|
"auto_repeat",
|
||||||
@ -191,8 +193,7 @@
|
|||||||
"inter_company_invoice_reference",
|
"inter_company_invoice_reference",
|
||||||
"is_old_subcontracting_flow",
|
"is_old_subcontracting_flow",
|
||||||
"remarks",
|
"remarks",
|
||||||
"connections_tab",
|
"connections_tab"
|
||||||
"column_break_38"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -383,7 +384,8 @@
|
|||||||
"label": "Supplier Invoice No",
|
"label": "Supplier Invoice No",
|
||||||
"oldfieldname": "bill_no",
|
"oldfieldname": "bill_no",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"print_hide": 1
|
"print_hide": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_15",
|
"fieldname": "column_break_15",
|
||||||
@ -406,7 +408,8 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Purchase Invoice",
|
"options": "Purchase Invoice",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_addresses",
|
"fieldname": "section_addresses",
|
||||||
@ -990,6 +993,7 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"fieldname": "cash_bank_account",
|
"fieldname": "cash_bank_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Cash/Bank Account",
|
"label": "Cash/Bank Account",
|
||||||
@ -1053,6 +1057,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"depends_on": "eval:flt(doc.write_off_amount)!=0",
|
"depends_on": "eval:flt(doc.write_off_amount)!=0",
|
||||||
"fieldname": "write_off_account",
|
"fieldname": "write_off_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -1217,6 +1222,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"default": "No",
|
"default": "No",
|
||||||
"fieldname": "is_opening",
|
"fieldname": "is_opening",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@ -1349,6 +1355,7 @@
|
|||||||
"options": "Project"
|
"options": "Project"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"depends_on": "eval:doc.is_internal_supplier",
|
"depends_on": "eval:doc.is_internal_supplier",
|
||||||
"description": "Unrealized Profit/Loss account for intra-company transfers",
|
"description": "Unrealized Profit/Loss account for intra-company transfers",
|
||||||
"fieldname": "unrealized_profit_loss_account",
|
"fieldname": "unrealized_profit_loss_account",
|
||||||
@ -1381,6 +1388,7 @@
|
|||||||
"depends_on": "eval:doc.is_subcontracted",
|
"depends_on": "eval:doc.is_subcontracted",
|
||||||
"fieldname": "supplier_warehouse",
|
"fieldname": "supplier_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
"label": "Supplier Warehouse",
|
"label": "Supplier Warehouse",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Warehouse",
|
"options": "Warehouse",
|
||||||
@ -1504,10 +1512,6 @@
|
|||||||
"fieldname": "column_break_6",
|
"fieldname": "column_break_6",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_38",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_50",
|
"fieldname": "column_break_50",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
@ -1578,13 +1582,29 @@
|
|||||||
"fieldname": "use_company_roundoff_cost_center",
|
"fieldname": "use_company_roundoff_cost_center",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Use Company Default Round Off Cost Center"
|
"label": "Use Company Default Round Off Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "repost_required",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Repost Required",
|
||||||
|
"options": "Account",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "use_transaction_date_exchange_rate",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Use Transaction Date Exchange Rate",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-25 17:22:59.145031",
|
"modified": "2023-11-03 15:47:30.319200",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
@ -11,6 +11,9 @@ from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate,
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||||
|
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||||
|
validate_docs_for_deferred_accounting,
|
||||||
|
)
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||||
check_if_return_invoice_linked_with_payment_entry,
|
check_if_return_invoice_linked_with_payment_entry,
|
||||||
get_total_in_party_account_currency,
|
get_total_in_party_account_currency,
|
||||||
@ -30,7 +33,7 @@ from erpnext.accounts.general_ledger import (
|
|||||||
)
|
)
|
||||||
from erpnext.accounts.party import get_due_date, get_party_account
|
from erpnext.accounts.party import get_due_date, get_party_account
|
||||||
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
|
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
|
||||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
|
||||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||||
from erpnext.controllers.accounts_controller import validate_account_head
|
from erpnext.controllers.accounts_controller import validate_account_head
|
||||||
@ -278,9 +281,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# in case of auto inventory accounting,
|
# in case of auto inventory accounting,
|
||||||
# expense account is always "Stock Received But Not Billed" for a stock item
|
# expense account is always "Stock Received But Not Billed" for a stock item
|
||||||
# except opening entry, drop-ship entry and fixed asset items
|
# except opening entry, drop-ship entry and fixed asset items
|
||||||
if item.item_code:
|
|
||||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
auto_accounting_for_stock
|
auto_accounting_for_stock
|
||||||
and item.item_code in stock_items
|
and item.item_code in stock_items
|
||||||
@ -347,22 +347,26 @@ class PurchaseInvoice(BuyingController):
|
|||||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = stock_not_billed_account
|
item.expense_account = stock_not_billed_account
|
||||||
|
elif item.is_fixed_asset and item.pr_detail:
|
||||||
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
if not asset_received_but_not_billed:
|
||||||
|
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||||
|
item.expense_account = asset_received_but_not_billed
|
||||||
|
elif item.is_fixed_asset:
|
||||||
|
account_type = (
|
||||||
|
"capital_work_in_progress_account"
|
||||||
|
if is_cwip_accounting_enabled(item.asset_category)
|
||||||
|
else "fixed_asset_account"
|
||||||
|
)
|
||||||
asset_category_account = get_asset_category_account(
|
asset_category_account = get_asset_category_account(
|
||||||
"fixed_asset_account", item=item.item_code, company=self.company
|
account_type, item=item.item_code, company=self.company
|
||||||
)
|
)
|
||||||
if not asset_category_account:
|
if not asset_category_account:
|
||||||
form_link = get_link_to_form("Asset Category", asset_category)
|
form_link = get_link_to_form("Asset Category", item.asset_category)
|
||||||
throw(
|
throw(
|
||||||
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
|
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
|
||||||
title=_("Missing Account"),
|
title=_("Missing Account"),
|
||||||
)
|
)
|
||||||
item.expense_account = asset_category_account
|
item.expense_account = asset_category_account
|
||||||
elif item.is_fixed_asset and item.pr_detail:
|
|
||||||
if not asset_received_but_not_billed:
|
|
||||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
|
||||||
item.expense_account = asset_received_but_not_billed
|
|
||||||
elif not item.expense_account and for_validate:
|
elif not item.expense_account and for_validate:
|
||||||
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
|
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
|
||||||
|
|
||||||
@ -484,6 +488,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
_("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt)
|
_("Stock cannot be updated against Purchase Receipt {0}").format(item.purchase_receipt)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_for_repost(self):
|
||||||
|
self.validate_write_off_account()
|
||||||
|
self.validate_expense_account()
|
||||||
|
validate_docs_for_deferred_accounting([], [self.name])
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
super(PurchaseInvoice, self).on_submit()
|
super(PurchaseInvoice, self).on_submit()
|
||||||
|
|
||||||
@ -522,6 +531,19 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
self.process_common_party_accounting()
|
self.process_common_party_accounting()
|
||||||
|
|
||||||
|
def on_update_after_submit(self):
|
||||||
|
if hasattr(self, "repost_required"):
|
||||||
|
fields_to_check = [
|
||||||
|
"cash_bank_account",
|
||||||
|
"write_off_account",
|
||||||
|
"unrealized_profit_loss_account",
|
||||||
|
]
|
||||||
|
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
|
||||||
|
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||||
|
if self.needs_repost:
|
||||||
|
self.validate_for_repost()
|
||||||
|
self.db_set("repost_required", self.needs_repost)
|
||||||
|
|
||||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||||
if not gl_entries:
|
if not gl_entries:
|
||||||
gl_entries = self.get_gl_entries()
|
gl_entries = self.get_gl_entries()
|
||||||
@ -563,12 +585,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
def get_gl_entries(self, warehouse_account=None):
|
def get_gl_entries(self, warehouse_account=None):
|
||||||
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
|
||||||
|
|
||||||
if self.auto_accounting_for_stock:
|
if self.auto_accounting_for_stock:
|
||||||
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
|
self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed")
|
||||||
self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
|
|
||||||
else:
|
else:
|
||||||
self.stock_received_but_not_billed = None
|
self.stock_received_but_not_billed = None
|
||||||
self.expenses_included_in_valuation = None
|
|
||||||
|
|
||||||
self.negative_expense_to_be_booked = 0.0
|
self.negative_expense_to_be_booked = 0.0
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
@ -577,9 +598,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.make_item_gl_entries(gl_entries)
|
self.make_item_gl_entries(gl_entries)
|
||||||
self.make_precision_loss_gl_entry(gl_entries)
|
self.make_precision_loss_gl_entry(gl_entries)
|
||||||
|
|
||||||
if self.check_asset_cwip_enabled():
|
|
||||||
self.get_asset_gl_entry(gl_entries)
|
|
||||||
|
|
||||||
self.make_tax_gl_entries(gl_entries)
|
self.make_tax_gl_entries(gl_entries)
|
||||||
self.make_internal_transfer_gl_entries(gl_entries)
|
self.make_internal_transfer_gl_entries(gl_entries)
|
||||||
|
|
||||||
@ -682,7 +700,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if item.item_code:
|
if item.item_code:
|
||||||
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||||
|
|
||||||
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
|
if (
|
||||||
|
self.update_stock
|
||||||
|
and self.auto_accounting_for_stock
|
||||||
|
and (item.item_code in stock_items or item.is_fixed_asset)
|
||||||
|
):
|
||||||
# warehouse account
|
# warehouse account
|
||||||
warehouse_debit_amount = self.make_stock_adjustment_entry(
|
warehouse_debit_amount = self.make_stock_adjustment_entry(
|
||||||
gl_entries, item, voucher_wise_stock_value, account_currency
|
gl_entries, item, voucher_wise_stock_value, account_currency
|
||||||
@ -803,9 +825,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif not item.is_fixed_asset or (
|
else:
|
||||||
item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category)
|
|
||||||
):
|
|
||||||
expense_account = (
|
expense_account = (
|
||||||
item.expense_account
|
item.expense_account
|
||||||
if (not item.enable_deferred_expense or self.is_return)
|
if (not item.enable_deferred_expense or self.is_return)
|
||||||
@ -901,42 +921,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# If asset is bought through this document and not linked to PR
|
|
||||||
if self.update_stock and item.landed_cost_voucher_amount:
|
|
||||||
expenses_included_in_asset_valuation = self.get_company_default(
|
|
||||||
"expenses_included_in_asset_valuation"
|
|
||||||
)
|
|
||||||
# Amount added through landed-cost-voucher
|
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": expenses_included_in_asset_valuation,
|
|
||||||
"against_type": "Account",
|
|
||||||
"against": expense_account,
|
|
||||||
"cost_center": item.cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
|
||||||
"credit": flt(item.landed_cost_voucher_amount),
|
|
||||||
"project": item.project or self.project,
|
|
||||||
},
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": expense_account,
|
|
||||||
"against_type": "Account",
|
|
||||||
"against": expenses_included_in_asset_valuation,
|
|
||||||
"cost_center": item.cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
|
||||||
"debit": flt(item.landed_cost_voucher_amount),
|
|
||||||
"project": item.project or self.project,
|
|
||||||
},
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# update gross amount of asset bought through this document
|
# update gross amount of asset bought through this document
|
||||||
assets = frappe.db.get_all(
|
assets = frappe.db.get_all(
|
||||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||||
@ -961,11 +945,17 @@ class PurchaseInvoice(BuyingController):
|
|||||||
(item.purchase_receipt, valuation_tax_accounts),
|
(item.purchase_receipt, valuation_tax_accounts),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
stock_rbnb = (
|
||||||
|
self.get_company_default("asset_received_but_not_billed")
|
||||||
|
if item.is_fixed_asset
|
||||||
|
else self.stock_received_but_not_billed
|
||||||
|
)
|
||||||
|
|
||||||
if not negative_expense_booked_in_pr:
|
if not negative_expense_booked_in_pr:
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": self.stock_received_but_not_billed,
|
"account": stock_rbnb,
|
||||||
"against_type": "Supplier",
|
"against_type": "Supplier",
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
||||||
@ -981,154 +971,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
item.item_tax_amount, item.precision("item_tax_amount")
|
item.item_tax_amount, item.precision("item_tax_amount")
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_asset_gl_entry(self, gl_entries):
|
|
||||||
arbnb_account = None
|
|
||||||
eiiav_account = None
|
|
||||||
asset_eiiav_currency = None
|
|
||||||
|
|
||||||
for item in self.get("items"):
|
|
||||||
if item.is_fixed_asset:
|
|
||||||
asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
|
|
||||||
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
|
|
||||||
|
|
||||||
item_exp_acc_type = frappe.get_cached_value("Account", item.expense_account, "account_type")
|
|
||||||
if not item.expense_account or item_exp_acc_type not in [
|
|
||||||
"Asset Received But Not Billed",
|
|
||||||
"Fixed Asset",
|
|
||||||
]:
|
|
||||||
if not arbnb_account:
|
|
||||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
|
||||||
item.expense_account = arbnb_account
|
|
||||||
|
|
||||||
if not self.update_stock:
|
|
||||||
arbnb_currency = get_account_currency(item.expense_account)
|
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": item.expense_account,
|
|
||||||
"against_type": "Supplier",
|
|
||||||
"against": self.supplier,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
|
||||||
"debit": base_asset_amount,
|
|
||||||
"debit_in_account_currency": (
|
|
||||||
base_asset_amount if arbnb_currency == self.company_currency else asset_amount
|
|
||||||
),
|
|
||||||
"cost_center": item.cost_center,
|
|
||||||
"project": item.project or self.project,
|
|
||||||
},
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if item.item_tax_amount:
|
|
||||||
if not eiiav_account or not asset_eiiav_currency:
|
|
||||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
|
||||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
|
||||||
|
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": eiiav_account,
|
|
||||||
"against_type": "Supplier",
|
|
||||||
"against": self.supplier,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
|
||||||
"cost_center": item.cost_center,
|
|
||||||
"project": item.project or self.project,
|
|
||||||
"credit": item.item_tax_amount,
|
|
||||||
"credit_in_account_currency": (
|
|
||||||
item.item_tax_amount
|
|
||||||
if asset_eiiav_currency == self.company_currency
|
|
||||||
else item.item_tax_amount / self.conversion_rate
|
|
||||||
),
|
|
||||||
},
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
cwip_account = get_asset_account(
|
|
||||||
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
|
|
||||||
)
|
|
||||||
|
|
||||||
cwip_account_currency = get_account_currency(cwip_account)
|
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": cwip_account,
|
|
||||||
"against_type": "Supplier",
|
|
||||||
"against": self.supplier,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
|
||||||
"debit": base_asset_amount,
|
|
||||||
"debit_in_account_currency": (
|
|
||||||
base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
|
|
||||||
),
|
|
||||||
"cost_center": self.cost_center,
|
|
||||||
"project": item.project or self.project,
|
|
||||||
},
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
|
|
||||||
if not eiiav_account or not asset_eiiav_currency:
|
|
||||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
|
||||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
|
||||||
|
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": eiiav_account,
|
|
||||||
"against_type": "Supplier",
|
|
||||||
"against": self.supplier,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
|
||||||
"cost_center": item.cost_center,
|
|
||||||
"credit": item.item_tax_amount,
|
|
||||||
"project": item.project or self.project,
|
|
||||||
"credit_in_account_currency": (
|
|
||||||
item.item_tax_amount
|
|
||||||
if asset_eiiav_currency == self.company_currency
|
|
||||||
else item.item_tax_amount / self.conversion_rate
|
|
||||||
),
|
|
||||||
},
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Assets are bought through this document then it will be linked to this document
|
|
||||||
if flt(item.landed_cost_voucher_amount):
|
|
||||||
if not eiiav_account:
|
|
||||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
|
||||||
|
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": eiiav_account,
|
|
||||||
"against_type": "Account",
|
|
||||||
"against": cwip_account,
|
|
||||||
"cost_center": item.cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
|
||||||
"credit": flt(item.landed_cost_voucher_amount),
|
|
||||||
"project": item.project or self.project,
|
|
||||||
},
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
gl_entries.append(
|
|
||||||
self.get_gl_dict(
|
|
||||||
{
|
|
||||||
"account": cwip_account,
|
|
||||||
"against_type": "Account",
|
|
||||||
"against": eiiav_account,
|
|
||||||
"cost_center": item.cost_center,
|
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
|
||||||
"debit": flt(item.landed_cost_voucher_amount),
|
|
||||||
"project": item.project or self.project,
|
|
||||||
},
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# update gross amount of assets bought through this document
|
|
||||||
assets = frappe.db.get_all(
|
assets = frappe.db.get_all(
|
||||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||||
)
|
)
|
||||||
@ -1136,8 +978,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||||
|
|
||||||
return gl_entries
|
|
||||||
|
|
||||||
def make_stock_adjustment_entry(
|
def make_stock_adjustment_entry(
|
||||||
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
||||||
):
|
):
|
||||||
@ -1845,6 +1685,7 @@ def make_purchase_receipt(source_name, target_doc=None):
|
|||||||
"po_detail": "purchase_order_item",
|
"po_detail": "purchase_order_item",
|
||||||
"material_request": "material_request",
|
"material_request": "material_request",
|
||||||
"material_request_item": "material_request_item",
|
"material_request_item": "material_request_item",
|
||||||
|
"wip_composite_asset": "wip_composite_asset",
|
||||||
},
|
},
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),
|
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
|
from frappe.utils import add_days, cint, flt, getdate, nowdate, today
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@ -38,7 +38,7 @@ test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Templ
|
|||||||
test_ignore = ["Serial No"]
|
test_ignore = ["Serial No"]
|
||||||
|
|
||||||
|
|
||||||
class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(self):
|
||||||
unlink_payment_on_cancel_of_invoice()
|
unlink_payment_on_cancel_of_invoice()
|
||||||
@ -48,6 +48,9 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
def tearDownClass(self):
|
def tearDownClass(self):
|
||||||
unlink_payment_on_cancel_of_invoice(0)
|
unlink_payment_on_cancel_of_invoice(0)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
def test_purchase_invoice_received_qty(self):
|
def test_purchase_invoice_received_qty(self):
|
||||||
"""
|
"""
|
||||||
1. Test if received qty is validated against accepted + rejected
|
1. Test if received qty is validated against accepted + rejected
|
||||||
@ -422,6 +425,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
self.assertEqual(tax.tax_amount, expected_values[i][1])
|
self.assertEqual(tax.tax_amount, expected_values[i][1])
|
||||||
self.assertEqual(tax.total, expected_values[i][2])
|
self.assertEqual(tax.total, expected_values[i][2])
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||||
def test_purchase_invoice_with_advance(self):
|
def test_purchase_invoice_with_advance(self):
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||||
test_records as jv_test_records,
|
test_records as jv_test_records,
|
||||||
@ -476,6 +480,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||||
def test_invoice_with_advance_and_multi_payment_terms(self):
|
def test_invoice_with_advance_and_multi_payment_terms(self):
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||||
test_records as jv_test_records,
|
test_records as jv_test_records,
|
||||||
@ -1220,6 +1225,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||||
acc_settings.save()
|
acc_settings.save()
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||||
def test_gain_loss_with_advance_entry(self):
|
def test_gain_loss_with_advance_entry(self):
|
||||||
unlink_enabled = frappe.db.get_value(
|
unlink_enabled = frappe.db.get_value(
|
||||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
|
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice"
|
||||||
@ -1420,6 +1426,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
)
|
)
|
||||||
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
|
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||||
def test_purchase_invoice_advance_taxes(self):
|
def test_purchase_invoice_advance_taxes(self):
|
||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
|
|
||||||
@ -1744,7 +1751,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
|
|
||||||
pi = make_purchase_invoice(
|
pi = make_purchase_invoice(
|
||||||
company="_Test Company",
|
company="_Test Company",
|
||||||
customer="_Test Supplier",
|
|
||||||
do_not_save=True,
|
do_not_save=True,
|
||||||
do_not_submit=True,
|
do_not_submit=True,
|
||||||
rate=1000,
|
rate=1000,
|
||||||
@ -1862,7 +1868,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
|
|
||||||
pi = make_purchase_invoice(
|
pi = make_purchase_invoice(
|
||||||
company="_Test Company",
|
company="_Test Company",
|
||||||
customer="_Test Supplier",
|
|
||||||
do_not_save=True,
|
do_not_save=True,
|
||||||
do_not_submit=True,
|
do_not_submit=True,
|
||||||
rate=1000,
|
rate=1000,
|
||||||
@ -1892,6 +1897,82 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
clear_dimension_defaults("Branch")
|
clear_dimension_defaults("Branch")
|
||||||
disable_dimension()
|
disable_dimension()
|
||||||
|
|
||||||
|
def test_repost_accounting_entries(self):
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
rate=1000,
|
||||||
|
price_list_rate=1000,
|
||||||
|
qty=1,
|
||||||
|
)
|
||||||
|
expected_gle = [
|
||||||
|
["_Test Account Cost for Goods Sold - _TC", 1000, 0.0, nowdate()],
|
||||||
|
["Creditors - _TC", 0.0, 1000, nowdate()],
|
||||||
|
]
|
||||||
|
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||||
|
|
||||||
|
pi.items[0].expense_account = "Service - _TC"
|
||||||
|
pi.save()
|
||||||
|
pi.load_from_db()
|
||||||
|
self.assertTrue(pi.repost_required)
|
||||||
|
pi.repost_accounting_entries()
|
||||||
|
|
||||||
|
expected_gle = [
|
||||||
|
["Creditors - _TC", 0.0, 1000, nowdate()],
|
||||||
|
["Service - _TC", 1000, 0.0, nowdate()],
|
||||||
|
]
|
||||||
|
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||||
|
pi.load_from_db()
|
||||||
|
self.assertFalse(pi.repost_required)
|
||||||
|
|
||||||
|
@change_settings("Buying Settings", {"supplier_group": None})
|
||||||
|
def test_purchase_invoice_without_supplier_group(self):
|
||||||
|
# Create a Supplier
|
||||||
|
test_supplier_name = "_Test Supplier Without Supplier Group"
|
||||||
|
if not frappe.db.exists("Supplier", test_supplier_name):
|
||||||
|
supplier = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Supplier",
|
||||||
|
"supplier_name": test_supplier_name,
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
self.assertEqual(supplier.supplier_group, None)
|
||||||
|
|
||||||
|
po = create_purchase_order(
|
||||||
|
supplier=test_supplier_name,
|
||||||
|
rate=3000,
|
||||||
|
item="_Test Non Stock Item",
|
||||||
|
posting_date="2021-09-15",
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(supplier=test_supplier_name)
|
||||||
|
|
||||||
|
self.assertEqual(po.docstatus, 1)
|
||||||
|
self.assertEqual(pi.docstatus, 1)
|
||||||
|
|
||||||
|
def test_default_cost_center_for_purchase(self):
|
||||||
|
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||||
|
|
||||||
|
for c_center in ["_Test Cost Center Selling", "_Test Cost Center Buying"]:
|
||||||
|
create_cost_center(cost_center_name=c_center)
|
||||||
|
|
||||||
|
item = create_item(
|
||||||
|
"_Test Cost Center Item For Purchase",
|
||||||
|
is_stock_item=1,
|
||||||
|
buying_cost_center="_Test Cost Center Buying - _TC",
|
||||||
|
selling_cost_center="_Test Cost Center Selling - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
item=item.name, qty=1, rate=1000, update_stock=True, do_not_submit=True, cost_center=""
|
||||||
|
)
|
||||||
|
|
||||||
|
pi.items[0].cost_center = ""
|
||||||
|
pi.set_missing_values()
|
||||||
|
pi.calculate_taxes_and_totals()
|
||||||
|
pi.save()
|
||||||
|
|
||||||
|
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
||||||
|
|
||||||
|
|
||||||
def set_advance_flag(company, flag, default_account):
|
def set_advance_flag(company, flag, default_account):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
|
@ -77,6 +77,7 @@
|
|||||||
"manufacturer_part_no",
|
"manufacturer_part_no",
|
||||||
"accounting",
|
"accounting",
|
||||||
"expense_account",
|
"expense_account",
|
||||||
|
"wip_composite_asset",
|
||||||
"col_break5",
|
"col_break5",
|
||||||
"is_fixed_asset",
|
"is_fixed_asset",
|
||||||
"asset_location",
|
"asset_location",
|
||||||
@ -473,6 +474,7 @@
|
|||||||
"label": "Accounting"
|
"label": "Accounting"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"fieldname": "expense_account",
|
"fieldname": "expense_account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Expense Head",
|
"label": "Expense Head",
|
||||||
@ -902,12 +904,18 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "wip_composite_asset",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "WIP Composite Asset",
|
||||||
|
"options": "Asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-26 12:54:53.178156",
|
"modified": "2023-10-03 21:01:01.824892",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
@ -86,6 +86,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "account_head",
|
"fieldname": "account_head",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -97,6 +98,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"default": ":Company",
|
"default": ":Company",
|
||||||
"fieldname": "cost_center",
|
"fieldname": "cost_center",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-27 15:47:58.975034",
|
"modified": "2023-09-26 14:21:27.362567",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Repost Accounting Ledger",
|
"name": "Repost Accounting Ledger",
|
||||||
@ -77,5 +77,6 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
}
|
}
|
@ -21,29 +21,8 @@ class RepostAccountingLedger(Document):
|
|||||||
|
|
||||||
def validate_for_deferred_accounting(self):
|
def validate_for_deferred_accounting(self):
|
||||||
sales_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Sales Invoice"]
|
sales_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Sales Invoice"]
|
||||||
docs_with_deferred_revenue = frappe.db.get_all(
|
|
||||||
"Sales Invoice Item",
|
|
||||||
filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True},
|
|
||||||
fields=["parent"],
|
|
||||||
as_list=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
purchase_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Purchase Invoice"]
|
purchase_docs = [x.voucher_no for x in self.vouchers if x.voucher_type == "Purchase Invoice"]
|
||||||
docs_with_deferred_expense = frappe.db.get_all(
|
validate_docs_for_deferred_accounting(sales_docs, purchase_docs)
|
||||||
"Purchase Invoice Item",
|
|
||||||
filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1},
|
|
||||||
fields=["parent"],
|
|
||||||
as_list=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
if docs_with_deferred_revenue or docs_with_deferred_expense:
|
|
||||||
frappe.throw(
|
|
||||||
_("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
|
|
||||||
frappe.bold(
|
|
||||||
comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_for_closed_fiscal_year(self):
|
def validate_for_closed_fiscal_year(self):
|
||||||
if self.vouchers:
|
if self.vouchers:
|
||||||
@ -139,6 +118,7 @@ class RepostAccountingLedger(Document):
|
|||||||
return rendered_page
|
return rendered_page
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
if len(self.vouchers) > 1:
|
||||||
job_name = "repost_accounting_ledger_" + self.name
|
job_name = "repost_accounting_ledger_" + self.name
|
||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
|
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
|
||||||
@ -147,6 +127,8 @@ class RepostAccountingLedger(Document):
|
|||||||
job_name=job_name,
|
job_name=job_name,
|
||||||
)
|
)
|
||||||
frappe.msgprint(_("Repost has started in the background"))
|
frappe.msgprint(_("Repost has started in the background"))
|
||||||
|
else:
|
||||||
|
start_repost(self.name)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@ -181,3 +163,26 @@ def start_repost(account_repost_doc=str) -> None:
|
|||||||
doc.make_gl_entries()
|
doc.make_gl_entries()
|
||||||
|
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
||||||
|
docs_with_deferred_revenue = frappe.db.get_all(
|
||||||
|
"Sales Invoice Item",
|
||||||
|
filters={"parent": ["in", sales_docs], "docstatus": 1, "enable_deferred_revenue": True},
|
||||||
|
fields=["parent"],
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
docs_with_deferred_expense = frappe.db.get_all(
|
||||||
|
"Purchase Invoice Item",
|
||||||
|
filters={"parent": ["in", purchase_docs], "docstatus": 1, "enable_deferred_expense": 1},
|
||||||
|
fields=["parent"],
|
||||||
|
as_list=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
if docs_with_deferred_revenue or docs_with_deferred_expense:
|
||||||
|
frappe.throw(
|
||||||
|
_("Documents: {0} have deferred revenue/expense enabled for them. Cannot repost.").format(
|
||||||
|
frappe.bold(comma_and([x[0] for x in docs_with_deferred_expense + docs_with_deferred_revenue]))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@ -99,7 +99,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-08 07:38:40.079038",
|
"modified": "2023-09-26 14:21:35.719727",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Repost Payment Ledger",
|
"name": "Repost Payment Ledger",
|
||||||
@ -155,5 +155,6 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
}
|
}
|
@ -26,6 +26,7 @@
|
|||||||
"is_return",
|
"is_return",
|
||||||
"return_against",
|
"return_against",
|
||||||
"update_billed_amount_in_sales_order",
|
"update_billed_amount_in_sales_order",
|
||||||
|
"update_billed_amount_in_delivery_note",
|
||||||
"is_debit_note",
|
"is_debit_note",
|
||||||
"amended_from",
|
"amended_from",
|
||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
@ -2153,6 +2154,13 @@
|
|||||||
"fieldname": "use_company_roundoff_cost_center",
|
"fieldname": "use_company_roundoff_cost_center",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Use Company default Cost Center for Round off"
|
"label": "Use Company default Cost Center for Round off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval: doc.is_return",
|
||||||
|
"fieldname": "update_billed_amount_in_delivery_note",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Update Billed Amount in Delivery Note"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@ -2165,7 +2173,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-07-25 16:02:18.988799",
|
"modified": "2023-11-03 14:39:38.012346",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -11,13 +11,13 @@ from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form
|
|||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|
||||||
get_accounting_dimensions,
|
|
||||||
)
|
|
||||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||||
get_loyalty_program_details_with_points,
|
get_loyalty_program_details_with_points,
|
||||||
validate_loyalty_points,
|
validate_loyalty_points,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||||
|
validate_docs_for_deferred_accounting,
|
||||||
|
)
|
||||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||||
get_party_tax_withholding_details,
|
get_party_tax_withholding_details,
|
||||||
)
|
)
|
||||||
@ -168,6 +168,12 @@ class SalesInvoice(SellingController):
|
|||||||
self.validate_account_for_change_amount()
|
self.validate_account_for_change_amount()
|
||||||
self.validate_income_account()
|
self.validate_income_account()
|
||||||
|
|
||||||
|
def validate_for_repost(self):
|
||||||
|
self.validate_write_off_account()
|
||||||
|
self.validate_account_for_change_amount()
|
||||||
|
self.validate_income_account()
|
||||||
|
validate_docs_for_deferred_accounting([self.name], [])
|
||||||
|
|
||||||
def validate_fixed_asset(self):
|
def validate_fixed_asset(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
|
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
|
||||||
@ -247,6 +253,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
self.update_status_updater_args()
|
self.update_status_updater_args()
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
|
|
||||||
self.update_billing_status_in_dn()
|
self.update_billing_status_in_dn()
|
||||||
self.clear_unallocated_mode_of_payments()
|
self.clear_unallocated_mode_of_payments()
|
||||||
|
|
||||||
@ -517,90 +524,22 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
def on_update_after_submit(self):
|
def on_update_after_submit(self):
|
||||||
if hasattr(self, "repost_required"):
|
if hasattr(self, "repost_required"):
|
||||||
needs_repost = 0
|
fields_to_check = [
|
||||||
|
|
||||||
# Check if any field affecting accounting entry is altered
|
|
||||||
doc_before_update = self.get_doc_before_save()
|
|
||||||
accounting_dimensions = get_accounting_dimensions() + ["cost_center", "project"]
|
|
||||||
|
|
||||||
# Check if opening entry check updated
|
|
||||||
if doc_before_update.get("is_opening") != self.is_opening:
|
|
||||||
needs_repost = 1
|
|
||||||
|
|
||||||
if not needs_repost:
|
|
||||||
# Parent Level Accounts excluding party account
|
|
||||||
for field in (
|
|
||||||
"additional_discount_account",
|
"additional_discount_account",
|
||||||
"cash_bank_account",
|
"cash_bank_account",
|
||||||
"account_for_change_amount",
|
"account_for_change_amount",
|
||||||
"write_off_account",
|
"write_off_account",
|
||||||
"loyalty_redemption_account",
|
"loyalty_redemption_account",
|
||||||
"unrealized_profit_loss_account",
|
"unrealized_profit_loss_account",
|
||||||
):
|
]
|
||||||
if doc_before_update.get(field) != self.get(field):
|
child_tables = {
|
||||||
needs_repost = 1
|
"items": ("income_account", "expense_account", "discount_account"),
|
||||||
break
|
"taxes": ("account_head",),
|
||||||
|
}
|
||||||
# Check for parent accounting dimensions
|
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||||
for dimension in accounting_dimensions:
|
if self.needs_repost:
|
||||||
if doc_before_update.get(dimension) != self.get(dimension):
|
self.validate_for_repost()
|
||||||
needs_repost = 1
|
self.db_set("repost_required", self.needs_repost)
|
||||||
break
|
|
||||||
|
|
||||||
# Check for child tables
|
|
||||||
if self.check_if_child_table_updated(
|
|
||||||
"items",
|
|
||||||
doc_before_update,
|
|
||||||
("income_account", "expense_account", "discount_account"),
|
|
||||||
accounting_dimensions,
|
|
||||||
):
|
|
||||||
needs_repost = 1
|
|
||||||
|
|
||||||
if self.check_if_child_table_updated(
|
|
||||||
"taxes", doc_before_update, ("account_head",), accounting_dimensions
|
|
||||||
):
|
|
||||||
needs_repost = 1
|
|
||||||
|
|
||||||
self.validate_accounts()
|
|
||||||
|
|
||||||
# validate if deferred revenue is enabled for any item
|
|
||||||
# Don't allow to update the invoice if deferred revenue is enabled
|
|
||||||
if needs_repost:
|
|
||||||
for item in self.get("items"):
|
|
||||||
if item.enable_deferred_revenue:
|
|
||||||
frappe.throw(
|
|
||||||
_(
|
|
||||||
"Deferred Revenue is enabled for item {0}. You cannot update the invoice after submission."
|
|
||||||
).format(item.item_code)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.db_set("repost_required", needs_repost)
|
|
||||||
|
|
||||||
def check_if_child_table_updated(
|
|
||||||
self, child_table, doc_before_update, fields_to_check, accounting_dimensions
|
|
||||||
):
|
|
||||||
# Check if any field affecting accounting entry is altered
|
|
||||||
for index, item in enumerate(self.get(child_table)):
|
|
||||||
for field in fields_to_check:
|
|
||||||
if doc_before_update.get(child_table)[index].get(field) != item.get(field):
|
|
||||||
return True
|
|
||||||
|
|
||||||
for dimension in accounting_dimensions:
|
|
||||||
if doc_before_update.get(child_table)[index].get(dimension) != item.get(dimension):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def repost_accounting_entries(self):
|
|
||||||
if self.repost_required:
|
|
||||||
self.docstatus = 2
|
|
||||||
self.make_gl_entries_on_cancel()
|
|
||||||
self.docstatus = 1
|
|
||||||
self.make_gl_entries()
|
|
||||||
self.db_set("repost_required", 0)
|
|
||||||
else:
|
|
||||||
frappe.throw(_("No updates pending for reposting"))
|
|
||||||
|
|
||||||
def set_paid_amount(self):
|
def set_paid_amount(self):
|
||||||
paid_amount = 0.0
|
paid_amount = 0.0
|
||||||
@ -1081,7 +1020,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
def make_customer_gl_entry(self, gl_entries):
|
def make_customer_gl_entry(self, gl_entries):
|
||||||
# Checked both rounding_adjustment and rounded_total
|
# Checked both rounding_adjustment and rounded_total
|
||||||
# because rounded_total had value even before introcution of posting GLE based on rounded total
|
# because rounded_total had value even before introduction of posting GLE based on rounded total
|
||||||
grand_total = (
|
grand_total = (
|
||||||
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
|
||||||
)
|
)
|
||||||
@ -1336,7 +1275,7 @@ class SalesInvoice(SellingController):
|
|||||||
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
|
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
|
||||||
payment_mode.base_amount -= flt(self.change_amount)
|
payment_mode.base_amount -= flt(self.change_amount)
|
||||||
|
|
||||||
if payment_mode.amount:
|
if payment_mode.base_amount:
|
||||||
# POS, make payment entries
|
# POS, make payment entries
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
@ -1505,6 +1444,8 @@ class SalesInvoice(SellingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update_billing_status_in_dn(self, update_modified=True):
|
def update_billing_status_in_dn(self, update_modified=True):
|
||||||
|
if self.is_return and not self.update_billed_amount_in_delivery_note:
|
||||||
|
return
|
||||||
updated_delivery_notes = []
|
updated_delivery_notes = []
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.dn_detail:
|
if d.dn_detail:
|
||||||
|
@ -6,7 +6,7 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||||
from frappe.tests.utils import change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, flt, getdate, nowdate, today
|
from frappe.utils import add_days, flt, getdate, nowdate, today
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@ -26,6 +26,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
|
|||||||
from erpnext.controllers.accounts_controller import update_invoice_status
|
from erpnext.controllers.accounts_controller import update_invoice_status
|
||||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
|
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
|
||||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||||
|
from erpnext.selling.doctype.customer.test_customer import get_customer_dict
|
||||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
@ -44,13 +45,17 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
|
|||||||
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
|
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
|
||||||
|
|
||||||
|
|
||||||
class TestSalesInvoice(unittest.TestCase):
|
class TestSalesInvoice(FrappeTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
|
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
|
||||||
|
|
||||||
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
|
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
|
||||||
create_internal_parties()
|
create_internal_parties()
|
||||||
setup_accounts()
|
setup_accounts()
|
||||||
|
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
def make(self):
|
def make(self):
|
||||||
w = frappe.copy_doc(test_records[0])
|
w = frappe.copy_doc(test_records[0])
|
||||||
@ -178,6 +183,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertRaises(frappe.LinkExistsError, si.cancel)
|
self.assertRaises(frappe.LinkExistsError, si.cancel)
|
||||||
unlink_payment_on_cancel_of_invoice()
|
unlink_payment_on_cancel_of_invoice()
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||||
def test_payment_entry_unlink_against_standalone_credit_note(self):
|
def test_payment_entry_unlink_against_standalone_credit_note(self):
|
||||||
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
|
||||||
|
|
||||||
@ -510,70 +516,72 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(si.grand_total, 5474.0)
|
self.assertEqual(si.grand_total, 5474.0)
|
||||||
|
|
||||||
def test_tax_calculation_with_item_tax_template(self):
|
def test_tax_calculation_with_item_tax_template(self):
|
||||||
|
import json
|
||||||
|
|
||||||
|
from erpnext.stock.get_item_details import get_item_details
|
||||||
|
|
||||||
|
# set tax template in item
|
||||||
|
item = frappe.get_cached_doc("Item", "_Test Item")
|
||||||
|
item.set(
|
||||||
|
"taxes",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"item_tax_template": "_Test Item Tax Template 1 - _TC",
|
||||||
|
"valid_from": add_days(nowdate(), -5),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
# create sales invoice with item
|
||||||
si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True)
|
si = create_sales_invoice(qty=84, rate=4.6, do_not_save=True)
|
||||||
item_row = si.get("items")[0]
|
item_details = get_item_details(
|
||||||
|
doc=si,
|
||||||
|
args={
|
||||||
|
"item_code": item.item_code,
|
||||||
|
"company": si.company,
|
||||||
|
"doctype": "Sales Invoice",
|
||||||
|
"conversion_rate": 1.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
tax_map = json.loads(item_details.item_tax_rate)
|
||||||
|
for tax in tax_map:
|
||||||
|
si.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": tax,
|
||||||
|
"rate": tax_map[tax],
|
||||||
|
"description": "Test",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
si.submit()
|
||||||
|
si.load_from_db()
|
||||||
|
|
||||||
add_items = [
|
# check if correct tax values are applied from tax template
|
||||||
(54, "_Test Account Excise Duty @ 12 - _TC"),
|
self.assertEqual(si.net_total, 386.4)
|
||||||
(288, "_Test Account Excise Duty @ 15 - _TC"),
|
|
||||||
(144, "_Test Account Excise Duty @ 20 - _TC"),
|
expected_taxes = [
|
||||||
(430, "_Test Item Tax Template 1 - _TC"),
|
{
|
||||||
|
"tax_amount": 19.32,
|
||||||
|
"total": 405.72,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tax_amount": 38.64,
|
||||||
|
"total": 444.36,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tax_amount": 57.96,
|
||||||
|
"total": 502.32,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
for qty, item_tax_template in add_items:
|
|
||||||
item_row_copy = copy.deepcopy(item_row)
|
|
||||||
item_row_copy.qty = qty
|
|
||||||
item_row_copy.item_tax_template = item_tax_template
|
|
||||||
si.append("items", item_row_copy)
|
|
||||||
|
|
||||||
si.append(
|
for i in range(len(expected_taxes)):
|
||||||
"taxes",
|
for key in expected_taxes[i]:
|
||||||
{
|
self.assertEqual(expected_taxes[i][key], si.get("taxes")[i].get(key))
|
||||||
"account_head": "_Test Account Excise Duty - _TC",
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
"description": "Excise Duty",
|
|
||||||
"doctype": "Sales Taxes and Charges",
|
|
||||||
"rate": 11,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
si.append(
|
|
||||||
"taxes",
|
|
||||||
{
|
|
||||||
"account_head": "_Test Account Education Cess - _TC",
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
"description": "Education Cess",
|
|
||||||
"doctype": "Sales Taxes and Charges",
|
|
||||||
"rate": 0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
si.append(
|
|
||||||
"taxes",
|
|
||||||
{
|
|
||||||
"account_head": "_Test Account S&H Education Cess - _TC",
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
"description": "S&H Education Cess",
|
|
||||||
"doctype": "Sales Taxes and Charges",
|
|
||||||
"rate": 3,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
si.insert()
|
|
||||||
|
|
||||||
self.assertEqual(si.net_total, 4600)
|
self.assertEqual(si.get("base_total_taxes_and_charges"), 115.92)
|
||||||
|
|
||||||
self.assertEqual(si.get("taxes")[0].tax_amount, 502.41)
|
|
||||||
self.assertEqual(si.get("taxes")[0].total, 5102.41)
|
|
||||||
|
|
||||||
self.assertEqual(si.get("taxes")[1].tax_amount, 197.80)
|
|
||||||
self.assertEqual(si.get("taxes")[1].total, 5300.21)
|
|
||||||
|
|
||||||
self.assertEqual(si.get("taxes")[2].tax_amount, 375.36)
|
|
||||||
self.assertEqual(si.get("taxes")[2].total, 5675.57)
|
|
||||||
|
|
||||||
self.assertEqual(si.grand_total, 5675.57)
|
|
||||||
self.assertEqual(si.rounding_adjustment, 0.43)
|
|
||||||
self.assertEqual(si.rounded_total, 5676.0)
|
|
||||||
|
|
||||||
def test_tax_calculation_with_multiple_items_and_discount(self):
|
def test_tax_calculation_with_multiple_items_and_discount(self):
|
||||||
si = create_sales_invoice(qty=1, rate=75, do_not_save=True)
|
si = create_sales_invoice(qty=1, rate=75, do_not_save=True)
|
||||||
@ -1299,6 +1307,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
dn.submit()
|
dn.submit()
|
||||||
return dn
|
return dn
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1})
|
||||||
def test_sales_invoice_with_advance(self):
|
def test_sales_invoice_with_advance(self):
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||||
test_records as jv_test_records,
|
test_records as jv_test_records,
|
||||||
@ -2491,12 +2500,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
"stock_received_but_not_billed",
|
"stock_received_but_not_billed",
|
||||||
"Stock Received But Not Billed - _TC1",
|
"Stock Received But Not Billed - _TC1",
|
||||||
)
|
)
|
||||||
frappe.db.set_value(
|
|
||||||
"Company",
|
|
||||||
"_Test Company 1",
|
|
||||||
"expenses_included_in_valuation",
|
|
||||||
"Expenses Included In Valuation - _TC1",
|
|
||||||
)
|
|
||||||
|
|
||||||
# begin test
|
# begin test
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
@ -2546,6 +2549,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
si = frappe.copy_doc(test_records[0])
|
si = frappe.copy_doc(test_records[0])
|
||||||
|
si.customer = "_Test Internal Customer 3"
|
||||||
si.update_stock = 1
|
si.update_stock = 1
|
||||||
si.set_warehouse = "Finished Goods - _TC"
|
si.set_warehouse = "Finished Goods - _TC"
|
||||||
si.set_target_warehouse = "Stores - _TC"
|
si.set_target_warehouse = "Stores - _TC"
|
||||||
@ -2774,6 +2778,13 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
company="_Test Company",
|
company="_Test Company",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tds_payable_account = create_account(
|
||||||
|
account_name="TDS Payable",
|
||||||
|
account_type="Tax",
|
||||||
|
parent_account="Duties and Taxes - _TC",
|
||||||
|
company="_Test Company",
|
||||||
|
)
|
||||||
|
|
||||||
si = create_sales_invoice(parent_cost_center="Main - _TC", do_not_save=1)
|
si = create_sales_invoice(parent_cost_center="Main - _TC", do_not_save=1)
|
||||||
si.apply_discount_on = "Grand Total"
|
si.apply_discount_on = "Grand Total"
|
||||||
si.additional_discount_account = additional_discount_account
|
si.additional_discount_account = additional_discount_account
|
||||||
@ -3072,8 +3083,8 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
si.commission_rate = commission_rate
|
si.commission_rate = commission_rate
|
||||||
self.assertRaises(frappe.ValidationError, si.save)
|
self.assertRaises(frappe.ValidationError, si.save)
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"acc_frozen_upto": add_days(getdate(), 1)})
|
||||||
def test_sales_invoice_submission_post_account_freezing_date(self):
|
def test_sales_invoice_submission_post_account_freezing_date(self):
|
||||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", add_days(getdate(), 1))
|
|
||||||
si = create_sales_invoice(do_not_save=True)
|
si = create_sales_invoice(do_not_save=True)
|
||||||
si.posting_date = add_days(getdate(), 1)
|
si.posting_date = add_days(getdate(), 1)
|
||||||
si.save()
|
si.save()
|
||||||
@ -3082,8 +3093,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
si.posting_date = getdate()
|
si.posting_date = getdate()
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
|
||||||
|
|
||||||
def test_over_billing_case_against_delivery_note(self):
|
def test_over_billing_case_against_delivery_note(self):
|
||||||
"""
|
"""
|
||||||
Test a case where duplicating the item with qty = 1 in the invoice
|
Test a case where duplicating the item with qty = 1 in the invoice
|
||||||
@ -3112,6 +3121,13 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", over_billing_allowance)
|
frappe.db.set_single_value("Accounts Settings", "over_billing_allowance", over_billing_allowance)
|
||||||
|
|
||||||
|
@change_settings(
|
||||||
|
"Accounts Settings",
|
||||||
|
{
|
||||||
|
"book_deferred_entries_via_journal_entry": 1,
|
||||||
|
"submit_journal_entries": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
def test_multi_currency_deferred_revenue_via_journal_entry(self):
|
def test_multi_currency_deferred_revenue_via_journal_entry(self):
|
||||||
deferred_account = create_account(
|
deferred_account = create_account(
|
||||||
account_name="Deferred Revenue",
|
account_name="Deferred Revenue",
|
||||||
@ -3119,11 +3135,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
company="_Test Company",
|
company="_Test Company",
|
||||||
)
|
)
|
||||||
|
|
||||||
acc_settings = frappe.get_single("Accounts Settings")
|
|
||||||
acc_settings.book_deferred_entries_via_journal_entry = 1
|
|
||||||
acc_settings.submit_journal_entries = 1
|
|
||||||
acc_settings.save()
|
|
||||||
|
|
||||||
item = create_item("_Test Item for Deferred Accounting")
|
item = create_item("_Test Item for Deferred Accounting")
|
||||||
item.enable_deferred_expense = 1
|
item.enable_deferred_expense = 1
|
||||||
item.item_defaults[0].deferred_revenue_account = deferred_account
|
item.item_defaults[0].deferred_revenue_account = deferred_account
|
||||||
@ -3189,13 +3200,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(expected_gle[i][2], gle.debit)
|
self.assertEqual(expected_gle[i][2], gle.debit)
|
||||||
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
self.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||||
|
|
||||||
acc_settings = frappe.get_single("Accounts Settings")
|
|
||||||
acc_settings.book_deferred_entries_via_journal_entry = 0
|
|
||||||
acc_settings.submit_journal_entries = 0
|
|
||||||
acc_settings.save()
|
|
||||||
|
|
||||||
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
|
||||||
|
|
||||||
def test_standalone_serial_no_return(self):
|
def test_standalone_serial_no_return(self):
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1
|
item_code="_Test Serialized Item With Series", update_stock=True, is_return=True, qty=-1
|
||||||
@ -3400,6 +3404,24 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
set_advance_flag(company="_Test Company", flag=0, default_account="")
|
set_advance_flag(company="_Test Company", flag=0, default_account="")
|
||||||
|
|
||||||
|
@change_settings("Selling Settings", {"customer_group": None, "territory": None})
|
||||||
|
def test_sales_invoice_without_customer_group_and_territory(self):
|
||||||
|
# create a customer
|
||||||
|
if not frappe.db.exists("Customer", "_Test Simple Customer"):
|
||||||
|
customer_dict = get_customer_dict("_Test Simple Customer")
|
||||||
|
customer_dict.pop("customer_group")
|
||||||
|
customer_dict.pop("territory")
|
||||||
|
customer = frappe.get_doc(customer_dict).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
self.assertEqual(customer.customer_group, None)
|
||||||
|
self.assertEqual(customer.territory, None)
|
||||||
|
|
||||||
|
# create a sales invoice
|
||||||
|
si = create_sales_invoice(customer="_Test Simple Customer")
|
||||||
|
self.assertEqual(si.docstatus, 1)
|
||||||
|
self.assertEqual(si.customer_group, None)
|
||||||
|
self.assertEqual(si.territory, None)
|
||||||
|
|
||||||
@change_settings("Selling Settings", {"allow_negative_rates_for_items": 0})
|
@change_settings("Selling Settings", {"allow_negative_rates_for_items": 0})
|
||||||
def test_sales_return_negative_rate(self):
|
def test_sales_return_negative_rate(self):
|
||||||
si = create_sales_invoice(is_return=1, qty=-2, rate=-10, do_not_save=True)
|
si = create_sales_invoice(is_return=1, qty=-2, rate=-10, do_not_save=True)
|
||||||
@ -3671,6 +3693,20 @@ def create_internal_parties():
|
|||||||
allowed_to_interact_with="_Test Company with perpetual inventory",
|
allowed_to_interact_with="_Test Company with perpetual inventory",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
create_internal_customer(
|
||||||
|
customer_name="_Test Internal Customer 3",
|
||||||
|
represents_company="_Test Company",
|
||||||
|
allowed_to_interact_with="_Test Company",
|
||||||
|
)
|
||||||
|
|
||||||
|
account = create_account(
|
||||||
|
account_name="Unrealized Profit",
|
||||||
|
parent_account="Current Liabilities - _TC",
|
||||||
|
company="_Test Company",
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_value("Company", "_Test Company", "unrealized_profit_loss_account", account)
|
||||||
|
|
||||||
create_internal_supplier(
|
create_internal_supplier(
|
||||||
supplier_name="_Test Internal Supplier",
|
supplier_name="_Test Internal Supplier",
|
||||||
represents_company="Wind Power LLC",
|
represents_company="Wind Power LLC",
|
||||||
|
@ -157,7 +157,6 @@
|
|||||||
"oldfieldname": "description",
|
"oldfieldname": "description",
|
||||||
"oldfieldtype": "Text",
|
"oldfieldtype": "Text",
|
||||||
"print_width": "200px",
|
"print_width": "200px",
|
||||||
"reqd": 1,
|
|
||||||
"width": "200px"
|
"width": "200px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -18,6 +18,14 @@ frappe.ui.form.on('Subscription', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query('sales_tax_template', function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils.data import (
|
from frappe.utils.data import (
|
||||||
add_days,
|
add_days,
|
||||||
add_months,
|
add_months,
|
||||||
@ -21,11 +22,15 @@ from erpnext.accounts.doctype.subscription.subscription import get_prorata_facto
|
|||||||
test_dependencies = ("UOM", "Item Group", "Item")
|
test_dependencies = ("UOM", "Item Group", "Item")
|
||||||
|
|
||||||
|
|
||||||
class TestSubscription(unittest.TestCase):
|
class TestSubscription(FrappeTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
make_plans()
|
make_plans()
|
||||||
create_parties()
|
create_parties()
|
||||||
reset_settings()
|
reset_settings()
|
||||||
|
frappe.db.set_single_value("Accounts Settings", "acc_frozen_upto", None)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
def test_create_subscription_with_trial_with_correct_period(self):
|
def test_create_subscription_with_trial_with_correct_period(self):
|
||||||
subscription = create_subscription(
|
subscription = create_subscription(
|
||||||
|
@ -22,7 +22,7 @@ class SubscriptionPlan(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_plan_rate(
|
def get_plan_rate(
|
||||||
plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1
|
plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1, party=None
|
||||||
):
|
):
|
||||||
plan = frappe.get_doc("Subscription Plan", plan)
|
plan = frappe.get_doc("Subscription Plan", plan)
|
||||||
if plan.price_determination == "Fixed Rate":
|
if plan.price_determination == "Fixed Rate":
|
||||||
@ -40,6 +40,7 @@ def get_plan_rate(
|
|||||||
customer_group=customer_group,
|
customer_group=customer_group,
|
||||||
company=None,
|
company=None,
|
||||||
qty=quantity,
|
qty=quantity,
|
||||||
|
party=party,
|
||||||
)
|
)
|
||||||
if not price:
|
if not price:
|
||||||
return 0
|
return 0
|
||||||
|
@ -8,7 +8,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.contacts.doctype.address.address import get_default_address
|
from frappe.contacts.doctype.address.address import get_default_address
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint, cstr
|
from frappe.utils import cstr
|
||||||
from frappe.utils.nestedset import get_root_of
|
from frappe.utils.nestedset import get_root_of
|
||||||
|
|
||||||
from erpnext.setup.doctype.customer_group.customer_group import get_parent_customer_groups
|
from erpnext.setup.doctype.customer_group.customer_group import get_parent_customer_groups
|
||||||
@ -34,7 +34,6 @@ class TaxRule(Document):
|
|||||||
self.validate_tax_template()
|
self.validate_tax_template()
|
||||||
self.validate_from_to_dates("from_date", "to_date")
|
self.validate_from_to_dates("from_date", "to_date")
|
||||||
self.validate_filters()
|
self.validate_filters()
|
||||||
self.validate_use_for_shopping_cart()
|
|
||||||
|
|
||||||
def validate_tax_template(self):
|
def validate_tax_template(self):
|
||||||
if self.tax_type == "Sales":
|
if self.tax_type == "Sales":
|
||||||
@ -106,21 +105,6 @@ class TaxRule(Document):
|
|||||||
if tax_rule[0].priority == self.priority:
|
if tax_rule[0].priority == self.priority:
|
||||||
frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
|
frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
|
||||||
|
|
||||||
def validate_use_for_shopping_cart(self):
|
|
||||||
"""If shopping cart is enabled and no tax rule exists for shopping cart, enable this one"""
|
|
||||||
if (
|
|
||||||
not self.use_for_shopping_cart
|
|
||||||
and cint(frappe.db.get_single_value("E Commerce Settings", "enabled"))
|
|
||||||
and not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1, "name": ["!=", self.name]})
|
|
||||||
):
|
|
||||||
|
|
||||||
self.use_for_shopping_cart = 1
|
|
||||||
frappe.msgprint(
|
|
||||||
_(
|
|
||||||
"Enabling 'Use for Shopping Cart', as Shopping Cart is enabled and there should be at least one Tax Rule for Shopping Cart"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_party_details(party, party_type, args=None):
|
def get_party_details(party, party_type, args=None):
|
||||||
|
@ -41,7 +41,7 @@ def make_gl_entries(
|
|||||||
from_repost=from_repost,
|
from_repost=from_repost,
|
||||||
)
|
)
|
||||||
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
||||||
# Post GL Map proccess there may no be any GL Entries
|
# Post GL Map process there may no be any GL Entries
|
||||||
elif gl_map:
|
elif gl_map:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
|
@ -5,13 +5,8 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint, scrub
|
from frappe import _, msgprint, qb, scrub
|
||||||
from frappe.contacts.doctype.address.address import (
|
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
|
||||||
get_address_display,
|
|
||||||
get_company_address,
|
|
||||||
get_default_address,
|
|
||||||
)
|
|
||||||
from frappe.contacts.doctype.contact.contact import get_contact_details
|
|
||||||
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
|
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
|
||||||
from frappe.model.utils import get_fetch_values
|
from frappe.model.utils import get_fetch_values
|
||||||
from frappe.query_builder.functions import Abs, Date, Sum
|
from frappe.query_builder.functions import Abs, Date, Sum
|
||||||
@ -133,6 +128,7 @@ def _get_party_details(
|
|||||||
party_address,
|
party_address,
|
||||||
company_address,
|
company_address,
|
||||||
shipping_address,
|
shipping_address,
|
||||||
|
ignore_permissions=ignore_permissions,
|
||||||
)
|
)
|
||||||
set_contact_details(party_details, party, party_type)
|
set_contact_details(party_details, party, party_type)
|
||||||
set_other_values(party_details, party, party_type)
|
set_other_values(party_details, party, party_type)
|
||||||
@ -193,6 +189,8 @@ def set_address_details(
|
|||||||
party_address=None,
|
party_address=None,
|
||||||
company_address=None,
|
company_address=None,
|
||||||
shipping_address=None,
|
shipping_address=None,
|
||||||
|
*,
|
||||||
|
ignore_permissions=False
|
||||||
):
|
):
|
||||||
billing_address_field = (
|
billing_address_field = (
|
||||||
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
|
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
|
||||||
@ -205,13 +203,17 @@ def set_address_details(
|
|||||||
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
|
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
|
||||||
)
|
)
|
||||||
# address display
|
# address display
|
||||||
party_details.address_display = get_address_display(party_details[billing_address_field])
|
party_details.address_display = render_address(
|
||||||
|
party_details[billing_address_field], check_permissions=not ignore_permissions
|
||||||
|
)
|
||||||
# shipping address
|
# shipping address
|
||||||
if party_type in ["Customer", "Lead"]:
|
if party_type in ["Customer", "Lead"]:
|
||||||
party_details.shipping_address_name = shipping_address or get_party_shipping_address(
|
party_details.shipping_address_name = shipping_address or get_party_shipping_address(
|
||||||
party_type, party.name
|
party_type, party.name
|
||||||
)
|
)
|
||||||
party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
|
party_details.shipping_address = render_address(
|
||||||
|
party_details["shipping_address_name"], check_permissions=not ignore_permissions
|
||||||
|
)
|
||||||
if doctype:
|
if doctype:
|
||||||
party_details.update(
|
party_details.update(
|
||||||
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
|
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
|
||||||
@ -229,7 +231,7 @@ def set_address_details(
|
|||||||
if shipping_address:
|
if shipping_address:
|
||||||
party_details.update(
|
party_details.update(
|
||||||
shipping_address=shipping_address,
|
shipping_address=shipping_address,
|
||||||
shipping_address_display=get_address_display(shipping_address),
|
shipping_address_display=render_address(shipping_address),
|
||||||
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -238,7 +240,8 @@ def set_address_details(
|
|||||||
party_details.update(
|
party_details.update(
|
||||||
billing_address=party_details.company_address,
|
billing_address=party_details.company_address,
|
||||||
billing_address_display=(
|
billing_address_display=(
|
||||||
party_details.company_address_display or get_address_display(party_details.company_address)
|
party_details.company_address_display
|
||||||
|
or render_address(party_details.company_address, check_permissions=False)
|
||||||
),
|
),
|
||||||
**get_fetch_values(doctype, "billing_address", party_details.company_address)
|
**get_fetch_values(doctype, "billing_address", party_details.company_address)
|
||||||
)
|
)
|
||||||
@ -290,7 +293,21 @@ def set_contact_details(party_details, party, party_type):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
party_details.update(get_contact_details(party_details.contact_person))
|
fields = [
|
||||||
|
"name as contact_person",
|
||||||
|
"full_name as contact_display",
|
||||||
|
"email_id as contact_email",
|
||||||
|
"mobile_no as contact_mobile",
|
||||||
|
"phone as contact_phone",
|
||||||
|
"designation as contact_designation",
|
||||||
|
"department as contact_department",
|
||||||
|
]
|
||||||
|
|
||||||
|
contact_details = frappe.db.get_value(
|
||||||
|
"Contact", party_details.contact_person, fields, as_dict=True
|
||||||
|
)
|
||||||
|
|
||||||
|
party_details.update(contact_details)
|
||||||
|
|
||||||
|
|
||||||
def set_other_values(party_details, party, party_type):
|
def set_other_values(party_details, party, party_type):
|
||||||
@ -463,11 +480,19 @@ def get_party_account_currency(party_type, party, company):
|
|||||||
|
|
||||||
def get_party_gle_currency(party_type, party, company):
|
def get_party_gle_currency(party_type, party, company):
|
||||||
def generator():
|
def generator():
|
||||||
existing_gle_currency = frappe.db.sql(
|
gl = qb.DocType("GL Entry")
|
||||||
"""select account_currency from `tabGL Entry`
|
existing_gle_currency = (
|
||||||
where docstatus=1 and company=%(company)s and party_type=%(party_type)s and party=%(party)s
|
qb.from_(gl)
|
||||||
limit 1""",
|
.select(gl.account_currency)
|
||||||
{"company": company, "party_type": party_type, "party": party},
|
.where(
|
||||||
|
(gl.docstatus == 1)
|
||||||
|
& (gl.company == company)
|
||||||
|
& (gl.party_type == party_type)
|
||||||
|
& (gl.party == party)
|
||||||
|
& (gl.is_cancelled == 0)
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
.run()
|
||||||
)
|
)
|
||||||
|
|
||||||
return existing_gle_currency[0][0] if existing_gle_currency else None
|
return existing_gle_currency[0][0] if existing_gle_currency else None
|
||||||
@ -995,3 +1020,13 @@ def add_party_account(party_type, party, company, account):
|
|||||||
doc.append("accounts", accounts)
|
doc.append("accounts", accounts)
|
||||||
|
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
|
|
||||||
|
def render_address(address, check_permissions=True):
|
||||||
|
try:
|
||||||
|
from frappe.contacts.doctype.address.address import render_address as _render
|
||||||
|
except ImportError:
|
||||||
|
# Older frappe versions where this function is not available
|
||||||
|
from frappe.contacts.doctype.address.address import get_address_display as _render
|
||||||
|
|
||||||
|
return frappe.call(_render, address, check_permissions=check_permissions)
|
||||||
|
@ -97,28 +97,25 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
{
|
{
|
||||||
"fieldname":"party_type",
|
"fieldname":"party_type",
|
||||||
"label": __("Party Type"),
|
"label": __("Party Type"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Autocomplete",
|
||||||
"options": "Party Type",
|
options: get_party_type_options(),
|
||||||
get_query: () => {
|
on_change: function() {
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'account_type': 'Payable'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
on_change: () => {
|
|
||||||
frappe.query_report.set_filter_value('party', "");
|
frappe.query_report.set_filter_value('party', "");
|
||||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
|
||||||
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"party",
|
"fieldname":"party",
|
||||||
"label": __("Party"),
|
"label": __("Party"),
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "MultiSelectList",
|
||||||
"options": "party_type",
|
get_data: function(txt) {
|
||||||
|
if (!frappe.query_report.filters) return;
|
||||||
|
|
||||||
|
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||||
|
if (!party_type) return;
|
||||||
|
|
||||||
|
return frappe.db.get_link_options(party_type, txt);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "supplier_group",
|
"fieldname": "supplier_group",
|
||||||
@ -146,7 +143,13 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
"fieldname": "show_future_payments",
|
"fieldname": "show_future_payments",
|
||||||
"label": __("Show Future Payments"),
|
"label": __("Show Future Payments"),
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ignore_accounts",
|
||||||
|
"label": __("Group by Voucher"),
|
||||||
|
"fieldtype": "Check",
|
||||||
}
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"formatter": function(value, row, column, data, default_formatter) {
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
@ -167,3 +170,15 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.add_dimensions('Accounts Payable', 9);
|
erpnext.utils.add_dimensions('Accounts Payable', 9);
|
||||||
|
|
||||||
|
function get_party_type_options() {
|
||||||
|
let options = [];
|
||||||
|
frappe.db.get_list(
|
||||||
|
"Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
|
||||||
|
).then((res) => {
|
||||||
|
res.forEach((party_type) => {
|
||||||
|
options.push(party_type.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
@ -34,7 +34,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"party_type": "Supplier",
|
"party_type": "Supplier",
|
||||||
"party": self.supplier,
|
"party": [self.supplier],
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range1": 30,
|
||||||
"range2": 60,
|
"range2": 60,
|
||||||
|
@ -72,10 +72,27 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"supplier",
|
"fieldname":"party_type",
|
||||||
"label": __("Supplier"),
|
"label": __("Party Type"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Autocomplete",
|
||||||
"options": "Supplier"
|
options: get_party_type_options(),
|
||||||
|
on_change: function() {
|
||||||
|
frappe.query_report.set_filter_value('party', "");
|
||||||
|
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"party",
|
||||||
|
"label": __("Party"),
|
||||||
|
"fieldtype": "MultiSelectList",
|
||||||
|
get_data: function(txt) {
|
||||||
|
if (!frappe.query_report.filters) return;
|
||||||
|
|
||||||
|
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||||
|
if (!party_type) return;
|
||||||
|
|
||||||
|
return frappe.db.get_link_options(party_type, txt);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"payment_terms_template",
|
"fieldname":"payment_terms_template",
|
||||||
@ -105,3 +122,15 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
|
erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
|
||||||
|
|
||||||
|
function get_party_type_options() {
|
||||||
|
let options = [];
|
||||||
|
frappe.db.get_list(
|
||||||
|
"Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
|
||||||
|
).then((res) => {
|
||||||
|
res.forEach((party_type) => {
|
||||||
|
options.push(party_type.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
@ -1,6 +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
|
||||||
|
|
||||||
|
frappe.provide("erpnext.utils");
|
||||||
|
|
||||||
frappe.query_reports["Accounts Receivable"] = {
|
frappe.query_reports["Accounts Receivable"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
{
|
{
|
||||||
@ -40,28 +42,25 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
{
|
{
|
||||||
"fieldname":"party_type",
|
"fieldname":"party_type",
|
||||||
"label": __("Party Type"),
|
"label": __("Party Type"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Autocomplete",
|
||||||
"options": "Party Type",
|
options: get_party_type_options(),
|
||||||
"Default": "Customer",
|
on_change: function() {
|
||||||
get_query: () => {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'account_type': 'Receivable'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
on_change: () => {
|
|
||||||
frappe.query_report.set_filter_value('party', "");
|
frappe.query_report.set_filter_value('party', "");
|
||||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
|
||||||
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"party",
|
"fieldname":"party",
|
||||||
"label": __("Party"),
|
"label": __("Party"),
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "MultiSelectList",
|
||||||
"options": "party_type",
|
get_data: function(txt) {
|
||||||
|
if (!frappe.query_report.filters) return;
|
||||||
|
|
||||||
|
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||||
|
if (!party_type) return;
|
||||||
|
|
||||||
|
return frappe.db.get_link_options(party_type, txt);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_account",
|
"fieldname": "party_account",
|
||||||
@ -173,7 +172,13 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
"fieldname": "show_remarks",
|
"fieldname": "show_remarks",
|
||||||
"label": __("Show Remarks"),
|
"label": __("Show Remarks"),
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ignore_accounts",
|
||||||
|
"label": __("Group by Voucher"),
|
||||||
|
"fieldtype": "Check",
|
||||||
}
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"formatter": function(value, row, column, data, default_formatter) {
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
@ -194,3 +199,16 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.add_dimensions('Accounts Receivable', 9);
|
erpnext.utils.add_dimensions('Accounts Receivable', 9);
|
||||||
|
|
||||||
|
|
||||||
|
function get_party_type_options() {
|
||||||
|
let options = [];
|
||||||
|
frappe.db.get_list(
|
||||||
|
"Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
|
||||||
|
).then((res) => {
|
||||||
|
res.forEach((party_type) => {
|
||||||
|
options.push(party_type.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
@ -116,7 +116,12 @@ class ReceivablePayableReport(object):
|
|||||||
# build all keys, since we want to exclude vouchers beyond the report date
|
# build all keys, since we want to exclude vouchers beyond the report date
|
||||||
for ple in self.ple_entries:
|
for ple in self.ple_entries:
|
||||||
# get the balance object for voucher_type
|
# get the balance object for voucher_type
|
||||||
|
|
||||||
|
if self.filters.get("ingore_accounts"):
|
||||||
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||||
|
else:
|
||||||
|
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
||||||
|
|
||||||
if not key in self.voucher_balance:
|
if not key in self.voucher_balance:
|
||||||
self.voucher_balance[key] = frappe._dict(
|
self.voucher_balance[key] = frappe._dict(
|
||||||
voucher_type=ple.voucher_type,
|
voucher_type=ple.voucher_type,
|
||||||
@ -183,7 +188,10 @@ class ReceivablePayableReport(object):
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.filters.get("ingore_accounts"):
|
||||||
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||||
|
else:
|
||||||
|
key = (ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||||
|
|
||||||
# If payment is made against credit note
|
# If payment is made against credit note
|
||||||
# and credit note is made against a Sales Invoice
|
# and credit note is made against a Sales Invoice
|
||||||
@ -192,13 +200,19 @@ class ReceivablePayableReport(object):
|
|||||||
if ple.against_voucher_no in self.return_entries:
|
if ple.against_voucher_no in self.return_entries:
|
||||||
return_against = self.return_entries.get(ple.against_voucher_no)
|
return_against = self.return_entries.get(ple.against_voucher_no)
|
||||||
if return_against:
|
if return_against:
|
||||||
|
if self.filters.get("ingore_accounts"):
|
||||||
key = (ple.against_voucher_type, return_against, ple.party)
|
key = (ple.against_voucher_type, return_against, ple.party)
|
||||||
|
else:
|
||||||
|
key = (ple.account, ple.against_voucher_type, return_against, ple.party)
|
||||||
|
|
||||||
row = self.voucher_balance.get(key)
|
row = self.voucher_balance.get(key)
|
||||||
|
|
||||||
if not row:
|
if not row:
|
||||||
# no invoice, this is an invoice / stand-alone payment / credit note
|
# no invoice, this is an invoice / stand-alone payment / credit note
|
||||||
|
if self.filters.get("ingore_accounts"):
|
||||||
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
|
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
|
||||||
|
else:
|
||||||
|
row = self.voucher_balance.get((ple.account, ple.voucher_type, ple.voucher_no, ple.party))
|
||||||
|
|
||||||
row.party_type = ple.party_type
|
row.party_type = ple.party_type
|
||||||
return row
|
return row
|
||||||
@ -718,6 +732,7 @@ class ReceivablePayableReport(object):
|
|||||||
query = (
|
query = (
|
||||||
qb.from_(ple)
|
qb.from_(ple)
|
||||||
.select(
|
.select(
|
||||||
|
ple.name,
|
||||||
ple.account,
|
ple.account,
|
||||||
ple.voucher_type,
|
ple.voucher_type,
|
||||||
ple.voucher_no,
|
ple.voucher_no,
|
||||||
@ -731,13 +746,15 @@ class ReceivablePayableReport(object):
|
|||||||
ple.account_currency,
|
ple.account_currency,
|
||||||
ple.amount,
|
ple.amount,
|
||||||
ple.amount_in_account_currency,
|
ple.amount_in_account_currency,
|
||||||
ple.remarks,
|
|
||||||
)
|
)
|
||||||
.where(ple.delinked == 0)
|
.where(ple.delinked == 0)
|
||||||
.where(Criterion.all(self.qb_selection_filter))
|
.where(Criterion.all(self.qb_selection_filter))
|
||||||
.where(Criterion.any(self.or_filters))
|
.where(Criterion.any(self.or_filters))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.filters.get("show_remarks"):
|
||||||
|
query = query.select(ple.remarks)
|
||||||
|
|
||||||
if self.filters.get("group_by_party"):
|
if self.filters.get("group_by_party"):
|
||||||
query = query.orderby(self.ple.party, self.ple.posting_date)
|
query = query.orderby(self.ple.party, self.ple.posting_date)
|
||||||
else:
|
else:
|
||||||
@ -801,7 +818,7 @@ class ReceivablePayableReport(object):
|
|||||||
self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type)
|
self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type)
|
||||||
|
|
||||||
if self.filters.get("party"):
|
if self.filters.get("party"):
|
||||||
self.qb_selection_filter.append(self.filters.party == self.ple.party)
|
self.qb_selection_filter.append(self.ple.party.isin(self.filters.party))
|
||||||
|
|
||||||
if self.filters.party_account:
|
if self.filters.party_account:
|
||||||
self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
|
self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, flt, getdate, today
|
from frappe.utils import add_days, flt, getdate, today
|
||||||
|
|
||||||
@ -23,29 +24,6 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
def create_usd_account(self):
|
|
||||||
name = "Debtors USD"
|
|
||||||
exists = frappe.db.get_list(
|
|
||||||
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"}
|
|
||||||
)
|
|
||||||
if exists:
|
|
||||||
self.debtors_usd = exists[0].name
|
|
||||||
else:
|
|
||||||
debtors = frappe.get_doc(
|
|
||||||
"Account",
|
|
||||||
frappe.db.get_list(
|
|
||||||
"Account", filters={"company": "_Test Company 2", "account_name": "Debtors"}
|
|
||||||
)[0].name,
|
|
||||||
)
|
|
||||||
|
|
||||||
debtors_usd = frappe.new_doc("Account")
|
|
||||||
debtors_usd.company = debtors.company
|
|
||||||
debtors_usd.account_name = "Debtors USD"
|
|
||||||
debtors_usd.account_currency = "USD"
|
|
||||||
debtors_usd.parent_account = debtors.parent_account
|
|
||||||
debtors_usd.account_type = debtors.account_type
|
|
||||||
self.debtors_usd = debtors_usd.save().name
|
|
||||||
|
|
||||||
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False):
|
def create_sales_invoice(self, no_payment_schedule=False, do_not_submit=False):
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
@ -573,7 +551,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"party_type": "Customer",
|
"party_type": "Customer",
|
||||||
"party": self.customer,
|
"party": [self.customer],
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range1": 30,
|
||||||
"range2": 60,
|
"range2": 60,
|
||||||
@ -605,3 +583,132 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
for field in expected:
|
for field in expected:
|
||||||
with self.subTest(field=field):
|
with self.subTest(field=field):
|
||||||
self.assertEqual(report_output.get(field), expected.get(field))
|
self.assertEqual(report_output.get(field), expected.get(field))
|
||||||
|
|
||||||
|
def test_multi_select_party_filter(self):
|
||||||
|
self.customer1 = self.customer
|
||||||
|
self.create_customer("_Test Customer 2")
|
||||||
|
self.customer2 = self.customer
|
||||||
|
self.create_customer("_Test Customer 3")
|
||||||
|
self.customer3 = self.customer
|
||||||
|
|
||||||
|
filters = {
|
||||||
|
"company": self.company,
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": [self.customer1, self.customer3],
|
||||||
|
"report_date": today(),
|
||||||
|
"range1": 30,
|
||||||
|
"range2": 60,
|
||||||
|
"range3": 90,
|
||||||
|
"range4": 120,
|
||||||
|
}
|
||||||
|
|
||||||
|
si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||||
|
si1.customer = self.customer1
|
||||||
|
si1.save().submit()
|
||||||
|
|
||||||
|
si2 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||||
|
si2.customer = self.customer2
|
||||||
|
si2.save().submit()
|
||||||
|
|
||||||
|
si3 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||||
|
si3.customer = self.customer3
|
||||||
|
si3.save().submit()
|
||||||
|
|
||||||
|
# check invoice grand total and invoiced column's value for 3 payment terms
|
||||||
|
report = execute(filters)
|
||||||
|
|
||||||
|
expected_output = {self.customer1, self.customer3}
|
||||||
|
self.assertEqual(len(report[1]), 2)
|
||||||
|
output_for = set([x.party for x in report[1]])
|
||||||
|
self.assertEqual(output_for, expected_output)
|
||||||
|
|
||||||
|
def test_report_output_if_party_is_missing(self):
|
||||||
|
acc_name = "Additional Debtors"
|
||||||
|
if not frappe.db.get_value(
|
||||||
|
"Account", filters={"account_name": acc_name, "company": self.company}
|
||||||
|
):
|
||||||
|
additional_receivable_acc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"account_name": acc_name,
|
||||||
|
"parent_account": "Accounts Receivable - " + self.company_abbr,
|
||||||
|
"company": self.company,
|
||||||
|
"account_type": "Receivable",
|
||||||
|
}
|
||||||
|
).save()
|
||||||
|
self.debtors2 = additional_receivable_acc.name
|
||||||
|
|
||||||
|
je = frappe.new_doc("Journal Entry")
|
||||||
|
je.company = self.company
|
||||||
|
je.posting_date = today()
|
||||||
|
je.append(
|
||||||
|
"accounts",
|
||||||
|
{
|
||||||
|
"account": self.debit_to,
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": self.customer,
|
||||||
|
"debit_in_account_currency": 150,
|
||||||
|
"credit_in_account_currency": 0,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
je.append(
|
||||||
|
"accounts",
|
||||||
|
{
|
||||||
|
"account": self.debtors2,
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": self.customer,
|
||||||
|
"debit_in_account_currency": 200,
|
||||||
|
"credit_in_account_currency": 0,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
je.append(
|
||||||
|
"accounts",
|
||||||
|
{
|
||||||
|
"account": self.cash,
|
||||||
|
"debit_in_account_currency": 0,
|
||||||
|
"credit_in_account_currency": 350,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
je.save().submit()
|
||||||
|
|
||||||
|
# manually remove party from Payment Ledger
|
||||||
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
|
qb.update(ple).set(ple.party, None).where(ple.voucher_no == je.name).run()
|
||||||
|
|
||||||
|
filters = {
|
||||||
|
"company": self.company,
|
||||||
|
"report_date": today(),
|
||||||
|
"range1": 30,
|
||||||
|
"range2": 60,
|
||||||
|
"range3": 90,
|
||||||
|
"range4": 120,
|
||||||
|
}
|
||||||
|
|
||||||
|
report_ouput = execute(filters)[1]
|
||||||
|
expected_data = [
|
||||||
|
[self.debtors2, je.doctype, je.name, "Customer", self.customer, 200.0, 0.0, 0.0, 200.0],
|
||||||
|
[self.debit_to, je.doctype, je.name, "Customer", self.customer, 150.0, 0.0, 0.0, 150.0],
|
||||||
|
]
|
||||||
|
self.assertEqual(len(report_ouput), 2)
|
||||||
|
# fetch only required fields
|
||||||
|
report_output = [
|
||||||
|
[
|
||||||
|
x.party_account,
|
||||||
|
x.voucher_type,
|
||||||
|
x.voucher_no,
|
||||||
|
"Customer",
|
||||||
|
self.customer,
|
||||||
|
x.invoiced,
|
||||||
|
x.paid,
|
||||||
|
x.credit_note,
|
||||||
|
x.outstanding,
|
||||||
|
]
|
||||||
|
for x in report_ouput
|
||||||
|
]
|
||||||
|
# use account name to sort
|
||||||
|
# post sorting output should be [[Additional Debtors, ...], [Debtors, ...]]
|
||||||
|
report_output = sorted(report_output, key=lambda x: x[0])
|
||||||
|
self.assertEqual(expected_data, report_output)
|
||||||
|
@ -72,10 +72,27 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"customer",
|
"fieldname":"party_type",
|
||||||
"label": __("Customer"),
|
"label": __("Party Type"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Autocomplete",
|
||||||
"options": "Customer"
|
options: get_party_type_options(),
|
||||||
|
on_change: function() {
|
||||||
|
frappe.query_report.set_filter_value('party', "");
|
||||||
|
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"party",
|
||||||
|
"label": __("Party"),
|
||||||
|
"fieldtype": "MultiSelectList",
|
||||||
|
get_data: function(txt) {
|
||||||
|
if (!frappe.query_report.filters) return;
|
||||||
|
|
||||||
|
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||||
|
if (!party_type) return;
|
||||||
|
|
||||||
|
return frappe.db.get_link_options(party_type, txt);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"customer_group",
|
"fieldname":"customer_group",
|
||||||
@ -133,3 +150,15 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.add_dimensions('Accounts Receivable Summary', 9);
|
erpnext.utils.add_dimensions('Accounts Receivable Summary', 9);
|
||||||
|
|
||||||
|
function get_party_type_options() {
|
||||||
|
let options = [];
|
||||||
|
frappe.db.get_list(
|
||||||
|
"Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
|
||||||
|
).then((res) => {
|
||||||
|
res.forEach((party_type) => {
|
||||||
|
options.push(party_type.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
@ -99,13 +99,11 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
|
|
||||||
# Add all amount columns
|
# Add all amount columns
|
||||||
for k in list(self.party_total[d.party]):
|
for k in list(self.party_total[d.party]):
|
||||||
if k not in ["currency", "sales_person"]:
|
if isinstance(self.party_total[d.party][k], float):
|
||||||
|
self.party_total[d.party][k] += d.get(k) or 0.0
|
||||||
self.party_total[d.party][k] += d.get(k, 0.0)
|
|
||||||
|
|
||||||
# set territory, customer_group, sales person etc
|
# set territory, customer_group, sales person etc
|
||||||
self.set_party_details(d)
|
self.set_party_details(d)
|
||||||
self.party_total[d.party].update({"party_type": d.party_type})
|
|
||||||
|
|
||||||
def init_party_total(self, row):
|
def init_party_total(self, row):
|
||||||
self.party_total.setdefault(
|
self.party_total.setdefault(
|
||||||
@ -124,6 +122,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
"total_due": 0.0,
|
"total_due": 0.0,
|
||||||
"future_amount": 0.0,
|
"future_amount": 0.0,
|
||||||
"sales_person": [],
|
"sales_person": [],
|
||||||
|
"party_type": row.party_type,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -133,13 +132,12 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
|
|
||||||
for key in ("territory", "customer_group", "supplier_group"):
|
for key in ("territory", "customer_group", "supplier_group"):
|
||||||
if row.get(key):
|
if row.get(key):
|
||||||
self.party_total[row.party][key] = row.get(key)
|
self.party_total[row.party][key] = row.get(key, "")
|
||||||
|
|
||||||
if row.sales_person:
|
if row.sales_person:
|
||||||
self.party_total[row.party].sales_person.append(row.sales_person)
|
self.party_total[row.party].sales_person.append(row.get("sales_person", ""))
|
||||||
|
|
||||||
if self.filters.sales_partner:
|
if self.filters.sales_partner:
|
||||||
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner")
|
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner", "")
|
||||||
|
|
||||||
def get_columns(self):
|
def get_columns(self):
|
||||||
self.columns = []
|
self.columns = []
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// 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
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function () {
|
|
||||||
frappe.query_reports["Balance Sheet"] = $.extend(
|
frappe.query_reports["Balance Sheet"] = $.extend(
|
||||||
{},
|
{},
|
||||||
erpnext.financial_statements
|
erpnext.financial_statements
|
||||||
@ -22,4 +21,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
|
|||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
default: 1,
|
default: 1,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
// Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
frappe.query_reports["Cash Flow"] = $.extend(
|
||||||
frappe.query_reports["Cash Flow"] = $.extend({},
|
{},
|
||||||
erpnext.financial_statements);
|
erpnext.financial_statements
|
||||||
|
);
|
||||||
|
|
||||||
erpnext.utils.add_dimensions('Cash Flow', 10);
|
erpnext.utils.add_dimensions('Cash Flow', 10);
|
||||||
|
|
||||||
@ -21,4 +22,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"default": 1
|
"default": 1
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|
||||||
frappe.query_reports["Consolidated Financial Statement"] = {
|
frappe.query_reports["Consolidated Financial Statement"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
{
|
{
|
||||||
@ -150,4 +149,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|
||||||
frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
|
frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
{
|
{
|
||||||
@ -70,7 +69,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"initial_depth": 3
|
"initial_depth": 3
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
function get_accounting_dimension_options() {
|
function get_accounting_dimension_options() {
|
||||||
let options =["Cost Center", "Project"];
|
let options =["Cost Center", "Project"];
|
||||||
|
@ -177,8 +177,8 @@ def add_solvency_ratios(
|
|||||||
return_on_equity_ratio = {"ratio": "Return on Equity Ratio"}
|
return_on_equity_ratio = {"ratio": "Return on Equity Ratio"}
|
||||||
|
|
||||||
for year in years:
|
for year in years:
|
||||||
profit_after_tax = total_income[year] + total_expense[year]
|
profit_after_tax = flt(total_income.get(year)) + flt(total_expense.get(year))
|
||||||
share_holder_fund = total_asset[year] - total_liability[year]
|
share_holder_fund = flt(total_asset.get(year)) - flt(total_liability.get(year))
|
||||||
|
|
||||||
debt_equity_ratio[year] = calculate_ratio(
|
debt_equity_ratio[year] = calculate_ratio(
|
||||||
total_liability.get(year), share_holder_fund, precision
|
total_liability.get(year), share_holder_fund, precision
|
||||||
|
@ -79,7 +79,9 @@ class General_Payment_Ledger_Comparison(object):
|
|||||||
.select(
|
.select(
|
||||||
gle.company,
|
gle.company,
|
||||||
gle.account,
|
gle.account,
|
||||||
|
gle.voucher_type,
|
||||||
gle.voucher_no,
|
gle.voucher_no,
|
||||||
|
gle.party_type,
|
||||||
gle.party,
|
gle.party,
|
||||||
outstanding,
|
outstanding,
|
||||||
)
|
)
|
||||||
@ -89,7 +91,9 @@ class General_Payment_Ledger_Comparison(object):
|
|||||||
& (gle.account.isin(val.accounts))
|
& (gle.account.isin(val.accounts))
|
||||||
)
|
)
|
||||||
.where(Criterion.all(filter_criterion))
|
.where(Criterion.all(filter_criterion))
|
||||||
.groupby(gle.company, gle.account, gle.voucher_no, gle.party)
|
.groupby(
|
||||||
|
gle.company, gle.account, gle.voucher_type, gle.voucher_no, gle.party_type, gle.party
|
||||||
|
)
|
||||||
.run()
|
.run()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -112,7 +116,13 @@ class General_Payment_Ledger_Comparison(object):
|
|||||||
self.account_types[acc_type].ple = (
|
self.account_types[acc_type].ple = (
|
||||||
qb.from_(ple)
|
qb.from_(ple)
|
||||||
.select(
|
.select(
|
||||||
ple.company, ple.account, ple.voucher_no, ple.party, Sum(ple.amount).as_("outstanding")
|
ple.company,
|
||||||
|
ple.account,
|
||||||
|
ple.voucher_type,
|
||||||
|
ple.voucher_no,
|
||||||
|
ple.party_type,
|
||||||
|
ple.party,
|
||||||
|
Sum(ple.amount).as_("outstanding"),
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(ple.company == self.filters.company)
|
(ple.company == self.filters.company)
|
||||||
@ -120,7 +130,9 @@ class General_Payment_Ledger_Comparison(object):
|
|||||||
& (ple.account.isin(val.accounts))
|
& (ple.account.isin(val.accounts))
|
||||||
)
|
)
|
||||||
.where(Criterion.all(filter_criterion))
|
.where(Criterion.all(filter_criterion))
|
||||||
.groupby(ple.company, ple.account, ple.voucher_no, ple.party)
|
.groupby(
|
||||||
|
ple.company, ple.account, ple.voucher_type, ple.voucher_no, ple.party_type, ple.party
|
||||||
|
)
|
||||||
.run()
|
.run()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -133,15 +145,17 @@ class General_Payment_Ledger_Comparison(object):
|
|||||||
self.gle_balances = set(val.gle) | self.gle_balances
|
self.gle_balances = set(val.gle) | self.gle_balances
|
||||||
self.ple_balances = set(val.ple) | self.ple_balances
|
self.ple_balances = set(val.ple) | self.ple_balances
|
||||||
|
|
||||||
self.diff1 = self.gle_balances.difference(self.ple_balances)
|
self.variation_in_payment_ledger = self.gle_balances.difference(self.ple_balances)
|
||||||
self.diff2 = self.ple_balances.difference(self.gle_balances)
|
self.variation_in_general_ledger = self.ple_balances.difference(self.gle_balances)
|
||||||
self.diff = frappe._dict({})
|
self.diff = frappe._dict({})
|
||||||
|
|
||||||
for x in self.diff1:
|
for x in self.variation_in_payment_ledger:
|
||||||
self.diff[(x[0], x[1], x[2], x[3])] = frappe._dict({"gl_balance": x[4]})
|
self.diff[(x[0], x[1], x[2], x[3], x[4], x[5])] = frappe._dict({"gl_balance": x[6]})
|
||||||
|
|
||||||
for x in self.diff2:
|
for x in self.variation_in_general_ledger:
|
||||||
self.diff[(x[0], x[1], x[2], x[3])].update(frappe._dict({"pl_balance": x[4]}))
|
self.diff.setdefault(
|
||||||
|
(x[0], x[1], x[2], x[3], x[4], x[5]), frappe._dict({"gl_balance": 0.0})
|
||||||
|
).update(frappe._dict({"pl_balance": x[6]}))
|
||||||
|
|
||||||
def generate_data(self):
|
def generate_data(self):
|
||||||
self.data = []
|
self.data = []
|
||||||
@ -149,8 +163,12 @@ class General_Payment_Ledger_Comparison(object):
|
|||||||
self.data.append(
|
self.data.append(
|
||||||
frappe._dict(
|
frappe._dict(
|
||||||
{
|
{
|
||||||
"voucher_no": key[2],
|
"company": key[0],
|
||||||
"party": key[3],
|
"account": key[1],
|
||||||
|
"voucher_type": key[2],
|
||||||
|
"voucher_no": key[3],
|
||||||
|
"party_type": key[4],
|
||||||
|
"party": key[5],
|
||||||
"gl_balance": val.gl_balance,
|
"gl_balance": val.gl_balance,
|
||||||
"pl_balance": val.pl_balance,
|
"pl_balance": val.pl_balance,
|
||||||
}
|
}
|
||||||
@ -160,12 +178,52 @@ class General_Payment_Ledger_Comparison(object):
|
|||||||
def get_columns(self):
|
def get_columns(self):
|
||||||
self.columns = []
|
self.columns = []
|
||||||
options = None
|
options = None
|
||||||
|
self.columns.append(
|
||||||
|
dict(
|
||||||
|
label=_("Company"),
|
||||||
|
fieldname="company",
|
||||||
|
fieldtype="Link",
|
||||||
|
options="Company",
|
||||||
|
width="100",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.columns.append(
|
||||||
|
dict(
|
||||||
|
label=_("Account"),
|
||||||
|
fieldname="account",
|
||||||
|
fieldtype="Link",
|
||||||
|
options="Account",
|
||||||
|
width="100",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.columns.append(
|
||||||
|
dict(
|
||||||
|
label=_("Voucher Type"),
|
||||||
|
fieldname="voucher_type",
|
||||||
|
fieldtype="Link",
|
||||||
|
options="DocType",
|
||||||
|
width="100",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.columns.append(
|
self.columns.append(
|
||||||
dict(
|
dict(
|
||||||
label=_("Voucher No"),
|
label=_("Voucher No"),
|
||||||
fieldname="voucher_no",
|
fieldname="voucher_no",
|
||||||
fieldtype="Data",
|
fieldtype="Dynamic Link",
|
||||||
options=options,
|
options="voucher_type",
|
||||||
|
width="100",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.columns.append(
|
||||||
|
dict(
|
||||||
|
label=_("Party Type"),
|
||||||
|
fieldname="party_type",
|
||||||
|
fieldtype="Link",
|
||||||
|
options="DocType",
|
||||||
width="100",
|
width="100",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -174,8 +232,8 @@ class General_Payment_Ledger_Comparison(object):
|
|||||||
dict(
|
dict(
|
||||||
label=_("Party"),
|
label=_("Party"),
|
||||||
fieldname="party",
|
fieldname="party",
|
||||||
fieldtype="Data",
|
fieldtype="Dynamic Link",
|
||||||
options=options,
|
options="party_type",
|
||||||
width="100",
|
width="100",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -50,7 +50,11 @@ class TestGeneralAndPaymentLedger(FrappeTestCase, AccountsTestMixin):
|
|||||||
self.assertEqual(len(data), 1)
|
self.assertEqual(len(data), 1)
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
|
"company": sinv.company,
|
||||||
|
"account": sinv.debit_to,
|
||||||
|
"voucher_type": sinv.doctype,
|
||||||
"voucher_no": sinv.name,
|
"voucher_no": sinv.name,
|
||||||
|
"party_type": "Customer",
|
||||||
"party": sinv.customer,
|
"party": sinv.customer,
|
||||||
"gl_balance": sinv.grand_total,
|
"gl_balance": sinv.grand_total,
|
||||||
"pl_balance": sinv.grand_total - 1,
|
"pl_balance": sinv.grand_total - 1,
|
||||||
|
@ -193,7 +193,13 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
"fieldname": "add_values_in_transaction_currency",
|
"fieldname": "add_values_in_transaction_currency",
|
||||||
"label": __("Add Columns in Transaction Currency"),
|
"label": __("Add Columns in Transaction Currency"),
|
||||||
"fieldtype": "Check"
|
"fieldtype": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "show_remarks",
|
||||||
|
"label": __("Show Remarks"),
|
||||||
|
"fieldtype": "Check"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +163,9 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
select_fields = """, debit, credit, debit_in_account_currency,
|
select_fields = """, debit, credit, debit_in_account_currency,
|
||||||
credit_in_account_currency """
|
credit_in_account_currency """
|
||||||
|
|
||||||
|
if filters.get("show_remarks"):
|
||||||
|
select_fields += """,remarks"""
|
||||||
|
|
||||||
order_by_statement = "order by posting_date, account, creation"
|
order_by_statement = "order by posting_date, account, creation"
|
||||||
|
|
||||||
if filters.get("include_dimensions"):
|
if filters.get("include_dimensions"):
|
||||||
@ -195,7 +198,7 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
voucher_type, voucher_no, {dimension_fields}
|
voucher_type, voucher_no, {dimension_fields}
|
||||||
cost_center, project, {transaction_currency_fields}
|
cost_center, project, {transaction_currency_fields}
|
||||||
against_voucher_type, against_voucher, account_currency,
|
against_voucher_type, against_voucher, account_currency,
|
||||||
remarks, against, is_opening, creation {select_fields}
|
against, is_opening, creation {select_fields}
|
||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
where company=%(company)s {conditions}
|
where company=%(company)s {conditions}
|
||||||
{order_by_statement}
|
{order_by_statement}
|
||||||
@ -631,8 +634,10 @@ def get_columns(filters):
|
|||||||
"width": 100,
|
"width": 100,
|
||||||
},
|
},
|
||||||
{"label": _("Supplier Invoice No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 100},
|
{"label": _("Supplier Invoice No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 100},
|
||||||
{"label": _("Remarks"), "fieldname": "remarks", "width": 400},
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if filters.get("show_remarks"):
|
||||||
|
columns.extend([{"label": _("Remarks"), "fieldname": "remarks", "width": 400}])
|
||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
@ -2,14 +2,10 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
frappe.query_reports["Gross and Net Profit Report"] = {
|
frappe.query_reports["Gross and Net Profit Report"] = $.extend(
|
||||||
"filters": [
|
{},
|
||||||
|
erpnext.financial_statements
|
||||||
]
|
);
|
||||||
}
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|
||||||
frappe.query_reports["Gross and Net Profit Report"] = $.extend({},
|
|
||||||
erpnext.financial_statements);
|
|
||||||
|
|
||||||
frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
|
frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
|
||||||
{
|
{
|
||||||
@ -18,4 +14,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"fieldtype": "Check"
|
"fieldtype": "Check"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
@ -544,6 +544,8 @@ class GrossProfitGenerator(object):
|
|||||||
new_row.qty += flt(row.qty)
|
new_row.qty += flt(row.qty)
|
||||||
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
|
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
|
||||||
new_row.base_amount += flt(row.base_amount, self.currency_precision)
|
new_row.base_amount += flt(row.base_amount, self.currency_precision)
|
||||||
|
if self.filters.get("group_by") == "Sales Person":
|
||||||
|
new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision)
|
||||||
new_row = self.set_average_rate(new_row)
|
new_row = self.set_average_rate(new_row)
|
||||||
self.grouped_data.append(new_row)
|
self.grouped_data.append(new_row)
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// 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
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function () {
|
|
||||||
frappe.query_reports["Profit and Loss Statement"] = $.extend(
|
frappe.query_reports["Profit and Loss Statement"] = $.extend(
|
||||||
{},
|
{},
|
||||||
erpnext.financial_statements
|
erpnext.financial_statements
|
||||||
@ -15,4 +14,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
|
|||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
default: 1,
|
default: 1,
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|
||||||
frappe.query_reports["Profitability Analysis"] = {
|
frappe.query_reports["Profitability Analysis"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
{
|
{
|
||||||
@ -33,13 +32,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"label": __("Accounting Dimension"),
|
"label": __("Accounting Dimension"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Accounting Dimension",
|
"options": "Accounting Dimension",
|
||||||
"get_query": () =>{
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
"disabled": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "fiscal_year",
|
"fieldname": "fiscal_year",
|
||||||
@ -112,7 +104,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"to_fiscal_year": data.fiscal_year
|
"to_fiscal_year": data.fiscal_year
|
||||||
};
|
};
|
||||||
|
|
||||||
if(data.based_on == 'cost_center'){
|
if(data.based_on == 'Cost Center'){
|
||||||
frappe.route_options["cost_center"] = data.account
|
frappe.route_options["cost_center"] = data.account
|
||||||
} else {
|
} else {
|
||||||
frappe.route_options["project"] = data.account
|
frappe.route_options["project"] = data.account
|
||||||
@ -130,4 +122,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]);
|
frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
|
||||||
|
@ -47,6 +47,7 @@ def get_result(
|
|||||||
out = []
|
out = []
|
||||||
for name, details in gle_map.items():
|
for name, details in gle_map.items():
|
||||||
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
|
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
|
||||||
|
bill_no, bill_date = "", ""
|
||||||
tax_withholding_category = tax_category_map.get(name)
|
tax_withholding_category = tax_category_map.get(name)
|
||||||
rate = tax_rate_map.get(tax_withholding_category)
|
rate = tax_rate_map.get(tax_withholding_category)
|
||||||
|
|
||||||
@ -68,6 +69,13 @@ def get_result(
|
|||||||
tax_amount += entry.credit - entry.debit
|
tax_amount += entry.credit - entry.debit
|
||||||
|
|
||||||
if net_total_map.get(name):
|
if net_total_map.get(name):
|
||||||
|
if voucher_type == "Journal Entry" and tax_amount and rate:
|
||||||
|
# back calcalute total amount from rate and tax_amount
|
||||||
|
if rate:
|
||||||
|
total_amount = grand_total = base_total = tax_amount / (rate / 100)
|
||||||
|
elif voucher_type == "Purchase Invoice":
|
||||||
|
total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get(name)
|
||||||
|
else:
|
||||||
total_amount, grand_total, base_total = net_total_map.get(name)
|
total_amount, grand_total, base_total = net_total_map.get(name)
|
||||||
else:
|
else:
|
||||||
total_amount += entry.credit
|
total_amount += entry.credit
|
||||||
@ -92,7 +100,7 @@ def get_result(
|
|||||||
|
|
||||||
row.update(
|
row.update(
|
||||||
{
|
{
|
||||||
"section_code": tax_withholding_category,
|
"section_code": tax_withholding_category or "",
|
||||||
"entity_type": party_map.get(party, {}).get(party_type),
|
"entity_type": party_map.get(party, {}).get(party_type),
|
||||||
"rate": rate,
|
"rate": rate,
|
||||||
"total_amount": total_amount,
|
"total_amount": total_amount,
|
||||||
@ -102,10 +110,14 @@ def get_result(
|
|||||||
"transaction_date": posting_date,
|
"transaction_date": posting_date,
|
||||||
"transaction_type": voucher_type,
|
"transaction_type": voucher_type,
|
||||||
"ref_no": name,
|
"ref_no": name,
|
||||||
|
"supplier_invoice_no": bill_no,
|
||||||
|
"supplier_invoice_date": bill_date,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
out.append(row)
|
out.append(row)
|
||||||
|
|
||||||
|
out.sort(key=lambda x: x["section_code"])
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@ -153,14 +165,14 @@ def get_gle_map(documents):
|
|||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
||||||
columns = [
|
columns = [
|
||||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
|
|
||||||
{
|
{
|
||||||
"label": _(filters.get("party_type")),
|
"label": _("Section Code"),
|
||||||
"fieldname": "party",
|
"options": "Tax Withholding Category",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldname": "section_code",
|
||||||
"options": "party_type",
|
"fieldtype": "Link",
|
||||||
"width": 180,
|
"width": 90,
|
||||||
},
|
},
|
||||||
|
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
|
||||||
]
|
]
|
||||||
|
|
||||||
if filters.naming_series == "Naming Series":
|
if filters.naming_series == "Naming Series":
|
||||||
@ -175,51 +187,60 @@ def get_columns(filters):
|
|||||||
|
|
||||||
columns.extend(
|
columns.extend(
|
||||||
[
|
[
|
||||||
{
|
|
||||||
"label": _("Date of Transaction"),
|
|
||||||
"fieldname": "transaction_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"width": 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Section Code"),
|
|
||||||
"options": "Tax Withholding Category",
|
|
||||||
"fieldname": "section_code",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"width": 90,
|
|
||||||
},
|
|
||||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
|
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if filters.party_type == "Supplier":
|
||||||
|
columns.extend(
|
||||||
|
[
|
||||||
{
|
{
|
||||||
"label": _("Total Amount"),
|
"label": _("Supplier Invoice No"),
|
||||||
"fieldname": "total_amount",
|
"fieldname": "supplier_invoice_no",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Data",
|
||||||
"width": 90,
|
"width": 120,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": _("Supplier Invoice Date"),
|
||||||
|
"fieldname": "supplier_invoice_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
columns.extend(
|
||||||
|
[
|
||||||
{
|
{
|
||||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"width": 90,
|
"width": 60,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Tax Amount"),
|
"label": _("Total Amount"),
|
||||||
"fieldname": "tax_amount",
|
"fieldname": "total_amount",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"width": 90,
|
"width": 120,
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": _("Grand Total"),
|
|
||||||
"fieldname": "grand_total",
|
|
||||||
"fieldtype": "Float",
|
|
||||||
"width": 90,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Base Total"),
|
"label": _("Base Total"),
|
||||||
"fieldname": "base_total",
|
"fieldname": "base_total",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"width": 90,
|
"width": 120,
|
||||||
},
|
},
|
||||||
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100},
|
{
|
||||||
|
"label": _("Tax Amount"),
|
||||||
|
"fieldname": "tax_amount",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Grand Total"),
|
||||||
|
"fieldname": "grand_total",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130},
|
||||||
{
|
{
|
||||||
"label": _("Reference No."),
|
"label": _("Reference No."),
|
||||||
"fieldname": "ref_no",
|
"fieldname": "ref_no",
|
||||||
@ -227,6 +248,12 @@ def get_columns(filters):
|
|||||||
"options": "transaction_type",
|
"options": "transaction_type",
|
||||||
"width": 180,
|
"width": 180,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": _("Date of Transaction"),
|
||||||
|
"fieldname": "transaction_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -249,27 +276,7 @@ def get_tds_docs(filters):
|
|||||||
"Tax Withholding Account", {"company": filters.get("company")}, pluck="account"
|
"Tax Withholding Account", {"company": filters.get("company")}, pluck="account"
|
||||||
)
|
)
|
||||||
|
|
||||||
query_filters = {
|
tds_docs = get_tds_docs_query(filters, bank_accounts, tds_accounts).run(as_dict=True)
|
||||||
"account": ("in", tds_accounts),
|
|
||||||
"posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
|
|
||||||
"is_cancelled": 0,
|
|
||||||
"against": ("not in", bank_accounts),
|
|
||||||
}
|
|
||||||
|
|
||||||
party = frappe.get_all(filters.get("party_type"), pluck="name")
|
|
||||||
or_filters.update({"against": ("in", party), "voucher_type": "Journal Entry"})
|
|
||||||
|
|
||||||
if filters.get("party"):
|
|
||||||
del query_filters["account"]
|
|
||||||
del query_filters["against"]
|
|
||||||
or_filters = {"against": filters.get("party"), "party": filters.get("party")}
|
|
||||||
|
|
||||||
tds_docs = frappe.get_all(
|
|
||||||
"GL Entry",
|
|
||||||
filters=query_filters,
|
|
||||||
or_filters=or_filters,
|
|
||||||
fields=["voucher_no", "voucher_type", "against", "party"],
|
|
||||||
)
|
|
||||||
|
|
||||||
for d in tds_docs:
|
for d in tds_docs:
|
||||||
if d.voucher_type == "Purchase Invoice":
|
if d.voucher_type == "Purchase Invoice":
|
||||||
@ -305,6 +312,47 @@ def get_tds_docs(filters):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_tds_docs_query(filters, bank_accounts, tds_accounts):
|
||||||
|
if not tds_accounts:
|
||||||
|
frappe.throw(
|
||||||
|
_("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")),
|
||||||
|
title="Accounts Missing Error",
|
||||||
|
)
|
||||||
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(gle)
|
||||||
|
.select("voucher_no", "voucher_type", "against", "party")
|
||||||
|
.where((gle.is_cancelled == 0))
|
||||||
|
)
|
||||||
|
|
||||||
|
if filters.get("from_date"):
|
||||||
|
query = query.where(gle.posting_date >= filters.get("from_date"))
|
||||||
|
if filters.get("to_date"):
|
||||||
|
query = query.where(gle.posting_date <= filters.get("to_date"))
|
||||||
|
|
||||||
|
if bank_accounts:
|
||||||
|
query = query.where(gle.against.notin(bank_accounts))
|
||||||
|
|
||||||
|
if filters.get("party"):
|
||||||
|
party = [filters.get("party")]
|
||||||
|
query = query.where(
|
||||||
|
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
|
||||||
|
| ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")))
|
||||||
|
| gle.party.isin(party)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
party = frappe.get_all(filters.get("party_type"), pluck="name")
|
||||||
|
query = query.where(
|
||||||
|
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
|
||||||
|
| (
|
||||||
|
(gle.voucher_type == "Journal Entry")
|
||||||
|
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
|
||||||
|
)
|
||||||
|
| gle.party.isin(party)
|
||||||
|
)
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
def get_journal_entry_party_map(journal_entries):
|
def get_journal_entry_party_map(journal_entries):
|
||||||
journal_entry_party_map = {}
|
journal_entry_party_map = {}
|
||||||
for d in frappe.db.get_all(
|
for d in frappe.db.get_all(
|
||||||
@ -331,6 +379,8 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
|
|||||||
"base_tax_withholding_net_total",
|
"base_tax_withholding_net_total",
|
||||||
"grand_total",
|
"grand_total",
|
||||||
"base_total",
|
"base_total",
|
||||||
|
"bill_no",
|
||||||
|
"bill_date",
|
||||||
],
|
],
|
||||||
"Sales Invoice": ["base_net_total", "grand_total", "base_total"],
|
"Sales Invoice": ["base_net_total", "grand_total", "base_total"],
|
||||||
"Payment Entry": [
|
"Payment Entry": [
|
||||||
@ -349,7 +399,13 @@ def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
|
|||||||
for entry in entries:
|
for entry in entries:
|
||||||
tax_category_map.update({entry.name: entry.tax_withholding_category})
|
tax_category_map.update({entry.name: entry.tax_withholding_category})
|
||||||
if doctype == "Purchase Invoice":
|
if doctype == "Purchase Invoice":
|
||||||
value = [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total]
|
value = [
|
||||||
|
entry.base_tax_withholding_net_total,
|
||||||
|
entry.grand_total,
|
||||||
|
entry.base_total,
|
||||||
|
entry.bill_no,
|
||||||
|
entry.bill_date,
|
||||||
|
]
|
||||||
elif doctype == "Sales Invoice":
|
elif doctype == "Sales Invoice":
|
||||||
value = [entry.base_net_total, entry.grand_total, entry.base_total]
|
value = [entry.base_net_total, entry.grand_total, entry.base_total]
|
||||||
elif doctype == "Payment Entry":
|
elif doctype == "Payment Entry":
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// 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
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|
||||||
frappe.query_reports["Trial Balance"] = {
|
frappe.query_reports["Trial Balance"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
{
|
{
|
||||||
@ -115,4 +114,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.add_dimensions('Trial Balance', 6);
|
erpnext.utils.add_dimensions('Trial Balance', 6);
|
||||||
});
|
|
||||||
|
@ -10,7 +10,7 @@ import frappe.defaults
|
|||||||
from frappe import _, qb, throw
|
from frappe import _, qb, throw
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.query_builder import AliasedQuery, Criterion, Table
|
from frappe.query_builder import AliasedQuery, Criterion, Table
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Round, Sum
|
||||||
from frappe.query_builder.utils import DocType
|
from frappe.query_builder.utils import DocType
|
||||||
from frappe.utils import (
|
from frappe.utils import (
|
||||||
cint,
|
cint,
|
||||||
@ -536,6 +536,8 @@ def check_if_advance_entry_modified(args):
|
|||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
precision = frappe.get_precision("Payment Entry", "unallocated_amount")
|
||||||
|
|
||||||
payment_entry = frappe.qb.DocType("Payment Entry")
|
payment_entry = frappe.qb.DocType("Payment Entry")
|
||||||
payment_ref = frappe.qb.DocType("Payment Entry Reference")
|
payment_ref = frappe.qb.DocType("Payment Entry Reference")
|
||||||
|
|
||||||
@ -557,7 +559,10 @@ def check_if_advance_entry_modified(args):
|
|||||||
.where(payment_ref.allocated_amount == args.get("unreconciled_amount"))
|
.where(payment_ref.allocated_amount == args.get("unreconciled_amount"))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
q = q.where(payment_entry.unallocated_amount == args.get("unreconciled_amount"))
|
q = q.where(
|
||||||
|
Round(payment_entry.unallocated_amount, precision)
|
||||||
|
== Round(args.get("unreconciled_amount"), precision)
|
||||||
|
)
|
||||||
|
|
||||||
ret = q.run(as_dict=True)
|
ret = q.run(as_dict=True)
|
||||||
|
|
||||||
@ -645,7 +650,7 @@ def update_reference_in_payment_entry(
|
|||||||
"outstanding_amount": d.outstanding_amount,
|
"outstanding_amount": d.outstanding_amount,
|
||||||
"allocated_amount": d.allocated_amount,
|
"allocated_amount": d.allocated_amount,
|
||||||
"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(),
|
"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(),
|
||||||
"exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation
|
"exchange_gain_loss": d.exchange_gain_loss,
|
||||||
"account": d.account,
|
"account": d.account,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -658,28 +663,29 @@ def update_reference_in_payment_entry(
|
|||||||
existing_row.reference_doctype, existing_row.reference_name
|
existing_row.reference_doctype, existing_row.reference_name
|
||||||
).set_total_advance_paid()
|
).set_total_advance_paid()
|
||||||
|
|
||||||
original_row = existing_row.as_dict().copy()
|
if d.allocated_amount <= existing_row.allocated_amount:
|
||||||
existing_row.update(reference_details)
|
existing_row.allocated_amount -= d.allocated_amount
|
||||||
|
|
||||||
if d.allocated_amount < original_row.allocated_amount:
|
|
||||||
new_row = payment_entry.append("references")
|
new_row = payment_entry.append("references")
|
||||||
new_row.docstatus = 1
|
new_row.docstatus = 1
|
||||||
for field in list(reference_details):
|
for field in list(reference_details):
|
||||||
new_row.set(field, original_row[field])
|
new_row.set(field, reference_details[field])
|
||||||
|
|
||||||
new_row.allocated_amount = original_row.allocated_amount - d.allocated_amount
|
|
||||||
else:
|
else:
|
||||||
new_row = payment_entry.append("references")
|
new_row = payment_entry.append("references")
|
||||||
new_row.docstatus = 1
|
new_row.docstatus = 1
|
||||||
new_row.update(reference_details)
|
new_row.update(reference_details)
|
||||||
|
|
||||||
payment_entry.flags.ignore_validate_update_after_submit = True
|
payment_entry.flags.ignore_validate_update_after_submit = True
|
||||||
|
payment_entry.clear_unallocated_reference_document_rows()
|
||||||
payment_entry.setup_party_account_field()
|
payment_entry.setup_party_account_field()
|
||||||
payment_entry.set_missing_values()
|
payment_entry.set_missing_values()
|
||||||
if not skip_ref_details_update_for_pe:
|
if not skip_ref_details_update_for_pe:
|
||||||
payment_entry.set_missing_ref_details()
|
payment_entry.set_missing_ref_details()
|
||||||
payment_entry.set_amounts()
|
payment_entry.set_amounts()
|
||||||
payment_entry.make_exchange_gain_loss_journal()
|
payment_entry.make_exchange_gain_loss_journal(
|
||||||
|
frappe._dict({"difference_posting_date": d.difference_posting_date})
|
||||||
|
)
|
||||||
|
|
||||||
if not do_not_save:
|
if not do_not_save:
|
||||||
payment_entry.save(ignore_permissions=True)
|
payment_entry.save(ignore_permissions=True)
|
||||||
|
@ -9,7 +9,6 @@ frappe.ui.form.on('Asset', {
|
|||||||
frm.set_query("item_code", function() {
|
frm.set_query("item_code", function() {
|
||||||
return {
|
return {
|
||||||
"filters": {
|
"filters": {
|
||||||
"disabled": 0,
|
|
||||||
"is_fixed_asset": 1,
|
"is_fixed_asset": 1,
|
||||||
"is_stock_item": 0
|
"is_stock_item": 0
|
||||||
}
|
}
|
||||||
@ -148,6 +147,15 @@ frappe.ui.form.on('Asset', {
|
|||||||
|
|
||||||
if (frm.doc.docstatus == 0) {
|
if (frm.doc.docstatus == 0) {
|
||||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||||
|
|
||||||
|
if (frm.doc.is_composite_asset && !frm.doc.capitalized_in) {
|
||||||
|
$('.primary-action').prop('hidden', true);
|
||||||
|
$('.form-message').text('Capitalize this asset to confirm');
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Capitalize Asset"), function() {
|
||||||
|
frm.trigger("create_asset_capitalization");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -169,7 +177,7 @@ frappe.ui.form.on('Asset', {
|
|||||||
frm.set_df_property('purchase_invoice', 'read_only', 1);
|
frm.set_df_property('purchase_invoice', 'read_only', 1);
|
||||||
frm.set_df_property('purchase_receipt', 'read_only', 1);
|
frm.set_df_property('purchase_receipt', 'read_only', 1);
|
||||||
}
|
}
|
||||||
else if (frm.doc.is_existing_asset) {
|
else if (frm.doc.is_existing_asset || frm.doc.is_composite_asset) {
|
||||||
frm.toggle_reqd('purchase_receipt', 0);
|
frm.toggle_reqd('purchase_receipt', 0);
|
||||||
frm.toggle_reqd('purchase_invoice', 0);
|
frm.toggle_reqd('purchase_invoice', 0);
|
||||||
}
|
}
|
||||||
@ -239,7 +247,7 @@ frappe.ui.form.on('Asset', {
|
|||||||
|
|
||||||
datatable.style.setStyle(`.dt-scrollable`, {'font-size': '0.75rem', 'margin-bottom': '1rem', 'margin-left': '0.35rem', 'margin-right': '0.35rem'});
|
datatable.style.setStyle(`.dt-scrollable`, {'font-size': '0.75rem', 'margin-bottom': '1rem', 'margin-left': '0.35rem', 'margin-right': '0.35rem'});
|
||||||
datatable.style.setStyle(`.dt-header`, {'margin-left': '0.35rem', 'margin-right': '0.35rem'});
|
datatable.style.setStyle(`.dt-header`, {'margin-left': '0.35rem', 'margin-right': '0.35rem'});
|
||||||
datatable.style.setStyle(`.dt-cell--header`, {'color': 'var(--text-muted)'});
|
datatable.style.setStyle(`.dt-cell--header .dt-cell__content`, {'color': 'var(--gray-600)', 'font-size': 'var(--text-sm)'});
|
||||||
datatable.style.setStyle(`.dt-cell`, {'color': 'var(--text-color)'});
|
datatable.style.setStyle(`.dt-cell`, {'color': 'var(--text-color)'});
|
||||||
datatable.style.setStyle(`.dt-cell--col-1`, {'text-align': 'center'});
|
datatable.style.setStyle(`.dt-cell--col-1`, {'text-align': 'center'});
|
||||||
datatable.style.setStyle(`.dt-cell--col-2`, {'font-weight': 600});
|
datatable.style.setStyle(`.dt-cell--col-2`, {'font-weight': 600});
|
||||||
@ -328,7 +336,7 @@ frappe.ui.form.on('Asset', {
|
|||||||
|
|
||||||
|
|
||||||
item_code: function(frm) {
|
item_code: function(frm) {
|
||||||
if(frm.doc.item_code && frm.doc.calculate_depreciation) {
|
if(frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
|
||||||
frm.trigger('set_finance_book');
|
frm.trigger('set_finance_book');
|
||||||
} else {
|
} else {
|
||||||
frm.set_value('finance_books', []);
|
frm.set_value('finance_books', []);
|
||||||
@ -340,7 +348,8 @@ frappe.ui.form.on('Asset', {
|
|||||||
method: "erpnext.assets.doctype.asset.asset.get_item_details",
|
method: "erpnext.assets.doctype.asset.asset.get_item_details",
|
||||||
args: {
|
args: {
|
||||||
item_code: frm.doc.item_code,
|
item_code: frm.doc.item_code,
|
||||||
asset_category: frm.doc.asset_category
|
asset_category: frm.doc.asset_category,
|
||||||
|
gross_purchase_amount: frm.doc.gross_purchase_amount
|
||||||
},
|
},
|
||||||
callback: function(r, rt) {
|
callback: function(r, rt) {
|
||||||
if(r.message) {
|
if(r.message) {
|
||||||
@ -352,7 +361,17 @@ frappe.ui.form.on('Asset', {
|
|||||||
|
|
||||||
is_existing_asset: function(frm) {
|
is_existing_asset: function(frm) {
|
||||||
frm.trigger("toggle_reference_doc");
|
frm.trigger("toggle_reference_doc");
|
||||||
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
},
|
||||||
|
|
||||||
|
is_composite_asset: function(frm) {
|
||||||
|
if(frm.doc.is_composite_asset) {
|
||||||
|
frm.set_value('gross_purchase_amount', 0);
|
||||||
|
frm.set_df_property('gross_purchase_amount', 'read_only', 1);
|
||||||
|
} else {
|
||||||
|
frm.set_df_property('gross_purchase_amount', 'read_only', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.trigger("toggle_reference_doc");
|
||||||
},
|
},
|
||||||
|
|
||||||
make_sales_invoice: function(frm) {
|
make_sales_invoice: function(frm) {
|
||||||
@ -402,6 +421,19 @@ frappe.ui.form.on('Asset', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_asset_capitalization: function(frm) {
|
||||||
|
frappe.call({
|
||||||
|
args: {
|
||||||
|
"asset": frm.doc.name,
|
||||||
|
},
|
||||||
|
method: "erpnext.assets.doctype.asset.asset.create_asset_capitalization",
|
||||||
|
callback: function(r) {
|
||||||
|
var doclist = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
split_asset: function(frm) {
|
split_asset: function(frm) {
|
||||||
const title = __('Split Asset');
|
const title = __('Split Asset');
|
||||||
|
|
||||||
@ -457,7 +489,7 @@ frappe.ui.form.on('Asset', {
|
|||||||
|
|
||||||
calculate_depreciation: function(frm) {
|
calculate_depreciation: function(frm) {
|
||||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||||
if (frm.doc.item_code && frm.doc.calculate_depreciation ) {
|
if (frm.doc.item_code && frm.doc.calculate_depreciation && frm.doc.gross_purchase_amount) {
|
||||||
frm.trigger("set_finance_book");
|
frm.trigger("set_finance_book");
|
||||||
} else {
|
} else {
|
||||||
frm.set_value("finance_books", []);
|
frm.set_value("finance_books", []);
|
||||||
@ -465,9 +497,11 @@ frappe.ui.form.on('Asset', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
gross_purchase_amount: function(frm) {
|
gross_purchase_amount: function(frm) {
|
||||||
|
if (frm.doc.finance_books) {
|
||||||
frm.doc.finance_books.forEach(d => {
|
frm.doc.finance_books.forEach(d => {
|
||||||
frm.events.set_depreciation_rate(frm, d);
|
frm.events.set_depreciation_rate(frm, d);
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
purchase_receipt: (frm) => {
|
purchase_receipt: (frm) => {
|
||||||
@ -546,7 +580,21 @@ frappe.ui.form.on('Asset', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
set_salvage_value_percentage_or_expected_value_after_useful_life: function(frm, row, salvage_value_percentage_changed, expected_value_after_useful_life_changed) {
|
||||||
|
if (expected_value_after_useful_life_changed) {
|
||||||
|
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true;
|
||||||
|
const new_salvage_value_percentage = flt((row.expected_value_after_useful_life * 100) / frm.doc.gross_purchase_amount, precision("salvage_value_percentage", row));
|
||||||
|
frappe.model.set_value(row.doctype, row.name, "salvage_value_percentage", new_salvage_value_percentage);
|
||||||
|
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false;
|
||||||
|
} else if (salvage_value_percentage_changed) {
|
||||||
|
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true;
|
||||||
|
const new_expected_value_after_useful_life = flt(frm.doc.gross_purchase_amount * (row.salvage_value_percentage / 100), precision('gross_purchase_amount'));
|
||||||
|
frappe.model.set_value(row.doctype, row.name, "expected_value_after_useful_life", new_expected_value_after_useful_life);
|
||||||
|
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('Asset Finance Book', {
|
frappe.ui.form.on('Asset Finance Book', {
|
||||||
@ -557,9 +605,19 @@ frappe.ui.form.on('Asset Finance Book', {
|
|||||||
|
|
||||||
expected_value_after_useful_life: function(frm, cdt, cdn) {
|
expected_value_after_useful_life: function(frm, cdt, cdn) {
|
||||||
const row = locals[cdt][cdn];
|
const row = locals[cdt][cdn];
|
||||||
|
if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) {
|
||||||
|
frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, false, true);
|
||||||
|
}
|
||||||
frm.events.set_depreciation_rate(frm, row);
|
frm.events.set_depreciation_rate(frm, row);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
salvage_value_percentage: function(frm, cdt, cdn) {
|
||||||
|
const row = locals[cdt][cdn];
|
||||||
|
if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) {
|
||||||
|
frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, true, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
frequency_of_depreciation: function(frm, cdt, cdn) {
|
frequency_of_depreciation: function(frm, cdt, cdn) {
|
||||||
const row = locals[cdt][cdn];
|
const row = locals[cdt][cdn];
|
||||||
frm.events.set_depreciation_rate(frm, row);
|
frm.events.set_depreciation_rate(frm, row);
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"asset_owner",
|
"asset_owner",
|
||||||
"asset_owner_company",
|
"asset_owner_company",
|
||||||
"is_existing_asset",
|
"is_existing_asset",
|
||||||
|
"is_composite_asset",
|
||||||
"supplier",
|
"supplier",
|
||||||
"customer",
|
"customer",
|
||||||
"image",
|
"image",
|
||||||
@ -72,7 +73,8 @@
|
|||||||
"purchase_receipt_amount",
|
"purchase_receipt_amount",
|
||||||
"default_finance_book",
|
"default_finance_book",
|
||||||
"depr_entry_posting_status",
|
"depr_entry_posting_status",
|
||||||
"amended_from"
|
"amended_from",
|
||||||
|
"capitalized_in"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -199,7 +201,7 @@
|
|||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Purchase Date",
|
"label": "Purchase Date",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"read_only_depends_on": "eval:!doc.is_existing_asset",
|
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -219,11 +221,11 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!(doc.is_composite_asset && !doc.capitalized_in)",
|
||||||
"fieldname": "gross_purchase_amount",
|
"fieldname": "gross_purchase_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Gross Purchase Amount",
|
"label": "Gross Purchase Amount",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1,
|
|
||||||
"read_only_depends_on": "eval:!doc.is_existing_asset",
|
"read_only_depends_on": "eval:!doc.is_existing_asset",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@ -237,10 +239,12 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "calculate_depreciation",
|
"fieldname": "calculate_depreciation",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Calculate Depreciation"
|
"label": "Calculate Depreciation",
|
||||||
|
"read_only_depends_on": "eval:doc.is_composite_asset && !doc.gross_purchase_amount"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval:!doc.is_composite_asset",
|
||||||
"fieldname": "is_existing_asset",
|
"fieldname": "is_existing_asset",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Existing Asset"
|
"label": "Is Existing Asset"
|
||||||
@ -395,6 +399,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
|
||||||
"fieldname": "purchase_receipt",
|
"fieldname": "purchase_receipt",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Purchase Receipt",
|
"label": "Purchase Receipt",
|
||||||
@ -412,6 +417,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
|
||||||
"fieldname": "purchase_invoice",
|
"fieldname": "purchase_invoice",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Purchase Invoice",
|
"label": "Purchase Invoice",
|
||||||
@ -475,10 +481,11 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval.doc.asset_quantity",
|
||||||
"fieldname": "asset_quantity",
|
"fieldname": "asset_quantity",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Asset Quantity",
|
"label": "Asset Quantity",
|
||||||
"read_only_depends_on": "eval:!doc.is_existing_asset"
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "depr_entry_posting_status",
|
"fieldname": "depr_entry_posting_status",
|
||||||
@ -507,6 +514,21 @@
|
|||||||
"fieldname": "is_fully_depreciated",
|
"fieldname": "is_fully_depreciated",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Fully Depreciated"
|
"label": "Is Fully Depreciated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:!doc.is_existing_asset",
|
||||||
|
"fieldname": "is_composite_asset",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Composite Asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "capitalized_in",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Capitalized In",
|
||||||
|
"options": "Asset Capitalization",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 72,
|
"idx": 72,
|
||||||
@ -543,9 +565,14 @@
|
|||||||
"link_doctype": "Journal Entry",
|
"link_doctype": "Journal Entry",
|
||||||
"link_fieldname": "reference_name",
|
"link_fieldname": "reference_name",
|
||||||
"table_fieldname": "accounts"
|
"table_fieldname": "accounts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group": "Asset Capitalization",
|
||||||
|
"link_doctype": "Asset Capitalization",
|
||||||
|
"link_fieldname": "target_asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-07-28 20:12:44.819616",
|
"modified": "2023-10-27 17:03:46.629617",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
@ -198,7 +198,9 @@ class Asset(AccountsController):
|
|||||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||||
|
|
||||||
if self.item_code and not self.get("finance_books"):
|
if self.item_code and not self.get("finance_books"):
|
||||||
finance_books = get_item_details(self.item_code, self.asset_category)
|
finance_books = get_item_details(
|
||||||
|
self.item_code, self.asset_category, self.gross_purchase_amount
|
||||||
|
)
|
||||||
self.set("finance_books", finance_books)
|
self.set("finance_books", finance_books)
|
||||||
|
|
||||||
def validate_finance_books(self):
|
def validate_finance_books(self):
|
||||||
@ -226,7 +228,7 @@ class Asset(AccountsController):
|
|||||||
if not self.asset_category:
|
if not self.asset_category:
|
||||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||||
|
|
||||||
if not flt(self.gross_purchase_amount):
|
if not flt(self.gross_purchase_amount) and not self.is_composite_asset:
|
||||||
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
|
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
|
||||||
|
|
||||||
if is_cwip_accounting_enabled(self.asset_category):
|
if is_cwip_accounting_enabled(self.asset_category):
|
||||||
@ -768,6 +770,15 @@ def create_asset_repair(asset, asset_name):
|
|||||||
return asset_repair
|
return asset_repair
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_asset_capitalization(asset):
|
||||||
|
asset_capitalization = frappe.new_doc("Asset Capitalization")
|
||||||
|
asset_capitalization.update(
|
||||||
|
{"target_asset": asset, "capitalization_method": "Choose a WIP composite asset"}
|
||||||
|
)
|
||||||
|
return asset_capitalization
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_asset_value_adjustment(asset, asset_category, company):
|
def create_asset_value_adjustment(asset, asset_category, company):
|
||||||
asset_value_adjustment = frappe.new_doc("Asset Value Adjustment")
|
asset_value_adjustment = frappe.new_doc("Asset Value Adjustment")
|
||||||
@ -799,7 +810,7 @@ def transfer_asset(args):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_item_details(item_code, asset_category):
|
def get_item_details(item_code, asset_category, gross_purchase_amount):
|
||||||
asset_category_doc = frappe.get_doc("Asset Category", asset_category)
|
asset_category_doc = frappe.get_doc("Asset Category", asset_category)
|
||||||
books = []
|
books = []
|
||||||
for d in asset_category_doc.finance_books:
|
for d in asset_category_doc.finance_books:
|
||||||
@ -809,7 +820,11 @@ def get_item_details(item_code, asset_category):
|
|||||||
"depreciation_method": d.depreciation_method,
|
"depreciation_method": d.depreciation_method,
|
||||||
"total_number_of_depreciations": d.total_number_of_depreciations,
|
"total_number_of_depreciations": d.total_number_of_depreciations,
|
||||||
"frequency_of_depreciation": d.frequency_of_depreciation,
|
"frequency_of_depreciation": d.frequency_of_depreciation,
|
||||||
"start_date": nowdate(),
|
"daily_prorata_based": d.daily_prorata_based,
|
||||||
|
"salvage_value_percentage": d.salvage_value_percentage,
|
||||||
|
"expected_value_after_useful_life": flt(gross_purchase_amount)
|
||||||
|
* flt(d.salvage_value_percentage / 100),
|
||||||
|
"depreciation_start_date": d.depreciation_start_date or nowdate(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -780,6 +780,15 @@ def get_disposal_account_and_cost_center(company):
|
|||||||
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
|
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
|
||||||
asset_doc = frappe.get_doc("Asset", asset)
|
asset_doc = frappe.get_doc("Asset", asset)
|
||||||
|
|
||||||
|
if asset_doc.available_for_use_date > getdate(disposal_date):
|
||||||
|
frappe.throw(
|
||||||
|
"Disposal date {0} cannot be before available for use date {1} of the asset.".format(
|
||||||
|
disposal_date, asset_doc.available_for_use_date
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif asset_doc.available_for_use_date == getdate(disposal_date):
|
||||||
|
return flt(asset_doc.gross_purchase_amount - asset_doc.opening_accumulated_depreciation)
|
||||||
|
|
||||||
if not asset_doc.calculate_depreciation:
|
if not asset_doc.calculate_depreciation:
|
||||||
return flt(asset_doc.value_after_depreciation)
|
return flt(asset_doc.value_after_depreciation)
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ from frappe.utils import (
|
|||||||
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.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.assets.doctype.asset.asset import (
|
from erpnext.assets.doctype.asset.asset import (
|
||||||
get_asset_value_after_depreciation,
|
|
||||||
make_sales_invoice,
|
make_sales_invoice,
|
||||||
split_asset,
|
split_asset,
|
||||||
update_maintenance_status,
|
update_maintenance_status,
|
||||||
@ -194,6 +193,7 @@ class TestAsset(AssetSetup):
|
|||||||
def test_is_fixed_asset_set(self):
|
def test_is_fixed_asset_set(self):
|
||||||
asset = create_asset(is_existing_asset=1)
|
asset = create_asset(is_existing_asset=1)
|
||||||
doc = frappe.new_doc("Purchase Invoice")
|
doc = frappe.new_doc("Purchase Invoice")
|
||||||
|
doc.company = "_Test Company"
|
||||||
doc.supplier = "_Test Supplier"
|
doc.supplier = "_Test Supplier"
|
||||||
doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
|
doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name})
|
||||||
|
|
||||||
@ -534,7 +534,7 @@ class TestAsset(AssetSetup):
|
|||||||
|
|
||||||
self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account)
|
self.assertEqual("Asset Received But Not Billed - _TC", doc.items[0].expense_account)
|
||||||
|
|
||||||
# CWIP: Capital Work In Progress
|
# Capital Work In Progress
|
||||||
def test_cwip_accounting(self):
|
def test_cwip_accounting(self):
|
||||||
pr = make_purchase_receipt(
|
pr = make_purchase_receipt(
|
||||||
item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location"
|
item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location"
|
||||||
@ -567,7 +567,8 @@ class TestAsset(AssetSetup):
|
|||||||
pr.submit()
|
pr.submit()
|
||||||
|
|
||||||
expected_gle = (
|
expected_gle = (
|
||||||
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
|
("_Test Account Shipping Charges - _TC", 0.0, 250.0),
|
||||||
|
("Asset Received But Not Billed - _TC", 0.0, 5000.0),
|
||||||
("CWIP Account - _TC", 5250.0, 0.0),
|
("CWIP Account - _TC", 5250.0, 0.0),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -586,9 +587,8 @@ class TestAsset(AssetSetup):
|
|||||||
expected_gle = (
|
expected_gle = (
|
||||||
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
||||||
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
||||||
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
|
("Asset Received But Not Billed - _TC", 5000.0, 0.0),
|
||||||
("Creditors - _TC", 0.0, 5500.0),
|
("Creditors - _TC", 0.0, 5500.0),
|
||||||
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
pi_gle = frappe.db.sql(
|
pi_gle = frappe.db.sql(
|
||||||
@ -755,7 +755,9 @@ class TestDepreciationMethods(AssetSetup):
|
|||||||
|
|
||||||
self.assertEqual(schedules, expected_schedules)
|
self.assertEqual(schedules, expected_schedules)
|
||||||
|
|
||||||
def test_schedule_for_straight_line_method_with_daily_depreciation(self):
|
def test_schedule_for_straight_line_method_with_daily_prorata_based(
|
||||||
|
self,
|
||||||
|
):
|
||||||
asset = create_asset(
|
asset = create_asset(
|
||||||
calculate_depreciation=1,
|
calculate_depreciation=1,
|
||||||
available_for_use_date="2023-01-01",
|
available_for_use_date="2023-01-01",
|
||||||
@ -764,7 +766,7 @@ class TestDepreciationMethods(AssetSetup):
|
|||||||
depreciation_start_date="2023-01-31",
|
depreciation_start_date="2023-01-31",
|
||||||
total_number_of_depreciations=12,
|
total_number_of_depreciations=12,
|
||||||
frequency_of_depreciation=1,
|
frequency_of_depreciation=1,
|
||||||
daily_depreciation=1,
|
daily_prorata_based=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
expected_schedules = [
|
expected_schedules = [
|
||||||
@ -1744,6 +1746,7 @@ def create_asset(**args):
|
|||||||
"location": args.location or "Test Location",
|
"location": args.location or "Test Location",
|
||||||
"asset_owner": args.asset_owner or "Company",
|
"asset_owner": args.asset_owner or "Company",
|
||||||
"is_existing_asset": args.is_existing_asset or 1,
|
"is_existing_asset": args.is_existing_asset or 1,
|
||||||
|
"is_composite_asset": args.is_composite_asset or 0,
|
||||||
"asset_quantity": args.get("asset_quantity") or 1,
|
"asset_quantity": args.get("asset_quantity") or 1,
|
||||||
"depr_entry_posting_status": args.depr_entry_posting_status or "",
|
"depr_entry_posting_status": args.depr_entry_posting_status or "",
|
||||||
}
|
}
|
||||||
@ -1759,7 +1762,7 @@ def create_asset(**args):
|
|||||||
"total_number_of_depreciations": args.total_number_of_depreciations or 5,
|
"total_number_of_depreciations": args.total_number_of_depreciations or 5,
|
||||||
"expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
|
"expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
|
||||||
"depreciation_start_date": args.depreciation_start_date,
|
"depreciation_start_date": args.depreciation_start_date,
|
||||||
"daily_depreciation": args.daily_depreciation or 0,
|
"daily_prorata_based": args.daily_prorata_based or 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1788,6 +1791,7 @@ def create_asset_category():
|
|||||||
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
||||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
|
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
|
||||||
"depreciation_expense_account": "_Test Depreciations - _TC",
|
"depreciation_expense_account": "_Test Depreciations - _TC",
|
||||||
|
"capital_work_in_progress_account": "CWIP Account - _TC",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
asset_category.append(
|
asset_category.append(
|
||||||
|
@ -75,13 +75,14 @@
|
|||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-01 11:09:52.584482",
|
"modified": "2023-09-29 15:56:17.608643",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Activity",
|
"name": "Asset Activity",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
@ -89,6 +90,7 @@
|
|||||||
"share": 1
|
"share": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
@ -96,6 +98,7 @@
|
|||||||
"share": 1
|
"share": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
|
@ -16,9 +16,15 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
|||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.show_general_ledger();
|
this.show_general_ledger();
|
||||||
|
|
||||||
if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) {
|
if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) {
|
||||||
this.show_stock_ledger();
|
this.show_stock_ledger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.frm.doc.stock_items && !this.frm.doc.stock_items.length && this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
|
||||||
|
this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
|
||||||
|
this.get_target_asset_details();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_queries() {
|
setup_queries() {
|
||||||
@ -35,18 +41,9 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
|||||||
});
|
});
|
||||||
|
|
||||||
me.frm.set_query("target_asset", function() {
|
me.frm.set_query("target_asset", function() {
|
||||||
var filters = {};
|
|
||||||
|
|
||||||
if (me.frm.doc.target_item_code) {
|
|
||||||
filters['item_code'] = me.frm.doc.target_item_code;
|
|
||||||
}
|
|
||||||
|
|
||||||
filters['status'] = ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]];
|
|
||||||
filters['docstatus'] = 1;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: filters
|
filters: {'is_composite_asset': 1, 'docstatus': 0 }
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
me.frm.set_query("asset", "asset_items", function() {
|
me.frm.set_query("asset", "asset_items", function() {
|
||||||
@ -128,6 +125,39 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
|||||||
return this.get_target_item_details();
|
return this.get_target_item_details();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target_asset() {
|
||||||
|
if (this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
|
||||||
|
this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
|
||||||
|
this.get_target_asset_details();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_consumed_stock_items_tagged_to_wip_composite_asset(asset) {
|
||||||
|
var me = this;
|
||||||
|
|
||||||
|
if (asset) {
|
||||||
|
return me.frm.call({
|
||||||
|
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_items_tagged_to_wip_composite_asset",
|
||||||
|
args: {
|
||||||
|
asset: asset,
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
if (!r.exc && r.message) {
|
||||||
|
me.frm.clear_table("stock_items");
|
||||||
|
|
||||||
|
for (let item of r.message) {
|
||||||
|
me.frm.add_child("stock_items", item);
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_field("stock_items");
|
||||||
|
|
||||||
|
me.calculate_totals();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item_code(doc, cdt, cdn) {
|
item_code(doc, cdt, cdn) {
|
||||||
var row = frappe.get_doc(cdt, cdn);
|
var row = frappe.get_doc(cdt, cdn);
|
||||||
if (cdt === "Asset Capitalization Stock Item") {
|
if (cdt === "Asset Capitalization Stock Item") {
|
||||||
@ -242,6 +272,26 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_target_asset_details() {
|
||||||
|
var me = this;
|
||||||
|
|
||||||
|
if (me.frm.doc.target_asset) {
|
||||||
|
return me.frm.call({
|
||||||
|
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details",
|
||||||
|
child: me.frm.doc,
|
||||||
|
args: {
|
||||||
|
asset: me.frm.doc.target_asset,
|
||||||
|
company: me.frm.doc.company,
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
if (!r.exc) {
|
||||||
|
me.frm.refresh_fields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get_consumed_stock_item_details(row) {
|
get_consumed_stock_item_details(row) {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
|
@ -8,24 +8,25 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"title",
|
"title",
|
||||||
|
"company",
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"entry_type",
|
"entry_type",
|
||||||
"target_item_code",
|
|
||||||
"target_asset",
|
|
||||||
"target_item_name",
|
"target_item_name",
|
||||||
"target_is_fixed_asset",
|
"target_is_fixed_asset",
|
||||||
"target_has_batch_no",
|
"target_has_batch_no",
|
||||||
"target_has_serial_no",
|
"target_has_serial_no",
|
||||||
"column_break_9",
|
"column_break_9",
|
||||||
"target_asset_name",
|
"capitalization_method",
|
||||||
|
"target_item_code",
|
||||||
"target_asset_location",
|
"target_asset_location",
|
||||||
|
"target_asset",
|
||||||
|
"target_asset_name",
|
||||||
"target_warehouse",
|
"target_warehouse",
|
||||||
"target_qty",
|
"target_qty",
|
||||||
"target_stock_uom",
|
"target_stock_uom",
|
||||||
"target_batch_no",
|
"target_batch_no",
|
||||||
"target_serial_no",
|
"target_serial_no",
|
||||||
"column_break_5",
|
"column_break_5",
|
||||||
"company",
|
|
||||||
"finance_book",
|
"finance_book",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"posting_time",
|
"posting_time",
|
||||||
@ -57,12 +58,13 @@
|
|||||||
"label": "Title"
|
"label": "Title"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:(doc.target_item_code && !doc.__islocal && doc.capitalization_method !== 'Choose a WIP composite asset') || ((doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization')",
|
||||||
"fieldname": "target_item_code",
|
"fieldname": "target_item_code",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Target Item Code",
|
"label": "Target Item Code",
|
||||||
"options": "Item",
|
"mandatory_depends_on": "eval:(doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset') || doc.entry_type=='Decapitalization'",
|
||||||
"reqd": 1
|
"options": "Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.target_item_code && doc.target_item_name != doc.target_item_code",
|
"depends_on": "eval:doc.target_item_code && doc.target_item_name != doc.target_item_code",
|
||||||
@ -86,16 +88,18 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:(doc.target_asset && !doc.__islocal) || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')",
|
||||||
"fieldname": "target_asset",
|
"fieldname": "target_asset",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Target Asset",
|
"label": "Target Asset",
|
||||||
|
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset'",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Asset",
|
"options": "Asset",
|
||||||
"read_only": 1
|
"read_only_depends_on": "eval:(doc.entry_type=='Decapitalization') || (doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset')"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
"depends_on": "eval:(doc.target_asset_name && !doc.__islocal) || (doc.target_asset && doc.entry_type=='Capitalization' && doc.capitalization_method=='Choose a WIP composite asset')",
|
||||||
"fetch_from": "target_asset.asset_name",
|
"fetch_from": "target_asset.asset_name",
|
||||||
"fieldname": "target_asset_name",
|
"fieldname": "target_asset_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@ -186,12 +190,14 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
|
"depends_on": "eval:doc.entry_type=='Decapitalization'",
|
||||||
"fieldname": "target_qty",
|
"fieldname": "target_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Target Qty",
|
"label": "Target Qty",
|
||||||
"read_only_depends_on": "eval:doc.entry_type=='Capitalization'"
|
"read_only_depends_on": "eval:doc.entry_type=='Capitalization'"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:doc.entry_type=='Decapitalization'",
|
||||||
"fetch_from": "target_item_code.stock_uom",
|
"fetch_from": "target_item_code.stock_uom",
|
||||||
"fieldname": "target_stock_uom",
|
"fieldname": "target_stock_uom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@ -331,18 +337,26 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
"depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'",
|
||||||
"fieldname": "target_asset_location",
|
"fieldname": "target_asset_location",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Target Asset Location",
|
"label": "Target Asset Location",
|
||||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
|
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization' && doc.capitalization_method=='Create a new composite asset'",
|
||||||
"options": "Location"
|
"options": "Location"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||||
|
"fieldname": "capitalization_method",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Capitalization Method",
|
||||||
|
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||||
|
"options": "\nCreate a new composite asset\nChoose a WIP composite asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-22 14:17:07.995120",
|
"modified": "2023-10-03 22:55:59.461456",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Capitalization",
|
"name": "Asset Capitalization",
|
||||||
|
@ -53,6 +53,7 @@ class AssetCapitalization(StockController):
|
|||||||
self.validate_posting_time()
|
self.validate_posting_time()
|
||||||
self.set_missing_values(for_validate=True)
|
self.set_missing_values(for_validate=True)
|
||||||
self.validate_target_item()
|
self.validate_target_item()
|
||||||
|
self.validate_target_asset()
|
||||||
self.validate_consumed_stock_item()
|
self.validate_consumed_stock_item()
|
||||||
self.validate_consumed_asset_item()
|
self.validate_consumed_asset_item()
|
||||||
self.validate_service_item()
|
self.validate_service_item()
|
||||||
@ -67,12 +68,12 @@ class AssetCapitalization(StockController):
|
|||||||
|
|
||||||
def before_submit(self):
|
def before_submit(self):
|
||||||
self.validate_source_mandatory()
|
self.validate_source_mandatory()
|
||||||
if self.entry_type == "Capitalization":
|
|
||||||
self.create_target_asset()
|
self.create_target_asset()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
self.update_target_asset()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.ignore_linked_doctypes = (
|
self.ignore_linked_doctypes = (
|
||||||
@ -94,6 +95,11 @@ class AssetCapitalization(StockController):
|
|||||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||||
self.set(k, v)
|
self.set(k, v)
|
||||||
|
|
||||||
|
target_asset_details = get_target_asset_details(self.target_asset, self.company)
|
||||||
|
for k, v in target_asset_details.items():
|
||||||
|
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||||
|
self.set(k, v)
|
||||||
|
|
||||||
for d in self.stock_items:
|
for d in self.stock_items:
|
||||||
args = self.as_dict()
|
args = self.as_dict()
|
||||||
args.update(d.as_dict())
|
args.update(d.as_dict())
|
||||||
@ -155,6 +161,33 @@ class AssetCapitalization(StockController):
|
|||||||
|
|
||||||
self.validate_item(target_item)
|
self.validate_item(target_item)
|
||||||
|
|
||||||
|
def validate_target_asset(self):
|
||||||
|
if self.target_asset:
|
||||||
|
target_asset = self.get_asset_for_validation(self.target_asset)
|
||||||
|
|
||||||
|
if not target_asset.is_composite_asset:
|
||||||
|
frappe.throw(_("Target Asset {0} needs to be composite asset").format(target_asset.name))
|
||||||
|
|
||||||
|
if target_asset.item_code != self.target_item_code:
|
||||||
|
frappe.throw(
|
||||||
|
_("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code)
|
||||||
|
)
|
||||||
|
|
||||||
|
if target_asset.status in ("Scrapped", "Sold", "Capitalized", "Decapitalized"):
|
||||||
|
frappe.throw(
|
||||||
|
_("Target Asset {0} cannot be {1}").format(target_asset.name, target_asset.status)
|
||||||
|
)
|
||||||
|
|
||||||
|
if target_asset.docstatus == 1:
|
||||||
|
frappe.throw(_("Target Asset {0} cannot be submitted").format(target_asset.name))
|
||||||
|
elif target_asset.docstatus == 2:
|
||||||
|
frappe.throw(_("Target Asset {0} cannot be cancelled").format(target_asset.name))
|
||||||
|
|
||||||
|
if target_asset.company != self.company:
|
||||||
|
frappe.throw(
|
||||||
|
_("Target Asset {0} does not belong to company {1}").format(target_asset.name, self.company)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_consumed_stock_item(self):
|
def validate_consumed_stock_item(self):
|
||||||
for d in self.stock_items:
|
for d in self.stock_items:
|
||||||
if d.item_code:
|
if d.item_code:
|
||||||
@ -179,7 +212,23 @@ class AssetCapitalization(StockController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
asset = self.get_asset_for_validation(d.asset)
|
asset = self.get_asset_for_validation(d.asset)
|
||||||
self.validate_asset(asset)
|
|
||||||
|
if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Consumed Asset {1} cannot be {2}").format(d.idx, asset.name, asset.status)
|
||||||
|
)
|
||||||
|
|
||||||
|
if asset.docstatus == 0:
|
||||||
|
frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be Draft").format(d.idx, asset.name))
|
||||||
|
elif asset.docstatus == 2:
|
||||||
|
frappe.throw(_("Row #{0}: Consumed Asset {1} cannot be cancelled").format(d.idx, asset.name))
|
||||||
|
|
||||||
|
if asset.company != self.company:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Consumed Asset {1} does not belong to company {2}").format(
|
||||||
|
d.idx, asset.name, self.company
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def validate_service_item(self):
|
def validate_service_item(self):
|
||||||
for d in self.service_items:
|
for d in self.service_items:
|
||||||
@ -214,21 +263,12 @@ class AssetCapitalization(StockController):
|
|||||||
|
|
||||||
def get_asset_for_validation(self, asset):
|
def get_asset_for_validation(self, asset):
|
||||||
return frappe.db.get_value(
|
return frappe.db.get_value(
|
||||||
"Asset", asset, ["name", "item_code", "company", "status", "docstatus"], as_dict=1
|
"Asset",
|
||||||
|
asset,
|
||||||
|
["name", "item_code", "company", "status", "docstatus", "is_composite_asset"],
|
||||||
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_asset(self, asset):
|
|
||||||
if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"):
|
|
||||||
frappe.throw(_("Asset {0} is {1}").format(asset.name, asset.status))
|
|
||||||
|
|
||||||
if asset.docstatus == 0:
|
|
||||||
frappe.throw(_("Asset {0} is Draft").format(asset.name))
|
|
||||||
if asset.docstatus == 2:
|
|
||||||
frappe.throw(_("Asset {0} is cancelled").format(asset.name))
|
|
||||||
|
|
||||||
if asset.company != self.company:
|
|
||||||
frappe.throw(_("Asset {0} does not belong to company {1}").format(asset.name, self.company))
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def set_warehouse_details(self):
|
def set_warehouse_details(self):
|
||||||
for d in self.get("stock_items"):
|
for d in self.get("stock_items"):
|
||||||
@ -501,16 +541,25 @@ class AssetCapitalization(StockController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def create_target_asset(self):
|
def create_target_asset(self):
|
||||||
|
if (
|
||||||
|
self.entry_type != "Capitalization"
|
||||||
|
or self.capitalization_method != "Create a new composite asset"
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||||
|
|
||||||
asset_doc = frappe.new_doc("Asset")
|
asset_doc = frappe.new_doc("Asset")
|
||||||
asset_doc.company = self.company
|
asset_doc.company = self.company
|
||||||
asset_doc.item_code = self.target_item_code
|
asset_doc.item_code = self.target_item_code
|
||||||
asset_doc.is_existing_asset = 1
|
asset_doc.is_composite_asset = 1
|
||||||
asset_doc.location = self.target_asset_location
|
asset_doc.location = self.target_asset_location
|
||||||
asset_doc.available_for_use_date = self.posting_date
|
asset_doc.available_for_use_date = self.posting_date
|
||||||
asset_doc.purchase_date = self.posting_date
|
asset_doc.purchase_date = self.posting_date
|
||||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||||
|
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||||
|
asset_doc.capitalized_in = self.name
|
||||||
asset_doc.flags.ignore_validate = True
|
asset_doc.flags.ignore_validate = True
|
||||||
asset_doc.flags.asset_created_via_asset_capitalization = True
|
asset_doc.flags.asset_created_via_asset_capitalization = True
|
||||||
asset_doc.insert()
|
asset_doc.insert()
|
||||||
@ -534,6 +583,28 @@ class AssetCapitalization(StockController):
|
|||||||
).format(get_link_to_form("Asset", asset_doc.name))
|
).format(get_link_to_form("Asset", asset_doc.name))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update_target_asset(self):
|
||||||
|
if (
|
||||||
|
self.entry_type != "Capitalization"
|
||||||
|
or self.capitalization_method != "Choose a WIP composite asset"
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||||
|
|
||||||
|
asset_doc = frappe.get_doc("Asset", self.target_asset)
|
||||||
|
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||||
|
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||||
|
asset_doc.capitalized_in = self.name
|
||||||
|
asset_doc.flags.ignore_validate = True
|
||||||
|
asset_doc.save()
|
||||||
|
|
||||||
|
frappe.msgprint(
|
||||||
|
_(
|
||||||
|
"Asset {0} has been updated. Please set the depreciation details if any and submit it."
|
||||||
|
).format(get_link_to_form("Asset", asset_doc.name))
|
||||||
|
)
|
||||||
|
|
||||||
def restore_consumed_asset_items(self):
|
def restore_consumed_asset_items(self):
|
||||||
for item in self.asset_items:
|
for item in self.asset_items:
|
||||||
asset = frappe.get_doc("Asset", item.asset)
|
asset = frappe.get_doc("Asset", item.asset)
|
||||||
@ -618,6 +689,33 @@ def get_target_item_details(item_code=None, company=None):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_target_asset_details(asset=None, company=None):
|
||||||
|
out = frappe._dict()
|
||||||
|
|
||||||
|
# Get Asset Details
|
||||||
|
asset_details = frappe._dict()
|
||||||
|
if asset:
|
||||||
|
asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1)
|
||||||
|
if not asset_details:
|
||||||
|
frappe.throw(_("Asset {0} does not exist").format(asset))
|
||||||
|
|
||||||
|
# Re-set item code from Asset
|
||||||
|
out.target_item_code = asset_details.item_code
|
||||||
|
|
||||||
|
# Set Asset Details
|
||||||
|
out.asset_name = asset_details.asset_name
|
||||||
|
|
||||||
|
if asset_details.item_code:
|
||||||
|
out.target_fixed_asset_account = get_asset_category_account(
|
||||||
|
"fixed_asset_account", item=asset_details.item_code, company=company
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
out.target_fixed_asset_account = None
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_consumed_stock_item_details(args):
|
def get_consumed_stock_item_details(args):
|
||||||
if isinstance(args, str):
|
if isinstance(args, str):
|
||||||
@ -766,3 +864,26 @@ def get_service_item_details(args):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_items_tagged_to_wip_composite_asset(asset):
|
||||||
|
fields = [
|
||||||
|
"item_code",
|
||||||
|
"item_name",
|
||||||
|
"batch_no",
|
||||||
|
"serial_no",
|
||||||
|
"stock_qty",
|
||||||
|
"stock_uom",
|
||||||
|
"warehouse",
|
||||||
|
"cost_center",
|
||||||
|
"qty",
|
||||||
|
"valuation_rate",
|
||||||
|
"amount",
|
||||||
|
]
|
||||||
|
|
||||||
|
pr_items = frappe.get_all(
|
||||||
|
"Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields
|
||||||
|
)
|
||||||
|
|
||||||
|
return pr_items
|
||||||
|
@ -58,6 +58,7 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
# Create and submit Asset Captitalization
|
# Create and submit Asset Captitalization
|
||||||
asset_capitalization = create_asset_capitalization(
|
asset_capitalization = create_asset_capitalization(
|
||||||
entry_type="Capitalization",
|
entry_type="Capitalization",
|
||||||
|
capitalization_method="Create a new composite asset",
|
||||||
target_item_code="Macbook Pro",
|
target_item_code="Macbook Pro",
|
||||||
target_asset_location="Test Location",
|
target_asset_location="Test Location",
|
||||||
stock_qty=stock_qty,
|
stock_qty=stock_qty,
|
||||||
@ -147,6 +148,7 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
# Create and submit Asset Captitalization
|
# Create and submit Asset Captitalization
|
||||||
asset_capitalization = create_asset_capitalization(
|
asset_capitalization = create_asset_capitalization(
|
||||||
entry_type="Capitalization",
|
entry_type="Capitalization",
|
||||||
|
capitalization_method="Create a new composite asset",
|
||||||
target_item_code="Macbook Pro",
|
target_item_code="Macbook Pro",
|
||||||
target_asset_location="Test Location",
|
target_asset_location="Test Location",
|
||||||
stock_qty=stock_qty,
|
stock_qty=stock_qty,
|
||||||
@ -212,6 +214,77 @@ class TestAssetCapitalization(unittest.TestCase):
|
|||||||
self.assertFalse(get_actual_gle_dict(asset_capitalization.name))
|
self.assertFalse(get_actual_gle_dict(asset_capitalization.name))
|
||||||
self.assertFalse(get_actual_sle_dict(asset_capitalization.name))
|
self.assertFalse(get_actual_sle_dict(asset_capitalization.name))
|
||||||
|
|
||||||
|
def test_capitalization_with_wip_composite_asset(self):
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
set_depreciation_settings_in_company(company=company)
|
||||||
|
|
||||||
|
stock_rate = 1000
|
||||||
|
stock_qty = 2
|
||||||
|
stock_amount = 2000
|
||||||
|
|
||||||
|
total_amount = 2000
|
||||||
|
|
||||||
|
wip_composite_asset = create_asset(
|
||||||
|
asset_name="Asset Capitalization WIP Composite Asset",
|
||||||
|
is_composite_asset=1,
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
company=company,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create and submit Asset Captitalization
|
||||||
|
asset_capitalization = create_asset_capitalization(
|
||||||
|
entry_type="Capitalization",
|
||||||
|
capitalization_method="Choose a WIP composite asset",
|
||||||
|
target_asset=wip_composite_asset.name,
|
||||||
|
target_asset_location="Test Location",
|
||||||
|
stock_qty=stock_qty,
|
||||||
|
stock_rate=stock_rate,
|
||||||
|
service_expense_account="Expenses Included In Asset Valuation - TCP1",
|
||||||
|
company=company,
|
||||||
|
submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test Asset Capitalization values
|
||||||
|
self.assertEqual(asset_capitalization.entry_type, "Capitalization")
|
||||||
|
self.assertEqual(asset_capitalization.capitalization_method, "Choose a WIP composite asset")
|
||||||
|
self.assertEqual(asset_capitalization.target_qty, 1)
|
||||||
|
|
||||||
|
self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate)
|
||||||
|
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
|
||||||
|
self.assertEqual(asset_capitalization.stock_items_total, stock_amount)
|
||||||
|
|
||||||
|
self.assertEqual(asset_capitalization.total_value, total_amount)
|
||||||
|
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
|
||||||
|
|
||||||
|
# Test Target Asset values
|
||||||
|
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
|
||||||
|
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
||||||
|
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
|
||||||
|
|
||||||
|
# Test General Ledger Entries
|
||||||
|
expected_gle = {
|
||||||
|
"_Test Fixed Asset - TCP1": 2000,
|
||||||
|
"_Test Warehouse - TCP1": -2000,
|
||||||
|
}
|
||||||
|
actual_gle = get_actual_gle_dict(asset_capitalization.name)
|
||||||
|
|
||||||
|
self.assertEqual(actual_gle, expected_gle)
|
||||||
|
|
||||||
|
# Test Stock Ledger Entries
|
||||||
|
expected_sle = {
|
||||||
|
("Capitalization Source Stock Item", "_Test Warehouse - TCP1"): {
|
||||||
|
"actual_qty": -stock_qty,
|
||||||
|
"stock_value_difference": -stock_amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual_sle = get_actual_sle_dict(asset_capitalization.name)
|
||||||
|
self.assertEqual(actual_sle, expected_sle)
|
||||||
|
|
||||||
|
# Cancel Asset Capitalization and make test entries and status are reversed
|
||||||
|
asset_capitalization.cancel()
|
||||||
|
self.assertFalse(get_actual_gle_dict(asset_capitalization.name))
|
||||||
|
self.assertFalse(get_actual_sle_dict(asset_capitalization.name))
|
||||||
|
|
||||||
def test_decapitalization_with_depreciation(self):
|
def test_decapitalization_with_depreciation(self):
|
||||||
# Variables
|
# Variables
|
||||||
purchase_date = "2020-01-01"
|
purchase_date = "2020-01-01"
|
||||||
@ -349,6 +422,7 @@ def create_asset_capitalization(**args):
|
|||||||
asset_capitalization.update(
|
asset_capitalization.update(
|
||||||
{
|
{
|
||||||
"entry_type": args.entry_type or "Capitalization",
|
"entry_type": args.entry_type or "Capitalization",
|
||||||
|
"capitalization_method": args.capitalization_method or None,
|
||||||
"company": company,
|
"company": company,
|
||||||
"posting_date": args.posting_date or now.strftime("%Y-%m-%d"),
|
"posting_date": args.posting_date or now.strftime("%Y-%m-%d"),
|
||||||
"posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"),
|
"posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"),
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"depreciation_method",
|
"depreciation_method",
|
||||||
"total_number_of_depreciations",
|
"total_number_of_depreciations",
|
||||||
"rate_of_depreciation",
|
"rate_of_depreciation",
|
||||||
"daily_depreciation",
|
"daily_prorata_based",
|
||||||
"column_break_8",
|
"column_break_8",
|
||||||
"frequency_of_depreciation",
|
"frequency_of_depreciation",
|
||||||
"expected_value_after_useful_life",
|
"expected_value_after_useful_life",
|
||||||
@ -179,9 +179,9 @@
|
|||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
|
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
|
||||||
"fieldname": "daily_depreciation",
|
"fieldname": "daily_prorata_based",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Daily Depreciation",
|
"label": "Depreciate based on daily pro-rata",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
}
|
}
|
||||||
@ -189,7 +189,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-10 22:22:09.722968",
|
"modified": "2023-11-03 21:32:15.021796",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Depreciation Schedule",
|
"name": "Asset Depreciation Schedule",
|
||||||
|
@ -153,7 +153,7 @@ class AssetDepreciationSchedule(Document):
|
|||||||
self.frequency_of_depreciation = row.frequency_of_depreciation
|
self.frequency_of_depreciation = row.frequency_of_depreciation
|
||||||
self.rate_of_depreciation = row.rate_of_depreciation
|
self.rate_of_depreciation = row.rate_of_depreciation
|
||||||
self.expected_value_after_useful_life = row.expected_value_after_useful_life
|
self.expected_value_after_useful_life = row.expected_value_after_useful_life
|
||||||
self.daily_depreciation = row.daily_depreciation
|
self.daily_prorata_based = row.daily_prorata_based
|
||||||
self.status = "Draft"
|
self.status = "Draft"
|
||||||
|
|
||||||
def make_depr_schedule(
|
def make_depr_schedule(
|
||||||
@ -573,7 +573,7 @@ def get_straight_line_or_manual_depr_amount(
|
|||||||
)
|
)
|
||||||
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
|
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
|
||||||
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
|
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
|
||||||
if row.daily_depreciation:
|
if row.daily_prorata_based:
|
||||||
daily_depr_amount = (
|
daily_depr_amount = (
|
||||||
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
||||||
) / date_diff(
|
) / date_diff(
|
||||||
@ -618,7 +618,7 @@ def get_straight_line_or_manual_depr_amount(
|
|||||||
) / number_of_pending_depreciations
|
) / number_of_pending_depreciations
|
||||||
# if the Depreciation Schedule is being prepared for the first time
|
# if the Depreciation Schedule is being prepared for the first time
|
||||||
else:
|
else:
|
||||||
if row.daily_depreciation:
|
if row.daily_prorata_based:
|
||||||
daily_depr_amount = (
|
daily_depr_amount = (
|
||||||
flt(asset.gross_purchase_amount)
|
flt(asset.gross_purchase_amount)
|
||||||
- flt(asset.opening_accumulated_depreciation)
|
- flt(asset.opening_accumulated_depreciation)
|
||||||
|
@ -8,10 +8,11 @@
|
|||||||
"finance_book",
|
"finance_book",
|
||||||
"depreciation_method",
|
"depreciation_method",
|
||||||
"total_number_of_depreciations",
|
"total_number_of_depreciations",
|
||||||
"daily_depreciation",
|
"daily_prorata_based",
|
||||||
"column_break_5",
|
"column_break_5",
|
||||||
"frequency_of_depreciation",
|
"frequency_of_depreciation",
|
||||||
"depreciation_start_date",
|
"depreciation_start_date",
|
||||||
|
"salvage_value_percentage",
|
||||||
"expected_value_after_useful_life",
|
"expected_value_after_useful_life",
|
||||||
"value_after_depreciation",
|
"value_after_depreciation",
|
||||||
"rate_of_depreciation"
|
"rate_of_depreciation"
|
||||||
@ -85,18 +86,23 @@
|
|||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"label": "Rate of Depreciation"
|
"label": "Rate of Depreciation"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "salvage_value_percentage",
|
||||||
|
"fieldtype": "Percent",
|
||||||
|
"label": "Salvage Value Percentage"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
|
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
|
||||||
"fieldname": "daily_depreciation",
|
"fieldname": "daily_prorata_based",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Daily Depreciation"
|
"label": "Depreciate based on daily pro-rata"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-10 22:10:36.576199",
|
"modified": "2023-11-03 21:30:24.266601",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Finance Book",
|
"name": "Asset Finance Book",
|
||||||
|
@ -13,25 +13,22 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
|
|||||||
class TestAssetMaintenance(unittest.TestCase):
|
class TestAssetMaintenance(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
set_depreciation_settings_in_company()
|
set_depreciation_settings_in_company()
|
||||||
create_asset_data()
|
self.pr = make_purchase_receipt(
|
||||||
create_maintenance_team()
|
|
||||||
|
|
||||||
def test_create_asset_maintenance(self):
|
|
||||||
pr = make_purchase_receipt(
|
|
||||||
item_code="Photocopier", qty=1, rate=100000.0, location="Test Location"
|
item_code="Photocopier", qty=1, rate=100000.0, location="Test Location"
|
||||||
)
|
)
|
||||||
|
self.asset_name = frappe.db.get_value("Asset", {"purchase_receipt": self.pr.name}, "name")
|
||||||
|
self.asset_doc = frappe.get_doc("Asset", self.asset_name)
|
||||||
|
|
||||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
|
def test_create_asset_maintenance_with_log(self):
|
||||||
asset_doc = frappe.get_doc("Asset", asset_name)
|
|
||||||
month_end_date = get_last_day(nowdate())
|
month_end_date = get_last_day(nowdate())
|
||||||
|
|
||||||
purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
|
purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
|
||||||
|
|
||||||
asset_doc.available_for_use_date = purchase_date
|
self.asset_doc.available_for_use_date = purchase_date
|
||||||
asset_doc.purchase_date = purchase_date
|
self.asset_doc.purchase_date = purchase_date
|
||||||
|
|
||||||
asset_doc.calculate_depreciation = 1
|
self.asset_doc.calculate_depreciation = 1
|
||||||
asset_doc.append(
|
self.asset_doc.append(
|
||||||
"finance_books",
|
"finance_books",
|
||||||
{
|
{
|
||||||
"expected_value_after_useful_life": 200,
|
"expected_value_after_useful_life": 200,
|
||||||
@ -42,13 +39,12 @@ class TestAssetMaintenance(unittest.TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
asset_doc.save()
|
self.asset_doc.save()
|
||||||
|
|
||||||
if not frappe.db.exists("Asset Maintenance", "Photocopier"):
|
|
||||||
asset_maintenance = frappe.get_doc(
|
asset_maintenance = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Asset Maintenance",
|
"doctype": "Asset Maintenance",
|
||||||
"asset_name": "Photocopier",
|
"asset_name": self.asset_name,
|
||||||
"maintenance_team": "Team Awesome",
|
"maintenance_team": "Team Awesome",
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"asset_maintenance_tasks": get_maintenance_tasks(),
|
"asset_maintenance_tasks": get_maintenance_tasks(),
|
||||||
@ -58,83 +54,27 @@ class TestAssetMaintenance(unittest.TestCase):
|
|||||||
next_due_date = calculate_next_due_date(nowdate(), "Monthly")
|
next_due_date = calculate_next_due_date(nowdate(), "Monthly")
|
||||||
self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date)
|
self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date)
|
||||||
|
|
||||||
def test_create_asset_maintenance_log(self):
|
asset_maintenance_log = frappe.db.get_value(
|
||||||
if not frappe.db.exists("Asset Maintenance Log", "Photocopier"):
|
"Asset Maintenance Log",
|
||||||
asset_maintenance_log = frappe.get_doc(
|
{"asset_maintenance": asset_maintenance.name, "task_name": "Change Oil"},
|
||||||
|
"name",
|
||||||
|
)
|
||||||
|
|
||||||
|
asset_maintenance_log_doc = frappe.get_doc("Asset Maintenance Log", asset_maintenance_log)
|
||||||
|
asset_maintenance_log_doc.update(
|
||||||
{
|
{
|
||||||
"doctype": "Asset Maintenance Log",
|
|
||||||
"asset_maintenance": "Photocopier",
|
|
||||||
"task": "Change Oil",
|
|
||||||
"completion_date": add_days(nowdate(), 2),
|
"completion_date": add_days(nowdate(), 2),
|
||||||
"maintenance_status": "Completed",
|
"maintenance_status": "Completed",
|
||||||
}
|
}
|
||||||
).insert()
|
)
|
||||||
asset_maintenance = frappe.get_doc("Asset Maintenance", "Photocopier")
|
|
||||||
next_due_date = calculate_next_due_date(asset_maintenance_log.completion_date, "Monthly")
|
asset_maintenance_log_doc.save()
|
||||||
|
next_due_date = calculate_next_due_date(asset_maintenance_log_doc.completion_date, "Monthly")
|
||||||
|
|
||||||
|
asset_maintenance.reload()
|
||||||
self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date)
|
self.assertEqual(asset_maintenance.asset_maintenance_tasks[0].next_due_date, next_due_date)
|
||||||
|
|
||||||
|
|
||||||
def create_asset_data():
|
|
||||||
if not frappe.db.exists("Asset Category", "Equipment"):
|
|
||||||
create_asset_category()
|
|
||||||
|
|
||||||
if not frappe.db.exists("Location", "Test Location"):
|
|
||||||
frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()
|
|
||||||
|
|
||||||
if not frappe.db.exists("Item", "Photocopier"):
|
|
||||||
meta = frappe.get_meta("Asset")
|
|
||||||
naming_series = meta.get_field("naming_series").options
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Item",
|
|
||||||
"item_code": "Photocopier",
|
|
||||||
"item_name": "Photocopier",
|
|
||||||
"item_group": "All Item Groups",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"is_fixed_asset": 1,
|
|
||||||
"is_stock_item": 0,
|
|
||||||
"asset_category": "Equipment",
|
|
||||||
"auto_create_assets": 1,
|
|
||||||
"asset_naming_series": naming_series,
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
|
|
||||||
def create_maintenance_team():
|
|
||||||
user_list = ["marcus@abc.com", "thalia@abc.com", "mathias@abc.com"]
|
|
||||||
if not frappe.db.exists("Role", "Technician"):
|
|
||||||
frappe.get_doc({"doctype": "Role", "role_name": "Technician"}).insert()
|
|
||||||
for user in user_list:
|
|
||||||
if not frappe.db.get_value("User", user):
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "User",
|
|
||||||
"email": user,
|
|
||||||
"first_name": user,
|
|
||||||
"new_password": "password",
|
|
||||||
"roles": [{"doctype": "Has Role", "role": "Technician"}],
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
if not frappe.db.exists("Asset Maintenance Team", "Team Awesome"):
|
|
||||||
frappe.get_doc(
|
|
||||||
{
|
|
||||||
"doctype": "Asset Maintenance Team",
|
|
||||||
"maintenance_manager": "marcus@abc.com",
|
|
||||||
"maintenance_team_name": "Team Awesome",
|
|
||||||
"company": "_Test Company",
|
|
||||||
"maintenance_team_members": get_maintenance_team(user_list),
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
|
|
||||||
|
|
||||||
def get_maintenance_team(user_list):
|
|
||||||
return [
|
|
||||||
{"team_member": user, "full_name": user, "maintenance_role": "Technician"}
|
|
||||||
for user in user_list[1:]
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_maintenance_tasks():
|
def get_maintenance_tasks():
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -156,23 +96,6 @@ def get_maintenance_tasks():
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def create_asset_category():
|
|
||||||
asset_category = frappe.new_doc("Asset Category")
|
|
||||||
asset_category.asset_category_name = "Equipment"
|
|
||||||
asset_category.total_number_of_depreciations = 3
|
|
||||||
asset_category.frequency_of_depreciation = 3
|
|
||||||
asset_category.append(
|
|
||||||
"accounts",
|
|
||||||
{
|
|
||||||
"company_name": "_Test Company",
|
|
||||||
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
|
||||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
|
|
||||||
"depreciation_expense_account": "_Test Depreciations - _TC",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
asset_category.insert()
|
|
||||||
|
|
||||||
|
|
||||||
def set_depreciation_settings_in_company():
|
def set_depreciation_settings_in_company():
|
||||||
company = frappe.get_doc("Company", "_Test Company")
|
company = frappe.get_doc("Company", "_Test Company")
|
||||||
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC"
|
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC"
|
||||||
|
68
erpnext/assets/doctype/asset_maintenance/test_records.json
Normal file
68
erpnext/assets/doctype/asset_maintenance/test_records.json
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"doctype": "Asset Category",
|
||||||
|
"asset_category_name": "Equipment",
|
||||||
|
"total_number_of_depreciations": 3,
|
||||||
|
"frequency_of_depreciation": 3,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"company_name": "_Test Company",
|
||||||
|
"fixed_asset_account": "_Test Fixed Asset - _TC",
|
||||||
|
"accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
|
||||||
|
"depreciation_expense_account": "_Test Depreciations - _TC"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Location",
|
||||||
|
"location_name": "Test Location"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Role",
|
||||||
|
"role_name": "Technician"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "User",
|
||||||
|
"email": "marcus@abc.com",
|
||||||
|
"first_name": "marcus@abc.com",
|
||||||
|
"new_password": "password",
|
||||||
|
"roles": [{"doctype": "Has Role", "role": "Technician"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "User",
|
||||||
|
"email": "thalia@abc.com",
|
||||||
|
"first_name": "thalia@abc.com",
|
||||||
|
"new_password": "password",
|
||||||
|
"roles": [{"doctype": "Has Role", "role": "Technician"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "User",
|
||||||
|
"email": "mathias@abc.com",
|
||||||
|
"first_name": "mathias@abc.com",
|
||||||
|
"new_password": "password",
|
||||||
|
"roles": [{"doctype": "Has Role", "role": "Technician"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Asset Maintenance Team",
|
||||||
|
"maintenance_manager": "marcus@abc.com",
|
||||||
|
"maintenance_team_name": "Team Awesome",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"maintenance_team_members": [
|
||||||
|
{"team_member": "marcus@abc.com", "full_name": "marcus@abc.com", "maintenance_role": "Technician"},
|
||||||
|
{"team_member": "thalia@abc.com", "full_name": "thalia@abc.com", "maintenance_role": "Technician"},
|
||||||
|
{"team_member": "mathias@abc.com", "full_name": "mathias@abc.com", "maintenance_role": "Technician"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"doctype": "Item",
|
||||||
|
"item_code": "Photocopier",
|
||||||
|
"item_name": "Photocopier",
|
||||||
|
"item_group": "All Item Groups",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"is_fixed_asset": 1,
|
||||||
|
"is_stock_item": 0,
|
||||||
|
"asset_category": "Equipment",
|
||||||
|
"auto_create_assets": 1,
|
||||||
|
"asset_naming_series": "ABC.###"
|
||||||
|
}
|
||||||
|
]
|
@ -177,7 +177,7 @@ class AssetRepair(AccountsController):
|
|||||||
"item_code": stock_item.item_code,
|
"item_code": stock_item.item_code,
|
||||||
"qty": stock_item.consumed_quantity,
|
"qty": stock_item.consumed_quantity,
|
||||||
"basic_rate": stock_item.valuation_rate,
|
"basic_rate": stock_item.valuation_rate,
|
||||||
"serial_no": stock_item.serial_and_batch_bundle,
|
"serial_and_batch_bundle": stock_item.serial_and_batch_bundle,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"project": self.project,
|
"project": self.project,
|
||||||
},
|
},
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"transaction_settings_section",
|
"transaction_settings_section",
|
||||||
"po_required",
|
"po_required",
|
||||||
"pr_required",
|
"pr_required",
|
||||||
"over_order_allowance",
|
"blanket_order_allowance",
|
||||||
"column_break_12",
|
"column_break_12",
|
||||||
"maintain_same_rate",
|
"maintain_same_rate",
|
||||||
"set_landed_cost_based_on_purchase_invoice_rate",
|
"set_landed_cost_based_on_purchase_invoice_rate",
|
||||||
@ -24,6 +24,7 @@
|
|||||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||||
"disable_last_purchase_rate",
|
"disable_last_purchase_rate",
|
||||||
"show_pay_button",
|
"show_pay_button",
|
||||||
|
"use_transaction_date_exchange_rate",
|
||||||
"subcontract",
|
"subcontract",
|
||||||
"backflush_raw_materials_of_subcontract_based_on",
|
"backflush_raw_materials_of_subcontract_based_on",
|
||||||
"column_break_11",
|
"column_break_11",
|
||||||
@ -160,10 +161,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
|
"description": "While making Purchase Invoice from Purchase Order, use Exchange Rate on Invoice's transaction date rather than inheriting it from Purchase Order. Only applies for Purchase Invoice.",
|
||||||
"fieldname": "over_order_allowance",
|
"fieldname": "use_transaction_date_exchange_rate",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Use Transaction Date Exchange Rate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Percentage you are allowed to order beyond the Blanket Order quantity.",
|
||||||
|
"fieldname": "blanket_order_allowance",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Over Order Allowance (%)"
|
"label": "Blanket Order Allowance (%)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@ -171,7 +179,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-02 17:02:14.404622",
|
"modified": "2023-10-25 14:03:32.520418",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
@ -477,6 +477,7 @@
|
|||||||
"depends_on": "eval:doc.is_subcontracted",
|
"depends_on": "eval:doc.is_subcontracted",
|
||||||
"fieldname": "supplier_warehouse",
|
"fieldname": "supplier_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
"label": "Supplier Warehouse",
|
"label": "Supplier Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
},
|
},
|
||||||
@ -1274,7 +1275,7 @@
|
|||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-13 16:21:07.361700",
|
"modified": "2023-10-01 20:58:07.851037",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
@ -556,6 +556,9 @@ def make_purchase_receipt(source_name, target_doc=None):
|
|||||||
"bom": "bom",
|
"bom": "bom",
|
||||||
"material_request": "material_request",
|
"material_request": "material_request",
|
||||||
"material_request_item": "material_request_item",
|
"material_request_item": "material_request_item",
|
||||||
|
"sales_order": "sales_order",
|
||||||
|
"sales_order_item": "sales_order_item",
|
||||||
|
"wip_composite_asset": "wip_composite_asset",
|
||||||
},
|
},
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
||||||
@ -632,6 +635,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
|||||||
"field_map": {
|
"field_map": {
|
||||||
"name": "po_detail",
|
"name": "po_detail",
|
||||||
"parent": "purchase_order",
|
"parent": "purchase_order",
|
||||||
|
"wip_composite_asset": "wip_composite_asset",
|
||||||
},
|
},
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),
|
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),
|
||||||
|
@ -86,6 +86,8 @@
|
|||||||
"billed_amt",
|
"billed_amt",
|
||||||
"accounting_details",
|
"accounting_details",
|
||||||
"expense_account",
|
"expense_account",
|
||||||
|
"column_break_fyqr",
|
||||||
|
"wip_composite_asset",
|
||||||
"manufacture_details",
|
"manufacture_details",
|
||||||
"manufacturer",
|
"manufacturer",
|
||||||
"manufacturer_part_no",
|
"manufacturer_part_no",
|
||||||
@ -180,7 +182,6 @@
|
|||||||
"oldfieldname": "description",
|
"oldfieldname": "description",
|
||||||
"oldfieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"print_width": "300px",
|
"print_width": "300px",
|
||||||
"reqd": 1,
|
|
||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -897,13 +898,23 @@
|
|||||||
"fieldname": "apply_tds",
|
"fieldname": "apply_tds",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Apply TDS"
|
"label": "Apply TDS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "wip_composite_asset",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "WIP Composite Asset",
|
||||||
|
"options": "Asset"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_fyqr",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-13 16:22:40.825092",
|
"modified": "2023-10-27 15:50:42.655573",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
|
@ -84,7 +84,6 @@
|
|||||||
"oldfieldname": "description",
|
"oldfieldname": "description",
|
||||||
"oldfieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"print_width": "300px",
|
"print_width": "300px",
|
||||||
"reqd": 1,
|
|
||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user