From b55d8597e8dffee0412c3806f76b99ac1920e35c Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:14:38 +0200 Subject: [PATCH 01/29] fix: translatable web footer (#40834) (cherry picked from commit f3bcdbe5bd1a5b1da0b02a6c8fe0b213f59a62f8) --- erpnext/templates/includes/footer/footer_powered.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html index faf5e9278c7..8310063e575 100644 --- a/erpnext/templates/includes/footer/footer_powered.html +++ b/erpnext/templates/includes/footer/footer_powered.html @@ -1 +1 @@ -Powered by ERPNext +{{ _("Powered by {0}").format('ERPNext') }} From 9dea6d3393c6f02feed666e05d3ae792bee2afc1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 4 Apr 2024 12:06:46 +0530 Subject: [PATCH 02/29] fix: group warehouse added in the stock reconciliation (cherry picked from commit 8f53bc00969e8be6a8e40d1e9254b348917f31ed) --- .../stock_reconciliation.py | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 878af0e22ca..c7608cc2dd9 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -744,7 +744,9 @@ def get_items( warehouse, posting_date, posting_time, company, item_code=None, ignore_empty_stock=False ): ignore_empty_stock = cint(ignore_empty_stock) - items = [frappe._dict({"item_code": item_code, "warehouse": warehouse})] + items = [] + if item_code and warehouse: + items = get_item_and_warehouses(item_code, warehouse) if not item_code: items = get_items_for_stock_reco(warehouse, company) @@ -789,6 +791,20 @@ def get_items( return res +def get_item_and_warehouses(item_code, warehouse): + from frappe.utils.nestedset import get_descendants_of + + items = [] + if frappe.get_cached_value("Warehouse", warehouse, "is_group"): + childrens = get_descendants_of("Warehouse", warehouse, ignore_permissions=True, order_by="lft") + for ch_warehouse in childrens: + items.append(frappe._dict({"item_code": item_code, "warehouse": ch_warehouse})) + else: + items = [frappe._dict({"item_code": item_code, "warehouse": warehouse})] + + return items + + def get_items_for_stock_reco(warehouse, company): lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) items = frappe.db.sql( @@ -803,7 +819,7 @@ def get_items_for_stock_reco(warehouse, company): and i.is_stock_item = 1 and i.has_variants = 0 and exists( - select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse + select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse and is_group = 0 ) """, as_dict=1, @@ -818,7 +834,7 @@ def get_items_for_stock_reco(warehouse, company): where i.name = id.parent and exists( - select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse + select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse and is_group = 0 ) and i.is_stock_item = 1 and i.has_variants = 0 @@ -880,7 +896,7 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None): frappe._dict( { "item_code": row[0], - "warehouse": warehouse, + "warehouse": row[3], "qty": row[8], "item_name": row[1], "batch_no": row[4], From 09cda60bdf11444bb05d6ec8968deffee59559f7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 4 Apr 2024 11:58:05 +0530 Subject: [PATCH 03/29] fix: use reference type name to update exc rate (cherry picked from commit c15690e475f5f1f19d635bb78e6f1261ad9bcb1f) --- .../doctype/payment_entry/payment_entry.py | 10 +++++++--- erpnext/accounts/utils.py | 14 +++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e0e8e2154a6..fefb331ff27 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -338,7 +338,7 @@ class PaymentEntry(AccountsController): self, force: bool = False, update_ref_details_only_for: list | None = None, - ref_exchange_rate: float | None = None, + reference_exchange_details: dict | None = None, ) -> None: for d in self.get("references"): if d.allocated_amount: @@ -352,8 +352,12 @@ class PaymentEntry(AccountsController): ) # Only update exchange rate when the reference is Journal Entry - if ref_exchange_rate and d.reference_doctype == "Journal Entry": - ref_details.update({"exchange_rate": ref_exchange_rate}) + if ( + reference_exchange_details + and d.reference_doctype == reference_exchange_details.reference_doctype + and d.reference_name == reference_exchange_details.reference_name + ): + ref_details.update({"exchange_rate": reference_exchange_details.exchange_rate}) for field, value in ref_details.items(): if d.exchange_gain_loss: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6ca4aa2ada6..a8338e26a2c 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -682,7 +682,19 @@ def update_reference_in_payment_entry( payment_entry.setup_party_account_field() payment_entry.set_missing_values() if not skip_ref_details_update_for_pe: - payment_entry.set_missing_ref_details(ref_exchange_rate=d.exchange_rate or None) + reference_exchange_details = frappe._dict() + if d.against_voucher_type == "Journal Entry" and d.exchange_rate: + reference_exchange_details.update( + { + "reference_doctype": d.against_voucher_type, + "reference_name": d.against_voucher, + "exchange_rate": d.exchange_rate, + } + ) + payment_entry.set_missing_ref_details( + update_ref_details_only_for=[(d.against_voucher_type, d.against_voucher)], + reference_exchange_details=reference_exchange_details, + ) payment_entry.set_amounts() payment_entry.make_exchange_gain_loss_journal( From 8f2fd5dec93205e816bb976da14220aa2e5ccb7b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 4 Apr 2024 14:42:06 +0530 Subject: [PATCH 04/29] test: payment against JE reconciliation with different rates (cherry picked from commit fe84558b772a345845e67f1423c6955620153fbb) --- .../tests/test_accounts_controller.py | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index e39d03dccb9..e348d296cd7 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -56,7 +56,8 @@ class TestAccountsController(FrappeTestCase): 20 series - Sales Invoice against Journals 30 series - Sales Invoice against Credit Notes 40 series - Company default Cost center is unset - 50 series = Journals against Journals + 50 series - Journals against Journals + 60 series - Journals against Payment Entries 90 series - Dimension inheritence """ @@ -1574,3 +1575,70 @@ class TestAccountsController(FrappeTestCase): exc_je_for_je = self.get_journals_for(journal_as_payment.doctype, journal_as_payment.name) self.assertEqual(exc_je_for_si, []) self.assertEqual(exc_je_for_je, []) + + def test_60_payment_entry_against_journal(self): + # Invoices + exc_rate1 = 75 + exc_rate2 = 77 + amount = 1 + je1 = self.create_journal_entry( + acc1=self.debit_usd, + acc1_exc_rate=exc_rate1, + acc2=self.cash, + acc1_amount=amount, + acc2_amount=(amount * 75), + acc2_exc_rate=1, + ) + je1.accounts[0].party_type = "Customer" + je1.accounts[0].party = self.customer + je1 = je1.save().submit() + + je2 = self.create_journal_entry( + acc1=self.debit_usd, + acc1_exc_rate=exc_rate2, + acc2=self.cash, + acc1_amount=amount, + acc2_amount=(amount * exc_rate2), + acc2_exc_rate=1, + ) + je2.accounts[0].party_type = "Customer" + je2.accounts[0].party = self.customer + je2 = je2.save().submit() + + # Payment + pe = self.create_payment_entry(amount=2, source_exc_rate=exc_rate1).save().submit() + + pr = self.create_payment_reconciliation() + pr.receivable_payable_account = self.debit_usd + pr.get_unreconciled_entries() + self.assertEqual(len(pr.invoices), 2) + self.assertEqual(len(pr.payments), 1) + invoices = [x.as_dict() for x in pr.invoices] + payments = [x.as_dict() for x in pr.payments] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.reconcile() + self.assertEqual(len(pr.invoices), 0) + self.assertEqual(len(pr.payments), 0) + + # There should be no outstanding in both currencies + self.assert_ledger_outstanding(je1.doctype, je1.name, 0.0, 0.0) + self.assert_ledger_outstanding(je2.doctype, je2.name, 0.0, 0.0) + + # Exchange Gain/Loss Journal should've been created only for JE2 + exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name) + exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name) + self.assertEqual(exc_je_for_je1, []) + self.assertEqual(len(exc_je_for_je2), 1) + + # Cancel Payment + pe.reload() + pe.cancel() + + self.assert_ledger_outstanding(je1.doctype, je1.name, (amount * exc_rate1), amount) + self.assert_ledger_outstanding(je2.doctype, je2.name, (amount * exc_rate2), amount) + + # Exchange Gain/Loss Journal should've been cancelled + exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name) + exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name) + self.assertEqual(exc_je_for_je1, []) + self.assertEqual(exc_je_for_je2, []) From a6145fa13c7dc6d5334cef7aeb9d712c2c9f4c27 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 7 Apr 2024 11:48:07 +0530 Subject: [PATCH 05/29] fix: query_report.trigger_refresh is not a function (cherry picked from commit 30bbb58ca146604da9b2dd362717ded1470adaa5) --- erpnext/stock/report/item_prices/item_prices.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/stock/report/item_prices/item_prices.js b/erpnext/stock/report/item_prices/item_prices.js index 868b503eb73..6724c1a2909 100644 --- a/erpnext/stock/report/item_prices/item_prices.js +++ b/erpnext/stock/report/item_prices/item_prices.js @@ -9,9 +9,6 @@ frappe.query_reports["Item Prices"] = { fieldtype: "Select", options: "Enabled Items only\nDisabled Items only\nAll Items", default: "Enabled Items only", - on_change: function (query_report) { - query_report.trigger_refresh(); - }, }, ], }; From 7e1ab75b382d18d8453d59c496f20641567851de Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 6 Apr 2024 15:29:00 +0530 Subject: [PATCH 06/29] fix: incorrect operator causing incorrect validation (cherry picked from commit 6b317b0c0d94f8c65743bedb444ef0a3d10beaa6) --- .../doctype/closing_stock_balance/closing_stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py index e905f673365..c762ad68adc 100644 --- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py +++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py @@ -44,7 +44,7 @@ class ClosingStockBalance(Document): & ( (table.from_date.between(self.from_date, self.to_date)) | (table.to_date.between(self.from_date, self.to_date)) - | (table.from_date >= self.from_date and table.to_date >= self.to_date) + | ((table.from_date >= self.from_date) & (table.to_date >= self.to_date)) ) ) ) From 10d760089e3ad44859c89dac2250c296c68dc764 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sun, 7 Apr 2024 14:08:45 +0530 Subject: [PATCH 07/29] fix: Get pro-rata depr amount based on correct days --- erpnext/assets/doctype/asset/asset.py | 84 +++++++++++++--------- erpnext/assets/doctype/asset/test_asset.py | 42 ++++++----- 2 files changed, 75 insertions(+), 51 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 7755f3e0b64..95c627735d6 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -437,6 +437,7 @@ class Asset(AccountsController): depreciation_amount, from_date, date_of_disposal, + original_schedule_date=schedule_date, ) if depreciation_amount > 0: @@ -1118,14 +1119,20 @@ class Asset(AccountsController): return flt((100 * (1 - depreciation_rate)), float_precision) def get_pro_rata_amt( - self, row, depreciation_amount, from_date, to_date, has_wdv_or_dd_non_yearly_pro_rata=False + self, + row, + depreciation_amount, + from_date, + to_date, + has_wdv_or_dd_non_yearly_pro_rata=False, + original_schedule_date=None, ): days = date_diff(to_date, from_date) months = month_diff(to_date, from_date) if has_wdv_or_dd_non_yearly_pro_rata: - total_days = get_total_days(to_date, 12) + total_days = get_total_days(original_schedule_date or to_date, 12) else: - total_days = get_total_days(to_date, row.frequency_of_depreciation) + total_days = get_total_days(original_schedule_date or to_date, row.frequency_of_depreciation) return (depreciation_amount * flt(days)) / flt(total_days), days, months @@ -1445,32 +1452,35 @@ def get_straight_line_or_manual_depr_amount( # if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value elif asset.flags.decrease_in_asset_value_due_to_value_adjustment: if row.daily_prorata_based: - daily_depr_amount = ( - flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) - ) / date_diff( - get_last_day( - add_months( - row.depreciation_start_date, - flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) - * row.frequency_of_depreciation, - ) - ), - add_days( + amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life) + total_days = ( + date_diff( get_last_day( add_months( row.depreciation_start_date, - flt( - row.total_number_of_depreciations - - asset.number_of_depreciations_booked - - number_of_pending_depreciations - - 1 - ) + flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) * row.frequency_of_depreciation, ) ), - 1, - ), + add_days( + get_last_day( + add_months( + row.depreciation_start_date, + flt( + row.total_number_of_depreciations + - asset.number_of_depreciations_booked + - number_of_pending_depreciations + - 1 + ) + * row.frequency_of_depreciation, + ) + ), + 1, + ), + ) + + 1 ) + daily_depr_amount = amount / total_days to_date = get_last_day( add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) @@ -1490,22 +1500,28 @@ def get_straight_line_or_manual_depr_amount( # if the Depreciation Schedule is being prepared for the first time else: if row.daily_prorata_based: - daily_depr_amount = ( + + amount = ( flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) - flt(row.expected_value_after_useful_life) - ) / date_diff( - get_last_day( - add_months( - row.depreciation_start_date, - flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) - * row.frequency_of_depreciation, - ) - ), - add_days( - get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), 1 - ), ) + total_days = ( + date_diff( + get_last_day( + add_months( + row.depreciation_start_date, + flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1) + * row.frequency_of_depreciation, + ) + ), + add_days( + get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), 1 + ), + ) + + 1 + ) + daily_depr_amount = amount / total_days to_date = get_last_day( add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 708172b2980..ea5b95aaf02 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -219,7 +219,11 @@ class TestAsset(AssetSetup): asset.precision("gross_purchase_amount"), ) pro_rata_amount, _, _ = asset.get_pro_rata_amt( - asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date + asset.finance_books[0], + 9000, + get_last_day(add_months(purchase_date, 1)), + date, + original_schedule_date=get_last_day(nowdate()), ) pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) self.assertEquals( @@ -287,7 +291,11 @@ class TestAsset(AssetSetup): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") pro_rata_amount, _, _ = asset.get_pro_rata_amt( - asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date + asset.finance_books[0], + 9000, + get_last_day(add_months(purchase_date, 1)), + date, + original_schedule_date=get_last_day(nowdate()), ) pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount")) @@ -349,7 +357,7 @@ class TestAsset(AssetSetup): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") - expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1742.47, 37742.47]] + expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1737.7, 37737.7]] for i, schedule in enumerate(asset.schedules): self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) @@ -360,7 +368,7 @@ class TestAsset(AssetSetup): expected_gle = ( ( "_Test Accumulated Depreciations - _TC", - 37742.47, + 37737.7, 0.0, ), ( @@ -371,7 +379,7 @@ class TestAsset(AssetSetup): ( "_Test Gain/Loss on Asset Disposal - _TC", 0.0, - 17742.47, + 17737.7, ), ("Debtors - _TC", 40000.0, 0.0), ) @@ -691,18 +699,18 @@ class TestDepreciationMethods(AssetSetup): ) expected_schedules = [ - ["2023-01-31", 1021.98, 1021.98], - ["2023-02-28", 923.08, 1945.06], - ["2023-03-31", 1021.98, 2967.04], - ["2023-04-30", 989.01, 3956.05], - ["2023-05-31", 1021.98, 4978.03], - ["2023-06-30", 989.01, 5967.04], - ["2023-07-31", 1021.98, 6989.02], - ["2023-08-31", 1021.98, 8011.0], - ["2023-09-30", 989.01, 9000.01], - ["2023-10-31", 1021.98, 10021.99], - ["2023-11-30", 989.01, 11011.0], - ["2023-12-31", 989.0, 12000.0], + ["2023-01-31", 1019.18, 1019.18], + ["2023-02-28", 920.55, 1939.73], + ["2023-03-31", 1019.18, 2958.91], + ["2023-04-30", 986.3, 3945.21], + ["2023-05-31", 1019.18, 4964.39], + ["2023-06-30", 986.3, 5950.69], + ["2023-07-31", 1019.18, 6969.87], + ["2023-08-31", 1019.18, 7989.05], + ["2023-09-30", 986.3, 8975.35], + ["2023-10-31", 1019.18, 9994.53], + ["2023-11-30", 986.3, 10980.83], + ["2023-12-31", 1019.17, 12000.0], ] schedules = [ From 22b16a6b0e98ea92cc8c5822d633f1b18e8013cd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Apr 2024 16:25:35 +0530 Subject: [PATCH 08/29] refactor: merge taxes from delivery note to Sales Invoice (cherry picked from commit 550cbbd91c2b3addc493b86744b8a2e3407c4b6e) --- erpnext/controllers/accounts_controller.py | 31 ++++++++++++++++++ erpnext/public/js/utils.js | 7 ++-- .../doctype/delivery_note/delivery_note.py | 13 ++++++-- .../purchase_receipt/purchase_receipt.py | 32 +------------------ 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 61bdc4f52a9..52f95ee6323 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3313,6 +3313,37 @@ def check_if_child_table_updated( return False +def merge_taxes(source_taxes, target_doc): + from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( + update_item_wise_tax_detail, + ) + + existing_taxes = target_doc.get("taxes") or [] + idx = 1 + for tax in source_taxes: + found = False + for t in existing_taxes: + if t.account_head == tax.account_head and t.cost_center == tax.cost_center: + t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount) + t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount) + update_item_wise_tax_detail(t, tax) + found = True + + if not found: + tax.charge_type = "Actual" + tax.idx = idx + idx += 1 + tax.included_in_print_rate = 0 + tax.dont_recompute_tax = 1 + tax.row_id = "" + tax.tax_amount = tax.tax_amount_after_discount_amount + tax.base_tax_amount = tax.base_tax_amount_after_discount_amount + tax.item_wise_tax_detail = tax.item_wise_tax_detail + existing_taxes.append(tax) + + target_doc.set("taxes", existing_taxes) + + @erpnext.allow_regional def validate_regional(doc): pass diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 429363f1e5c..716b655d739 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -819,7 +819,7 @@ erpnext.utils.map_current_doc = function (opts) { if (opts.source_doctype) { let data_fields = []; - if (opts.source_doctype == "Purchase Receipt") { + if (["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype)) { data_fields.push({ fieldname: "merge_taxes", fieldtype: "Check", @@ -845,7 +845,10 @@ erpnext.utils.map_current_doc = function (opts) { return; } opts.source_name = values; - if (opts.allow_child_item_selection || opts.source_doctype == "Purchase Receipt") { + if ( + opts.allow_child_item_selection || + ["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype) + ) { // args contains filtered child docnames opts.args = args; } diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 0a389b602a5..88552d8de08 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -10,7 +10,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values from frappe.utils import cint, flt -from erpnext.controllers.accounts_controller import get_taxes_and_charges +from erpnext.controllers.accounts_controller import get_taxes_and_charges, merge_taxes from erpnext.controllers.selling_controller import SellingController from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no @@ -623,7 +623,7 @@ def get_returned_qty_map(delivery_note): @frappe.whitelist() -def make_sales_invoice(source_name, target_doc=None): +def make_sales_invoice(source_name, target_doc=None, args=None): doc = frappe.get_doc("Delivery Note", source_name) to_make_invoice_qty_map = {} @@ -637,6 +637,9 @@ def make_sales_invoice(source_name, target_doc=None): if len(target.get("items")) == 0: frappe.throw(_("All these items have already been Invoiced/Returned")) + if args and args.get("merge_taxes"): + merge_taxes(source.get("taxes") or [], target) + target.run_method("calculate_taxes_and_totals") # set company address @@ -701,7 +704,11 @@ def make_sales_invoice(source_name, target_doc=None): if not doc.get("is_return") else get_pending_qty(d) > 0, }, - "Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True}, + "Sales Taxes and Charges": { + "doctype": "Sales Taxes and Charges", + "add_if_empty": True, + "ignore": args.get("merge_taxes") if args else 0, + }, "Sales Team": { "doctype": "Sales Team", "field_map": {"incentives": "incentives"}, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 79e6ab84d95..064c717c320 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -14,6 +14,7 @@ import erpnext from erpnext.accounts.utils import get_account_currency from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled from erpnext.buying.utils import check_on_hold_or_closed_status +from erpnext.controllers.accounts_controller import merge_taxes from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction @@ -974,37 +975,6 @@ def get_item_wise_returned_qty(pr_doc): ) -def merge_taxes(source_taxes, target_doc): - from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( - update_item_wise_tax_detail, - ) - - existing_taxes = target_doc.get("taxes") or [] - idx = 1 - for tax in source_taxes: - found = False - for t in existing_taxes: - if t.account_head == tax.account_head and t.cost_center == tax.cost_center: - t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount) - t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount) - update_item_wise_tax_detail(t, tax) - found = True - - if not found: - tax.charge_type = "Actual" - tax.idx = idx - idx += 1 - tax.included_in_print_rate = 0 - tax.dont_recompute_tax = 1 - tax.row_id = "" - tax.tax_amount = tax.tax_amount_after_discount_amount - tax.base_tax_amount = tax.base_tax_amount_after_discount_amount - tax.item_wise_tax_detail = tax.item_wise_tax_detail - existing_taxes.append(tax) - - target_doc.set("taxes", existing_taxes) - - @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None, args=None): from erpnext.accounts.party import get_payment_terms_template From ae1858f4653590d0cbd44eaec00d933e908fbae9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 8 Apr 2024 14:20:45 +0530 Subject: [PATCH 09/29] test: tax merging from 2 Delivery Note to Sales Invoice (cherry picked from commit 39a48a2e2a711312dc8b5d2d10d171ed46bcf74f) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py --- .../sales_invoice/test_sales_invoice.py | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4d0f6446da4..730f1fd23fe 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3439,12 +3439,230 @@ class TestSalesInvoice(FrappeTestCase): si.save() +<<<<<<< HEAD def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = "INV-2020-.#####" si.items = [] si.append( "items", +======= + company = "_Test Company" + customer = "_Test Customer" + debtors_acc = "Debtors - _TC" + advance_account = create_account( + parent_account="Current Liabilities - _TC", + account_name="Advances Received", + company="_Test Company", + account_type="Receivable", + ) + + set_advance_flag(company="_Test Company", flag=1, default_account=advance_account) + + pe = create_payment_entry( + company=company, + payment_type="Receive", + party_type="Customer", + party=customer, + paid_from=advance_account, + paid_to="Cash - _TC", + paid_amount=1000, + ) + pe.submit() + + si = create_sales_invoice( + company=company, + customer=customer, + do_not_save=True, + do_not_submit=True, + rate=1000, + price_list_rate=1000, + ) + si.base_grand_total = 1000 + si.grand_total = 1000 + si.set_advances() + for advance in si.advances: + advance.allocated_amount = 200 if advance.reference_name == pe.name else 0 + si.save() + si.submit() + + self.assertEqual(si.advances[0].allocated_amount, 200) + + # Check GL Entry against partial from advance + expected_gle = [ + [advance_account, 0.0, 1000.0, nowdate()], + [advance_account, 200.0, 0.0, nowdate()], + ["Cash - _TC", 1000.0, 0.0, nowdate()], + [debtors_acc, 0.0, 200.0, nowdate()], + ] + check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry") + si.reload() + self.assertEqual(si.outstanding_amount, 800.0) + + pr = frappe.get_doc("Payment Reconciliation") + pr.company = company + pr.party_type = "Customer" + pr.party = customer + pr.receivable_payable_account = debtors_acc + pr.default_advance_account = advance_account + pr.get_unreconciled_entries() + + # allocate some more of the same advance + # self.assertEqual(len(pr.invoices), 1) + # self.assertEqual(len(pr.payments), 1) + invoices = [x.as_dict() for x in pr.invoices if x.get("invoice_number") == si.name] + payments = [x.as_dict() for x in pr.payments if x.get("reference_name") == pe.name] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.allocation[0].allocated_amount = 300 + pr.reconcile() + + si.reload() + self.assertEqual(si.outstanding_amount, 500.0) + + # Check GL Entry against multi partial allocations from advance + expected_gle = [ + [advance_account, 0.0, 1000.0, nowdate()], + [advance_account, 200.0, 0.0, nowdate()], + [advance_account, 300.0, 0.0, nowdate()], + ["Cash - _TC", 1000.0, 0.0, nowdate()], + [debtors_acc, 0.0, 200.0, nowdate()], + [debtors_acc, 0.0, 300.0, nowdate()], + ] + check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry") + set_advance_flag(company="_Test Company", flag=0, default_account="") + + def test_pulling_advance_based_on_debit_to(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry + + debtors2 = create_account( + parent_account="Accounts Receivable - _TC", + account_name="Debtors 2", + company="_Test Company", + account_type="Receivable", + ) + si = create_sales_invoice(do_not_submit=True) + si.debit_to = debtors2 + si.save() + + pe = create_payment_entry( + company=si.company, + payment_type="Receive", + party_type="Customer", + party=si.customer, + paid_from=debtors2, + paid_to="Cash - _TC", + paid_amount=1000, + ) + pe.submit() + advances = si.get_advance_entries() + self.assertEqual(1, len(advances)) + self.assertEqual(advances[0].reference_name, pe.name) + + def test_taxes_merging_from_delivery_note(self): + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + dn1 = create_delivery_note(do_not_submit=1) + dn1.items[0].qty = 10 + dn1.items[0].rate = 100 + dn1.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TC", + "description": "movement charges", + "tax_amount": 100, + }, + ) + dn1.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "Marketing Expenses - _TC", + "description": "marketing", + "tax_amount": 150, + }, + ) + dn1.save().submit() + + dn2 = create_delivery_note(do_not_submit=1) + dn2.items[0].qty = 5 + dn2.items[0].rate = 100 + dn2.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TC", + "description": "movement charges", + "tax_amount": 20, + }, + ) + dn2.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "Miscellaneous Expenses - _TC", + "description": "marketing", + "tax_amount": 60, + }, + ) + dn2.save().submit() + + # si = make_sales_invoice(dn1.name) + si = create_sales_invoice(do_not_submit=True) + si.customer = dn1.customer + si.items.clear() + + from frappe.model.mapper import map_docs + + map_docs( + method="erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", + source_names=frappe.json.dumps([dn1.name, dn2.name]), + target_doc=si, + args=frappe.json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}), + ) + si.save().submit() + + expected = [ + { + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TC", + "tax_amount": 120.0, + "total": 1520.0, + "base_total": 1520.0, + }, + { + "charge_type": "Actual", + "account_head": "Marketing Expenses - _TC", + "tax_amount": 150.0, + "total": 1670.0, + "base_total": 1670.0, + }, + { + "charge_type": "Actual", + "account_head": "Miscellaneous Expenses - _TC", + "tax_amount": 60.0, + "total": 1610.0, + "base_total": 1610.0, + }, + ] + actual = [ + dict( + charge_type=x.charge_type, + account_head=x.account_head, + tax_amount=x.tax_amount, + total=x.total, + base_total=x.base_total, + ) + for x in si.taxes + ] + self.assertEqual(expected, actual) + + +def set_advance_flag(company, flag, default_account): + frappe.db.set_value( + "Company", + company, +>>>>>>> 39a48a2e2a (test: tax merging from 2 Delivery Note to Sales Invoice) { "item_code": "_Test Item", "uom": "Nos", From b76c00de68f33889378c4a4e8cb129c92b9e73f8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 8 Apr 2024 15:44:13 +0530 Subject: [PATCH 10/29] chore: resolve conflict --- .../sales_invoice/test_sales_invoice.py | 131 +----------------- 1 file changed, 6 insertions(+), 125 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 730f1fd23fe..5993c81a174 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3438,126 +3438,6 @@ class TestSalesInvoice(FrappeTestCase): si.items[0].rate = 10 si.save() - -<<<<<<< HEAD -def get_sales_invoice_for_e_invoice(): - si = make_sales_invoice_for_ewaybill() - si.naming_series = "INV-2020-.#####" - si.items = [] - si.append( - "items", -======= - company = "_Test Company" - customer = "_Test Customer" - debtors_acc = "Debtors - _TC" - advance_account = create_account( - parent_account="Current Liabilities - _TC", - account_name="Advances Received", - company="_Test Company", - account_type="Receivable", - ) - - set_advance_flag(company="_Test Company", flag=1, default_account=advance_account) - - pe = create_payment_entry( - company=company, - payment_type="Receive", - party_type="Customer", - party=customer, - paid_from=advance_account, - paid_to="Cash - _TC", - paid_amount=1000, - ) - pe.submit() - - si = create_sales_invoice( - company=company, - customer=customer, - do_not_save=True, - do_not_submit=True, - rate=1000, - price_list_rate=1000, - ) - si.base_grand_total = 1000 - si.grand_total = 1000 - si.set_advances() - for advance in si.advances: - advance.allocated_amount = 200 if advance.reference_name == pe.name else 0 - si.save() - si.submit() - - self.assertEqual(si.advances[0].allocated_amount, 200) - - # Check GL Entry against partial from advance - expected_gle = [ - [advance_account, 0.0, 1000.0, nowdate()], - [advance_account, 200.0, 0.0, nowdate()], - ["Cash - _TC", 1000.0, 0.0, nowdate()], - [debtors_acc, 0.0, 200.0, nowdate()], - ] - check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry") - si.reload() - self.assertEqual(si.outstanding_amount, 800.0) - - pr = frappe.get_doc("Payment Reconciliation") - pr.company = company - pr.party_type = "Customer" - pr.party = customer - pr.receivable_payable_account = debtors_acc - pr.default_advance_account = advance_account - pr.get_unreconciled_entries() - - # allocate some more of the same advance - # self.assertEqual(len(pr.invoices), 1) - # self.assertEqual(len(pr.payments), 1) - invoices = [x.as_dict() for x in pr.invoices if x.get("invoice_number") == si.name] - payments = [x.as_dict() for x in pr.payments if x.get("reference_name") == pe.name] - pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) - pr.allocation[0].allocated_amount = 300 - pr.reconcile() - - si.reload() - self.assertEqual(si.outstanding_amount, 500.0) - - # Check GL Entry against multi partial allocations from advance - expected_gle = [ - [advance_account, 0.0, 1000.0, nowdate()], - [advance_account, 200.0, 0.0, nowdate()], - [advance_account, 300.0, 0.0, nowdate()], - ["Cash - _TC", 1000.0, 0.0, nowdate()], - [debtors_acc, 0.0, 200.0, nowdate()], - [debtors_acc, 0.0, 300.0, nowdate()], - ] - check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry") - set_advance_flag(company="_Test Company", flag=0, default_account="") - - def test_pulling_advance_based_on_debit_to(self): - from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry - - debtors2 = create_account( - parent_account="Accounts Receivable - _TC", - account_name="Debtors 2", - company="_Test Company", - account_type="Receivable", - ) - si = create_sales_invoice(do_not_submit=True) - si.debit_to = debtors2 - si.save() - - pe = create_payment_entry( - company=si.company, - payment_type="Receive", - party_type="Customer", - party=si.customer, - paid_from=debtors2, - paid_to="Cash - _TC", - paid_amount=1000, - ) - pe.submit() - advances = si.get_advance_entries() - self.assertEqual(1, len(advances)) - self.assertEqual(advances[0].reference_name, pe.name) - def test_taxes_merging_from_delivery_note(self): from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note @@ -3658,11 +3538,12 @@ def get_sales_invoice_for_e_invoice(): self.assertEqual(expected, actual) -def set_advance_flag(company, flag, default_account): - frappe.db.set_value( - "Company", - company, ->>>>>>> 39a48a2e2a (test: tax merging from 2 Delivery Note to Sales Invoice) +def get_sales_invoice_for_e_invoice(): + si = make_sales_invoice_for_ewaybill() + si.naming_series = "INV-2020-.#####" + si.items = [] + si.append( + "items", { "item_code": "_Test Item", "uom": "Nos", From 0172880fd3a20a4a6a07685c208bbb5d1df7afd9 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 8 Apr 2024 15:47:18 +0530 Subject: [PATCH 11/29] feat: new hook `fields_for_group_similar_items` to group additional fields for print formats (#40831) (cherry picked from commit f7c9e1538b0814c06ec1dd8abe971113e0662132) # Conflicts: # erpnext/hooks.py --- erpnext/controllers/accounts_controller.py | 22 ++++++++++++++-------- erpnext/hooks.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 61bdc4f52a9..4f2856d98c8 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3,6 +3,7 @@ import json +from collections import defaultdict import frappe from frappe import _, bold, qb, throw @@ -1947,21 +1948,26 @@ class AccountsController(TransactionBase): ) def group_similar_items(self): - group_item_qty = {} - group_item_amount = {} + grouped_items = {} # to update serial number in print count = 0 + fields_to_group = frappe.get_hooks("fields_for_group_similar_items") + fields_to_group = set(fields_to_group) + for item in self.items: - group_item_qty[item.item_code] = group_item_qty.get(item.item_code, 0) + item.qty - group_item_amount[item.item_code] = group_item_amount.get(item.item_code, 0) + item.amount + item_values = grouped_items.setdefault(item.item_code, defaultdict(int)) + + for field in fields_to_group: + item_values[field] += item.get(field, 0) duplicate_list = [] for item in self.items: - if item.item_code in group_item_qty: + if item.item_code in grouped_items: count += 1 - item.qty = group_item_qty[item.item_code] - item.amount = group_item_amount[item.item_code] + + for field in fields_to_group: + item.set(field, grouped_items[item.item_code][field]) if item.qty: item.rate = flt(flt(item.amount) / flt(item.qty), item.precision("rate")) @@ -1969,7 +1975,7 @@ class AccountsController(TransactionBase): item.rate = 0 item.idx = count - del group_item_qty[item.item_code] + del grouped_items[item.item_code] else: duplicate_list.append(item) for item in duplicate_list: diff --git a/erpnext/hooks.py b/erpnext/hooks.py index e328c686f5d..68340f9f68a 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -643,3 +643,15 @@ extend_bootinfo = [ "erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes", "erpnext.startup.boot.bootinfo", ] +<<<<<<< HEAD +======= + + +default_log_clearing_doctypes = { + "Repost Item Valuation": 60, +} + +export_python_type_annotations = True + +fields_for_group_similar_items = ["qty", "amount"] +>>>>>>> f7c9e1538b (feat: new hook `fields_for_group_similar_items` to group additional fields for print formats (#40831)) From 24709ab4007c1f2d29abe350386e483842a1eea3 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 8 Apr 2024 15:50:58 +0530 Subject: [PATCH 12/29] chore: fix conflict --- erpnext/hooks.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 68340f9f68a..fdaae5cc7e4 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -643,15 +643,5 @@ extend_bootinfo = [ "erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes", "erpnext.startup.boot.bootinfo", ] -<<<<<<< HEAD -======= - - -default_log_clearing_doctypes = { - "Repost Item Valuation": 60, -} - -export_python_type_annotations = True fields_for_group_similar_items = ["qty", "amount"] ->>>>>>> f7c9e1538b (feat: new hook `fields_for_group_similar_items` to group additional fields for print formats (#40831)) From 3165682d7a6efb73b1123998417ef0356c276bd2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 8 Apr 2024 19:03:36 +0530 Subject: [PATCH 13/29] perf: memory consumption for the Batch-Wise Balance History report --- .../batch_wise_balance_history.py | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 31ea576ec1d..e6490f1717a 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -117,34 +117,37 @@ def get_stock_ledger_entries(filters): if filters.get(field): query = query.where(sle[field] == filters.get(field)) - return query.run(as_dict=True) + return query def get_item_warehouse_batch_map(filters, float_precision): - sle = get_stock_ledger_entries(filters) - iwb_map = {} + with frappe.db.unbuffered_cursor(): + sle = get_stock_ledger_entries(filters) + sle = sle.run(as_dict=True, as_iterator=True) - from_date = getdate(filters["from_date"]) - to_date = getdate(filters["to_date"]) + iwb_map = {} - for d in sle: - iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {}).setdefault( - d.batch_no, frappe._dict({"opening_qty": 0.0, "in_qty": 0.0, "out_qty": 0.0, "bal_qty": 0.0}) - ) - qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no] - if d.posting_date < from_date: - qty_dict.opening_qty = flt(qty_dict.opening_qty, float_precision) + flt( - d.actual_qty, float_precision + from_date = getdate(filters["from_date"]) + to_date = getdate(filters["to_date"]) + + for d in sle: + iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {}).setdefault( + d.batch_no, frappe._dict({"opening_qty": 0.0, "in_qty": 0.0, "out_qty": 0.0, "bal_qty": 0.0}) ) - elif d.posting_date >= from_date and d.posting_date <= to_date: - if flt(d.actual_qty) > 0: - qty_dict.in_qty = flt(qty_dict.in_qty, float_precision) + flt(d.actual_qty, float_precision) - else: - qty_dict.out_qty = flt(qty_dict.out_qty, float_precision) + abs( - flt(d.actual_qty, float_precision) + qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no] + if d.posting_date < from_date: + qty_dict.opening_qty = flt(qty_dict.opening_qty, float_precision) + flt( + d.actual_qty, float_precision ) + elif d.posting_date >= from_date and d.posting_date <= to_date: + if flt(d.actual_qty) > 0: + qty_dict.in_qty = flt(qty_dict.in_qty, float_precision) + flt(d.actual_qty, float_precision) + else: + qty_dict.out_qty = flt(qty_dict.out_qty, float_precision) + abs( + flt(d.actual_qty, float_precision) + ) - qty_dict.bal_qty = flt(qty_dict.bal_qty, float_precision) + flt(d.actual_qty, float_precision) + qty_dict.bal_qty = flt(qty_dict.bal_qty, float_precision) + flt(d.actual_qty, float_precision) return iwb_map From 5896e755bf3cf9e5b4385699b6ddfc7cef852b4f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 9 Apr 2024 17:39:32 +0530 Subject: [PATCH 14/29] fix: incorrect currency symbol in General Ledger print (cherry picked from commit 429e036e8c3c9f27d1abe2e1820f110072639191) --- erpnext/accounts/report/general_ledger/general_ledger.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index 2d5ca497654..3c4e1a05c97 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -55,10 +55,10 @@ - {%= format_currency(data[i].debit, filters.presentation_currency) %} + {%= format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %} - {%= format_currency(data[i].credit, filters.presentation_currency) %} + {%= format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %} {% } else { %} From b667a024709ce70851eaf308ff48e6550e9c07af Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 26 Mar 2024 17:04:48 +0530 Subject: [PATCH 15/29] feat: ledger health doctype (cherry picked from commit 9ed74dd8cc8911f25af31a21dfec1b5185b4b49a) --- .../doctype/ledger_health/__init__.py | 0 .../doctype/ledger_health/ledger_health.js | 8 +++ .../doctype/ledger_health/ledger_health.json | 55 +++++++++++++++++++ .../doctype/ledger_health/ledger_health.py | 23 ++++++++ .../ledger_health/test_ledger_health.py | 9 +++ 5 files changed, 95 insertions(+) create mode 100644 erpnext/accounts/doctype/ledger_health/__init__.py create mode 100644 erpnext/accounts/doctype/ledger_health/ledger_health.js create mode 100644 erpnext/accounts/doctype/ledger_health/ledger_health.json create mode 100644 erpnext/accounts/doctype/ledger_health/ledger_health.py create mode 100644 erpnext/accounts/doctype/ledger_health/test_ledger_health.py diff --git a/erpnext/accounts/doctype/ledger_health/__init__.py b/erpnext/accounts/doctype/ledger_health/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.js b/erpnext/accounts/doctype/ledger_health/ledger_health.js new file mode 100644 index 00000000000..e207daef511 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Ledger Health", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.json b/erpnext/accounts/doctype/ledger_health/ledger_health.json new file mode 100644 index 00000000000..b7dfdec937e --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2024-03-26 17:01:47.443986", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "voucher_type", + "voucher_no", + "debit_credit_mismatch" + ], + "fields": [ + { + "fieldname": "voucher_type", + "fieldtype": "Data", + "label": "Voucher Type" + }, + { + "fieldname": "voucher_no", + "fieldtype": "Data", + "label": "Voucher No" + }, + { + "default": "0", + "fieldname": "debit_credit_mismatch", + "fieldtype": "Check", + "label": "Debit-Credit mismatch" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-03-26 17:53:04.985881", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Ledger Health", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.py b/erpnext/accounts/doctype/ledger_health/ledger_health.py new file mode 100644 index 00000000000..f2fe23560fe --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.py @@ -0,0 +1,23 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LedgerHealth(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + debit_credit_mismatch: DF.Check + name: DF.Int | None + voucher_no: DF.Data | None + voucher_type: DF.Data | None + # end: auto-generated types + + pass diff --git a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py new file mode 100644 index 00000000000..ca647c99be9 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLedgerHealth(FrappeTestCase): + pass From 153fc914786df908c2a9392b5cdf3dd9380e5697 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 26 Mar 2024 17:57:04 +0530 Subject: [PATCH 16/29] refactor: date on which vouchers was reported (cherry picked from commit 402ffc6d27374b413af6b95d9b715acc82d34a20) --- .../accounts/doctype/ledger_health/ledger_health.json | 10 ++++++++-- .../accounts/doctype/ledger_health/ledger_health.py | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.json b/erpnext/accounts/doctype/ledger_health/ledger_health.json index b7dfdec937e..2bb1ccb48a8 100644 --- a/erpnext/accounts/doctype/ledger_health/ledger_health.json +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.json @@ -7,7 +7,8 @@ "field_order": [ "voucher_type", "voucher_no", - "debit_credit_mismatch" + "debit_credit_mismatch", + "checked_on" ], "fields": [ { @@ -25,11 +26,16 @@ "fieldname": "debit_credit_mismatch", "fieldtype": "Check", "label": "Debit-Credit mismatch" + }, + { + "fieldname": "checked_on", + "fieldtype": "Datetime", + "label": "Checked On" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-03-26 17:53:04.985881", + "modified": "2024-03-26 17:54:47.662290", "modified_by": "Administrator", "module": "Accounts", "name": "Ledger Health", diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.py b/erpnext/accounts/doctype/ledger_health/ledger_health.py index f2fe23560fe..f4a3af10f58 100644 --- a/erpnext/accounts/doctype/ledger_health/ledger_health.py +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.py @@ -14,6 +14,7 @@ class LedgerHealth(Document): if TYPE_CHECKING: from frappe.types import DF + checked_on: DF.Datetime | None debit_credit_mismatch: DF.Check name: DF.Int | None voucher_no: DF.Data | None From bbe4ca7d7459719d56ebc427063c007082274286 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 26 Mar 2024 20:00:48 +0530 Subject: [PATCH 17/29] refactor: flag for general and payment ledger mismatch (cherry picked from commit d620b9eae8899c62302531e671f5d8c47d06672b) --- .../accounts/doctype/ledger_health/ledger_health.json | 11 +++++++++-- .../accounts/doctype/ledger_health/ledger_health.py | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.json b/erpnext/accounts/doctype/ledger_health/ledger_health.json index 2bb1ccb48a8..e17f4e8f2d7 100644 --- a/erpnext/accounts/doctype/ledger_health/ledger_health.json +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.json @@ -7,8 +7,9 @@ "field_order": [ "voucher_type", "voucher_no", + "checked_on", "debit_credit_mismatch", - "checked_on" + "general_and_payment_ledger_mismatch" ], "fields": [ { @@ -31,11 +32,17 @@ "fieldname": "checked_on", "fieldtype": "Datetime", "label": "Checked On" + }, + { + "default": "0", + "fieldname": "general_and_payment_ledger_mismatch", + "fieldtype": "Check", + "label": "General and Payment Ledger mismatch" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-03-26 17:54:47.662290", + "modified": "2024-03-26 19:59:41.180161", "modified_by": "Administrator", "module": "Accounts", "name": "Ledger Health", diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.py b/erpnext/accounts/doctype/ledger_health/ledger_health.py index f4a3af10f58..590ff80cc11 100644 --- a/erpnext/accounts/doctype/ledger_health/ledger_health.py +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.py @@ -16,6 +16,7 @@ class LedgerHealth(Document): checked_on: DF.Datetime | None debit_credit_mismatch: DF.Check + general_and_payment_ledger_mismatch: DF.Check name: DF.Int | None voucher_no: DF.Data | None voucher_type: DF.Data | None From 58698c9aa4b04f80a6090bc9950e4ab68f4cd1f7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 26 Mar 2024 17:57:43 +0530 Subject: [PATCH 18/29] refactor: barebones method to run checks (cherry picked from commit 8c8d9be8105918b86b80c0488557f94bc82ecebd) # Conflicts: # erpnext/accounts/utils.py --- erpnext/accounts/utils.py | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index a8338e26a2c..29c08538c94 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -13,11 +13,13 @@ from frappe.query_builder import AliasedQuery, Criterion, Table from frappe.query_builder.functions import Round, Sum from frappe.query_builder.utils import DocType from frappe.utils import ( + add_days, cint, create_batch, cstr, flt, formatdate, + get_datetime, get_number_format_info, getdate, now, @@ -2084,3 +2086,47 @@ def create_gain_loss_journal( journal_entry.save() journal_entry.submit() return journal_entry.name +<<<<<<< HEAD +======= + + +def get_party_types_from_account_type(account_type): + return frappe.db.get_all("Party Type", {"account_type": account_type}, pluck="name") + + +def run_ledger_health_checks(): + # run for last 1 month + period_end = getdate() + period_start = add_days(period_end, -100) + + run_date = get_datetime() + + # Debit-Credit mismatch report + voucher_wise = frappe.get_doc("Report", "Voucher-wise Balance") + + # todo: company and dates should be configurable + filters = {"company": "நுண்ணறி", "from_date": period_start, "to_date": period_end} + + res = voucher_wise.execute_script_report(filters=filters) + for x in res[1]: + doc = frappe.new_doc("Ledger Health") + doc.voucher_type = x.voucher_type + doc.voucher_no = x.voucher_no + doc.debit_credit_mismatch = True + doc.checked_on = run_date + doc.save() + + # General Ledger and Payment Ledger discrepancy + gl_pl_comparison = frappe.get_doc("Report", "General and Payment Ledger Comparison") + # todo: company and dates should be configurable + filters = {"company": "நுண்ணறி", "period_start_date": period_start, "period_end_date": period_end} + res = gl_pl_comparison.execute_script_report(filters=filters) + + for x in res[1]: + doc = frappe.new_doc("Ledger Health") + doc.voucher_type = x.voucher_type + doc.voucher_no = x.voucher_no + doc.general_and_payment_ledger_mismatch = True + doc.checked_on = run_date + doc.save() +>>>>>>> 8c8d9be810 (refactor: barebones method to run checks) From 7a6ffccecc5955b5a18fcf4c3b425dd31926e6ee Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 27 Mar 2024 09:41:43 +0530 Subject: [PATCH 19/29] chore: settings page for health monitor (cherry picked from commit b2fb7843d1b441ccfad7e84e5828e581f8967930) --- .../doctype/ledger_health_monitor/__init__.py | 0 .../ledger_health_monitor.js | 8 +++ .../ledger_health_monitor.json | 70 +++++++++++++++++++ .../ledger_health_monitor.py | 23 ++++++ .../test_ledger_health_monitor.py | 9 +++ 5 files changed, 110 insertions(+) create mode 100644 erpnext/accounts/doctype/ledger_health_monitor/__init__.py create mode 100644 erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.js create mode 100644 erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json create mode 100644 erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py create mode 100644 erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py diff --git a/erpnext/accounts/doctype/ledger_health_monitor/__init__.py b/erpnext/accounts/doctype/ledger_health_monitor/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.js b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.js new file mode 100644 index 00000000000..cf112760f8c --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Ledger Health Monitor", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json new file mode 100644 index 00000000000..9a916484925 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json @@ -0,0 +1,70 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-03-27 09:38:07.427997", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enable_health_monitor", + "monitor_section", + "monitor_for_last_x_days", + "debit_credit_mismatch", + "general_and_payment_ledger_mismatch" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable_health_monitor", + "fieldtype": "Check", + "label": "Enable Health Monitor" + }, + { + "fieldname": "monitor_section", + "fieldtype": "Section Break", + "label": "Monitor" + }, + { + "default": "0", + "fieldname": "debit_credit_mismatch", + "fieldtype": "Check", + "label": "Debit-Credit Mismatch" + }, + { + "default": "0", + "fieldname": "general_and_payment_ledger_mismatch", + "fieldtype": "Check", + "label": "Discrepancy between General and Payment Ledger" + }, + { + "default": "60", + "fieldname": "monitor_for_last_x_days", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Monitor for Last 'X' days", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2024-03-27 09:53:00.674708", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Ledger Health Monitor", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py new file mode 100644 index 00000000000..c6a33790ee9 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py @@ -0,0 +1,23 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LedgerHealthMonitor(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + debit_credit_mismatch: DF.Check + enable_health_monitor: DF.Check + general_and_payment_ledger_mismatch: DF.Check + monitor_for_last_x_days: DF.Int + # end: auto-generated types + + pass diff --git a/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py b/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py new file mode 100644 index 00000000000..e0ba4435b82 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLedgerHealthMonitor(FrappeTestCase): + pass From 9981a900b2d66452eb29333dac4941d352313fc2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 27 Mar 2024 10:01:03 +0530 Subject: [PATCH 20/29] refactor: control monitoring through settings page (cherry picked from commit a42482ce352432a56425434181b738145eb736f4) # Conflicts: # erpnext/accounts/utils.py --- erpnext/accounts/utils.py | 52 +++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 29c08538c94..e617d12b9e6 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2095,27 +2095,38 @@ def get_party_types_from_account_type(account_type): def run_ledger_health_checks(): - # run for last 1 month - period_end = getdate() - period_start = add_days(period_end, -100) + health_monitor_settings = frappe.get_doc("Ledger Health Monitor") + if health_monitor_settings.enable_health_monitor: + period_end = getdate() + period_start = add_days(period_end, -abs(health_monitor_settings.monitor_for_last_x_days)) - run_date = get_datetime() + run_date = get_datetime() - # Debit-Credit mismatch report - voucher_wise = frappe.get_doc("Report", "Voucher-wise Balance") + # Debit-Credit mismatch report + if health_monitor_settings.debit_credit_mismatch: + voucher_wise = frappe.get_doc("Report", "Voucher-wise Balance") + filters = {"company": "நுண்ணறி", "from_date": period_start, "to_date": period_end} - # todo: company and dates should be configurable - filters = {"company": "நுண்ணறி", "from_date": period_start, "to_date": period_end} + res = voucher_wise.execute_script_report(filters=filters) + for x in res[1]: + doc = frappe.new_doc("Ledger Health") + doc.voucher_type = x.voucher_type + doc.voucher_no = x.voucher_no + doc.debit_credit_mismatch = True + doc.checked_on = run_date + doc.save() - res = voucher_wise.execute_script_report(filters=filters) - for x in res[1]: - doc = frappe.new_doc("Ledger Health") - doc.voucher_type = x.voucher_type - doc.voucher_no = x.voucher_no - doc.debit_credit_mismatch = True - doc.checked_on = run_date - doc.save() + # General Ledger and Payment Ledger discrepancy + if health_monitor_settings.general_and_payment_ledger_mismatch: + gl_pl_comparison = frappe.get_doc("Report", "General and Payment Ledger Comparison") + filters = { + "company": "நுண்ணறி", + "period_start_date": period_start, + "period_end_date": period_end, + } + res = gl_pl_comparison.execute_script_report(filters=filters) +<<<<<<< HEAD # General Ledger and Payment Ledger discrepancy gl_pl_comparison = frappe.get_doc("Report", "General and Payment Ledger Comparison") # todo: company and dates should be configurable @@ -2130,3 +2141,12 @@ def run_ledger_health_checks(): doc.checked_on = run_date doc.save() >>>>>>> 8c8d9be810 (refactor: barebones method to run checks) +======= + for x in res[1]: + doc = frappe.new_doc("Ledger Health") + doc.voucher_type = x.voucher_type + doc.voucher_no = x.voucher_no + doc.general_and_payment_ledger_mismatch = True + doc.checked_on = run_date + doc.save() +>>>>>>> a42482ce35 (refactor: control monitoring through settings page) From f86c035f888bc091dacb7dcad0d3addf42258aa3 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 27 Mar 2024 10:07:48 +0530 Subject: [PATCH 21/29] refactor: make health check configurable for companies (cherry picked from commit 704925549b4e044ee8b55f8322e4216e25c3764b) --- .../ledger_health_monitor.json | 18 +++++++++-- .../ledger_health_monitor.py | 5 +++ .../ledger_health_monitor_company/__init__.py | 0 .../ledger_health_monitor_company.json | 32 +++++++++++++++++++ .../ledger_health_monitor_company.py | 23 +++++++++++++ 5 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 erpnext/accounts/doctype/ledger_health_monitor_company/__init__.py create mode 100644 erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.json create mode 100644 erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.py diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json index 9a916484925..4c1c8f80ac9 100644 --- a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json +++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json @@ -9,7 +9,9 @@ "monitor_section", "monitor_for_last_x_days", "debit_credit_mismatch", - "general_and_payment_ledger_mismatch" + "general_and_payment_ledger_mismatch", + "section_break_xdsp", + "companies" ], "fields": [ { @@ -21,7 +23,7 @@ { "fieldname": "monitor_section", "fieldtype": "Section Break", - "label": "Monitor" + "label": "Configuration" }, { "default": "0", @@ -42,12 +44,22 @@ "in_list_view": 1, "label": "Monitor for Last 'X' days", "reqd": 1 + }, + { + "fieldname": "section_break_xdsp", + "fieldtype": "Section Break", + "label": "Companies" + }, + { + "fieldname": "companies", + "fieldtype": "Table", + "options": "Ledger Health Monitor Company" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-03-27 09:53:00.674708", + "modified": "2024-03-27 10:07:28.601546", "modified_by": "Administrator", "module": "Accounts", "name": "Ledger Health Monitor", diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py index c6a33790ee9..9f7c569d6d1 100644 --- a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py +++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.py @@ -14,6 +14,11 @@ class LedgerHealthMonitor(Document): if TYPE_CHECKING: from frappe.types import DF + from erpnext.accounts.doctype.ledger_health_monitor_company.ledger_health_monitor_company import ( + LedgerHealthMonitorCompany, + ) + + companies: DF.Table[LedgerHealthMonitorCompany] debit_credit_mismatch: DF.Check enable_health_monitor: DF.Check general_and_payment_ledger_mismatch: DF.Check diff --git a/erpnext/accounts/doctype/ledger_health_monitor_company/__init__.py b/erpnext/accounts/doctype/ledger_health_monitor_company/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.json b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.json new file mode 100644 index 00000000000..87fa3e32801 --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-03-27 10:04:45.727054", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-03-27 10:06:22.806155", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Ledger Health Monitor Company", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.py b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.py new file mode 100644 index 00000000000..5890410090d --- /dev/null +++ b/erpnext/accounts/doctype/ledger_health_monitor_company/ledger_health_monitor_company.py @@ -0,0 +1,23 @@ +# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LedgerHealthMonitorCompany(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + company: DF.Link | None + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + # end: auto-generated types + + pass From 6273a31b8c73426e37a23dd7f69e4df313dce1b0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 27 Mar 2024 10:10:55 +0530 Subject: [PATCH 22/29] refactor: only run checks on specified companies (cherry picked from commit 00eeacd06af69a5c65b90c629d7497d8b1357751) # Conflicts: # erpnext/accounts/utils.py --- erpnext/accounts/utils.py | 40 ++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index e617d12b9e6..c7fd17c33df 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2104,20 +2104,21 @@ def run_ledger_health_checks(): # Debit-Credit mismatch report if health_monitor_settings.debit_credit_mismatch: - voucher_wise = frappe.get_doc("Report", "Voucher-wise Balance") - filters = {"company": "நுண்ணறி", "from_date": period_start, "to_date": period_end} - - res = voucher_wise.execute_script_report(filters=filters) - for x in res[1]: - doc = frappe.new_doc("Ledger Health") - doc.voucher_type = x.voucher_type - doc.voucher_no = x.voucher_no - doc.debit_credit_mismatch = True - doc.checked_on = run_date - doc.save() + for x in health_monitor_settings.companies: + filters = {"company": x.company, "from_date": period_start, "to_date": period_end} + voucher_wise = frappe.get_doc("Report", "Voucher-wise Balance") + res = voucher_wise.execute_script_report(filters=filters) + for x in res[1]: + doc = frappe.new_doc("Ledger Health") + doc.voucher_type = x.voucher_type + doc.voucher_no = x.voucher_no + doc.debit_credit_mismatch = True + doc.checked_on = run_date + doc.save() # General Ledger and Payment Ledger discrepancy if health_monitor_settings.general_and_payment_ledger_mismatch: +<<<<<<< HEAD gl_pl_comparison = frappe.get_doc("Report", "General and Payment Ledger Comparison") filters = { "company": "நுண்ணறி", @@ -2150,3 +2151,20 @@ def run_ledger_health_checks(): doc.checked_on = run_date doc.save() >>>>>>> a42482ce35 (refactor: control monitoring through settings page) +======= + for x in health_monitor_settings.companies: + filters = { + "company": x.company, + "period_start_date": period_start, + "period_end_date": period_end, + } + gl_pl_comparison = frappe.get_doc("Report", "General and Payment Ledger Comparison") + res = gl_pl_comparison.execute_script_report(filters=filters) + for x in res[1]: + doc = frappe.new_doc("Ledger Health") + doc.voucher_type = x.voucher_type + doc.voucher_no = x.voucher_no + doc.general_and_payment_ledger_mismatch = True + doc.checked_on = run_date + doc.save() +>>>>>>> 00eeacd06a (refactor: only run checks on specified companies) From 1af6b4256fd7e03581856813553c9d730553a4d4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 27 Mar 2024 10:14:29 +0530 Subject: [PATCH 23/29] chore: permission and UI changes (cherry picked from commit 1a43ed763b85ffbe500d53eac600737457e7fa17) --- .../ledger_health_monitor.json | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json index 4c1c8f80ac9..6e688333e3f 100644 --- a/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json +++ b/erpnext/accounts/doctype/ledger_health_monitor/ledger_health_monitor.json @@ -56,10 +56,11 @@ "options": "Ledger Health Monitor Company" } ], + "hide_toolbar": 1, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-03-27 10:07:28.601546", + "modified": "2024-03-27 10:14:16.511681", "modified_by": "Administrator", "module": "Accounts", "name": "Ledger Health Monitor", @@ -74,9 +75,30 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts User", + "share": 1, + "write": 1 } ], "sort_field": "modified", "sort_order": "DESC", - "states": [] + "states": [], + "track_changes": 1 } \ No newline at end of file From 0591f72e0143d86a474ddf5ee9356d50a928a8e9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 27 Mar 2024 10:33:54 +0530 Subject: [PATCH 24/29] chore: schedule job to run daily (cherry picked from commit f96cf111ed0681254a1b9f4fe11d80e84368079d) --- erpnext/hooks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index fdaae5cc7e4..da4c1974247 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -449,6 +449,7 @@ scheduler_events = { "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status", "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily", + "erpnext.accounts.utils.run_ledger_health_checks", ], "weekly": [ "erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly", From 277717a2e0a75a85d6a70373938fd6ac748e749a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 9 Apr 2024 09:35:53 +0530 Subject: [PATCH 25/29] test: ledger monitoring function (cherry picked from commit 4776d660b535a19940b71b5d646d36792261fcb7) --- .../ledger_health/test_ledger_health.py | 106 +++++++++++++++++- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py index ca647c99be9..daad855abf0 100644 --- a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py +++ b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py @@ -1,9 +1,109 @@ # Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -# import frappe +import frappe +from frappe import qb from frappe.tests.utils import FrappeTestCase +from frappe.utils import nowdate + +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.accounts.utils import run_ledger_health_checks -class TestLedgerHealth(FrappeTestCase): - pass +class TestLedgerHealth(AccountsTestMixin, FrappeTestCase): + def setUp(self): + self.create_company() + self.create_customer() + self.configure_monitoring_tool() + self.clear_old_entries() + + def tearDown(self): + frappe.db.rollback() + + def configure_monitoring_tool(self): + monitor_settings = frappe.get_doc("Ledger Health Monitor") + monitor_settings.enable_health_monitor = True + monitor_settings.enable_for_last_x_days = 60 + monitor_settings.debit_credit_mismatch = True + monitor_settings.general_and_payment_ledger_mismatch = True + exists = [x for x in monitor_settings.companies if x.company == self.company] + if not exists: + monitor_settings.append("companies", {"company": self.company}) + monitor_settings.save() + + def clear_old_entries(self): + super(TestLedgerHealth, self).clear_old_entries() + lh = qb.DocType("Ledger Health") + qb.from_(lh).delete().run() + + def create_journal(self): + je = frappe.new_doc("Journal Entry") + je.company = self.company + je.voucher_type = "Journal Entry" + je.posting_date = nowdate() + je.append( + "accounts", + { + "account": self.debit_to, + "party_type": "Customer", + "party": self.customer, + "debit_in_account_currency": 10000, + }, + ) + je.append("accounts", {"account": self.income_account, "credit_in_account_currency": 10000}) + je.save().submit() + self.je = je + + def test_debit_credit_mismatch(self): + self.create_journal() + + # manually cause debit-credit mismatch + gle = frappe.db.get_all( + "GL Entry", filters={"voucher_no": self.je.name, "account": self.income_account} + )[0] + frappe.db.set_value("GL Entry", gle.name, "credit", 8000) + + run_ledger_health_checks() + expected = { + "voucher_type": self.je.doctype, + "voucher_no": self.je.name, + "debit_credit_mismatch": True, + "general_and_payment_ledger_mismatch": False, + } + actual = frappe.db.get_all( + "Ledger Health", + fields=[ + "voucher_type", + "voucher_no", + "debit_credit_mismatch", + "general_and_payment_ledger_mismatch", + ], + ) + self.assertEqual(len(actual), 1) + self.assertEqual(expected, actual[0]) + + def test_gl_and_pl_mismatch(self): + self.create_journal() + + # manually cause GL and PL discrepancy + ple = frappe.db.get_all("Payment Ledger Entry", filters={"voucher_no": self.je.name})[0] + frappe.db.set_value("Payment Ledger Entry", ple.name, "amount", 11000) + + run_ledger_health_checks() + expected = { + "voucher_type": self.je.doctype, + "voucher_no": self.je.name, + "debit_credit_mismatch": False, + "general_and_payment_ledger_mismatch": True, + } + actual = frappe.db.get_all( + "Ledger Health", + fields=[ + "voucher_type", + "voucher_no", + "debit_credit_mismatch", + "general_and_payment_ledger_mismatch", + ], + ) + self.assertEqual(len(actual), 1) + self.assertEqual(expected, actual[0]) From 98ad034b2130b538bdc44c606b989ecdaf3ae7f2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 9 Apr 2024 11:18:53 +0530 Subject: [PATCH 26/29] chore: make ledger health doctype read_only (cherry picked from commit dc79213bb372c0171f6f4d9bab92b91eb5d7314a) --- erpnext/accounts/doctype/ledger_health/ledger_health.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/ledger_health/ledger_health.json b/erpnext/accounts/doctype/ledger_health/ledger_health.json index e17f4e8f2d7..fb2da3d2564 100644 --- a/erpnext/accounts/doctype/ledger_health/ledger_health.json +++ b/erpnext/accounts/doctype/ledger_health/ledger_health.json @@ -40,9 +40,10 @@ "label": "General and Payment Ledger mismatch" } ], + "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-03-26 19:59:41.180161", + "modified": "2024-04-09 11:16:07.044484", "modified_by": "Administrator", "module": "Accounts", "name": "Ledger Health", @@ -62,6 +63,7 @@ "write": 1 } ], + "read_only": 1, "sort_field": "modified", "sort_order": "DESC", "states": [] From 5808feaccf606f4ddae69be98fff58371c55f975 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 9 Apr 2024 17:35:15 +0530 Subject: [PATCH 27/29] chore: use super() instead of super(__class__, self) (cherry picked from commit 24d37d22a3d5bb52a5b7ee2e0157bc400c50b323) --- erpnext/accounts/doctype/ledger_health/test_ledger_health.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py index daad855abf0..d35b39d9ea1 100644 --- a/erpnext/accounts/doctype/ledger_health/test_ledger_health.py +++ b/erpnext/accounts/doctype/ledger_health/test_ledger_health.py @@ -32,7 +32,7 @@ class TestLedgerHealth(AccountsTestMixin, FrappeTestCase): monitor_settings.save() def clear_old_entries(self): - super(TestLedgerHealth, self).clear_old_entries() + super().clear_old_entries() lh = qb.DocType("Ledger Health") qb.from_(lh).delete().run() From 46c76b166d66c61a4265c99e70d35ec753d1dd23 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 9 Apr 2024 19:59:09 +0530 Subject: [PATCH 28/29] chore: resolve conflicts --- erpnext/accounts/utils.py | 41 --------------------------------------- 1 file changed, 41 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index c7fd17c33df..a32c25fbb70 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -2086,12 +2086,6 @@ def create_gain_loss_journal( journal_entry.save() journal_entry.submit() return journal_entry.name -<<<<<<< HEAD -======= - - -def get_party_types_from_account_type(account_type): - return frappe.db.get_all("Party Type", {"account_type": account_type}, pluck="name") def run_ledger_health_checks(): @@ -2118,40 +2112,6 @@ def run_ledger_health_checks(): # General Ledger and Payment Ledger discrepancy if health_monitor_settings.general_and_payment_ledger_mismatch: -<<<<<<< HEAD - gl_pl_comparison = frappe.get_doc("Report", "General and Payment Ledger Comparison") - filters = { - "company": "நுண்ணறி", - "period_start_date": period_start, - "period_end_date": period_end, - } - res = gl_pl_comparison.execute_script_report(filters=filters) - -<<<<<<< HEAD - # General Ledger and Payment Ledger discrepancy - gl_pl_comparison = frappe.get_doc("Report", "General and Payment Ledger Comparison") - # todo: company and dates should be configurable - filters = {"company": "நுண்ணறி", "period_start_date": period_start, "period_end_date": period_end} - res = gl_pl_comparison.execute_script_report(filters=filters) - - for x in res[1]: - doc = frappe.new_doc("Ledger Health") - doc.voucher_type = x.voucher_type - doc.voucher_no = x.voucher_no - doc.general_and_payment_ledger_mismatch = True - doc.checked_on = run_date - doc.save() ->>>>>>> 8c8d9be810 (refactor: barebones method to run checks) -======= - for x in res[1]: - doc = frappe.new_doc("Ledger Health") - doc.voucher_type = x.voucher_type - doc.voucher_no = x.voucher_no - doc.general_and_payment_ledger_mismatch = True - doc.checked_on = run_date - doc.save() ->>>>>>> a42482ce35 (refactor: control monitoring through settings page) -======= for x in health_monitor_settings.companies: filters = { "company": x.company, @@ -2167,4 +2127,3 @@ def run_ledger_health_checks(): doc.general_and_payment_ledger_mismatch = True doc.checked_on = run_date doc.save() ->>>>>>> 00eeacd06a (refactor: only run checks on specified companies) From 9745724d413d65ff533654a4a2a66d8ddae892a9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 10 Apr 2024 13:21:24 +0530 Subject: [PATCH 29/29] fix: ignore dimension validation for cancelled entries (cherry picked from commit 971c867f292802f14a7635d5ebbae45a60ec868d) --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 939d8410900..346fee81812 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -135,6 +135,7 @@ class GLEntry(Document): and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled + and not self.is_cancelled ): if not self.get(dimension.fieldname): frappe.throw( @@ -148,6 +149,7 @@ class GLEntry(Document): and self.company == dimension.company and dimension.mandatory_for_bs and not dimension.disabled + and not self.is_cancelled ): if not self.get(dimension.fieldname): frappe.throw(