From 22b16a6b0e98ea92cc8c5822d633f1b18e8013cd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Apr 2024 16:25:35 +0530 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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",