diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4d0f6446da4..5993c81a174 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3438,6 +3438,105 @@ class TestSalesInvoice(FrappeTestCase): si.items[0].rate = 10 si.save() + 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 get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4f2856d98c8..534bddcd643 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -3319,6 +3319,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