From def622c13e5afab174a3dd2c2e976472b33e1ceb Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Tue, 21 Jun 2022 13:58:00 +0530 Subject: [PATCH 01/64] fix: set default_bom for item (cherry picked from commit dc2830da4d35045377425a8e84fd8a19eb48134f) # Conflicts: # erpnext/manufacturing/doctype/bom/test_bom.py --- erpnext/manufacturing/doctype/bom/bom.py | 1 + erpnext/manufacturing/doctype/bom/test_bom.py | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index e9602312df9..dd6c802aab4 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -444,6 +444,7 @@ class BOM(WebsiteGenerator): and self.is_active ): frappe.db.set(self, "is_default", 1) + frappe.db.set_value("Item", self.item, "default_bom", self.name) else: frappe.db.set(self, "is_default", 0) item = frappe.get_doc("Item", self.item) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 5ea5d1b8a45..cee2972ba3c 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -501,6 +501,7 @@ class TestBOM(FrappeTestCase): bom.submit() self.assertEqual(bom.items[0].rate, 42) +<<<<<<< HEAD def test_exclude_exploded_items_from_bom(self): bom_no = get_default_bom() new_bom = frappe.copy_doc(frappe.get_doc("BOM", bom_no)) @@ -518,6 +519,43 @@ class TestBOM(FrappeTestCase): self.assertFalse(row.bom_no) new_bom.delete() +======= + def test_set_default_bom_for_item_having_single_bom(self): + from erpnext.stock.doctype.item.test_item import make_item + + fg_item = make_item(properties={"is_stock_item": 1}) + bom_item = make_item(properties={"is_stock_item": 1}) + + # Step 1: Create BOM + bom = frappe.new_doc("BOM") + bom.item = fg_item.item_code + bom.quantity = 1 + bom.append( + "items", + { + "item_code": bom_item.item_code, + "qty": 1, + "uom": bom_item.stock_uom, + "stock_uom": bom_item.stock_uom, + "rate": 100.0, + }, + ) + bom.save() + bom.submit() + self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name) + + # Step 2: Uncheck is_active field + bom.is_active = 0 + bom.save() + bom.reload() + self.assertIsNone(frappe.get_value("Item", fg_item.item_code, "default_bom")) + + # Step 3: Check is_active field + bom.is_active = 1 + bom.save() + bom.reload() + self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name) +>>>>>>> dc2830da4d (fix: set default_bom for item) def get_default_bom(item_code="_Test FG Item 2"): From b17850ac3ea92f524785911d69a0770e13ec8ae8 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 11 Jul 2022 17:09:30 +0530 Subject: [PATCH 02/64] chore: conflicts --- erpnext/manufacturing/doctype/bom/test_bom.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index cee2972ba3c..edb97937f02 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -501,7 +501,6 @@ class TestBOM(FrappeTestCase): bom.submit() self.assertEqual(bom.items[0].rate, 42) -<<<<<<< HEAD def test_exclude_exploded_items_from_bom(self): bom_no = get_default_bom() new_bom = frappe.copy_doc(frappe.get_doc("BOM", bom_no)) @@ -519,7 +518,7 @@ class TestBOM(FrappeTestCase): self.assertFalse(row.bom_no) new_bom.delete() -======= + def test_set_default_bom_for_item_having_single_bom(self): from erpnext.stock.doctype.item.test_item import make_item @@ -555,7 +554,6 @@ class TestBOM(FrappeTestCase): bom.save() bom.reload() self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name) ->>>>>>> dc2830da4d (fix: set default_bom for item) def get_default_bom(item_code="_Test FG Item 2"): From 9ac9c46dab24126381d868fb71c0215a133005db Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Jul 2022 13:43:01 +0530 Subject: [PATCH 03/64] fix: Allow multi currency invoice against single party account (cherry picked from commit c83fbd5c50da1e2da9ff5ce6f33a171a11e896a0) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json --- .../accounts_settings/accounts_settings.json | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index baab628b210..f35ded657eb 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -19,6 +19,7 @@ "book_asset_depreciation_entry_automatically", "unlink_advance_payment_on_cancelation_of_order", "enable_common_party_accounting", +<<<<<<< HEAD "post_change_gl_entries", "enable_discount_accounting", "tax_settings_section", @@ -30,6 +31,11 @@ "frozen_accounts_modifier", "column_break_4", "credit_controller", +======= + "allow_multi_currency_invoices_against_single_party_account", + "report_setting_section", + "use_custom_cash_flow", +>>>>>>> c83fbd5c50 (fix: Allow multi currency invoice against single party account) "deferred_accounting_settings_section", "book_deferred_entries_based_on", "column_break_18", @@ -272,10 +278,37 @@ "label": "Enable Discount Accounting" }, { +<<<<<<< HEAD "default": "0", "fieldname": "enable_common_party_accounting", "fieldtype": "Check", "label": "Enable Common Party Accounting" +======= + "fieldname": "invoicing_features_section", + "fieldtype": "Section Break", + "label": "Invoicing Features" + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "fieldname": "pos_tab", + "fieldtype": "Tab Break", + "label": "POS" + }, + { + "fieldname": "report_setting_section", + "fieldtype": "Section Break", + "label": "Report Setting" + }, + { + "default": "0", + "description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency", + "fieldname": "allow_multi_currency_invoices_against_single_party_account", + "fieldtype": "Check", + "label": "Allow multi-currency invoices against single party account " +>>>>>>> c83fbd5c50 (fix: Allow multi currency invoice against single party account) } ], "icon": "icon-cog", @@ -283,7 +316,11 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2021-10-11 17:42:36.427699", +======= + "modified": "2022-07-11 13:37:50.605141", +>>>>>>> c83fbd5c50 (fix: Allow multi currency invoice against single party account) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 66c5290deec595d30ad093d87afa0a9fb7f88e22 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Jul 2022 13:46:59 +0530 Subject: [PATCH 04/64] chore: Ignore validation (cherry picked from commit 3cf609fab18c532bb8c404a9024b802d82d7d7fe) --- erpnext/controllers/accounts_controller.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index dce6d7525d0..b049996056f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1470,8 +1470,15 @@ class AccountsController(TransactionBase): self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to") ) party_account_currency = get_account_currency(party_account) + allow_multi_currency_invoices_against_single_party_account = frappe.get_single_value( + "Accounts Settings", "allow_multi_currency_invoices_against_single_party_account" + ) - if not party_gle_currency and (party_account_currency != self.currency): + if ( + not party_gle_currency + and (party_account_currency != self.currency) + and not allow_multi_currency_invoices_against_single_party_account + ): frappe.throw( _("Party Account {0} currency ({1}) and document currency ({2}) should be same").format( frappe.bold(party_account), party_account_currency, self.currency From 1c686c732ea85546648c054669a41d6affe6a62e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Jul 2022 19:25:18 +0530 Subject: [PATCH 05/64] chore: fix query (cherry picked from commit e04e67c6bf067128d5a5a46e3d6e6fb93531c8b8) --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b049996056f..4eee74b2cbd 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1470,7 +1470,7 @@ class AccountsController(TransactionBase): self.get("debit_to") if self.doctype == "Sales Invoice" else self.get("credit_to") ) party_account_currency = get_account_currency(party_account) - allow_multi_currency_invoices_against_single_party_account = frappe.get_single_value( + allow_multi_currency_invoices_against_single_party_account = frappe.db.get_singles_value( "Accounts Settings", "allow_multi_currency_invoices_against_single_party_account" ) From ab214bcdfcb03dc900cadc69b0ba0c729d14eaad Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 28 Apr 2022 14:54:42 +0530 Subject: [PATCH 06/64] update loan interest accrual types (cherry picked from commit 900c878e03be5d66a6ff5929826f4e44bb471f26) --- .../loan_interest_accrual/loan_interest_accrual.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index 30e2328442a..c05deeb9381 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -159,7 +159,7 @@ "fieldname": "accrual_type", "fieldtype": "Select", "label": "Accrual Type", - "options": "Regular\nRepayment\nDisbursement" + "options": "Regular\nRepayment\nDisbursement\nInterest Credit\nInterest Debit\nRefund" }, { "fieldname": "penalty_amount", @@ -185,10 +185,11 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-19 18:26:38.871889", + "modified": "2022-04-28 14:46:38.131536", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -225,5 +226,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From d24cb01a9d9ec2eb0ed2d1aab43c67984326c043 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 24 Jun 2022 16:07:16 +0530 Subject: [PATCH 07/64] Add more loan interest accrual types (cherry picked from commit a81da2ea85990de18a8d954d76bdb590320c52dd) --- .../doctype/loan_interest_accrual/loan_interest_accrual.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index c05deeb9381..5fbe26c3d3c 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -158,8 +158,9 @@ { "fieldname": "accrual_type", "fieldtype": "Select", + "in_filter": 1, "label": "Accrual Type", - "options": "Regular\nRepayment\nDisbursement\nInterest Credit\nInterest Debit\nRefund" + "options": "Regular\nRepayment\nDisbursement\nCredit Adjustment\nDebit Adjustment\nRefund" }, { "fieldname": "penalty_amount", @@ -185,7 +186,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-04-28 14:46:38.131536", + "modified": "2022-06-24 15:45:07.014679", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", From d9723a12c43e620d7845cd927eafb515f122f55d Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 24 Jun 2022 17:47:15 +0530 Subject: [PATCH 08/64] Add refund amount to loan (cherry picked from commit 88cd780ca17ae1312fa3b4d7a183201cb479e467) --- erpnext/loan_management/doctype/loan/loan.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index 61bbf712610..c0156c5ac70 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -48,6 +48,7 @@ "total_payment", "total_principal_paid", "written_off_amount", + "refund_amount", "column_break_19", "total_interest_payable", "total_amount_paid", @@ -379,12 +380,20 @@ "fieldtype": "Link", "label": "Cost Center", "options": "Cost Center" + }, + { + "fieldname": "refund_amount", + "fieldtype": "Currency", + "label": "Refund amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-03-10 11:50:31.957360", + "modified": "2022-06-24 16:17:57.018272", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", From 0d94653c5b58d0097cee3b57c926becc4322f02a Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Mon, 27 Jun 2022 15:21:39 +0530 Subject: [PATCH 09/64] Add Loan Refund doctype (cherry picked from commit e1682965c55b638ca1e0ca6182e59b1b607bdf6c) --- .../doctype/loan_refund/__init__.py | 0 .../doctype/loan_refund/loan_refund.js | 8 + .../doctype/loan_refund/loan_refund.json | 176 ++++++++++++++++++ .../doctype/loan_refund/loan_refund.py | 95 ++++++++++ .../doctype/loan_refund/test_loan_refund.py | 9 + 5 files changed, 288 insertions(+) create mode 100644 erpnext/loan_management/doctype/loan_refund/__init__.py create mode 100644 erpnext/loan_management/doctype/loan_refund/loan_refund.js create mode 100644 erpnext/loan_management/doctype/loan_refund/loan_refund.json create mode 100644 erpnext/loan_management/doctype/loan_refund/loan_refund.py create mode 100644 erpnext/loan_management/doctype/loan_refund/test_loan_refund.py diff --git a/erpnext/loan_management/doctype/loan_refund/__init__.py b/erpnext/loan_management/doctype/loan_refund/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.js b/erpnext/loan_management/doctype/loan_refund/loan_refund.js new file mode 100644 index 00000000000..f108bf7a281 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.js @@ -0,0 +1,8 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Loan Refund', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.json b/erpnext/loan_management/doctype/loan_refund/loan_refund.json new file mode 100644 index 00000000000..f78e55e9f94 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.json @@ -0,0 +1,176 @@ +{ + "actions": [], + "autoname": "LM-RF-.#####", + "creation": "2022-06-24 15:51:03.165498", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "loan", + "applicant_type", + "applicant", + "column_break_3", + "company", + "posting_date", + "accounting_dimensions_section", + "cost_center", + "section_break_9", + "refund_account", + "column_break_11", + "refund_amount", + "reference_number", + "amended_from" + ], + "fields": [ + { + "fieldname": "loan", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Loan", + "options": "Loan", + "reqd": 1 + }, + { + "fetch_from": "loan.applicant_type", + "fieldname": "applicant_type", + "fieldtype": "Select", + "label": "Applicant Type", + "options": "Employee\nMember\nCustomer", + "read_only": 1 + }, + { + "fetch_from": "loan.applicant", + "fieldname": "applicant", + "fieldtype": "Dynamic Link", + "label": "Applicant ", + "options": "applicant_type", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "loan.company", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Refund Details" + }, + { + "fieldname": "refund_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Refund Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "refund_amount", + "fieldtype": "Currency", + "label": "Refund Amount", + "options": "Company:company:default_currency", + "reqd": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Refund", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Refund", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "reference_number", + "fieldtype": "Data", + "label": "Reference Number" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2022-06-24 16:13:48.793486", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Refund", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Loan Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py new file mode 100644 index 00000000000..2a7f47871f6 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -0,0 +1,95 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class LoanRefund(Document): + """ + Add refund if total repayment is more than that is owed. + """ + def validate(self): + self.set_missing_values() + self.validate_refund_amount() + + def set_missing_values(self): + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.company) + + def validate_refund_amount(self): + precision = cint(frappe.db.get_default("currency_precision")) or 2 + total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value( + "Loan", + self.loan, + ["total_payment", "total_principal_paid", "total_interest_payable", "written_off_amount"], + ) + + excess_amount = flt( + flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount), + precision, + ) + + if self.refund_amount > excess_amount: + frappe.throw(_( + "Refund amount cannot be greater than excess amount {}".format( + excess_amount + ))) + + def on_submit(self): + self.update_outstanding_amount() + self.make_gl_entries() + + def on_cancel(self): + self.update_outstanding_amount(cancel=1) + self.ignore_linked_doctypes = ["GL Entry"] + self.make_gl_entries(cancel=1) + + def update_outstanding_amount(self, cancel=0): + refund_amount = frappe.db.get_value("Loan", self.loan, "refund_amount") + + if cancel: + refund_amount -= self.refund_amount + else: + refund_amount += self.refund_amount + + frappe.db.set_value("Loan", self.loan, "refund_amount", refund_amount) + + def make_gl_entries(self, cancel=0): + gl_entries = [] + loan_details = frappe.get_doc("Loan", self.loan) + + gl_entries.append( + self.get_gl_dict( + { + "account": self.refund_account, + "against": loan_details.loan_account, + "credit": self.refund_amount, + "credit_in_account_currency": self.refund_amount, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _("Against Loan:") + self.loan, + "cost_center": self.cost_center, + "posting_date": getdate(self.posting_date), + } + ) + ) + + gl_entries.append( + self.get_gl_dict( + { + "account": loan_details.loan_account, + "party_type": loan_details.applicant_type, + "party": loan_details.applicant, + "against": self.refund_account, + "debit": self.refund_amount, + "debit_in_account_currency": self.refund_amount, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _("Against Loan:") + self.loan, + "cost_center": self.cost_center, + "posting_date": getdate(self.posting_date), + } + ) + ) + + make_gl_entries(gl_entries, cancel=cancel, merge_entries=False) diff --git a/erpnext/loan_management/doctype/loan_refund/test_loan_refund.py b/erpnext/loan_management/doctype/loan_refund/test_loan_refund.py new file mode 100644 index 00000000000..de2f9e13727 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_refund/test_loan_refund.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLoanRefund(FrappeTestCase): + pass From a2125e694d6b8668855fd4c05e3466e1a23f4757 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Tue, 28 Jun 2022 17:51:11 +0530 Subject: [PATCH 10/64] Add Loan Balance Adjustment doctype (cherry picked from commit 2e8f0565147c2e176c8ab43fbc79686b238e4d23) --- .../loan_balance_adjustment/__init__.py | 0 .../loan_balance_adjustment.js | 8 + .../loan_balance_adjustment.json | 190 ++++++++++++++++++ .../loan_balance_adjustment.py | 132 ++++++++++++ .../test_loan_balance_adjustment.py | 9 + 5 files changed, 339 insertions(+) create mode 100644 erpnext/loan_management/doctype/loan_balance_adjustment/__init__.py create mode 100644 erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.js create mode 100644 erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json create mode 100644 erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py create mode 100644 erpnext/loan_management/doctype/loan_balance_adjustment/test_loan_balance_adjustment.py diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/__init__.py b/erpnext/loan_management/doctype/loan_balance_adjustment/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.js b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.js new file mode 100644 index 00000000000..8aec63ad75f --- /dev/null +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.js @@ -0,0 +1,8 @@ +// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Loan Balance Adjustment', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json new file mode 100644 index 00000000000..5fb25defcef --- /dev/null +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json @@ -0,0 +1,190 @@ +{ + "actions": [], + "autoname": "LM-ADJ-.#####", + "creation": "2022-06-28 14:48:47.736269", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "loan", + "applicant_type", + "applicant", + "column_break_3", + "company", + "posting_date", + "accounting_dimensions_section", + "cost_center", + "section_break_9", + "adjustment_account", + "column_break_11", + "adjustment_type", + "amount", + "reference_number", + "remarks", + "amended_from" + ], + "fields": [ + { + "fieldname": "loan", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Loan", + "options": "Loan", + "reqd": 1 + }, + { + "fetch_from": "loan.applicant_type", + "fieldname": "applicant_type", + "fieldtype": "Select", + "label": "Applicant Type", + "options": "Employee\nMember\nCustomer", + "read_only": 1 + }, + { + "fetch_from": "loan.applicant", + "fieldname": "applicant", + "fieldtype": "Dynamic Link", + "label": "Applicant ", + "options": "applicant_type", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "loan.company", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Adjustment Details" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "reference_number", + "fieldtype": "Data", + "label": "Reference Number" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Balance Adjustment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Balance Adjustment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "adjustment_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Adjustment Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "options": "Company:company:default_currency", + "reqd": 1 + }, + { + "fieldname": "adjustment_type", + "fieldtype": "Select", + "label": "Adjustment Type", + "options": "Credit Adjustment\nDebit Adjustment", + "reqd": 1 + }, + { + "fieldname": "remarks", + "fieldtype": "Data", + "label": "Remarks" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2022-06-28 14:54:52.792592", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Balance Adjustment", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Loan Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py new file mode 100644 index 00000000000..8f98ce64d8e --- /dev/null +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -0,0 +1,132 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class LoanBalanceAdjustment(Document): + """ + Add credit/debit adjustments to loan ledger. + """ + def validate(self): + self.set_missing_values() + + def on_submit(self): + self.set_status_and_amounts() + self.make_gl_entries() + + def on_cancel(self): + self.set_status_and_amounts(cancel=1) + self.make_gl_entries(cancel=1) + self.ignore_linked_doctypes = ["GL Entry"] + + def set_missing_values(self): + if not self.posting_date: + self.posting_date = nowdate() + + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.company) + + if not self.posting_date: + self.posting_date = self.posting_date or nowdate() + + def set_status_and_amounts(self, cancel=0): + loan_details = frappe.get_all( + "Loan", + fields=[ + "loan_amount", + "disbursed_amount", + "total_payment", + "total_principal_paid", + "total_interest_payable", + "status", + "is_term_loan", + "is_secured_loan", + ], + filters={"name": self.against_loan}, + )[0] + + if cancel: + disbursed_amount = self.get_values_on_cancel(loan_details) + else: + disbursed_amount = self.get_values_on_submit(loan_details) + + frappe.db.set_value( + "Loan", + self.against_loan, + { + "disbursed_amount": disbursed_amount, + }, + ) + + def get_values_on_cancel(self, loan_details): + if self.adjustment_type == "Credit Adjustment": + disbursed_amount = loan_details.disbursed_amount - self.amount + elif self.adjustment_type == "Debit Adjustment": + disbursed_amount = loan_details.disbursed_amount + self.amount + + return disbursed_amount + + def get_values_on_submit(self, loan_details): + if self.adjustment_type == "Credit": + disbursed_amount = loan_details.disbursed_amount + self.amount + elif self.adjustment_type == "Debit": + disbursed_amount = loan_details.disbursed_amount - self.amount + + total_payment = loan_details.total_payment + + if loan_details.status in ("Disbursed", "Partially Disbursed") and not loan_details.is_term_loan: + process_loan_interest_accrual_for_demand_loans( + posting_date=add_days(self.posting_date, -1), + loan=self.against_loan, + accrual_type=self.adjustment_type, + ) + + return disbursed_amount + + def make_gl_entries(self, cancel=0, adv_adj=0): + gle_map = [] + + loan_entry = { + "account": self.loan_account, + "against": self.adjustment_account, + "against_voucher_type": "Loan", + "against_voucher": self.against_loan, + "remarks": _("{} against loan:".format(self.adjustment_type)) \ + + self.against_loan, + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": self.posting_date, + } + company_entry = { + "account": self.adjustment_account, + "against": self.loan_account, + "against_voucher_type": "Loan", + "against_voucher": self.against_loan, + "remarks": _("{} against loan:".format(self.adjustment_type)) \ + + self.against_loan, + "cost_center": self.cost_center, + "posting_date": self.posting_date, + } + if self.adjustment_type == "Credit Adjustment": + loan_entry["credit"] = self.amount + loan_entry["credit_in_account_currency"] = self.amount + + company_entry["debit"] = self.amount + company_entry["debit_in_account_currency"] = self.amount + + elif self.adjustment_type == "Debit Adjustment": + loan_entry["debit"] = self.amount + loan_entry["debit_in_account_currency"] = self.amount + + company_entry["credit"] = self.amount + company_entry["credit_in_account_currency"] = self.amount + + + gle_map.append(self.get_gl_dict(loan_entry)) + + gle_map.append(self.get_gl_dict(company_entry)) + + if gle_map: + make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/test_loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/test_loan_balance_adjustment.py new file mode 100644 index 00000000000..7658d7b215d --- /dev/null +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/test_loan_balance_adjustment.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLoanBalanceAdjustment(FrappeTestCase): + pass From d5ce780e8704b6e7bc1067c1beae257d1c811892 Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Wed, 29 Jun 2022 14:16:23 +0530 Subject: [PATCH 11/64] feat: add adjustment amount to loan - fix: bugs in loan balance adjustment (cherry picked from commit 5c0a25012c602ed0d47136468e3b0bee11ddf5dd) --- .../loan_management/doctype/loan/loan.json | 13 +- .../loan_balance_adjustment.json | 11 +- .../loan_balance_adjustment.py | 258 +++++++++--------- .../doctype/loan_repayment/loan_repayment.py | 2 + .../process_loan_interest_accrual.json | 6 +- 5 files changed, 160 insertions(+), 130 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index c0156c5ac70..f8a3b2c5d46 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -49,6 +49,7 @@ "total_principal_paid", "written_off_amount", "refund_amount", + "adjustment_amount", "column_break_19", "total_interest_payable", "total_amount_paid", @@ -388,12 +389,20 @@ "no_copy": 1, "options": "Company:company:default_currency", "read_only": 1 - } + }, + { + "fieldname": "adjustment_amount", + "fieldtype": "Currency", + "label": "Adjustment amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-24 16:17:57.018272", + "modified": "2022-06-29 13:17:57.018272", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json index 5fb25defcef..35be555cb72 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json @@ -115,12 +115,13 @@ "read_only": 1 }, { + "fetch_from": "loan.payment_account", "fieldname": "adjustment_account", "fieldtype": "Link", "in_list_view": 1, "label": "Adjustment Account", "options": "Account", - "reqd": 1 + "read_only": 1 }, { "fieldname": "amount", @@ -129,6 +130,14 @@ "options": "Company:company:default_currency", "reqd": 1 }, + { + "fetch_from": "loan.loan_account", + "fieldname": "loan_account", + "fieldtype": "Link", + "label": "Loan Account", + "options": "Account", + "read_only": 1 + }, { "fieldname": "adjustment_type", "fieldtype": "Select", diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index 8f98ce64d8e..4b57060ce8e 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -1,132 +1,140 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe -from frappe.model.document import Document - -class LoanBalanceAdjustment(Document): - """ - Add credit/debit adjustments to loan ledger. - """ - def validate(self): - self.set_missing_values() - - def on_submit(self): - self.set_status_and_amounts() - self.make_gl_entries() - - def on_cancel(self): - self.set_status_and_amounts(cancel=1) - self.make_gl_entries(cancel=1) - self.ignore_linked_doctypes = ["GL Entry"] - - def set_missing_values(self): - if not self.posting_date: - self.posting_date = nowdate() - - if not self.cost_center: - self.cost_center = erpnext.get_default_cost_center(self.company) - - if not self.posting_date: - self.posting_date = self.posting_date or nowdate() - - def set_status_and_amounts(self, cancel=0): - loan_details = frappe.get_all( - "Loan", - fields=[ - "loan_amount", - "disbursed_amount", - "total_payment", - "total_principal_paid", - "total_interest_payable", - "status", - "is_term_loan", - "is_secured_loan", - ], - filters={"name": self.against_loan}, - )[0] - - if cancel: - disbursed_amount = self.get_values_on_cancel(loan_details) - else: - disbursed_amount = self.get_values_on_submit(loan_details) - - frappe.db.set_value( - "Loan", - self.against_loan, - { - "disbursed_amount": disbursed_amount, - }, - ) - - def get_values_on_cancel(self, loan_details): - if self.adjustment_type == "Credit Adjustment": - disbursed_amount = loan_details.disbursed_amount - self.amount - elif self.adjustment_type == "Debit Adjustment": - disbursed_amount = loan_details.disbursed_amount + self.amount - - return disbursed_amount - - def get_values_on_submit(self, loan_details): - if self.adjustment_type == "Credit": - disbursed_amount = loan_details.disbursed_amount + self.amount - elif self.adjustment_type == "Debit": - disbursed_amount = loan_details.disbursed_amount - self.amount - - total_payment = loan_details.total_payment - - if loan_details.status in ("Disbursed", "Partially Disbursed") and not loan_details.is_term_loan: - process_loan_interest_accrual_for_demand_loans( - posting_date=add_days(self.posting_date, -1), - loan=self.against_loan, - accrual_type=self.adjustment_type, - ) - - return disbursed_amount - - def make_gl_entries(self, cancel=0, adv_adj=0): - gle_map = [] - - loan_entry = { - "account": self.loan_account, - "against": self.adjustment_account, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": _("{} against loan:".format(self.adjustment_type)) \ - + self.against_loan, - "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, - "posting_date": self.posting_date, - } - company_entry = { - "account": self.adjustment_account, - "against": self.loan_account, - "against_voucher_type": "Loan", - "against_voucher": self.against_loan, - "remarks": _("{} against loan:".format(self.adjustment_type)) \ - + self.against_loan, - "cost_center": self.cost_center, - "posting_date": self.posting_date, - } - if self.adjustment_type == "Credit Adjustment": - loan_entry["credit"] = self.amount - loan_entry["credit_in_account_currency"] = self.amount - - company_entry["debit"] = self.amount - company_entry["debit_in_account_currency"] = self.amount - - elif self.adjustment_type == "Debit Adjustment": - loan_entry["debit"] = self.amount - loan_entry["debit_in_account_currency"] = self.amount - - company_entry["credit"] = self.amount - company_entry["credit_in_account_currency"] = self.amount +import frappe +import erpnext +from frappe import _ +from frappe.utils import nowdate, add_days +from erpnext.controllers.accounts_controller import AccountsController +from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( + process_loan_interest_accrual_for_demand_loans, +) +from erpnext.accounts.general_ledger import make_gl_entries - gle_map.append(self.get_gl_dict(loan_entry)) +class LoanBalanceAdjustment(AccountsController): + """ + Add credit/debit adjustments to loan ledger. + """ - gle_map.append(self.get_gl_dict(company_entry)) + def validate(self): + if self.amount == 0: + frappe.throw(_("Amount cannot be zero")) + if self.amount < 0: + frappe.throw(_("Amount cannot be negative")) + self.set_missing_values() - if gle_map: - make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj) + def on_submit(self): + self.set_status_and_amounts() + self.make_gl_entries() + + def on_cancel(self): + self.set_status_and_amounts(cancel=1) + self.make_gl_entries(cancel=1) + self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] + + def set_missing_values(self): + if not self.posting_date: + self.posting_date = nowdate() + + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.company) + + def set_status_and_amounts(self, cancel=0): + loan_details = frappe.get_all( + "Loan", + fields=[ + "loan_amount", + "adjustment_amount", + "total_payment", + "total_principal_paid", + "total_interest_payable", + "status", + "is_term_loan", + "is_secured_loan", + ], + filters={"name": self.loan}, + )[0] + + if cancel: + adjustment_amount = self.get_values_on_cancel(loan_details) + else: + adjustment_amount = self.get_values_on_submit(loan_details) + + frappe.db.set_value( + "Loan", + self.loan, + { + "adjustment_amount": adjustment_amount, + }, + ) + + def get_values_on_cancel(self, loan_details): + if self.adjustment_type == "Credit Adjustment": + adjustment_amount = loan_details.adjustment_amount - self.amount + elif self.adjustment_type == "Debit Adjustment": + adjustment_amount = loan_details.adjustment_amount + self.amount + + return adjustment_amount + + def get_values_on_submit(self, loan_details): + if self.adjustment_type == "Credit Adjustment": + adjustment_amount = loan_details.adjustment_amount + self.amount + elif self.adjustment_type == "Debit Adjustment": + adjustment_amount = loan_details.adjustment_amount - self.amount + + if ( + loan_details.status in ("Disbursed", "Partially Disbursed") + and not loan_details.is_term_loan + ): + process_loan_interest_accrual_for_demand_loans( + posting_date=add_days(self.posting_date, -1), + loan=self.loan, + accrual_type=self.adjustment_type, + ) + + return adjustment_amount + + def make_gl_entries(self, cancel=0, adv_adj=0): + gle_map = [] + + loan_entry = { + "account": self.loan_account, + "against": self.adjustment_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _("{} against loan:".format(self.adjustment_type)) + self.loan, + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": self.posting_date, + } + company_entry = { + "account": self.adjustment_account, + "against": self.loan_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _("{} against loan:".format(self.adjustment_type)) + self.loan, + "cost_center": self.cost_center, + "posting_date": self.posting_date, + } + if self.adjustment_type == "Credit Adjustment": + loan_entry["credit"] = self.amount + loan_entry["credit_in_account_currency"] = self.amount + + company_entry["debit"] = self.amount + company_entry["debit_in_account_currency"] = self.amount + + elif self.adjustment_type == "Debit Adjustment": + loan_entry["debit"] = self.amount + loan_entry["debit_in_account_currency"] = self.amount + + company_entry["credit"] = self.amount + company_entry["credit_in_account_currency"] = self.amount + + gle_map.append(self.get_gl_dict(loan_entry)) + + gle_map.append(self.get_gl_dict(company_entry)) + + if gle_map: + make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index d3840bfb2e2..9991c8b0350 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -624,6 +624,7 @@ def get_pending_principal_amount(loan): if loan.status in ("Disbursed", "Closed") or loan.disbursed_amount >= loan.loan_amount: pending_principal_amount = ( flt(loan.total_payment) + + flt(loan.adjustment_amount) - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) @@ -631,6 +632,7 @@ def get_pending_principal_amount(loan): else: pending_principal_amount = ( flt(loan.disbursed_amount) + + flt(loan.adjustment_amount) - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) diff --git a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json index 828df2e35f7..7fc4736216d 100644 --- a/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/process_loan_interest_accrual/process_loan_interest_accrual.json @@ -54,17 +54,18 @@ "fieldtype": "Select", "hidden": 1, "label": "Accrual Type", - "options": "Regular\nRepayment\nDisbursement", + "options": "Regular\nRepayment\nDisbursement\nCredit Adjustment\nDebit Adjustment\nRefund", "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-11-06 13:28:51.478909", + "modified": "2022-06-29 11:19:33.203088", "modified_by": "Administrator", "module": "Loan Management", "name": "Process Loan Interest Accrual", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -98,5 +99,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From c3e6d7ba298415d2f0d5dc91f6fcdd1da16d6810 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 11:29:17 +0530 Subject: [PATCH 12/64] Seperate credit and debit adjust amount fields in Loan (cherry picked from commit d6f632a7701894e6e09f3ee10ce8dccb22e848c5) --- .../loan_management/doctype/loan/loan.json | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index f8a3b2c5d46..5f02ff57caa 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -49,11 +49,12 @@ "total_principal_paid", "written_off_amount", "refund_amount", - "adjustment_amount", "column_break_19", "total_interest_payable", "total_amount_paid", - "amended_from" + "amended_from", + "credit_adjustment_amount", + "debit_adjustment_amount" ], "fields": [ { @@ -391,18 +392,22 @@ "read_only": 1 }, { - "fieldname": "adjustment_amount", - "fieldtype": "Currency", - "label": "Adjustment amount", - "no_copy": 1, - "options": "Company:company:default_currency", - "read_only": 1 - } + "fieldname": "credit_adjustment_amount", + "fieldtype": "Currency", + "label": "Credit Adjustment Amount", + "options": "Company:company:default_currency" + }, + { + "fieldname": "debit_adjustment_amount", + "fieldtype": "Currency", + "label": "Debit Adjustment Amount", + "options": "Company:company:default_currency" + } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-29 13:17:57.018272", + "modified": "2022-06-30 11:27:54.281113", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", From de69fee6aac9a1fc67a0a40710bcb48877be98e9 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 11:42:01 +0530 Subject: [PATCH 13/64] Use new adjustment amount fields (cherry picked from commit 7d468a8778a32abf14f5373f51380f96cf8ce24e) --- .../loan_balance_adjustment.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index 4b57060ce8e..69ab6468be4 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -61,27 +61,28 @@ class LoanBalanceAdjustment(AccountsController): else: adjustment_amount = self.get_values_on_submit(loan_details) + if self.adjustment_type == "Credit Adjustment": + adj_field = "credit_adjustment_amount" + elif self.adjustment_type == "Debit Adjustment": + adj_field = "debit_adjustment_amount" + frappe.db.set_value( - "Loan", - self.loan, - { - "adjustment_amount": adjustment_amount, - }, + "Loan", self.loan, {adj_field: adjustment_amount} ) def get_values_on_cancel(self, loan_details): if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.adjustment_amount - self.amount + adjustment_amount = loan_details.credit_adjustment_amount - self.amount elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.adjustment_amount + self.amount + adjustment_amount = loan_details.debit_adjustment_amount - self.amount return adjustment_amount def get_values_on_submit(self, loan_details): if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.adjustment_amount + self.amount + adjustment_amount = loan_details.credit_adjustment_amount + self.amount elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.adjustment_amount - self.amount + adjustment_amount = loan_details.debit_adjustment_amount + self.amount if ( loan_details.status in ("Disbursed", "Partially Disbursed") @@ -98,8 +99,10 @@ class LoanBalanceAdjustment(AccountsController): def make_gl_entries(self, cancel=0, adv_adj=0): gle_map = [] + loan_account = frappe.db.get_value("Loan", self.loan, "loan_account") + loan_entry = { - "account": self.loan_account, + "account": loan_account, "against": self.adjustment_account, "against_voucher_type": "Loan", "against_voucher": self.loan, @@ -111,7 +114,7 @@ class LoanBalanceAdjustment(AccountsController): } company_entry = { "account": self.adjustment_account, - "against": self.loan_account, + "against": loan_account, "against_voucher_type": "Loan", "against_voucher": self.loan, "remarks": _("{} against loan:".format(self.adjustment_type)) + self.loan, From 548aac5b115d09e65adb148b40ffd11601c8e4bc Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 11:42:33 +0530 Subject: [PATCH 14/64] Remove loan account field from doctype (cherry picked from commit 6febcd529b088aaa86991b11b9941b537ac67e9d) --- .../loan_balance_adjustment.json | 384 +++++++++--------- 1 file changed, 187 insertions(+), 197 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json index 35be555cb72..cfbde0f6ce8 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json @@ -1,199 +1,189 @@ { - "actions": [], - "autoname": "LM-ADJ-.#####", - "creation": "2022-06-28 14:48:47.736269", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "loan", - "applicant_type", - "applicant", - "column_break_3", - "company", - "posting_date", - "accounting_dimensions_section", - "cost_center", - "section_break_9", - "adjustment_account", - "column_break_11", - "adjustment_type", - "amount", - "reference_number", - "remarks", - "amended_from" - ], - "fields": [ - { - "fieldname": "loan", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Loan", - "options": "Loan", - "reqd": 1 - }, - { - "fetch_from": "loan.applicant_type", - "fieldname": "applicant_type", - "fieldtype": "Select", - "label": "Applicant Type", - "options": "Employee\nMember\nCustomer", - "read_only": 1 - }, - { - "fetch_from": "loan.applicant", - "fieldname": "applicant", - "fieldtype": "Dynamic Link", - "label": "Applicant ", - "options": "applicant_type", - "read_only": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fetch_from": "loan.company", - "fieldname": "company", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Company", - "options": "Company", - "read_only": 1, - "reqd": 1 - }, - { - "default": "Today", - "fieldname": "posting_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Posting Date", - "reqd": 1 - }, - { - "collapsible": 1, - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" - }, - { - "fieldname": "cost_center", - "fieldtype": "Link", - "label": "Cost Center", - "options": "Cost Center" - }, - { - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "label": "Adjustment Details" - }, - { - "fieldname": "column_break_11", - "fieldtype": "Column Break" - }, - { - "fieldname": "reference_number", - "fieldtype": "Data", - "label": "Reference Number" - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Loan Balance Adjustment", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Loan Balance Adjustment", - "print_hide": 1, - "read_only": 1 - }, - { - "fetch_from": "loan.payment_account", - "fieldname": "adjustment_account", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Adjustment Account", - "options": "Account", - "read_only": 1 - }, - { - "fieldname": "amount", - "fieldtype": "Currency", - "label": "Amount", - "options": "Company:company:default_currency", - "reqd": 1 - }, - { - "fetch_from": "loan.loan_account", - "fieldname": "loan_account", - "fieldtype": "Link", - "label": "Loan Account", - "options": "Account", - "read_only": 1 - }, - { - "fieldname": "adjustment_type", - "fieldtype": "Select", - "label": "Adjustment Type", - "options": "Credit Adjustment\nDebit Adjustment", - "reqd": 1 - }, - { - "fieldname": "remarks", - "fieldtype": "Data", - "label": "Remarks" - } - ], - "index_web_pages_for_search": 1, - "is_submittable": 1, - "links": [], - "modified": "2022-06-28 14:54:52.792592", - "modified_by": "Administrator", - "module": "Loan Management", - "name": "Loan Balance Adjustment", - "naming_rule": "Expression (old style)", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Loan Manager", - "share": 1, - "submit": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 + "actions": [], + "autoname": "LM-ADJ-.#####", + "creation": "2022-06-28 14:48:47.736269", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "loan", + "applicant_type", + "applicant", + "column_break_3", + "company", + "posting_date", + "accounting_dimensions_section", + "cost_center", + "section_break_9", + "adjustment_account", + "column_break_11", + "adjustment_type", + "amount", + "reference_number", + "remarks", + "amended_from" + ], + "fields": [ + { + "fieldname": "loan", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Loan", + "options": "Loan", + "reqd": 1 + }, + { + "fetch_from": "loan.applicant_type", + "fieldname": "applicant_type", + "fieldtype": "Select", + "label": "Applicant Type", + "options": "Employee\nMember\nCustomer", + "read_only": 1 + }, + { + "fetch_from": "loan.applicant", + "fieldname": "applicant", + "fieldtype": "Dynamic Link", + "label": "Applicant ", + "options": "applicant_type", + "read_only": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "loan.company", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "read_only": 1, + "reqd": 1 + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break", + "label": "Adjustment Details" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "reference_number", + "fieldtype": "Data", + "label": "Reference Number" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Balance Adjustment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Loan Balance Adjustment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "adjustment_account", + "fieldtype": "Link", + "label": "Adjustment Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "options": "Company:company:default_currency", + "reqd": 1 + }, + { + "fieldname": "adjustment_type", + "fieldtype": "Select", + "label": "Adjustment Type", + "options": "Credit\nDebit", + "reqd": 1 + }, + { + "fieldname": "remarks", + "fieldtype": "Data", + "label": "Remarks" + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2022-06-30 11:38:16.631994", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Balance Adjustment", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Loan Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 } \ No newline at end of file From 8181d61293440b26846ecdb89310ed7c2f64a005 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 11:59:38 +0530 Subject: [PATCH 15/64] Update list view for Accrual and Shortfall (cherry picked from commit 7d6e4898c48a28405d78c58f41f7f99b316989be) --- .../loan_interest_accrual.json | 9 ++++++- .../loan_security_shortfall.json | 25 ++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index 5fbe26c3d3c..08dc98c8304 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -35,12 +35,15 @@ { "fieldname": "loan", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Loan", "options": "Loan" }, { "fieldname": "posting_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Posting Date" }, { @@ -75,6 +78,8 @@ "fetch_from": "loan.applicant", "fieldname": "applicant", "fieldtype": "Dynamic Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Applicant", "options": "applicant_type" }, @@ -159,6 +164,8 @@ "fieldname": "accrual_type", "fieldtype": "Select", "in_filter": 1, + "in_list_view": 1, + "in_standard_filter": 1, "label": "Accrual Type", "options": "Regular\nRepayment\nDisbursement\nCredit Adjustment\nDebit Adjustment\nRefund" }, @@ -186,7 +193,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-24 15:45:07.014679", + "modified": "2022-06-30 11:51:31.911794", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json index 99b5c72b2db..d4007cb62dd 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json @@ -7,6 +7,8 @@ "engine": "InnoDB", "field_order": [ "loan", + "applicant_type", + "applicant", "status", "column_break_3", "shortfall_time", @@ -23,6 +25,8 @@ { "fieldname": "loan", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Loan ", "options": "Loan", "read_only": 1 @@ -91,17 +95,35 @@ { "fieldname": "shortfall_percentage", "fieldtype": "Percent", + "in_list_view": 1, "label": "Shortfall Percentage", "read_only": 1 + }, + { + "fetch_from": "loan.applicant_type", + "fieldname": "applicant_type", + "fieldtype": "Select", + "label": "Applicant Type", + "options": "Employee\nMember\nCustomer" + }, + { + "fetch_from": "loan.applicant", + "fieldname": "applicant", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Applicant", + "options": "applicant_type" } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-04-01 08:13:43.263772", + "modified": "2022-06-30 11:57:09.378089", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Shortfall", + "naming_rule": "Expression (old style)", "owner": "Administrator", "permissions": [ { @@ -132,5 +154,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From d81e7e242186c125b752af8bcb1739460fcde9b8 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 12:04:45 +0530 Subject: [PATCH 16/64] Add NPA checkbox in Loan (cherry picked from commit 27a8e16b28e5876b4dc965039addf626956f5bbe) --- erpnext/loan_management/doctype/loan/loan.json | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index 5f02ff57caa..0e8feba2f12 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -49,12 +49,13 @@ "total_principal_paid", "written_off_amount", "refund_amount", + "debit_adjustment_amount", + "credit_adjustment_amount", + "is_npa", "column_break_19", "total_interest_payable", "total_amount_paid", - "amended_from", - "credit_adjustment_amount", - "debit_adjustment_amount" + "amended_from" ], "fields": [ { @@ -402,12 +403,19 @@ "fieldtype": "Currency", "label": "Debit Adjustment Amount", "options": "Company:company:default_currency" + }, + { + "default": "0", + "description": "Mark Loan as a Nonperforming asset", + "fieldname": "is_npa", + "fieldtype": "Check", + "label": "Is NPA" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-30 11:27:54.281113", + "modified": "2022-06-30 12:04:13.728880", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", From a36e8a83d8802681caffe2ca853b2df8ab29322b Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 13:50:40 +0530 Subject: [PATCH 17/64] Add reference number to repayment remarks (cherry picked from commit 74dbf8c5d93ce389813f88e43d01b5d70b899148) # Conflicts: # erpnext/loan_management/doctype/loan_repayment/loan_repayment.py --- .../doctype/loan_repayment/loan_repayment.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 9991c8b0350..f2c5467cc22 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -387,15 +387,27 @@ class LoanRepayment(AccountsController): def make_gl_entries(self, cancel=0, adv_adj=0): gle_map = [] - if self.shortfall_amount and self.amount_paid > self.shortfall_amount: +<<<<<<< HEAD remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format( +======= + remarks = "Shortfall repayment of {0}.
Repayment against loan {1}".format( +>>>>>>> 74dbf8c5d9 (Add reference number to repayment remarks) self.shortfall_amount, self.against_loan ) elif self.shortfall_amount: - remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount) + remarks = "Shortfall repayment of {0} against loan {1}".format( + self.shortfall_amount, self.against_loan + ) else: +<<<<<<< HEAD remarks = _("Repayment against Loan: ") + self.against_loan +======= + remarks = "Repayment against loan " + self.against_loan + + if self.reference_number: + remarks += "with reference no. {}".format(self.reference_number) +>>>>>>> 74dbf8c5d9 (Add reference number to repayment remarks) if self.repay_from_salary: payment_account = self.payroll_payable_account @@ -446,7 +458,7 @@ class LoanRepayment(AccountsController): "debit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": remarks, + "remarks": _(remarks), "cost_center": self.cost_center, "posting_date": getdate(self.posting_date), } @@ -464,7 +476,7 @@ class LoanRepayment(AccountsController): "credit_in_account_currency": self.amount_paid, "against_voucher_type": "Loan", "against_voucher": self.against_loan, - "remarks": remarks, + "remarks": _(remarks), "cost_center": self.cost_center, "posting_date": getdate(self.posting_date), } From ff5ee2bbaca72b52949b9b48dd32a40ea89fcce8 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 13:58:13 +0530 Subject: [PATCH 18/64] Add ref no to balance adjustment remarks (cherry picked from commit a1a51ce1a6b6ab2415078eb94d5b19b66b8e16ed) --- .../loan_balance_adjustment/loan_balance_adjustment.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index 69ab6468be4..a582cfecc48 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -100,13 +100,18 @@ class LoanBalanceAdjustment(AccountsController): gle_map = [] loan_account = frappe.db.get_value("Loan", self.loan, "loan_account") + remarks = "{} against loan {}".format( + self.adjustment_type.capitalize(), self.loan + ) + if self.reference_number: + remarks += "with reference no. {}".format(self.reference_number) loan_entry = { "account": loan_account, "against": self.adjustment_account, "against_voucher_type": "Loan", "against_voucher": self.loan, - "remarks": _("{} against loan:".format(self.adjustment_type)) + self.loan, + "remarks": _(remarks), "cost_center": self.cost_center, "party_type": self.applicant_type, "party": self.applicant, @@ -117,7 +122,7 @@ class LoanBalanceAdjustment(AccountsController): "against": loan_account, "against_voucher_type": "Loan", "against_voucher": self.loan, - "remarks": _("{} against loan:".format(self.adjustment_type)) + self.loan, + "remarks": _(remarks), "cost_center": self.cost_center, "posting_date": self.posting_date, } From 0856e14c1345e84f0836e51d04fb424c1aa4daf8 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 30 Jun 2022 14:04:19 +0530 Subject: [PATCH 19/64] Use adjustment amounts in pending principal amnt (cherry picked from commit 1b5b2138ee95d376d77b07ea2f4c06946971a13a) --- .../doctype/loan_repayment/loan_repayment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index f2c5467cc22..88baa260e55 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -636,7 +636,8 @@ def get_pending_principal_amount(loan): if loan.status in ("Disbursed", "Closed") or loan.disbursed_amount >= loan.loan_amount: pending_principal_amount = ( flt(loan.total_payment) - + flt(loan.adjustment_amount) + + flt(loan.debit_adjustment_amount) + - flt(loan.credit_adjustment_amount) - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) @@ -644,7 +645,8 @@ def get_pending_principal_amount(loan): else: pending_principal_amount = ( flt(loan.disbursed_amount) - + flt(loan.adjustment_amount) + + flt(loan.debit_adjustment_amount) + - flt(loan.credit_adjustment_amount) - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) From b995338d0c9f112b1dd84644893c8613902c6f59 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 7 Jul 2022 17:20:21 +0530 Subject: [PATCH 20/64] fix lint (cherry picked from commit 8434ec09c39b08652d7f6463bd77111e9b3a974a) --- .../loan_balance_adjustment.py | 219 +++++++++--------- 1 file changed, 109 insertions(+), 110 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index a582cfecc48..92e9b6acde4 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -7,142 +7,141 @@ from frappe import _ from frappe.utils import nowdate, add_days from erpnext.controllers.accounts_controller import AccountsController from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( - process_loan_interest_accrual_for_demand_loans, + process_loan_interest_accrual_for_demand_loans, ) from erpnext.accounts.general_ledger import make_gl_entries class LoanBalanceAdjustment(AccountsController): - """ - Add credit/debit adjustments to loan ledger. - """ + """ + Add credit/debit adjustments to loan ledger. + """ - def validate(self): - if self.amount == 0: - frappe.throw(_("Amount cannot be zero")) - if self.amount < 0: - frappe.throw(_("Amount cannot be negative")) - self.set_missing_values() + def validate(self): + if self.amount == 0: + frappe.throw(_("Amount cannot be zero")) + if self.amount < 0: + frappe.throw(_("Amount cannot be negative")) + self.set_missing_values() - def on_submit(self): - self.set_status_and_amounts() - self.make_gl_entries() + def on_submit(self): + self.set_status_and_amounts() + self.make_gl_entries() - def on_cancel(self): - self.set_status_and_amounts(cancel=1) - self.make_gl_entries(cancel=1) - self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] + def on_cancel(self): + self.set_status_and_amounts(cancel=1) + self.make_gl_entries(cancel=1) + self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] - def set_missing_values(self): - if not self.posting_date: - self.posting_date = nowdate() + def set_missing_values(self): + if not self.posting_date: + self.posting_date = nowdate() - if not self.cost_center: - self.cost_center = erpnext.get_default_cost_center(self.company) + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.company) - def set_status_and_amounts(self, cancel=0): - loan_details = frappe.get_all( - "Loan", - fields=[ - "loan_amount", - "adjustment_amount", - "total_payment", - "total_principal_paid", - "total_interest_payable", - "status", - "is_term_loan", - "is_secured_loan", - ], - filters={"name": self.loan}, - )[0] + def set_status_and_amounts(self, cancel=0): + loan_details = frappe.get_all( + "Loan", + fields=[ + "loan_amount", + "adjustment_amount", + "total_payment", + "total_principal_paid", + "total_interest_payable", + "status", + "is_term_loan", + "is_secured_loan", + ], + filters={"name": self.loan}, + )[0] - if cancel: - adjustment_amount = self.get_values_on_cancel(loan_details) - else: - adjustment_amount = self.get_values_on_submit(loan_details) + if cancel: + adjustment_amount = self.get_values_on_cancel(loan_details) + else: + adjustment_amount = self.get_values_on_submit(loan_details) - if self.adjustment_type == "Credit Adjustment": - adj_field = "credit_adjustment_amount" - elif self.adjustment_type == "Debit Adjustment": - adj_field = "debit_adjustment_amount" + if self.adjustment_type == "Credit Adjustment": + adj_field = "credit_adjustment_amount" + elif self.adjustment_type == "Debit Adjustment": + adj_field = "debit_adjustment_amount" - frappe.db.set_value( - "Loan", self.loan, {adj_field: adjustment_amount} - ) + frappe.db.set_value( + "Loan", self.loan, {adj_field: adjustment_amount} + ) - def get_values_on_cancel(self, loan_details): - if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.credit_adjustment_amount - self.amount - elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.debit_adjustment_amount - self.amount + def get_values_on_cancel(self, loan_details): + if self.adjustment_type == "Credit Adjustment": + adjustment_amount = loan_details.credit_adjustment_amount - self.amount + elif self.adjustment_type == "Debit Adjustment": + adjustment_amount = loan_details.debit_adjustment_amount - self.amount - return adjustment_amount + return adjustment_amount - def get_values_on_submit(self, loan_details): - if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.credit_adjustment_amount + self.amount - elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.debit_adjustment_amount + self.amount + def get_values_on_submit(self, loan_details): + if self.adjustment_type == "Credit Adjustment": + adjustment_amount = loan_details.credit_adjustment_amount + self.amount + elif self.adjustment_type == "Debit Adjustment": + adjustment_amount = loan_details.debit_adjustment_amount + self.amount - if ( - loan_details.status in ("Disbursed", "Partially Disbursed") - and not loan_details.is_term_loan - ): - process_loan_interest_accrual_for_demand_loans( - posting_date=add_days(self.posting_date, -1), - loan=self.loan, - accrual_type=self.adjustment_type, - ) + if ( + loan_details.status in ("Disbursed", "Partially Disbursed") + and not loan_details.is_term_loan + ): + process_loan_interest_accrual_for_demand_loans( + posting_date=add_days(self.posting_date, -1), + loan=self.loan, + accrual_type=self.adjustment_type, + ) - return adjustment_amount + return adjustment_amount - def make_gl_entries(self, cancel=0, adv_adj=0): - gle_map = [] - - loan_account = frappe.db.get_value("Loan", self.loan, "loan_account") - remarks = "{} against loan {}".format( - self.adjustment_type.capitalize(), self.loan - ) + def make_gl_entries(self, cancel=0, adv_adj=0): + gle_map = [] + loan_account = frappe.db.get_value("Loan", self.loan, "loan_account") + remarks = "{} against loan {}".format( + self.adjustment_type.capitalize(), self.loan + ) if self.reference_number: remarks += "with reference no. {}".format(self.reference_number) - loan_entry = { - "account": loan_account, - "against": self.adjustment_account, - "against_voucher_type": "Loan", - "against_voucher": self.loan, - "remarks": _(remarks), - "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, - "posting_date": self.posting_date, - } - company_entry = { - "account": self.adjustment_account, - "against": loan_account, - "against_voucher_type": "Loan", - "against_voucher": self.loan, - "remarks": _(remarks), - "cost_center": self.cost_center, - "posting_date": self.posting_date, - } - if self.adjustment_type == "Credit Adjustment": - loan_entry["credit"] = self.amount - loan_entry["credit_in_account_currency"] = self.amount + loan_entry = { + "account": loan_account, + "against": self.adjustment_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _(remarks), + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": self.posting_date, + } + company_entry = { + "account": self.adjustment_account, + "against": loan_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _(remarks), + "cost_center": self.cost_center, + "posting_date": self.posting_date, + } + if self.adjustment_type == "Credit Adjustment": + loan_entry["credit"] = self.amount + loan_entry["credit_in_account_currency"] = self.amount - company_entry["debit"] = self.amount - company_entry["debit_in_account_currency"] = self.amount + company_entry["debit"] = self.amount + company_entry["debit_in_account_currency"] = self.amount - elif self.adjustment_type == "Debit Adjustment": - loan_entry["debit"] = self.amount - loan_entry["debit_in_account_currency"] = self.amount + elif self.adjustment_type == "Debit Adjustment": + loan_entry["debit"] = self.amount + loan_entry["debit_in_account_currency"] = self.amount - company_entry["credit"] = self.amount - company_entry["credit_in_account_currency"] = self.amount + company_entry["credit"] = self.amount + company_entry["credit_in_account_currency"] = self.amount - gle_map.append(self.get_gl_dict(loan_entry)) + gle_map.append(self.get_gl_dict(loan_entry)) - gle_map.append(self.get_gl_dict(company_entry)) + gle_map.append(self.get_gl_dict(company_entry)) - if gle_map: - make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) + if gle_map: + make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) From 86af1ef6edace33de8cd9cee9136abdb4b8b4b9a Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Thu, 7 Jul 2022 17:40:50 +0530 Subject: [PATCH 21/64] fix indent and imports (cherry picked from commit 0ed6382ab6e3250801489fcb2c87e281c97143cb) --- .../loan_balance_adjustment.py | 130 +++++++++--------- .../doctype/loan_refund/loan_refund.py | 14 +- 2 files changed, 71 insertions(+), 73 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index 92e9b6acde4..e714545a31c 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -2,14 +2,15 @@ # For license information, please see license.txt import frappe -import erpnext from frappe import _ -from frappe.utils import nowdate, add_days +from frappe.utils import add_days, nowdate + +import erpnext +from erpnext.accounts.general_ledger import make_gl_entries from erpnext.controllers.accounts_controller import AccountsController from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( - process_loan_interest_accrual_for_demand_loans, + process_loan_interest_accrual_for_demand_loans, ) -from erpnext.accounts.general_ledger import make_gl_entries class LoanBalanceAdjustment(AccountsController): @@ -19,9 +20,9 @@ class LoanBalanceAdjustment(AccountsController): def validate(self): if self.amount == 0: - frappe.throw(_("Amount cannot be zero")) + frappe.throw(_("Amount cannot be zero")) if self.amount < 0: - frappe.throw(_("Amount cannot be negative")) + frappe.throw(_("Amount cannot be negative")) self.set_missing_values() def on_submit(self): @@ -35,113 +36,106 @@ class LoanBalanceAdjustment(AccountsController): def set_missing_values(self): if not self.posting_date: - self.posting_date = nowdate() + self.posting_date = nowdate() if not self.cost_center: - self.cost_center = erpnext.get_default_cost_center(self.company) + self.cost_center = erpnext.get_default_cost_center(self.company) def set_status_and_amounts(self, cancel=0): loan_details = frappe.get_all( - "Loan", - fields=[ - "loan_amount", - "adjustment_amount", - "total_payment", - "total_principal_paid", - "total_interest_payable", - "status", - "is_term_loan", - "is_secured_loan", - ], - filters={"name": self.loan}, + "Loan", + fields=[ + "loan_amount", + "adjustment_amount", + "total_payment", + "total_principal_paid", + "total_interest_payable", + "status", + "is_term_loan", + "is_secured_loan", + ], + filters={"name": self.loan}, )[0] if cancel: - adjustment_amount = self.get_values_on_cancel(loan_details) + adjustment_amount = self.get_values_on_cancel(loan_details) else: - adjustment_amount = self.get_values_on_submit(loan_details) + adjustment_amount = self.get_values_on_submit(loan_details) if self.adjustment_type == "Credit Adjustment": - adj_field = "credit_adjustment_amount" + adj_field = "credit_adjustment_amount" elif self.adjustment_type == "Debit Adjustment": - adj_field = "debit_adjustment_amount" + adj_field = "debit_adjustment_amount" - frappe.db.set_value( - "Loan", self.loan, {adj_field: adjustment_amount} - ) + frappe.db.set_value("Loan", self.loan, {adj_field: adjustment_amount}) def get_values_on_cancel(self, loan_details): if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.credit_adjustment_amount - self.amount + adjustment_amount = loan_details.credit_adjustment_amount - self.amount elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.debit_adjustment_amount - self.amount + adjustment_amount = loan_details.debit_adjustment_amount - self.amount return adjustment_amount def get_values_on_submit(self, loan_details): if self.adjustment_type == "Credit Adjustment": - adjustment_amount = loan_details.credit_adjustment_amount + self.amount + adjustment_amount = loan_details.credit_adjustment_amount + self.amount elif self.adjustment_type == "Debit Adjustment": - adjustment_amount = loan_details.debit_adjustment_amount + self.amount + adjustment_amount = loan_details.debit_adjustment_amount + self.amount - if ( - loan_details.status in ("Disbursed", "Partially Disbursed") - and not loan_details.is_term_loan - ): - process_loan_interest_accrual_for_demand_loans( - posting_date=add_days(self.posting_date, -1), - loan=self.loan, - accrual_type=self.adjustment_type, - ) + if loan_details.status in ("Disbursed", "Partially Disbursed") and not loan_details.is_term_loan: + process_loan_interest_accrual_for_demand_loans( + posting_date=add_days(self.posting_date, -1), + loan=self.loan, + accrual_type=self.adjustment_type, + ) return adjustment_amount def make_gl_entries(self, cancel=0, adv_adj=0): gle_map = [] loan_account = frappe.db.get_value("Loan", self.loan, "loan_account") - remarks = "{} against loan {}".format( - self.adjustment_type.capitalize(), self.loan - ) + remarks = "{} against loan {}".format(self.adjustment_type.capitalize(), self.loan) if self.reference_number: remarks += "with reference no. {}".format(self.reference_number) loan_entry = { - "account": loan_account, - "against": self.adjustment_account, - "against_voucher_type": "Loan", - "against_voucher": self.loan, - "remarks": _(remarks), - "cost_center": self.cost_center, - "party_type": self.applicant_type, - "party": self.applicant, - "posting_date": self.posting_date, + "account": loan_account, + "against": self.adjustment_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _(remarks), + "cost_center": self.cost_center, + "party_type": self.applicant_type, + "party": self.applicant, + "posting_date": self.posting_date, } company_entry = { - "account": self.adjustment_account, - "against": loan_account, - "against_voucher_type": "Loan", - "against_voucher": self.loan, - "remarks": _(remarks), - "cost_center": self.cost_center, - "posting_date": self.posting_date, + "account": self.adjustment_account, + "against": loan_account, + "against_voucher_type": "Loan", + "against_voucher": self.loan, + "remarks": _(remarks), + "cost_center": self.cost_center, + "posting_date": self.posting_date, } if self.adjustment_type == "Credit Adjustment": - loan_entry["credit"] = self.amount - loan_entry["credit_in_account_currency"] = self.amount + loan_entry["credit"] = self.amount + loan_entry["credit_in_account_currency"] = self.amount - company_entry["debit"] = self.amount - company_entry["debit_in_account_currency"] = self.amount + company_entry["debit"] = self.amount + company_entry["debit_in_account_currency"] = self.amount elif self.adjustment_type == "Debit Adjustment": - loan_entry["debit"] = self.amount - loan_entry["debit_in_account_currency"] = self.amount + loan_entry["debit"] = self.amount + loan_entry["debit_in_account_currency"] = self.amount - company_entry["credit"] = self.amount - company_entry["credit_in_account_currency"] = self.amount + company_entry["credit"] = self.amount + company_entry["credit_in_account_currency"] = self.amount gle_map.append(self.get_gl_dict(loan_entry)) gle_map.append(self.get_gl_dict(company_entry)) if gle_map: - make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) + make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False) diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py index 2a7f47871f6..f63cb275129 100644 --- a/erpnext/loan_management/doctype/loan_refund/loan_refund.py +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -1,13 +1,20 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +import frappe +from frappe import _ from frappe.model.document import Document +from frappe.utils import cint, flt, getdate + +import erpnext +from erpnext.accounts.general_ledger import make_gl_entries + class LoanRefund(Document): """ Add refund if total repayment is more than that is owed. """ + def validate(self): self.set_missing_values() self.validate_refund_amount() @@ -30,10 +37,7 @@ class LoanRefund(Document): ) if self.refund_amount > excess_amount: - frappe.throw(_( - "Refund amount cannot be greater than excess amount {}".format( - excess_amount - ))) + frappe.throw(_("Refund amount cannot be greater than excess amount {}".format(excess_amount))) def on_submit(self): self.update_outstanding_amount() From 7ba0326bdd244bab5e583c044562a157c2e3a1b2 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 8 Jul 2022 16:38:41 +0530 Subject: [PATCH 22/64] fix adjustment amount field name (cherry picked from commit 6cc09ef3a203724c73d4fa583cf4b7720dacdd65) --- .../doctype/loan_balance_adjustment/loan_balance_adjustment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index e714545a31c..2b5b1f74265 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -46,7 +46,8 @@ class LoanBalanceAdjustment(AccountsController): "Loan", fields=[ "loan_amount", - "adjustment_amount", + "credit_adjustment_amount", + "debit_adjustment_amount", "total_payment", "total_principal_paid", "total_interest_payable", From 94be7a95f75c08b90b13368073bb0b33dc699342 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 8 Jul 2022 16:49:19 +0530 Subject: [PATCH 23/64] Update adjustment_type field options (cherry picked from commit 245b0c7818587b21874cdc5ddadd6eac787beeb2) --- .../loan_balance_adjustment/loan_balance_adjustment.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json index cfbde0f6ce8..80c3389ba14 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.json @@ -132,7 +132,7 @@ "fieldname": "adjustment_type", "fieldtype": "Select", "label": "Adjustment Type", - "options": "Credit\nDebit", + "options": "Credit Adjustment\nDebit Adjustment", "reqd": 1 }, { @@ -144,7 +144,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-06-30 11:38:16.631994", + "modified": "2022-07-08 16:48:54.480066", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Balance Adjustment", From 61b2a1c3cbb683349d052740cff55db6599ebb14 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 8 Jul 2022 17:52:36 +0530 Subject: [PATCH 24/64] fix excess amount calculation in loan refund (cherry picked from commit 9df1413adb94ec59cae19e3cdf1afe62ce8b5ce6) --- .../doctype/loan_refund/loan_refund.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py index f63cb275129..766cdb6f048 100644 --- a/erpnext/loan_management/doctype/loan_refund/loan_refund.py +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -4,10 +4,13 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, flt, getdate +from frappe.utils import cint, getdate import erpnext from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.loan_management.doctype.loan_repayment.loan_repayment import ( + get_pending_principal_amount, +) class LoanRefund(Document): @@ -31,10 +34,12 @@ class LoanRefund(Document): ["total_payment", "total_principal_paid", "total_interest_payable", "written_off_amount"], ) - excess_amount = flt( - flt(total_payment) - flt(interest_payable) - flt(principal_paid) - flt(written_off_amount), - precision, - ) + loan = frappe.get_doc("Loan", self.loan) + pending_amount = get_pending_principal_amount(loan) + if pending_amount >= 0: + frappe.throw(_("No excess amount to refund.")) + else: + excess_amount = pending_amount * -1 if self.refund_amount > excess_amount: frappe.throw(_("Refund amount cannot be greater than excess amount {}".format(excess_amount))) From 0713a240a717cc0b6dc8f5121e4f7e4c6476d6f3 Mon Sep 17 00:00:00 2001 From: Labeeb Mattra Date: Fri, 8 Jul 2022 17:53:29 +0530 Subject: [PATCH 25/64] Consider refund_amount in pending principal amount (cherry picked from commit 35f2717ad2d1e5c8b40455d3c58636fd6f51c5e0) --- .../loan_management/doctype/loan_repayment/loan_repayment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 88baa260e55..0cd94154e59 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -404,7 +404,7 @@ class LoanRepayment(AccountsController): remarks = _("Repayment against Loan: ") + self.against_loan ======= remarks = "Repayment against loan " + self.against_loan - + if self.reference_number: remarks += "with reference no. {}".format(self.reference_number) >>>>>>> 74dbf8c5d9 (Add reference number to repayment remarks) @@ -641,6 +641,7 @@ def get_pending_principal_amount(loan): - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) + + flt(loan.refund_amount) ) else: pending_principal_amount = ( @@ -650,6 +651,7 @@ def get_pending_principal_amount(loan): - flt(loan.total_principal_paid) - flt(loan.total_interest_payable) - flt(loan.written_off_amount) + + flt(loan.refund_amount) ) return pending_principal_amount From 8237d94b11758938dbf6f4fff630189d7200c1cd Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Fri, 8 Jul 2022 17:58:36 +0530 Subject: [PATCH 26/64] fix: on cancel for loan refund (cherry picked from commit 13b7ed1e2c3f22155d59026ebc23d72bc7cbd263) --- erpnext/loan_management/doctype/loan_refund/loan_refund.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py index 766cdb6f048..903494c5ad9 100644 --- a/erpnext/loan_management/doctype/loan_refund/loan_refund.py +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -3,7 +3,7 @@ import frappe from frappe import _ -from frappe.model.document import Document +from erpnext.controllers.accounts_controller import AccountsController from frappe.utils import cint, getdate import erpnext @@ -13,7 +13,7 @@ from erpnext.loan_management.doctype.loan_repayment.loan_repayment import ( ) -class LoanRefund(Document): +class LoanRefund(AccountsController): """ Add refund if total repayment is more than that is owed. """ @@ -50,7 +50,7 @@ class LoanRefund(Document): def on_cancel(self): self.update_outstanding_amount(cancel=1) - self.ignore_linked_doctypes = ["GL Entry"] + self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] self.make_gl_entries(cancel=1) def update_outstanding_amount(self, cancel=0): From c2a7dc7754c85be289032d8a3a16c16688781956 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Jul 2022 21:50:01 +0530 Subject: [PATCH 27/64] chore: Linting Issues (cherry picked from commit d933ff5cf6c9bb7146f38b2c43b28b6edc48b8b0) --- .../loan_balance_adjustment.py | 9 +++++---- .../doctype/loan_refund/loan_refund.py | 11 ++--------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py index 2b5b1f74265..0a576d69692 100644 --- a/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py +++ b/erpnext/loan_management/doctype/loan_balance_adjustment/loan_balance_adjustment.py @@ -42,9 +42,10 @@ class LoanBalanceAdjustment(AccountsController): self.cost_center = erpnext.get_default_cost_center(self.company) def set_status_and_amounts(self, cancel=0): - loan_details = frappe.get_all( + loan_details = frappe.db.get_value( "Loan", - fields=[ + self.loan, + [ "loan_amount", "credit_adjustment_amount", "debit_adjustment_amount", @@ -55,8 +56,8 @@ class LoanBalanceAdjustment(AccountsController): "is_term_loan", "is_secured_loan", ], - filters={"name": self.loan}, - )[0] + as_dict=1, + ) if cancel: adjustment_amount = self.get_values_on_cancel(loan_details) diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py index 903494c5ad9..265c9692111 100644 --- a/erpnext/loan_management/doctype/loan_refund/loan_refund.py +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -3,11 +3,11 @@ import frappe from frappe import _ -from erpnext.controllers.accounts_controller import AccountsController -from frappe.utils import cint, getdate +from frappe.utils import getdate import erpnext from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.controllers.accounts_controller import AccountsController from erpnext.loan_management.doctype.loan_repayment.loan_repayment import ( get_pending_principal_amount, ) @@ -27,13 +27,6 @@ class LoanRefund(AccountsController): self.cost_center = erpnext.get_default_cost_center(self.company) def validate_refund_amount(self): - precision = cint(frappe.db.get_default("currency_precision")) or 2 - total_payment, principal_paid, interest_payable, written_off_amount = frappe.get_value( - "Loan", - self.loan, - ["total_payment", "total_principal_paid", "total_interest_payable", "written_off_amount"], - ) - loan = frappe.get_doc("Loan", self.loan) pending_amount = get_pending_principal_amount(loan) if pending_amount >= 0: From 0b3958d69270d578fc0a6d84101b22a4042b851f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Jul 2022 22:04:52 +0530 Subject: [PATCH 28/64] chore: Linting Issues (cherry picked from commit 0bac030ca7b798e0c87d0f71318f22a1bf6563e3) --- erpnext/loan_management/doctype/loan_refund/loan_refund.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_refund/loan_refund.py b/erpnext/loan_management/doctype/loan_refund/loan_refund.py index 265c9692111..d7ab54ca974 100644 --- a/erpnext/loan_management/doctype/loan_refund/loan_refund.py +++ b/erpnext/loan_management/doctype/loan_refund/loan_refund.py @@ -35,7 +35,7 @@ class LoanRefund(AccountsController): excess_amount = pending_amount * -1 if self.refund_amount > excess_amount: - frappe.throw(_("Refund amount cannot be greater than excess amount {}".format(excess_amount))) + frappe.throw(_("Refund amount cannot be greater than excess amount {0}").format(excess_amount)) def on_submit(self): self.update_outstanding_amount() From 84f260e1d8ca943a1b1afbed1391734bed157b41 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Wed, 13 Jul 2022 01:39:36 +0530 Subject: [PATCH 29/64] fix: (india) (e-invoice) CN / DN with 0 qty Qty 0 is allowed when creating Credit or Debit Note this caused ZeroDivisionError during unit_rate calculation. fixed the issue by adding required conditionals. --- erpnext/regional/india/e_invoice/utils.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index d4a2e026453..a2b8dd4e61d 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -276,7 +276,19 @@ def get_item_list(invoice): if invoice.get("apply_discount_on"): item.discount_amount = item.base_amount - item.base_net_amount - item.unit_rate = abs(item.taxable_value - item.discount_amount) / item.qty + if invoice.get("is_return") or invoice.get("is_debit_note"): + item.unit_rate = abs(item.taxable_value - item.discount_amount) / ( + 1 if (item.qty == 0) else item.qty + ) + else: + try: + item.unit_rate = abs(item.taxable_value - item.discount_amount) / item.qty + except ZeroDivisionError: + # This will never run but added as safety measure + frappe.throw( + title="Error: Qty is Zero", + msg="Quantity can't be zero unless it's Credit/Debit Note.", + ) item.gross_amount = abs(item.taxable_value) + item.discount_amount item.taxable_value = abs(item.taxable_value) From cb80bcd0ee4cedd34e72053d7d442577bd4cb5c2 Mon Sep 17 00:00:00 2001 From: Solufyin <34390782+Solufyin@users.noreply.github.com> Date: Wed, 13 Jul 2022 11:55:47 +0530 Subject: [PATCH 30/64] fix: Opening Invoice Creation Tool msgprint --- .../opening_invoice_creation_tool.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js index 9f22ab0d76c..7eb5c4234d1 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.js @@ -49,7 +49,15 @@ frappe.ui.form.on('Opening Invoice Creation Tool', { doc: frm.doc, btn: $(btn_primary), method: "make_invoices", - freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]) + freeze: 1, + freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]), + callback: function(r) { + if (r.message.length == 1) { + frappe.msgprint(__("{0} Invoice created successfully.", [frm.doc.invoice_type])); + } else if (r.message.length < 50) { + frappe.msgprint(__("{0} Invoices created successfully.", [frm.doc.invoice_type])); + } + } }); }); From 82539fc18a07425c8b6135f0a2c842acdfb85cdf Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Thu, 14 Jul 2022 15:12:51 +0530 Subject: [PATCH 31/64] fix: (india) (e-invoice) allow generation for UIN Holders e-invoice is required for UIN Holders and they should be treated as Registered Regular. there was incorrect if hasattr check that prevented UIN number validation. --- erpnext/regional/india/e_invoice/utils.py | 12 +++++++----- erpnext/regional/india/setup.py | 2 +- erpnext/regional/india/utils.py | 11 +++++------ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index d4a2e026453..14c36dc4f7f 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -65,6 +65,7 @@ def validate_eligibility(doc): "SEZ", "Overseas", "Deemed Export", + "UIN Holders", ] company_transaction = doc.get("billing_address_gstin") == doc.get("company_gstin") @@ -130,9 +131,7 @@ def read_json(name): def get_transaction_details(invoice): supply_type = "" - if ( - invoice.gst_category == "Registered Regular" or invoice.gst_category == "Registered Composition" - ): + if invoice.gst_category in ("Registered Regular", "Registered Composition", "UIN Holders"): supply_type = "B2B" elif invoice.gst_category == "SEZ": if invoice.export_type == "Without Payment of Tax": @@ -148,15 +147,18 @@ def get_transaction_details(invoice): supply_type = "DEXP" if not supply_type: - rr, rc, sez, overseas, export = ( + rr, rc, sez, overseas, export, uin = ( bold("Registered Regular"), bold("Registered Composition"), bold("SEZ"), bold("Overseas"), bold("Deemed Export"), + bold("UIN Holders"), ) frappe.throw( - _("GST category should be one of {}, {}, {}, {}, {}").format(rr, rc, sez, overseas, export), + _("GST category should be one of {}, {}, {}, {}, {}, {}").format( + rr, rc, sez, overseas, export, uin + ), title=_("Invalid Supply Type"), ) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 82d734d845c..5c3239913c7 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -714,7 +714,7 @@ def get_custom_fields(): insert_after="customer", no_copy=1, print_hide=1, - depends_on='eval:in_list(["Registered Regular", "Registered Composition", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0', + depends_on='eval:in_list(["Registered Regular", "Registered Composition", "SEZ", "Overseas", "Deemed Export", "UIN Holders"], doc.gst_category) && doc.irn_cancelled === 0', ), dict( fieldname="irn_cancelled", diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 378f39a7e21..160eebca6e7 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -40,13 +40,12 @@ def validate_gstin_for_india(doc, method): gst_category = [] - if hasattr(doc, "gst_category"): - if len(doc.links): - link_doctype = doc.links[0].get("link_doctype") - link_name = doc.links[0].get("link_name") + if len(doc.links): + link_doctype = doc.links[0].get("link_doctype") + link_name = doc.links[0].get("link_name") - if link_doctype in ["Customer", "Supplier"]: - gst_category = frappe.db.get_value(link_doctype, {"name": link_name}, ["gst_category"]) + if link_doctype in ["Customer", "Supplier"]: + gst_category = frappe.db.get_value(link_doctype, {"name": link_name}, ["gst_category"]) doc.gstin = doc.gstin.upper().strip() if not doc.gstin or doc.gstin == "NA": From bdd15895ff6574d3151ac3899a0ea202146cd97c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 14 Jul 2022 17:06:01 +0530 Subject: [PATCH 32/64] chore: resolve conflicts --- .../doctype/loan_repayment/loan_repayment.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 0cd94154e59..18803504eb1 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -388,11 +388,7 @@ class LoanRepayment(AccountsController): def make_gl_entries(self, cancel=0, adv_adj=0): gle_map = [] if self.shortfall_amount and self.amount_paid > self.shortfall_amount: -<<<<<<< HEAD - remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format( -======= remarks = "Shortfall repayment of {0}.
Repayment against loan {1}".format( ->>>>>>> 74dbf8c5d9 (Add reference number to repayment remarks) self.shortfall_amount, self.against_loan ) elif self.shortfall_amount: @@ -400,14 +396,10 @@ class LoanRepayment(AccountsController): self.shortfall_amount, self.against_loan ) else: -<<<<<<< HEAD - remarks = _("Repayment against Loan: ") + self.against_loan -======= remarks = "Repayment against loan " + self.against_loan if self.reference_number: remarks += "with reference no. {}".format(self.reference_number) ->>>>>>> 74dbf8c5d9 (Add reference number to repayment remarks) if self.repay_from_salary: payment_account = self.payroll_payable_account From 0a5bc86d1bc917dde27e5eab7466f45c5cdc4411 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 14 Jul 2022 17:20:32 +0530 Subject: [PATCH 33/64] chore: resolve conflicts --- .../accounts_settings/accounts_settings.json | 43 +++---------------- 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index f35ded657eb..ea427aa7d80 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -19,7 +19,7 @@ "book_asset_depreciation_entry_automatically", "unlink_advance_payment_on_cancelation_of_order", "enable_common_party_accounting", -<<<<<<< HEAD + "allow_multi_currency_invoices_against_single_party_account", "post_change_gl_entries", "enable_discount_accounting", "tax_settings_section", @@ -31,11 +31,6 @@ "frozen_accounts_modifier", "column_break_4", "credit_controller", -======= - "allow_multi_currency_invoices_against_single_party_account", - "report_setting_section", - "use_custom_cash_flow", ->>>>>>> c83fbd5c50 (fix: Allow multi currency invoice against single party account) "deferred_accounting_settings_section", "book_deferred_entries_based_on", "column_break_18", @@ -278,49 +273,25 @@ "label": "Enable Discount Accounting" }, { -<<<<<<< HEAD "default": "0", "fieldname": "enable_common_party_accounting", "fieldtype": "Check", "label": "Enable Common Party Accounting" -======= - "fieldname": "invoicing_features_section", - "fieldtype": "Section Break", - "label": "Invoicing Features" }, { - "fieldname": "column_break_17", - "fieldtype": "Column Break" - }, - { - "fieldname": "pos_tab", - "fieldtype": "Tab Break", - "label": "POS" - }, - { - "fieldname": "report_setting_section", - "fieldtype": "Section Break", - "label": "Report Setting" - }, - { - "default": "0", - "description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency", - "fieldname": "allow_multi_currency_invoices_against_single_party_account", - "fieldtype": "Check", - "label": "Allow multi-currency invoices against single party account " ->>>>>>> c83fbd5c50 (fix: Allow multi currency invoice against single party account) - } + "default": "0", + "description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency", + "fieldname": "allow_multi_currency_invoices_against_single_party_account", + "fieldtype": "Check", + "label": "Allow multi-currency invoices against single party account" + } ], "icon": "icon-cog", "idx": 1, "index_web_pages_for_search": 1, "issingle": 1, "links": [], -<<<<<<< HEAD - "modified": "2021-10-11 17:42:36.427699", -======= "modified": "2022-07-11 13:37:50.605141", ->>>>>>> c83fbd5c50 (fix: Allow multi currency invoice against single party account) "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 1f4d4346752424c4b344cfebfe6042485c03da65 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Fri, 15 Jul 2022 16:03:42 +0530 Subject: [PATCH 34/64] fix: (india) (e-invoice) discount calculation I have added fixes for discount in e-inovice. --- erpnext/regional/india/e_invoice/utils.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index a2b8dd4e61d..569c4e7d142 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -273,21 +273,26 @@ def get_item_list(invoice): item.qty = abs(item.qty) - if invoice.get("apply_discount_on"): - item.discount_amount = item.base_amount - item.base_net_amount + if invoice.get("apply_discount_on") and (invoice.get("base_discount_amount") > 0.00): + # TODO: need to handle case when tax included in basic rate is checked. + item.discount_amount = (item.discount_amount * item.qty) + ( + item.base_amount - item.base_net_amount + ) + else: + item.discount_amount = item.discount_amount * item.qty if invoice.get("is_return") or invoice.get("is_debit_note"): - item.unit_rate = abs(item.taxable_value - item.discount_amount) / ( + item.unit_rate = abs(item.taxable_value + item.discount_amount) / ( 1 if (item.qty == 0) else item.qty ) else: try: - item.unit_rate = abs(item.taxable_value - item.discount_amount) / item.qty + item.unit_rate = abs(item.taxable_value + item.discount_amount) / item.qty except ZeroDivisionError: # This will never run but added as safety measure frappe.throw( - title="Error: Qty is Zero", - msg="Quantity can't be zero unless it's Credit/Debit Note.", + title=_("Error: Qty is Zero"), + msg=_("Quantity can't be zero unless it's Credit/Debit Note."), ) item.gross_amount = abs(item.taxable_value) + item.discount_amount From 844758a27cb5436e30ef07d6e2ceee88fad8b6c2 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Fri, 15 Jul 2022 16:54:10 +0530 Subject: [PATCH 35/64] fix: (india)(einvoice) discount for CN DN --- erpnext/regional/india/e_invoice/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 569c4e7d142..d9add99a7a7 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -273,16 +273,16 @@ def get_item_list(invoice): item.qty = abs(item.qty) - if invoice.get("apply_discount_on") and (invoice.get("base_discount_amount") > 0.00): + if invoice.get("apply_discount_on") and (abs(invoice.get("base_discount_amount")) > 0.00): # TODO: need to handle case when tax included in basic rate is checked. item.discount_amount = (item.discount_amount * item.qty) + ( - item.base_amount - item.base_net_amount + abs(item.base_amount) - abs(item.base_net_amount) ) else: item.discount_amount = item.discount_amount * item.qty if invoice.get("is_return") or invoice.get("is_debit_note"): - item.unit_rate = abs(item.taxable_value + item.discount_amount) / ( + item.unit_rate = (abs(item.taxable_value) + item.discount_amount) / ( 1 if (item.qty == 0) else item.qty ) else: From 0d6beed546ad9db8af94c0374c6f9dd4d51a4803 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 16 Jul 2022 14:06:01 +0530 Subject: [PATCH 36/64] test: Update test case --- .../sales_invoice/test_sales_invoice.py | 6 +++--- .../e_invoice_settings.json | 21 ++++++++++++++++++- erpnext/regional/india/e_invoice/utils.py | 2 +- .../doctype/item_price/test_item_price.py | 7 +++++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 2ff1b34e35e..0c75fcc9352 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2626,15 +2626,15 @@ class TestSalesInvoice(unittest.TestCase): # Normal Itemized Discount si = get_sales_invoice_for_e_invoice() si.apply_discount_on = "" - si.items[0].discount_amount = 4000 - si.items[1].discount_amount = 300 + si.items[0].discount_amount = 2 + si.items[1].discount_amount = 5 si.save() einvoice = make_einvoice(si) validate_totals(einvoice) self.assertEqual(einvoice["ItemList"][0]["Discount"], 4000) - self.assertEqual(einvoice["ItemList"][1]["Discount"], 300) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 2100) self.assertEqual(einvoice["ValDtls"]["Discount"], 0) # Invoice Discount on net total diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json index 16b29633010..0d224c7a20b 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -9,6 +9,9 @@ "section_break_2", "sandbox_mode", "applicable_from", + "column_break_4", + "dont_show_discounts_in_e_invoice", + "section_break_7", "credentials", "advanced_settings_section", "client_id", @@ -80,12 +83,28 @@ { "fieldname": "column_break_8", "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Enabling this will directly report net rates in e-Invoice post discounts", + "fieldname": "dont_show_discounts_in_e_invoice", + "fieldtype": "Check", + "label": "Don't show discounts in e-Invoice" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "hide_border": 1 } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-11-16 19:50:28.029517", + "modified": "2022-07-16 09:45:23.862046", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Settings", diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index d9add99a7a7..4e9687f31e8 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -273,7 +273,7 @@ def get_item_list(invoice): item.qty = abs(item.qty) - if invoice.get("apply_discount_on") and (abs(invoice.get("base_discount_amount")) > 0.00): + if invoice.get("apply_discount_on") and (abs(invoice.get("base_discount_amount") or 0.0) > 0.0): # TODO: need to handle case when tax included in basic rate is checked. item.discount_amount = (item.discount_amount * item.qty) + ( abs(item.base_amount) - abs(item.base_net_amount) diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py index 30d933e247d..aeaf9125121 100644 --- a/erpnext/stock/doctype/item_price/test_item_price.py +++ b/erpnext/stock/doctype/item_price/test_item_price.py @@ -163,5 +163,12 @@ class TestItemPrice(FrappeTestCase): self.assertEqual(price, 21) +def make_item_price(item_code, price_list, price_list_rate): + item_price = frappe.new_doc("Item Price") + item_price.price_list = price_list + item_price.item_code = item_code + item_price.price_list_rate = price_list_rate + item_price.insert() + return item_price test_records = frappe.get_test_records("Item Price") From b147ce42063fdd2cdd267f30260d1a53a14b6597 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 17 Jul 2022 00:43:25 +0530 Subject: [PATCH 37/64] fix(India): Inward supplies from Composition Supplier in 3B report --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index be062fcff85..379579b904f 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -144,7 +144,7 @@ class GSTR3BReport(Document): def get_inward_nil_exempt(self, state): inward_nil_exempt = frappe.db.sql( """ - SELECT p.place_of_supply, p.supplier_address, + SELECT p.name, p.place_of_supply, p.supplier_address, p.gst_category, i.base_amount, i.is_nil_exempt, i.is_non_gst FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i WHERE p.docstatus = 1 and p.name = i.parent From 2066e5a53a12a05e3216258a2797f3f023faaa52 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Sun, 17 Jul 2022 15:08:40 +0530 Subject: [PATCH 38/64] discount settings for e-invoice added and test cases --- .../sales_invoice/test_sales_invoice.py | 165 +++++++++++++++++- .../e_invoice_settings.json | 4 +- erpnext/regional/india/e_invoice/utils.py | 50 ++++-- 3 files changed, 194 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 0c75fcc9352..98dd30c7fbe 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2626,8 +2626,13 @@ class TestSalesInvoice(unittest.TestCase): # Normal Itemized Discount si = get_sales_invoice_for_e_invoice() si.apply_discount_on = "" - si.items[0].discount_amount = 2 + si.items[0].price_list_rate = 12 + si.items[0].discount_percentage = 16.6666666667 + si.items[0].rate = 10 + + si.items[1].price_list_rate = 15 si.items[1].discount_amount = 5 + si.items[1].rate = 10 si.save() einvoice = make_einvoice(si) @@ -2635,8 +2640,15 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(einvoice["ItemList"][0]["Discount"], 4000) self.assertEqual(einvoice["ItemList"][1]["Discount"], 2100) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 222) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 5555) self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10) + # Invoice Discount on net total si = get_sales_invoice_for_e_invoice() si.apply_discount_on = "Net Total" @@ -2646,10 +2658,17 @@ class TestSalesInvoice(unittest.TestCase): einvoice = make_einvoice(si) validate_totals(einvoice) - self.assertEqual(einvoice["ItemList"][0]["Discount"], 316.83) - self.assertEqual(einvoice["ItemList"][1]["Discount"], 83.17) + self.assertEqual(einvoice["ItemList"][0]["Discount"], 253.61) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 66.57) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 243.11) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 5613.71) self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10) + # Invoice Discount on grand total (Itemized Discount) si = get_sales_invoice_for_e_invoice() si.apply_discount_on = "Grand Total" @@ -2659,10 +2678,17 @@ class TestSalesInvoice(unittest.TestCase): einvoice = make_einvoice(si) validate_totals(einvoice) - self.assertEqual(einvoice["ItemList"][0]["Discount"], 268.5) - self.assertEqual(einvoice["ItemList"][1]["Discount"], 70.48) + self.assertEqual(einvoice["ItemList"][0]["Discount"], 214.93) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 56.42) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 239.89) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 5604.75) self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10) + # Invoice Discount on grand total (Cash/Non-Trade Discount) si = get_sales_invoice_for_e_invoice() si.apply_discount_on = "Grand Total" @@ -2675,8 +2701,107 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 222.0) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 5555.0) self.assertEqual(einvoice["ValDtls"]["Discount"], 400) + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 20) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 10) + + def test_einvoice_without_discounts(self): + from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals + + frappe.db.set_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice", True) + + # Normal Itemized Discount + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "" + si.items[0].price_list_rate = 12 + si.items[0].discount_percentage = 16.6666666667 + si.items[0].rate = 10 + + si.items[1].price_list_rate = 15 + si.items[1].discount_amount = 5 + si.items[1].rate = 10 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 0) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 10) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 10) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 18) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 5) + + # Invoice Discount on net total + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "Net Total" + si.discount_amount = 400 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 0) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 11.87) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 14.84) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 17.81) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 4.95) + + # Invoice Discount on grand total (Itemized Discount) + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "Grand Total" + si.discount_amount = 400 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 0) + self.assertEqual(einvoice["ValDtls"]["Discount"], 0) + + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 11.89) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 14.87) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 17.84) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 4.96) + + # Invoice Discount on grand total (Cash/Non-Trade Discount) + si = get_sales_invoice_for_e_invoice() + si.apply_discount_on = "Grand Total" + si.is_cash_or_non_trade_discount = 1 + si.discount_amount = 400 + si.save() + + einvoice = make_einvoice(si) + validate_totals(einvoice) + + self.assertEqual(einvoice["ItemList"][0]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][1]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][2]["Discount"], 0) + self.assertEqual(einvoice["ItemList"][3]["Discount"], 0) + self.assertEqual(einvoice["ValDtls"]["Discount"], 400) + + self.assertEqual(einvoice["ItemList"][0]["UnitPrice"], 12) + self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 15) + self.assertEqual(einvoice["ItemList"][2]["UnitPrice"], 18) + self.assertEqual(einvoice["ItemList"][3]["UnitPrice"], 5) + def test_item_tax_net_range(self): item = create_item("T Shirt") @@ -3276,6 +3401,36 @@ def get_sales_invoice_for_e_invoice(): }, ) + si.append( + "items", + { + "item_code": "_Test Item", + "uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + "qty": 111, + "price_list_rate": 20, + "discount_percentage": 10, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + ) + + si.append( + "items", + { + "item_code": "_Test Item 2", + "uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + "qty": 1111, + "price_list_rate": 10, + "rate": 5, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }, + ) + return si diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json index 0d224c7a20b..14fad403213 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -89,7 +89,7 @@ "fieldtype": "Column Break" }, { - "default": "0", + "default": "1", "description": "Enabling this will directly report net rates in e-Invoice post discounts", "fieldname": "dont_show_discounts_in_e_invoice", "fieldtype": "Check", @@ -104,7 +104,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-07-16 09:45:23.862046", + "modified": "2022-07-17 14:57:50.783517", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Settings", diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 4e9687f31e8..b0f837b51e7 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -273,27 +273,41 @@ def get_item_list(invoice): item.qty = abs(item.qty) - if invoice.get("apply_discount_on") and (abs(invoice.get("base_discount_amount") or 0.0) > 0.0): - # TODO: need to handle case when tax included in basic rate is checked. - item.discount_amount = (item.discount_amount * item.qty) + ( - abs(item.base_amount) - abs(item.base_net_amount) - ) - else: - item.discount_amount = item.discount_amount * item.qty + hide_discount_in_einvoice = cint( + frappe.db.get_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice") + ) + + if hide_discount_in_einvoice: + if flt(item.qty) != 0.0: + item.unit_rate = abs(item.taxable_value / item.qty) + else: + item.unit_rate = abs(item.taxable_value) + item.gross_amount = abs(item.taxable_value) + item.taxable_value = abs(item.taxable_value) + item.discount_amount = 0 - if invoice.get("is_return") or invoice.get("is_debit_note"): - item.unit_rate = (abs(item.taxable_value) + item.discount_amount) / ( - 1 if (item.qty == 0) else item.qty - ) else: - try: - item.unit_rate = abs(item.taxable_value + item.discount_amount) / item.qty - except ZeroDivisionError: - # This will never run but added as safety measure - frappe.throw( - title=_("Error: Qty is Zero"), - msg=_("Quantity can't be zero unless it's Credit/Debit Note."), + if invoice.get("apply_discount_on") and (abs(invoice.get("base_discount_amount") or 0.0) > 0.0): + # TODO: need to handle case when tax included in basic rate is checked. + item.discount_amount = (item.discount_amount * item.qty) + ( + abs(item.base_amount) - abs(item.base_net_amount) ) + else: + item.discount_amount = item.discount_amount * item.qty + + if invoice.get("is_return") or invoice.get("is_debit_note"): + item.unit_rate = (abs(item.taxable_value) + item.discount_amount) / ( + 1 if (item.qty == 0) else item.qty + ) + else: + try: + item.unit_rate = abs(item.taxable_value + item.discount_amount) / item.qty + except ZeroDivisionError: + # This will never run but added as safety measure + frappe.throw( + title=_("Error: Qty is Zero"), + msg=_("Quantity can't be zero unless it's Credit/Debit Note."), + ) item.gross_amount = abs(item.taxable_value) + item.discount_amount item.taxable_value = abs(item.taxable_value) From 9a3c84663fdbbfdad8a4a256f50211a23e8f896a Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Sun, 17 Jul 2022 15:22:37 +0530 Subject: [PATCH 39/64] fix: linter --- erpnext/stock/doctype/item_price/test_item_price.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py index aeaf9125121..d3988743f6d 100644 --- a/erpnext/stock/doctype/item_price/test_item_price.py +++ b/erpnext/stock/doctype/item_price/test_item_price.py @@ -163,6 +163,7 @@ class TestItemPrice(FrappeTestCase): self.assertEqual(price, 21) + def make_item_price(item_code, price_list, price_list_rate): item_price = frappe.new_doc("Item Price") item_price.price_list = price_list @@ -171,4 +172,5 @@ def make_item_price(item_code, price_list, price_list_rate): item_price.insert() return item_price + test_records = frappe.get_test_records("Item Price") From 9631ffd215de8a23bbeb8e25ce8ba8e7262f2877 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Sun, 17 Jul 2022 15:40:55 +0530 Subject: [PATCH 40/64] fix: test change setting to run test with correct use case. --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 98dd30c7fbe..0a5172eaba6 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2623,6 +2623,8 @@ class TestSalesInvoice(unittest.TestCase): def test_einvoice_discounts(self): from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals + frappe.db.set_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice", False) + # Normal Itemized Discount si = get_sales_invoice_for_e_invoice() si.apply_discount_on = "" From 90c751f64884ed49ec94e22fbf72d7ac18968d55 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 18 Jul 2022 12:39:09 +0530 Subject: [PATCH 41/64] fix: Tax amount not considered in Expense Claim Journal Entry - add a helper function `get_outstanding_amount_for_claim` for uniformity in usages --- .../doctype/journal_entry/journal_entry.py | 16 +++++----- .../doctype/payment_entry/payment_entry.py | 12 ++++---- .../hr/doctype/expense_claim/expense_claim.py | 30 +++++++++++++++---- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index c46b185e60a..8cc20038770 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -25,7 +25,10 @@ from erpnext.accounts.utils import ( get_stock_and_account_balance, ) from erpnext.controllers.accounts_controller import AccountsController -from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount +from erpnext.hr.doctype.expense_claim.expense_claim import ( + get_outstanding_amount_for_claim, + update_reimbursed_amount, +) class StockAccountInvalidTransaction(frappe.ValidationError): @@ -935,15 +938,12 @@ class JournalEntry(AccountsController): def validate_expense_claim(self): for d in self.accounts: if d.reference_type == "Expense Claim": - sanctioned_amount, reimbursed_amount = frappe.db.get_value( - "Expense Claim", d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed") - ) - pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount) - if d.debit > pending_amount: + outstanding_amt = get_outstanding_amount_for_claim(d.reference_name) + if d.debit > outstanding_amt: frappe.throw( _( - "Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}" - ).format(d.idx, d.reference_name, pending_amount) + "Row No {0}: Amount cannot be greater than the Outstanding Amount against Expense Claim {1}. Outstanding Amount is {2}" + ).format(d.idx, d.reference_name, outstanding_amt) ) def validate_credit_debit_note(self): diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 2728c37d5b7..d8ce89c59d2 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -30,7 +30,10 @@ from erpnext.controllers.accounts_controller import ( get_supplier_block_status, validate_taxes_and_charges, ) -from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount +from erpnext.hr.doctype.expense_claim.expense_claim import ( + get_outstanding_amount_for_claim, + update_reimbursed_amount, +) from erpnext.setup.utils import get_exchange_rate @@ -1649,12 +1652,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre outstanding_amount = ref_doc.get("outstanding_amount") bill_no = ref_doc.get("bill_no") elif reference_doctype == "Expense Claim": - outstanding_amount = ( - flt(ref_doc.get("total_sanctioned_amount")) - + flt(ref_doc.get("total_taxes_and_charges")) - - flt(ref_doc.get("total_amount_reimbursed")) - - flt(ref_doc.get("total_advance_amount")) - ) + outstanding_amount = get_outstanding_amount_for_claim(ref_doc) elif reference_doctype == "Employee Advance": outstanding_amount = flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount) if party_account_currency != ref_doc.currency: diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 89d86c1bc7c..ab0e62b378e 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -339,6 +339,30 @@ def update_reimbursed_amount(doc, amount): frappe.db.set_value("Expense Claim", doc.name, "status", doc.status) +def get_outstanding_amount_for_claim(claim): + if isinstance(claim, str): + claim = frappe.db.get_value( + "Expense Claim", + claim, + ( + "total_sanctioned_amount", + "total_taxes_and_charges", + "total_amount_reimbursed", + "total_advance_amount", + ), + as_dict=True, + ) + + outstanding_amt = ( + flt(claim.total_sanctioned_amount) + + flt(claim.total_taxes_and_charges) + - flt(claim.total_amount_reimbursed) + - flt(claim.total_advance_amount) + ) + + return outstanding_amt + + @frappe.whitelist() def make_bank_entry(dt, dn): from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account @@ -348,11 +372,7 @@ def make_bank_entry(dt, dn): if not default_bank_cash_account: default_bank_cash_account = get_default_bank_cash_account(expense_claim.company, "Cash") - payable_amount = ( - flt(expense_claim.total_sanctioned_amount) - - flt(expense_claim.total_amount_reimbursed) - - flt(expense_claim.total_advance_amount) - ) + payable_amount = get_outstanding_amount_for_claim(expense_claim) je = frappe.new_doc("Journal Entry") je.voucher_type = "Bank Entry" From 6a7549d4b2ede2b87dc85bb85b4dd709f8b7398e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 18 Jul 2022 12:57:50 +0530 Subject: [PATCH 42/64] test: Journal Entry against Expense Claim --- .../expense_claim/test_expense_claim.py | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 9b3d53a2105..62df1e02900 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -4,6 +4,7 @@ import unittest import frappe +from frappe.tests.utils import FrappeTestCase from frappe.utils import flt, nowdate, random_string from erpnext.accounts.doctype.account.test_account import create_account @@ -14,9 +15,18 @@ test_dependencies = ["Employee"] company_name = "_Test Company 3" -class TestExpenseClaim(unittest.TestCase): - def tearDown(self): - frappe.db.rollback() +class TestExpenseClaim(FrappeTestCase): + def setUp(self): + if not frappe.db.get_value("Cost Center", {"company": company_name}): + frappe.get_doc( + { + "doctype": "Cost Center", + "cost_center_name": "_Test Cost Center 3", + "parent_cost_center": "_Test Company 3 - _TC3", + "is_group": 0, + "company": company_name, + } + ).insert() def test_total_expense_claim_for_project(self): frappe.db.sql("""delete from `tabTask`""") @@ -58,12 +68,7 @@ class TestExpenseClaim(unittest.TestCase): payable_account, 300, 200, company_name, "Travel Expenses - _TC3" ) - je_dict = make_bank_entry("Expense Claim", expense_claim.name) - je = frappe.get_doc(je_dict) - je.posting_date = nowdate() - je.cheque_no = random_string(5) - je.cheque_date = nowdate() - je.submit() + je = make_journal_entry(expense_claim) expense_claim = frappe.get_doc("Expense Claim", expense_claim.name) self.assertEqual(expense_claim.status, "Paid") @@ -272,6 +277,24 @@ class TestExpenseClaim(unittest.TestCase): self.assertEqual(outstanding_amount, 0) self.assertEqual(total_amount_reimbursed, 5500) + def test_journal_entry_against_expense_claim(self): + payable_account = get_payable_account(company_name) + taxes = generate_taxes() + expense_claim = make_expense_claim( + payable_account, + 300, + 200, + company_name, + "Travel Expenses - _TC3", + do_not_submit=True, + taxes=taxes, + ) + expense_claim.submit() + + je = make_journal_entry(expense_claim) + + self.assertEqual(je.accounts[0].debit_in_account_currency, expense_claim.grand_total) + def get_payable_account(company): return frappe.get_cached_value("Company", company, "default_payable_account") @@ -370,3 +393,14 @@ def make_payment_entry(expense_claim, payable_account, amt): pe.references[0].allocated_amount = amt pe.insert() pe.submit() + + +def make_journal_entry(expense_claim): + je_dict = make_bank_entry("Expense Claim", expense_claim.name) + je = frappe.get_doc(je_dict) + je.posting_date = nowdate() + je.cheque_no = random_string(5) + je.cheque_date = nowdate() + je.submit() + + return je From 12d3d6ab4925228702ae5cdb98f1d510ef5ad5d4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 18 Jul 2022 13:41:42 +0530 Subject: [PATCH 43/64] fix: added Section translation in German for Salary Slip (backport #31608) (#31616) Co-authored-by: Wolfram Schmidt Co-authored-by: Rucha Mahabal --- erpnext/translations/de.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index f99c8ec3765..4251b0cb3a5 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -898,6 +898,7 @@ ERPNext Settings,ERPNext-Einstellungen, Earliest,Frühestens, Earnest Money,Anzahlung, Earning,Einkommen, +Earnings & Deductions,Verdienste & Abzüge, Edit,Bearbeiten, Edit Publishing Details,Bearbeitungsdetails bearbeiten, "Edit in full page for more options like assets, serial nos, batches etc.","Bearbeiten Sie in Vollansicht für weitere Optionen wie Vermögenswerte, Seriennummern, Chargen usw.", From 903e42fbddd6d9dab5a63f95051309156823356b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 18 Jul 2022 17:59:42 +0530 Subject: [PATCH 44/64] perf: Optimization of gl entry processing logic in period closing voucher --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 4 +- .../period_closing_voucher.json | 24 ++- .../period_closing_voucher.py | 201 ++++++++++-------- .../test_period_closing_voucher.py | 3 +- erpnext/accounts/general_ledger.py | 5 +- 5 files changed, 141 insertions(+), 96 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index e590beb0261..1a574edfe86 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -43,7 +43,7 @@ class GLEntry(Document): self.validate_and_set_fiscal_year() self.pl_must_have_cost_center() - if not self.flags.from_repost: + if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher": self.check_mandatory() self.validate_cost_center() self.check_pl_account() @@ -52,7 +52,7 @@ class GLEntry(Document): def on_update(self): adv_adj = self.flags.adv_adj - if not self.flags.from_repost: + if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher": self.validate_account_details(adv_adj) self.validate_dimensions_for_pl_and_bs() self.validate_allowed_dimensions() diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json index 84c941ecc10..95742640c96 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json @@ -10,10 +10,11 @@ "fiscal_year", "amended_from", "company", - "cost_center_wise_pnl", "column_break1", "closing_account_head", - "remarks" + "remarks", + "status", + "error_message" ], "fields": [ { @@ -86,17 +87,26 @@ "reqd": 1 }, { - "default": "0", - "fieldname": "cost_center_wise_pnl", - "fieldtype": "Check", - "label": "Book Cost Center Wise Profit/Loss" + "depends_on": "eval:doc.docstatus!=0", + "fieldname": "status", + "fieldtype": "Select", + "label": "GL Entry Processing Status", + "options": "In Progress\nCompleted\nFailed", + "read_only": 1 + }, + { + "depends_on": "eval:doc.status=='Failed'", + "fieldname": "error_message", + "fieldtype": "Text", + "label": "Error Message", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-20 15:27:37.210458", + "modified": "2022-07-15 14:51:04.714154", "modified_by": "Administrator", "module": "Accounts", "name": "Period Closing Voucher", diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 5a86376199c..7022a9e7144 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -20,13 +20,26 @@ class PeriodClosingVoucher(AccountsController): self.validate_posting_date() def on_submit(self): + self.status = "In Progress" self.make_gl_entries() def on_cancel(self): + self.status = "In Progress" self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") - from erpnext.accounts.general_ledger import make_reverse_gl_entries - - make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name) + gle_count = frappe.db.count( + "GL Entry", + { + "voucher_type": "Period Closing Voucher", + "voucher_no": self.name, + "is_cancelled": 0 + } + ) + if gle_count > 5000: + frappe.enqueue(make_reverse_gl_entries, voucher_type="Period Closing Voucher", + voucher_no=self.name, queue="long") + frappe.msgprint(_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True) + else: + make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name) def validate_account_head(self): closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type") @@ -63,116 +76,138 @@ class PeriodClosingVoucher(AccountsController): pce[0][0], self.posting_date ) ) - + def make_gl_entries(self): gl_entries = self.get_gl_entries() if gl_entries: - from erpnext.accounts.general_ledger import make_gl_entries - - make_gl_entries(gl_entries) + if len(gl_entries) > 5000: + frappe.enqueue(process_gl_entries, gl_entries=gl_entries, queue="long") + frappe.msgprint(_("The GL Entries will be processed in the background, it can take a few minutes."), alert=True) + else: + process_gl_entries(gl_entries) def get_gl_entries(self): gl_entries = [] - pl_accounts = self.get_pl_balances() - for acc in pl_accounts: + # pl account + for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True): + if flt(acc.bal_in_company_currency): + gl_entries.append(self.get_gle_for_pl_account(acc)) + + # closing liability account + for acc in self.get_pl_balances_based_on_dimensions(group_by_account=False): if flt(acc.bal_in_company_currency): - gl_entries.append( - self.get_gl_dict( - { - "account": acc.account, - "cost_center": acc.cost_center, - "finance_book": acc.finance_book, - "account_currency": acc.account_currency, - "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) - if flt(acc.bal_in_account_currency) < 0 - else 0, - "debit": abs(flt(acc.bal_in_company_currency)) - if flt(acc.bal_in_company_currency) < 0 - else 0, - "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) - if flt(acc.bal_in_account_currency) > 0 - else 0, - "credit": abs(flt(acc.bal_in_company_currency)) - if flt(acc.bal_in_company_currency) > 0 - else 0, - }, - item=acc, - ) - ) - - if gl_entries: - gle_for_net_pl_bal = self.get_pnl_gl_entry(pl_accounts) - gl_entries += gle_for_net_pl_bal + gl_entries.append(self.get_gle_for_closing_account(acc)) return gl_entries + + def get_gle_for_pl_account(self, acc): + gl_entry = self.get_gl_dict( + { + "account": acc.account, + "cost_center": acc.cost_center, + "finance_book": acc.finance_book, + "account_currency": acc.account_currency, + "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) + if flt(acc.bal_in_account_currency) < 0 + else 0, + "debit": abs(flt(acc.bal_in_company_currency)) + if flt(acc.bal_in_company_currency) < 0 + else 0, + "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) + if flt(acc.bal_in_account_currency) > 0 + else 0, + "credit": abs(flt(acc.bal_in_company_currency)) + if flt(acc.bal_in_company_currency) > 0 + else 0, + }, + item=acc, + ) + self.update_default_dimensions(gl_entry, acc) + return gl_entry + + def get_gle_for_closing_account(self, acc): + gl_entry = self.get_gl_dict( + { + "account": self.closing_account_head, + "cost_center": acc.cost_center, + "finance_book": acc.finance_book, + "account_currency": acc.account_currency, + "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) + if flt(acc.bal_in_account_currency) > 0 + else 0, + "debit": abs(flt(acc.bal_in_company_currency)) + if flt(acc.bal_in_company_currency) > 0 + else 0, + "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) + if flt(acc.bal_in_account_currency) < 0 + else 0, + "credit": abs(flt(acc.bal_in_company_currency)) + if flt(acc.bal_in_company_currency) < 0 + else 0, + }, + item=acc, + ) + self.update_default_dimensions(gl_entry, acc) + return gl_entry - def get_pnl_gl_entry(self, pl_accounts): - company_cost_center = frappe.db.get_value("Company", self.company, "cost_center") - gl_entries = [] - - for acc in pl_accounts: - if flt(acc.bal_in_company_currency): - cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center - gl_entry = self.get_gl_dict( - { - "account": self.closing_account_head, - "cost_center": cost_center, - "finance_book": acc.finance_book, - "account_currency": acc.account_currency, - "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) - if flt(acc.bal_in_account_currency) > 0 - else 0, - "debit": abs(flt(acc.bal_in_company_currency)) - if flt(acc.bal_in_company_currency) > 0 - else 0, - "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) - if flt(acc.bal_in_account_currency) < 0 - else 0, - "credit": abs(flt(acc.bal_in_company_currency)) - if flt(acc.bal_in_company_currency) < 0 - else 0, - }, - item=acc, - ) - - self.update_default_dimensions(gl_entry) - - gl_entries.append(gl_entry) - - return gl_entries - - def update_default_dimensions(self, gl_entry): + def update_default_dimensions(self, gl_entry, acc): if not self.accounting_dimensions: self.accounting_dimensions = get_accounting_dimensions() - _, default_dimensions = get_dimensions() for dimension in self.accounting_dimensions: - gl_entry.update({dimension: default_dimensions.get(self.company, {}).get(dimension)}) + gl_entry.update({dimension: acc.get(dimension)}) - def get_pl_balances(self): + def get_pl_balances_based_on_dimensions(self, group_by_account=False): """Get balance for dimension-wise pl accounts""" dimension_fields = ["t1.cost_center", "t1.finance_book"] - + self.accounting_dimensions = get_accounting_dimensions() for dimension in self.accounting_dimensions: dimension_fields.append("t1.{0}".format(dimension)) - return frappe.db.sql( - """ + if group_by_account: + dimension_fields.append("t1.account") + + return frappe.db.sql(""" select - t1.account, t2.account_currency, {dimension_fields}, + t2.account_currency, + {dimension_fields}, sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency, sum(t1.debit) - sum(t1.credit) as bal_in_company_currency from `tabGL Entry` t1, `tabAccount` t2 - where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss' - and t2.docstatus < 2 and t2.company = %s - and t1.posting_date between %s and %s - group by t1.account, {dimension_fields} + where + t1.is_cancelled = 0 + and t1.account = t2.name + and t2.report_type = 'Profit and Loss' + and t2.docstatus < 2 + and t2.company = %s + and t1.posting_date between %s and %s + group by {dimension_fields} """.format( dimension_fields=", ".join(dimension_fields) ), (self.company, self.get("year_start_date"), self.posting_date), as_dict=1, ) + +def process_gl_entries(gl_entries): + from erpnext.accounts.general_ledger import make_gl_entries + try: + make_gl_entries(gl_entries, merge_entries=False) + frappe.db.set_value("Period Closing Voucher", gl_entries[0].get("voucher_no"), "status", "Completed") + except Exception as e: + frappe.db.rollback() + frappe.log_error(e) + frappe.db.set_value("Period Closing Voucher", gl_entries[0].get("voucher_no"), "status", "Failed") + +def make_reverse_gl_entries(voucher_type, voucher_no): + from erpnext.accounts.general_ledger import make_reverse_gl_entries + try: + make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no) + frappe.db.set_value("Period Closing Voucher", voucher_no, "status", "Completed") + except Exception as e: + frappe.db.rollback() + frappe.log_error(e) + frappe.db.set_value("Period Closing Voucher", gl_entries[0].get("voucher_no"), "status", "Failed") \ No newline at end of file diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index a944a373832..59f0bced7f3 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -49,7 +49,7 @@ class TestPeriodClosingVoucher(unittest.TestCase): expected_gle = ( ("Cost of Goods Sold - TPC", 0.0, 600.0), - (surplus_account, 600.0, 400.0), + (surplus_account, 200.0, 0.0), ("Sales - TPC", 400.0, 0.0), ) @@ -59,7 +59,6 @@ class TestPeriodClosingVoucher(unittest.TestCase): """, (pcv.name), ) - self.assertEqual(pcv_gle, expected_gle) def test_cost_center_wise_posting(self): diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index dcf09940ed0..5736ac0e369 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -148,6 +148,7 @@ def update_net_values(entry): def merge_similar_entries(gl_map, precision=None): merged_gl_map = [] accounting_dimensions = get_accounting_dimensions() + for entry in gl_map: # if there is already an entry in this account then just add it # to that entry @@ -221,7 +222,6 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): for entry in gl_map: make_entry(entry, adv_adj, update_outstanding, from_repost) - def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle = frappe.new_doc("GL Entry") gle.update(args) @@ -229,9 +229,10 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle.flags.from_repost = from_repost gle.flags.adv_adj = adv_adj gle.flags.update_outstanding = update_outstanding or "Yes" + gle.flags.notify_update = False gle.submit() - if not from_repost: + if not from_repost and gle.voucher_type != "Period Closing Voucher": validate_expense_against_budget(args) From 85c554eab560a1e958935dbccdb534a8079f6722 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 18 Jul 2022 21:08:15 +0530 Subject: [PATCH 45/64] perf: Replace `db.get_all` with `db.exists` in `is_holiday` - widely used function call, fetching all rows for holidays --- erpnext/hr/doctype/employee/employee.json | 2 +- erpnext/hr/doctype/holiday_list/holiday_list.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index d592a9c79e2..ed45e5288c6 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -813,7 +813,7 @@ "idx": 24, "image_field": "image", "links": [], - "modified": "2021-06-17 11:31:37.730760", + "modified": "2022-07-18 20:03:43.188705", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index ea32ba744d8..de8d578d71c 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -115,6 +115,6 @@ def is_holiday(holiday_list, date=None): if date is None: date = today() if holiday_list: - return bool(frappe.get_all("Holiday List", dict(name=holiday_list, holiday_date=date))) + return bool(frappe.db.exists("Holiday List", {"name": holiday_list, "holiday_date": date})) else: return False From abb7ac5a0be7a8e48e63186dc8fb02d5656bc1aa Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 18 Jul 2022 21:13:02 +0530 Subject: [PATCH 46/64] perf: Replace `get_doc` with `db.get_value` in `get_shift_details` --- .../hr/doctype/shift_assignment/shift_assignment.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 868be6ef719..918b510df27 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -294,7 +294,18 @@ def get_shift_details(shift_type_name, for_date=None): return None if not for_date: for_date = nowdate() - shift_type = frappe.get_doc("Shift Type", shift_type_name) + shift_type = frappe.db.get_value( + "Shift Type", + shift_type_name, + [ + "name", + "start_time", + "end_time", + "begin_check_in_before_shift_start_time", + "allow_check_out_after_shift_end_time", + ], + as_dict=1, + ) start_datetime = datetime.combine(for_date, datetime.min.time()) + shift_type.start_time for_date = ( for_date + timedelta(days=1) if shift_type.start_time > shift_type.end_time else for_date From 711501be5e1f6b2fe39529d8287d912f0ccea9f2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 18 Jul 2022 21:32:04 +0530 Subject: [PATCH 47/64] fix: Remove unnecessary list comprehensions --- erpnext/hr/doctype/shift_type/shift_type.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index 2000eeb5443..e4a40c0807f 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -156,21 +156,19 @@ class ShiftType(Document): if not from_date: del filters["start_date"] - assigned_employees = frappe.get_all("Shift Assignment", "employee", filters, as_list=True) - assigned_employees = [x[0] for x in assigned_employees] + assigned_employees = frappe.get_all("Shift Assignment", filters, pluck="employee") if consider_default_shift: filters = {"default_shift": self.name, "status": ["!=", "Inactive"]} - default_shift_employees = frappe.get_all("Employee", "name", filters, as_list=True) - default_shift_employees = [x[0] for x in default_shift_employees] + default_shift_employees = frappe.get_all("Employee", filters, pluck="name") return list(set(assigned_employees + default_shift_employees)) return assigned_employees def process_auto_attendance_for_all_shifts(): - shift_list = frappe.get_all("Shift Type", "name", {"enable_auto_attendance": "1"}, as_list=True) + shift_list = frappe.get_all("Shift Type", filters={"enable_auto_attendance": "1"}, pluck="name") for shift in shift_list: - doc = frappe.get_doc("Shift Type", shift[0]) + doc = frappe.get_doc("Shift Type", shift) doc.process_auto_attendance() From 431d79f5161868ebd1fe80f924c3ed3a02b70f89 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 19 Jul 2022 10:14:44 +0530 Subject: [PATCH 48/64] fix: Supplier details in TDS monthly report (#31599) (cherry picked from commit a6ff4db2ec9cfc7c444c6012d920abc3212d8f81) --- .../tds_payable_monthly.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 30ea5a9e4e1..16e0ac1de60 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -8,11 +8,11 @@ from frappe import _ def execute(filters=None): validate_filters(filters) - tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters) + tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters) columns = get_columns(filters) - res = get_result(filters, tds_docs, tds_accounts, tax_category_map) + res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map) return columns, res @@ -22,10 +22,11 @@ def validate_filters(filters): frappe.throw(_("From Date must be before To Date")) -def get_result(filters, tds_docs, tds_accounts, tax_category_map): +def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map): supplier_map = get_supplier_pan_map() tax_rate_map = get_tax_rate_map(filters) gle_map = get_gle_map(tds_docs) + print(journal_entry_party_map) out = [] for name, details in gle_map.items(): @@ -38,6 +39,11 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): posting_date = entry.posting_date voucher_type = entry.voucher_type + if voucher_type == "Journal Entry": + suppliers = journal_entry_party_map.get(name) + if suppliers: + supplier = suppliers[0] + if not tax_withholding_category: tax_withholding_category = supplier_map.get(supplier, {}).get("tax_withholding_category") rate = tax_rate_map.get(tax_withholding_category) @@ -176,6 +182,7 @@ def get_tds_docs(filters): journal_entries = [] tax_category_map = {} or_filters = {} + journal_entry_party_map = {} bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name") tds_accounts = frappe.get_all( @@ -218,9 +225,24 @@ def get_tds_docs(filters): get_tax_category_map(payment_entries, "Payment Entry", tax_category_map) if journal_entries: + journal_entry_party_map = get_journal_entry_party_map(journal_entries) get_tax_category_map(journal_entries, "Journal Entry", tax_category_map) - return tds_documents, tds_accounts, tax_category_map + return tds_documents, tds_accounts, tax_category_map, journal_entry_party_map + + +def get_journal_entry_party_map(journal_entries): + journal_entry_party_map = {} + for d in frappe.db.get_all( + "Journal Entry Account", + {"parent": ("in", journal_entries), "party_type": "Supplier", "party": ("is", "set")}, + ["parent", "party"], + ): + if d.parent not in journal_entry_party_map: + journal_entry_party_map[d.parent] = [] + journal_entry_party_map[d.parent].append(d.party) + + return journal_entry_party_map def get_tax_category_map(vouchers, doctype, tax_category_map): From 72e8c02ae0970bd24acbde4fc0759afa371d8a3b Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 19 Jul 2022 11:47:25 +0530 Subject: [PATCH 49/64] fix: Set Amount in Supplied Item table --- erpnext/controllers/subcontracting.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py index 70830882efa..2ed963cf91f 100644 --- a/erpnext/controllers/subcontracting.py +++ b/erpnext/controllers/subcontracting.py @@ -355,6 +355,8 @@ class Subcontracting: rm_obj.purchase_order = item_row.purchase_order self.__set_batch_nos(bom_item, item_row, rm_obj, qty) + rm_obj.amount = flt(rm_obj.required_qty) * flt(rm_obj.rate) + def __set_batch_nos(self, bom_item, item_row, rm_obj, qty): key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order) From f0ecdbef5a9aeb3b962da61c9ba8cd12a0a9ec10 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 4 Jul 2022 17:46:54 +0530 Subject: [PATCH 50/64] fix: LCV updates wrong future qty/Bin qty - As -ve LCV SLE case is returned from `repost_current_voucher`, future qty is not updated - This just doubly shifts all future qty which is then fixed by a repost - Until the repost balance values are wrong - Bin continues to show wrong projected qty even after repost, this is fixed by next SLE that recalculates Bin (cherry picked from commit 7a5fd71a6c8bd3dfacc8814cee564804bae78284) --- erpnext/stock/stock_ledger.py | 37 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 068cc12ed91..bd88d1e0bb1 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -94,27 +94,26 @@ def repost_current_voucher(args, allow_negative_stock=False, via_landed_cost_vou if not args.get("posting_date"): args["posting_date"] = nowdate() - if args.get("is_cancelled") and via_landed_cost_voucher: - return - - # Reposts only current voucher SL Entries - # Updates valuation rate, stock value, stock queue for current transaction - update_entries_after( - { - "item_code": args.get("item_code"), - "warehouse": args.get("warehouse"), - "posting_date": args.get("posting_date"), - "posting_time": args.get("posting_time"), - "voucher_type": args.get("voucher_type"), - "voucher_no": args.get("voucher_no"), - "sle_id": args.get("name"), - "creation": args.get("creation"), - }, - allow_negative_stock=allow_negative_stock, - via_landed_cost_voucher=via_landed_cost_voucher, - ) + if not (args.get("is_cancelled") and via_landed_cost_voucher): + # Reposts only current voucher SL Entries + # Updates valuation rate, stock value, stock queue for current transaction + update_entries_after( + { + "item_code": args.get("item_code"), + "warehouse": args.get("warehouse"), + "posting_date": args.get("posting_date"), + "posting_time": args.get("posting_time"), + "voucher_type": args.get("voucher_type"), + "voucher_no": args.get("voucher_no"), + "sle_id": args.get("name"), + "creation": args.get("creation"), + }, + allow_negative_stock=allow_negative_stock, + via_landed_cost_voucher=via_landed_cost_voucher, + ) # update qty in future sle and Validate negative qty + # For LCV: update future balances with -ve LCV SLE, which will be balanced by +ve LCV SLE update_qty_in_future_sle(args, allow_negative_stock) From ca9c6e1651ac3bdb0e4e4c001d9ad5edbfc5edf6 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 12 Jul 2022 13:00:00 +0530 Subject: [PATCH 51/64] test: LCV impact on future stock balancees (cherry picked from commit de9ea70ce369377798734381de99a7be15ab35fd) --- .../test_landed_cost_voucher.py | 67 +++++++++++++++++-- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 1af99534516..790d284a3dc 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -4,7 +4,7 @@ import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_to_date, flt, now +from frappe.utils import add_days, add_to_date, flt, now from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice @@ -120,6 +120,61 @@ class TestLandedCostVoucher(FrappeTestCase): expected_values[gle.account][1], gle.credit, msg=f"incorrect credit for {gle.account}" ) + def test_landed_cost_voucher_stock_impact(self): + "Test impact of LCV on future stock balances." + from erpnext.stock.doctype.item.test_item import make_item + + item = make_item("LCV Stock Item", {"is_stock_item": 1}) + warehouse = "Stores - _TC" + + pr1 = make_purchase_receipt( + item_code=item.name, + warehouse=warehouse, + qty=500, + rate=80, + posting_date=add_days(frappe.utils.nowdate(), -2), + ) + pr2 = make_purchase_receipt( + item_code=item.name, + warehouse=warehouse, + qty=100, + rate=80, + posting_date=frappe.utils.nowdate(), + ) + + last_sle = frappe.db.get_value( # SLE of second PR + "Stock Ledger Entry", + { + "voucher_type": pr2.doctype, + "voucher_no": pr2.name, + "item_code": item.name, + "warehouse": warehouse, + "is_cancelled": 0, + }, + fieldname=["qty_after_transaction", "stock_value"], + as_dict=1, + ) + + create_landed_cost_voucher("Purchase Receipt", pr1.name, pr1.company) + + last_sle_after_landed_cost = frappe.db.get_value( # SLE of second PR after LCV's effect + "Stock Ledger Entry", + { + "voucher_type": pr2.doctype, + "voucher_no": pr2.name, + "item_code": item.name, + "warehouse": warehouse, + "is_cancelled": 0, + }, + fieldname=["qty_after_transaction", "stock_value"], + as_dict=1, + ) + + self.assertEqual( + last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction + ) + self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0) + def test_landed_cost_voucher_against_purchase_invoice(self): pi = make_purchase_invoice( @@ -219,11 +274,11 @@ class TestLandedCostVoucher(FrappeTestCase): landed costs, this should be allowed for serial nos too. Case: - - receipt a serial no @ X rate - - delivery the serial no @ X rate - - add LCV to receipt X + Y - - LCV should be successful - - delivery should reflect X+Y valuation. + - receipt a serial no @ X rate + - delivery the serial no @ X rate + - add LCV to receipt X + Y + - LCV should be successful + - delivery should reflect X+Y valuation. """ serial_no = "LCV_TEST_SR_NO" item_code = "_Test Serialized Item" From cfb11f4b84eb9bb5ebefcbdfb1c6bd33ebad2d0a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 19 Jul 2022 12:32:54 +0530 Subject: [PATCH 52/64] test: Added test for PCV cancellation --- .../period_closing_voucher.py | 77 ++++++++++--------- .../test_period_closing_voucher.py | 11 ++- erpnext/accounts/general_ledger.py | 3 +- 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 7022a9e7144..385d64a6333 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -8,7 +8,6 @@ from frappe.utils import flt from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, - get_dimensions, ) from erpnext.accounts.utils import get_account_currency from erpnext.controllers.accounts_controller import AccountsController @@ -27,17 +26,19 @@ class PeriodClosingVoucher(AccountsController): self.status = "In Progress" self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") gle_count = frappe.db.count( - "GL Entry", - { - "voucher_type": "Period Closing Voucher", - "voucher_no": self.name, - "is_cancelled": 0 - } + "GL Entry", + {"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0}, ) if gle_count > 5000: - frappe.enqueue(make_reverse_gl_entries, voucher_type="Period Closing Voucher", - voucher_no=self.name, queue="long") - frappe.msgprint(_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True) + frappe.enqueue( + make_reverse_gl_entries, + voucher_type="Period Closing Voucher", + voucher_no=self.name, + queue="long", + ) + frappe.msgprint( + _("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True + ) else: make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name) @@ -76,13 +77,16 @@ class PeriodClosingVoucher(AccountsController): pce[0][0], self.posting_date ) ) - + def make_gl_entries(self): gl_entries = self.get_gl_entries() if gl_entries: if len(gl_entries) > 5000: frappe.enqueue(process_gl_entries, gl_entries=gl_entries, queue="long") - frappe.msgprint(_("The GL Entries will be processed in the background, it can take a few minutes."), alert=True) + frappe.msgprint( + _("The GL Entries will be processed in the background, it can take a few minutes."), + alert=True, + ) else: process_gl_entries(gl_entries) @@ -91,7 +95,7 @@ class PeriodClosingVoucher(AccountsController): # pl account for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True): - if flt(acc.bal_in_company_currency): + if flt(acc.bal_in_company_currency): gl_entries.append(self.get_gle_for_pl_account(acc)) # closing liability account @@ -100,7 +104,7 @@ class PeriodClosingVoucher(AccountsController): gl_entries.append(self.get_gle_for_closing_account(acc)) return gl_entries - + def get_gle_for_pl_account(self, acc): gl_entry = self.get_gl_dict( { @@ -109,23 +113,19 @@ class PeriodClosingVoucher(AccountsController): "finance_book": acc.finance_book, "account_currency": acc.account_currency, "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) - if flt(acc.bal_in_account_currency) < 0 - else 0, - "debit": abs(flt(acc.bal_in_company_currency)) - if flt(acc.bal_in_company_currency) < 0 - else 0, + if flt(acc.bal_in_account_currency) < 0 + else 0, + "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0, "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) - if flt(acc.bal_in_account_currency) > 0 - else 0, - "credit": abs(flt(acc.bal_in_company_currency)) - if flt(acc.bal_in_company_currency) > 0 - else 0, + if flt(acc.bal_in_account_currency) > 0 + else 0, + "credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0, }, item=acc, ) self.update_default_dimensions(gl_entry, acc) return gl_entry - + def get_gle_for_closing_account(self, acc): gl_entry = self.get_gl_dict( { @@ -136,15 +136,11 @@ class PeriodClosingVoucher(AccountsController): "debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0, - "debit": abs(flt(acc.bal_in_company_currency)) - if flt(acc.bal_in_company_currency) > 0 - else 0, + "debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0, "credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0, - "credit": abs(flt(acc.bal_in_company_currency)) - if flt(acc.bal_in_company_currency) < 0 - else 0, + "credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0, }, item=acc, ) @@ -162,7 +158,7 @@ class PeriodClosingVoucher(AccountsController): """Get balance for dimension-wise pl accounts""" dimension_fields = ["t1.cost_center", "t1.finance_book"] - + self.accounting_dimensions = get_accounting_dimensions() for dimension in self.accounting_dimensions: dimension_fields.append("t1.{0}".format(dimension)) @@ -170,7 +166,8 @@ class PeriodClosingVoucher(AccountsController): if group_by_account: dimension_fields.append("t1.account") - return frappe.db.sql(""" + return frappe.db.sql( + """ select t2.account_currency, {dimension_fields}, @@ -192,22 +189,30 @@ class PeriodClosingVoucher(AccountsController): as_dict=1, ) + def process_gl_entries(gl_entries): from erpnext.accounts.general_ledger import make_gl_entries + try: make_gl_entries(gl_entries, merge_entries=False) - frappe.db.set_value("Period Closing Voucher", gl_entries[0].get("voucher_no"), "status", "Completed") + frappe.db.set_value( + "Period Closing Voucher", gl_entries[0].get("voucher_no"), "status", "Completed" + ) except Exception as e: frappe.db.rollback() frappe.log_error(e) - frappe.db.set_value("Period Closing Voucher", gl_entries[0].get("voucher_no"), "status", "Failed") + frappe.db.set_value( + "Period Closing Voucher", gl_entries[0].get("voucher_no"), "status", "Failed" + ) + def make_reverse_gl_entries(voucher_type, voucher_no): from erpnext.accounts.general_ledger import make_reverse_gl_entries + try: make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no) frappe.db.set_value("Period Closing Voucher", voucher_no, "status", "Completed") except Exception as e: frappe.db.rollback() frappe.log_error(e) - frappe.db.set_value("Period Closing Voucher", gl_entries[0].get("voucher_no"), "status", "Failed") \ No newline at end of file + frappe.db.set_value("Period Closing Voucher", voucher_no, "status", "Failed") diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 59f0bced7f3..0142510ca37 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -93,7 +93,6 @@ class TestPeriodClosingVoucher(unittest.TestCase): ) pcv = self.make_period_closing_voucher(submit=False) - pcv.cost_center_wise_pnl = 1 pcv.save() pcv.submit() surplus_account = pcv.closing_account_head @@ -116,6 +115,16 @@ class TestPeriodClosingVoucher(unittest.TestCase): self.assertEqual(pcv_gle, expected_gle) + pcv.reload() + pcv.cancel() + + self.assertFalse( + frappe.db.get_value( + "GL Entry", + {"voucher_type": "Period Closing Voucher", "voucher_no": pcv.name, "is_cancelled": 0}, + ) + ) + def test_period_closing_with_finance_book_entries(self): frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 5736ac0e369..7525369d4f6 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -148,7 +148,7 @@ def update_net_values(entry): def merge_similar_entries(gl_map, precision=None): merged_gl_map = [] accounting_dimensions = get_accounting_dimensions() - + for entry in gl_map: # if there is already an entry in this account then just add it # to that entry @@ -222,6 +222,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False): for entry in gl_map: make_entry(entry, adv_adj, update_outstanding, from_repost) + def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle = frappe.new_doc("GL Entry") gle.update(args) From 8c9035d914406e3134e9516b7194d4c50662f37d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 19 Jul 2022 14:47:14 +0530 Subject: [PATCH 53/64] perf: Use `get_cached_value` and `get_cached_doc` --- erpnext/hr/doctype/employee/employee.py | 4 +++- erpnext/hr/doctype/employee_checkin/employee_checkin.py | 4 ++-- erpnext/hr/doctype/holiday_list/holiday_list.py | 4 +++- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 6 +++--- erpnext/hr/doctype/shift_type/shift_type.py | 4 ++-- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index d24e7038422..8aaae5956b0 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -349,7 +349,9 @@ def get_employee_email(employee_doc): def get_holiday_list_for_employee(employee, raise_exception=True): if employee: - holiday_list, company = frappe.db.get_value("Employee", employee, ["holiday_list", "company"]) + holiday_list, company = frappe.get_cached_value( + "Employee", employee, ["holiday_list", "company"] + ) else: holiday_list = "" company = frappe.db.get_value("Global Defaults", None, "default_company") diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py index 58618b6358c..8b0a60e20e9 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py @@ -131,7 +131,7 @@ def mark_attendance_and_link_log( return None elif attendance_status in ("Present", "Absent", "Half Day"): - employee_doc = frappe.get_doc("Employee", employee) + company = frappe.get_cached_value("Employee", employee, "company") duplicate = frappe.db.exists( "Attendance", {"employee": employee, "attendance_date": attendance_date, "docstatus": ("!=", "2")}, @@ -144,7 +144,7 @@ def mark_attendance_and_link_log( "attendance_date": attendance_date, "status": attendance_status, "working_hours": working_hours, - "company": employee_doc.company, + "company": company, "shift": shift, "late_entry": late_entry, "early_exit": early_exit, diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index de8d578d71c..1cf44536b59 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -115,6 +115,8 @@ def is_holiday(holiday_list, date=None): if date is None: date = today() if holiday_list: - return bool(frappe.db.exists("Holiday List", {"name": holiday_list, "holiday_date": date})) + return bool( + frappe.db.exists("Holiday", {"parent": holiday_list, "holiday_date": date}, cache=True) + ) else: return False diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 918b510df27..483d0fb1c41 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -169,7 +169,7 @@ def get_employee_shift( """ if for_date is None: for_date = nowdate() - default_shift = frappe.db.get_value("Employee", employee, "default_shift") + default_shift = frappe.get_cached_value("Employee", employee, "default_shift") shift_type_name = None shift_assignment_details = frappe.db.get_value( "Shift Assignment", @@ -187,7 +187,7 @@ def get_employee_shift( if not shift_type_name and consider_default_shift: shift_type_name = default_shift if shift_type_name: - holiday_list_name = frappe.db.get_value("Shift Type", shift_type_name, "holiday_list") + holiday_list_name = frappe.get_cached_value("Shift Type", shift_type_name, "holiday_list") if not holiday_list_name: holiday_list_name = get_holiday_list_for_employee(employee, False) if holiday_list_name and is_holiday(holiday_list_name, for_date): @@ -294,7 +294,7 @@ def get_shift_details(shift_type_name, for_date=None): return None if not for_date: for_date = nowdate() - shift_type = frappe.db.get_value( + shift_type = frappe.get_cached_value( "Shift Type", shift_type_name, [ diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index e4a40c0807f..791c791ad07 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -107,7 +107,7 @@ class ShiftType(Document): """Marks Absents for the given employee on working days in this shift which have no attendance marked. The Absent is marked starting from 'process_attendance_after' or employee creation date. """ - date_of_joining, relieving_date, employee_creation = frappe.db.get_value( + date_of_joining, relieving_date, employee_creation = frappe.get_cached_value( "Employee", employee, ["date_of_joining", "relieving_date", "creation"] ) if not date_of_joining: @@ -168,7 +168,7 @@ class ShiftType(Document): def process_auto_attendance_for_all_shifts(): shift_list = frappe.get_all("Shift Type", filters={"enable_auto_attendance": "1"}, pluck="name") for shift in shift_list: - doc = frappe.get_doc("Shift Type", shift) + doc = frappe.get_cached_doc("Shift Type", shift) doc.process_auto_attendance() From 6e6b55ece02c695203018a74cdd980bbe5c2ea4a Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 19 Jul 2022 15:31:34 +0530 Subject: [PATCH 54/64] fix: (india) (e-invoice) discount in CN/DN discount fixes --- erpnext/regional/india/e_invoice/utils.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 2addb4017c2..628de807c44 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -273,19 +273,17 @@ def get_item_list(invoice): item.sr_no = d.idx item.description = sanitize_for_json(d.item_name) - item.qty = abs(item.qty) + item.qty = abs(item.qty) or 1 + item.discount_amount = abs(item.discount_amount) + item.taxable_value = abs(item.taxable_value) hide_discount_in_einvoice = cint( frappe.db.get_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice") ) if hide_discount_in_einvoice: - if flt(item.qty) != 0.0: - item.unit_rate = abs(item.taxable_value / item.qty) - else: - item.unit_rate = abs(item.taxable_value) - item.gross_amount = abs(item.taxable_value) - item.taxable_value = abs(item.taxable_value) + item.unit_rate = item.taxable_value / item.qty + item.gross_amount = item.taxable_value item.discount_amount = 0 else: @@ -298,12 +296,10 @@ def get_item_list(invoice): item.discount_amount = item.discount_amount * item.qty if invoice.get("is_return") or invoice.get("is_debit_note"): - item.unit_rate = (abs(item.taxable_value) + item.discount_amount) / ( - 1 if (item.qty == 0) else item.qty - ) + item.unit_rate = (item.taxable_value + item.discount_amount) / item.qty else: try: - item.unit_rate = abs(item.taxable_value + item.discount_amount) / item.qty + item.unit_rate = (item.taxable_value + item.discount_amount) / item.qty except ZeroDivisionError: # This will never run but added as safety measure frappe.throw( @@ -311,8 +307,8 @@ def get_item_list(invoice): msg=_("Quantity can't be zero unless it's Credit/Debit Note."), ) - item.gross_amount = abs(item.taxable_value) + item.discount_amount - item.taxable_value = abs(item.taxable_value) + item.gross_amount = item.taxable_value + item.discount_amount + item.taxable_value = item.taxable_value item.is_service_item = "Y" if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else "N" item.serial_no = "" From 6938025952d5d702c2e28b5086f18c7c9cb3b41a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 19 Jul 2022 16:02:31 +0530 Subject: [PATCH 55/64] perf: index shift type and employee in checkins and assignment to avoid full table scans --- erpnext/hr/doctype/employee_checkin/employee_checkin.json | 8 +++++--- erpnext/hr/doctype/shift_assignment/shift_assignment.json | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.json b/erpnext/hr/doctype/employee_checkin/employee_checkin.json index d34316dc0f3..19ef1b3c0ea 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.json +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.json @@ -26,7 +26,8 @@ "fieldtype": "Link", "label": "Employee", "options": "Employee", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "fetch_from": "employee.employee_name", @@ -48,7 +49,8 @@ "fieldtype": "Link", "label": "Shift", "options": "Shift Type", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "column_break_4", @@ -107,7 +109,7 @@ } ], "links": [], - "modified": "2020-07-08 11:02:32.660986", + "modified": "2022-07-19 15:38:41.767539", "modified_by": "Administrator", "module": "HR", "name": "Employee Checkin", diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.json b/erpnext/hr/doctype/shift_assignment/shift_assignment.json index ce2a10f229f..c2df00be628 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.json +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.json @@ -25,7 +25,8 @@ "fieldtype": "Link", "label": "Employee", "options": "Employee", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "fetch_from": "employee.employee_name", @@ -48,7 +49,8 @@ "in_list_view": 1, "label": "Shift Type", "options": "Shift Type", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "fieldname": "column_break_3", @@ -105,7 +107,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-06-15 14:27:54.310773", + "modified": "2022-07-19 15:27:54.310773", "modified_by": "Administrator", "module": "HR", "name": "Shift Assignment", From 8eb9aaafe9850913b05e3983e9ae5361f049f00c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 19 Jul 2022 16:07:18 +0530 Subject: [PATCH 56/64] fix: move auto attendance job to long queue --- erpnext/hooks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 386ac0a2888..cf1714a25e1 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -483,12 +483,12 @@ scheduler_events = { "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization", "erpnext.projects.doctype.project.project.hourly_reminder", "erpnext.projects.doctype.project.project.collect_project_status", - "erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", "erpnext.support.doctype.issue.issue.set_service_level_agreement_variance", "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders", ], "hourly_long": [ - "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries" + "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries", + "erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", ], "daily": [ "erpnext.support.doctype.issue.issue.auto_close_tickets", From a5dae9264db5456531491cb7e2394919860b5ad3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 19 Jul 2022 16:36:22 +0530 Subject: [PATCH 57/64] fix: slow stock reposting (cherry picked from commit 1d80d37ccfd83a4b82ba3d573f71e6f2ab403289) --- erpnext/stock/stock_ledger.py | 41 ++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index bd88d1e0bb1..c3cf8ad56f4 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -207,8 +207,12 @@ def repost_future_sle( allow_negative_stock=None, via_landed_cost_voucher=False, ): - if not args and voucher_type and voucher_no: - args = get_items_to_be_repost(voucher_type, voucher_no, doc) + + items_to_be_repost = get_items_to_be_repost( + voucher_type=voucher_type, voucher_no=voucher_no, doc=doc + ) + if items_to_be_repost: + args = items_to_be_repost distinct_item_warehouses = get_distinct_item_warehouse(args, doc) affected_transactions = get_affected_transactions(doc) @@ -275,17 +279,21 @@ def update_args_in_repost_item_valuation( ) -def get_items_to_be_repost(voucher_type, voucher_no, doc=None): +def get_items_to_be_repost(voucher_type=None, voucher_no=None, doc=None): + items_to_be_repost = [] if doc and doc.items_to_be_repost: - return json.loads(doc.items_to_be_repost) or [] + items_to_be_repost = json.loads(doc.items_to_be_repost) or [] - return frappe.db.get_all( - "Stock Ledger Entry", - filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, - fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"], - order_by="creation asc", - group_by="item_code, warehouse", - ) + if not items_to_be_repost and voucher_type and voucher_no: + items_to_be_repost = frappe.db.get_all( + "Stock Ledger Entry", + filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, + fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"], + order_by="creation asc", + group_by="item_code, warehouse", + ) + + return items_to_be_repost def get_distinct_item_warehouse(args=None, doc=None): @@ -485,7 +493,8 @@ class update_entries_after(object): elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data: return entries_to_fix else: - self.append_future_sle_for_dependant(dependant_sle, entries_to_fix) + self.initialize_previous_data(dependant_sle) + self.update_distinct_item_warehouses(dependant_sle) return entries_to_fix def update_distinct_item_warehouses(self, dependant_sle): @@ -503,14 +512,6 @@ class update_entries_after(object): self.distinct_item_warehouses[key] = val self.new_items_found = True - def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix): - self.initialize_previous_data(dependant_sle) - self.distinct_item_warehouses[(self.item_code, dependant_sle.warehouse)] = frappe._dict( - {"sle": dependant_sle} - ) - - self.new_items_found = True - def process_sle(self, sle): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos From b0efb98237ecad71bb37c92eb579e790219be049 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 19 Jul 2022 22:29:38 +0530 Subject: [PATCH 58/64] fix: (india) (e-invoice) qty should not be changed to 1 Qty of 0 is allowed so can't change item.qty to 1 instead created item_qty and used it. --- erpnext/regional/india/e_invoice/utils.py | 32 ++++++++++++----------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 628de807c44..bf304bc10dc 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -273,39 +273,41 @@ def get_item_list(invoice): item.sr_no = d.idx item.description = sanitize_for_json(d.item_name) - item.qty = abs(item.qty) or 1 + item.qty = abs(item.qty) + item_qty = item.qty + item.discount_amount = abs(item.discount_amount) item.taxable_value = abs(item.taxable_value) + if invoice.get("is_return") or invoice.get("is_debit_note"): + item_qty = item_qty or 1 + hide_discount_in_einvoice = cint( frappe.db.get_single_value("E Invoice Settings", "dont_show_discounts_in_e_invoice") ) if hide_discount_in_einvoice: - item.unit_rate = item.taxable_value / item.qty + item.unit_rate = item.taxable_value / item_qty item.gross_amount = item.taxable_value item.discount_amount = 0 else: if invoice.get("apply_discount_on") and (abs(invoice.get("base_discount_amount") or 0.0) > 0.0): # TODO: need to handle case when tax included in basic rate is checked. - item.discount_amount = (item.discount_amount * item.qty) + ( + item.discount_amount = (item.discount_amount * item_qty) + ( abs(item.base_amount) - abs(item.base_net_amount) ) else: - item.discount_amount = item.discount_amount * item.qty + item.discount_amount = item.discount_amount * item_qty - if invoice.get("is_return") or invoice.get("is_debit_note"): - item.unit_rate = (item.taxable_value + item.discount_amount) / item.qty - else: - try: - item.unit_rate = (item.taxable_value + item.discount_amount) / item.qty - except ZeroDivisionError: - # This will never run but added as safety measure - frappe.throw( - title=_("Error: Qty is Zero"), - msg=_("Quantity can't be zero unless it's Credit/Debit Note."), - ) + try: + item.unit_rate = (item.taxable_value + item.discount_amount) / item_qty + except ZeroDivisionError: + # This will never run but added as safety measure + frappe.throw( + title=_("Error: Qty is Zero"), + msg=_("Quantity can't be zero unless it's Credit/Debit Note."), + ) item.gross_amount = item.taxable_value + item.discount_amount item.taxable_value = item.taxable_value From 118013506775ab6546c4720674217fb22f9dafc5 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 20 Jul 2022 10:50:44 +0530 Subject: [PATCH 59/64] fix: Renamed status field to gle_processing_status --- .../period_closing_voucher.json | 8 ++++---- .../period_closing_voucher/period_closing_voucher.py | 12 ++++++------ .../test_period_closing_voucher.py | 1 + 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json index 95742640c96..54a76b34196 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json @@ -13,7 +13,7 @@ "column_break1", "closing_account_head", "remarks", - "status", + "gle_processing_status", "error_message" ], "fields": [ @@ -88,14 +88,14 @@ }, { "depends_on": "eval:doc.docstatus!=0", - "fieldname": "status", + "fieldname": "gle_processing_status", "fieldtype": "Select", "label": "GL Entry Processing Status", "options": "In Progress\nCompleted\nFailed", "read_only": 1 }, { - "depends_on": "eval:doc.status=='Failed'", + "depends_on": "eval:doc.gle_processing_status=='Failed'", "fieldname": "error_message", "fieldtype": "Text", "label": "Error Message", @@ -106,7 +106,7 @@ "idx": 1, "is_submittable": 1, "links": [], - "modified": "2022-07-15 14:51:04.714154", + "modified": "2022-07-20 14:51:04.714154", "modified_by": "Administrator", "module": "Accounts", "name": "Period Closing Voucher", diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 385d64a6333..a76cc18c69e 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -19,11 +19,11 @@ class PeriodClosingVoucher(AccountsController): self.validate_posting_date() def on_submit(self): - self.status = "In Progress" + self.gle_processing_status = "In Progress" self.make_gl_entries() def on_cancel(self): - self.status = "In Progress" + self.gle_processing_status = "In Progress" self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") gle_count = frappe.db.count( "GL Entry", @@ -196,13 +196,13 @@ def process_gl_entries(gl_entries): try: make_gl_entries(gl_entries, merge_entries=False) frappe.db.set_value( - "Period Closing Voucher", gl_entries[0].get("voucher_no"), "status", "Completed" + "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed" ) except Exception as e: frappe.db.rollback() frappe.log_error(e) frappe.db.set_value( - "Period Closing Voucher", gl_entries[0].get("voucher_no"), "status", "Failed" + "Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed" ) @@ -211,8 +211,8 @@ def make_reverse_gl_entries(voucher_type, voucher_no): try: make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no) - frappe.db.set_value("Period Closing Voucher", voucher_no, "status", "Completed") + frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Completed") except Exception as e: frappe.db.rollback() frappe.log_error(e) - frappe.db.set_value("Period Closing Voucher", voucher_no, "status", "Failed") + frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Failed") diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 0142510ca37..7999160a5eb 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -59,6 +59,7 @@ class TestPeriodClosingVoucher(unittest.TestCase): """, (pcv.name), ) + self.assetEqual(pcv.gle_processing_status, "Completed") self.assertEqual(pcv_gle, expected_gle) def test_cost_center_wise_posting(self): From 04b077a89ff0c447343726e2a3d045991a136b08 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 20 Jul 2022 10:55:10 +0530 Subject: [PATCH 60/64] fix: set status on submit/cancel --- .../doctype/period_closing_voucher/period_closing_voucher.py | 4 ++-- .../period_closing_voucher/test_period_closing_voucher.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index a76cc18c69e..866a94d04b1 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -19,11 +19,11 @@ class PeriodClosingVoucher(AccountsController): self.validate_posting_date() def on_submit(self): - self.gle_processing_status = "In Progress" + self.db_set("gle_processing_status", "In Progress") self.make_gl_entries() def on_cancel(self): - self.gle_processing_status = "In Progress" + self.db_set("gle_processing_status", "In Progress") self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") gle_count = frappe.db.count( "GL Entry", diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index 7999160a5eb..93869ed6c04 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -59,7 +59,8 @@ class TestPeriodClosingVoucher(unittest.TestCase): """, (pcv.name), ) - self.assetEqual(pcv.gle_processing_status, "Completed") + pcv.reload() + self.assertEqual(pcv.gle_processing_status, "Completed") self.assertEqual(pcv_gle, expected_gle) def test_cost_center_wise_posting(self): From 616e3c66b9f8da898fd4e3559e0b2a9ec4aaf910 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 20 Jul 2022 16:42:55 +0530 Subject: [PATCH 61/64] fix: Removed 'Allow Monthly Depreciation' checkbox and fixed wdv depreciation rate --- erpnext/assets/doctype/asset/asset.json | 12 +--- erpnext/assets/doctype/asset/asset.py | 67 +++---------------- erpnext/assets/doctype/asset/test_asset.py | 17 +++-- erpnext/patches.txt | 3 +- ..._and_frequency_for_monthly_depreciation.py | 16 +++++ erpnext/stock/stock_ledger.py | 2 + 6 files changed, 43 insertions(+), 74 deletions(-) create mode 100644 erpnext/patches/v13_0/fix_number_and_frequency_for_monthly_depreciation.py diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 411e1efe8ea..04e9c32f379 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -38,7 +38,6 @@ "purchase_date", "section_break_23", "calculate_depreciation", - "allow_monthly_depreciation", "column_break_33", "opening_accumulated_depreciation", "number_of_depreciations_booked", @@ -454,13 +453,6 @@ "fieldname": "dimension_col_break", "fieldtype": "Column Break" }, - { - "default": "0", - "depends_on": "calculate_depreciation", - "fieldname": "allow_monthly_depreciation", - "fieldtype": "Check", - "label": "Allow Monthly Depreciation" - }, { "collapsible": 1, "collapsible_depends_on": "is_existing_asset", @@ -503,7 +495,7 @@ "link_fieldname": "asset" } ], - "modified": "2022-01-30 20:19:24.680027", + "modified": "2022-07-20 16:22:44.437579", "modified_by": "Administrator", "module": "Assets", "name": "Asset", @@ -545,4 +537,4 @@ "sort_order": "DESC", "title_field": "asset_name", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 4d63a285534..1ce815ae2bd 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -353,61 +353,16 @@ class Asset(AccountsController): skip_row = True if depreciation_amount > 0: - # With monthly depreciation, each depreciation is divided by months remaining until next date - if self.allow_monthly_depreciation: - # month range is 1 to 12 - # In pro rata case, for first and last depreciation, month range would be different - if (has_pro_rata and n == 0 and not self.number_of_depreciations_booked) or ( - has_pro_rata and n == cint(number_of_pending_depreciations) - 1 - ): - month_range = months - else: - month_range = finance_book.frequency_of_depreciation - - for r in range(month_range): - if has_pro_rata and n == 0 and not self.number_of_depreciations_booked: - # For first entry of monthly depr - if r == 0: - days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date) + 1 - per_day_amt = depreciation_amount / days - depreciation_amount_for_current_month = per_day_amt * days_until_first_depr - depreciation_amount -= depreciation_amount_for_current_month - date = monthly_schedule_date - amount = depreciation_amount_for_current_month - else: - date = add_months(monthly_schedule_date, r) - amount = depreciation_amount / (month_range - 1) - elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint( - month_range - ) - 1: - # For last entry of monthly depr - date = last_schedule_date - amount = depreciation_amount / month_range - else: - date = add_months(monthly_schedule_date, r) - amount = depreciation_amount / month_range - - self.append( - "schedules", - { - "schedule_date": date, - "depreciation_amount": amount, - "depreciation_method": finance_book.depreciation_method, - "finance_book": finance_book.finance_book, - "finance_book_id": finance_book.idx, - }, - ) - else: - self.append( - "schedules", - { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": finance_book.depreciation_method, - "finance_book": finance_book.finance_book, - "finance_book_id": finance_book.idx, - }, - ) + self.append( + "schedules", + { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": finance_book.depreciation_method, + "finance_book": finance_book.finance_book, + "finance_book_id": finance_book.idx, + }, + ) # depreciation schedules need to be cleared before modification due to increase in asset life/asset sales # JE: Journal Entry, FB: Finance Book @@ -853,7 +808,7 @@ class Asset(AccountsController): depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2)) - return 100 * (1 - flt(depreciation_rate, float_precision)) + return flt((100 * (1 - depreciation_rate)), float_precision) def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date): days = date_diff(to_date, from_date) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 9bfafaf8657..13475f34c39 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -145,6 +145,7 @@ class TestAsset(AssetSetup): def test_is_fixed_asset_set(self): asset = create_asset(is_existing_asset=1) doc = frappe.new_doc("Purchase Invoice") + doc.company = "_Test Company" doc.supplier = "_Test Supplier" doc.append("items", {"item_code": "Macbook Pro", "qty": 1, "asset": asset.name}) @@ -702,6 +703,8 @@ class TestDepreciationMethods(AssetSetup): self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0) def test_monthly_depreciation_by_wdv_method(self): + existing_precision = frappe.db.get_default("float_precision") + frappe.db.set_default("float_precision", 3) asset = create_asset( calculate_depreciation=1, available_for_use_date="2022-02-15", @@ -715,12 +718,12 @@ class TestDepreciationMethods(AssetSetup): ) expected_schedules = [ - ["2022-02-28", 645.0, 645.0], - ["2022-03-31", 1206.8, 1851.8], - ["2022-04-30", 1051.12, 2902.92], - ["2022-05-31", 915.52, 3818.44], - ["2022-06-30", 797.42, 4615.86], - ["2022-07-15", 384.14, 5000.0], + ["2022-02-28", 647.25, 647.25], + ["2022-03-31", 1210.71, 1857.96], + ["2022-04-30", 1053.99, 2911.95], + ["2022-05-31", 917.55, 3829.5], + ["2022-06-30", 798.77, 4628.27], + ["2022-07-15", 371.73, 5000.0], ] schedules = [ @@ -731,8 +734,8 @@ class TestDepreciationMethods(AssetSetup): ] for d in asset.get("schedules") ] - self.assertEqual(schedules, expected_schedules) + frappe.db.set_default("float_precision", existing_precision) class TestDepreciationBasics(AssetSetup): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bdc43dd094a..8e8e2b25299 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -368,4 +368,5 @@ erpnext.patches.v13_0.set_per_billed_in_return_delivery_note erpnext.patches.v13_0.update_employee_advance_status erpnext.patches.v13_0.job_card_status_on_hold erpnext.patches.v13_0.add_cost_center_in_loans -erpnext.patches.v13_0.show_india_localisation_deprecation_warning \ No newline at end of file +erpnext.patches.v13_0.show_india_localisation_deprecation_warning +erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation diff --git a/erpnext/patches/v13_0/fix_number_and_frequency_for_monthly_depreciation.py b/erpnext/patches/v13_0/fix_number_and_frequency_for_monthly_depreciation.py new file mode 100644 index 00000000000..fe645c81b88 --- /dev/null +++ b/erpnext/patches/v13_0/fix_number_and_frequency_for_monthly_depreciation.py @@ -0,0 +1,16 @@ +import frappe + + +def execute(): + if not frappe.db.has_column("Asset", "allow_monthly_depreciation"): + return + + assets = frappe.get_all("Asset", filters={"allow_monthly_depreciation": 1}) + for d in assets: + print(d.name) + asset_doc = frappe.get_doc("Asset", d.name) + for i in asset_doc.get("finance_books"): + if i.frequency_of_depreciation != 1: + i.total_number_of_depreciations *= i.frequency_of_depreciation + i.frequency_of_depreciation = 1 + i.db_update() diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c3cf8ad56f4..4e64b7b1833 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -207,6 +207,8 @@ def repost_future_sle( allow_negative_stock=None, via_landed_cost_voucher=False, ): + if not args: + args = [] # set args to empty list if None to avoid enumerate error items_to_be_repost = get_items_to_be_repost( voucher_type=voucher_type, voucher_no=voucher_no, doc=doc From 55399f2e5837b424a5489823fd73e376e9cfbad6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 Jul 2022 18:59:36 +0530 Subject: [PATCH 62/64] fix: Patch to make accounting dimension in orders (cherry picked from commit d46e406db78ef490f461dbe01918fd02fffbb5fd) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 6 ++++++ .../v13_0/create_accounting_dimensions_in_orders.py | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8e8e2b25299..f77ad02c95f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -363,7 +363,13 @@ erpnext.patches.v13_0.copy_custom_field_filters_to_website_item erpnext.patches.v13_0.set_available_for_use_date_if_missing erpnext.patches.v13_0.education_deprecation_warning erpnext.patches.v13_0.requeue_recoverable_reposts +<<<<<<< HEAD erpnext.patches.v13_0.create_accounting_dimensions_in_orders +======= +erpnext.patches.v14_0.discount_accounting_separation +erpnext.patches.v14_0.delete_employee_transfer_property_doctype +erpnext.patches.v13_0.create_accounting_dimensions_in_orders #1 +>>>>>>> d46e406db7 (fix: Patch to make accounting dimension in orders) erpnext.patches.v13_0.set_per_billed_in_return_delivery_note erpnext.patches.v13_0.update_employee_advance_status erpnext.patches.v13_0.job_card_status_on_hold diff --git a/erpnext/patches/v13_0/create_accounting_dimensions_in_orders.py b/erpnext/patches/v13_0/create_accounting_dimensions_in_orders.py index 7e6e8200852..8e6bce66fe4 100644 --- a/erpnext/patches/v13_0/create_accounting_dimensions_in_orders.py +++ b/erpnext/patches/v13_0/create_accounting_dimensions_in_orders.py @@ -33,7 +33,10 @@ def execute(): "insert_after": insert_after_field, } - create_custom_field(doctype, df, ignore_validate=True) - frappe.clear_cache(doctype=doctype) + try: + create_custom_field(doctype, df, ignore_validate=True) + frappe.clear_cache(doctype=doctype) + except Exception as e: + pass count += 1 From 45e25b2d56452d01334cc7ee53bcec05bf7b927f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 Jul 2022 19:01:14 +0530 Subject: [PATCH 63/64] chore: do not re run patch (cherry picked from commit cad2035e07ea332e59cdd4707586435536a54666) # Conflicts: # erpnext/patches.txt --- erpnext/patches.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f77ad02c95f..44c66c6f527 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -368,8 +368,12 @@ erpnext.patches.v13_0.create_accounting_dimensions_in_orders ======= erpnext.patches.v14_0.discount_accounting_separation erpnext.patches.v14_0.delete_employee_transfer_property_doctype +<<<<<<< HEAD erpnext.patches.v13_0.create_accounting_dimensions_in_orders #1 >>>>>>> d46e406db7 (fix: Patch to make accounting dimension in orders) +======= +erpnext.patches.v13_0.create_accounting_dimensions_in_orders +>>>>>>> cad2035e07 (chore: do not re run patch) erpnext.patches.v13_0.set_per_billed_in_return_delivery_note erpnext.patches.v13_0.update_employee_advance_status erpnext.patches.v13_0.job_card_status_on_hold From e36c74ac444726d910550d241e6ac2a34dc8cefc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 Jul 2022 19:39:18 +0530 Subject: [PATCH 64/64] chore: resolve conflicts --- erpnext/patches.txt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 44c66c6f527..8e8e2b25299 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -363,17 +363,7 @@ erpnext.patches.v13_0.copy_custom_field_filters_to_website_item erpnext.patches.v13_0.set_available_for_use_date_if_missing erpnext.patches.v13_0.education_deprecation_warning erpnext.patches.v13_0.requeue_recoverable_reposts -<<<<<<< HEAD erpnext.patches.v13_0.create_accounting_dimensions_in_orders -======= -erpnext.patches.v14_0.discount_accounting_separation -erpnext.patches.v14_0.delete_employee_transfer_property_doctype -<<<<<<< HEAD -erpnext.patches.v13_0.create_accounting_dimensions_in_orders #1 ->>>>>>> d46e406db7 (fix: Patch to make accounting dimension in orders) -======= -erpnext.patches.v13_0.create_accounting_dimensions_in_orders ->>>>>>> cad2035e07 (chore: do not re run patch) erpnext.patches.v13_0.set_per_billed_in_return_delivery_note erpnext.patches.v13_0.update_employee_advance_status erpnext.patches.v13_0.job_card_status_on_hold