From fbb5058531278c0fa70f1bd6795c4eba83c66b72 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 14 Aug 2023 08:32:07 +0530 Subject: [PATCH 01/13] fix: AR/AP report based on payment terms (#36574) * fix: AR/AP report based on payment terms * fix: AR/AP report based on payment terms --- .../accounts_receivable/accounts_receivable.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 11bbb6f1e43..f78a84086a9 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -436,12 +436,11 @@ class ReceivablePayableReport(object): def allocate_outstanding_based_on_payment_terms(self, row): self.get_payment_terms(row) for term in row.payment_terms: - - # update "paid" and "oustanding" for this term + # update "paid" and "outstanding" for this term if not term.paid: self.allocate_closing_to_term(row, term, "paid") - # update "credit_note" and "oustanding" for this term + # update "credit_note" and "outstanding" for this term if term.outstanding: self.allocate_closing_to_term(row, term, "credit_note") @@ -453,7 +452,8 @@ class ReceivablePayableReport(object): """ select si.name, si.party_account_currency, si.currency, si.conversion_rate, - ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount + si.total_advance, ps.due_date, ps.payment_term, ps.payment_amount, ps.base_payment_amount, + ps.description, ps.paid_amount, ps.discounted_amount from `tab{0}` si, `tabPayment Schedule` ps where si.name = ps.parent and @@ -469,6 +469,10 @@ class ReceivablePayableReport(object): original_row = frappe._dict(row) row.payment_terms = [] + # Advance allocated during invoicing is not considered in payment terms + # Deduct that from paid amount pre allocation + row.paid -= flt(payment_terms_details[0].total_advance) + # If no or single payment terms, no need to split the row if len(payment_terms_details) <= 1: return @@ -483,7 +487,7 @@ class ReceivablePayableReport(object): ) and d.currency == d.party_account_currency: invoiced = d.payment_amount else: - invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) + invoiced = d.base_payment_amount row.payment_terms.append( term.update( From e023e33a15c9999569c2dd54735f37dc9ace40ea Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 14 Aug 2023 13:46:16 +0530 Subject: [PATCH 02/13] fix(demo): drop parent item group (#36636) This is translated according to user language, so "All Item Groups" might not exist on site. The code already finds root item group without doing anything. towards https://github.com/frappe/erpnext/issues/36635 --- erpnext/setup/demo_data/item_group.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/setup/demo_data/item_group.json b/erpnext/setup/demo_data/item_group.json index acb261f4ed5..f96944dc080 100644 --- a/erpnext/setup/demo_data/item_group.json +++ b/erpnext/setup/demo_data/item_group.json @@ -1,7 +1,6 @@ [ { "doctype": "Item Group", - "item_group_name": "Demo Item Group", - "parent_item_group": "All Item Groups" + "item_group_name": "Demo Item Group" } -] \ No newline at end of file +] From 75652799cd0e5b5e44dbcc418ee623d61adbaaa1 Mon Sep 17 00:00:00 2001 From: ViralKansodiya <141210323+viralkansodiya@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:14:49 +0530 Subject: [PATCH 03/13] feat: Tick on checkbox to include draft timesheets (#36577) feat: Tick on Check box to include Draft Timesheets --- erpnext/projects/report/billing_summary.py | 6 ++++-- .../employee_billing_summary/employee_billing_summary.js | 5 +++++ .../project_billing_summary/project_billing_summary.js | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py index bc8f2afb8c9..ac1524a49dd 100644 --- a/erpnext/projects/report/billing_summary.py +++ b/erpnext/projects/report/billing_summary.py @@ -98,9 +98,11 @@ def get_timesheets(filters): record_filters = [ ["start_date", "<=", filters.to_date], ["end_date", ">=", filters.from_date], - ["docstatus", "=", 1], ] - + if not filters.get("include_draft_timesheets"): + record_filters.append(["docstatus", "=", 1]) + else: + record_filters.append(["docstatus", "!=", 2]) if "employee" in filters: record_filters.append(["employee", "=", filters.employee]) diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js index 8566b1fc781..2c25465a616 100644 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js @@ -25,5 +25,10 @@ frappe.query_reports["Employee Billing Summary"] = { default: frappe.datetime.add_days(frappe.datetime.month_start(), -1), reqd: 1 }, + { + fieldname:"include_draft_timesheets", + label: __("Include Timesheets in Draft Status"), + fieldtype: "Check", + }, ] } diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.js b/erpnext/projects/report/project_billing_summary/project_billing_summary.js index 0242036dc72..fce0c68f111 100644 --- a/erpnext/projects/report/project_billing_summary/project_billing_summary.js +++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.js @@ -25,5 +25,10 @@ frappe.query_reports["Project Billing Summary"] = { default: frappe.datetime.add_days(frappe.datetime.month_start(),-1), reqd: 1 }, + { + fieldname:"include_draft_timesheets", + label: __("Include Timesheets in Draft Status"), + fieldtype: "Check", + }, ] } From 8b57979e9c3265e9233216fad6f1439d65718b44 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 14 Aug 2023 14:16:48 +0530 Subject: [PATCH 04/13] fix: clear demo data with unknown columns (#36637) --- erpnext/setup/demo.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py index 1c19974fce6..a6df419b4df 100644 --- a/erpnext/setup/demo.py +++ b/erpnext/setup/demo.py @@ -180,8 +180,16 @@ def clear_masters(): def clear_demo_record(document): document_type = document.get("doctype") del document["doctype"] - doc = frappe.get_doc(document_type, document) - frappe.delete_doc(doc.doctype, doc.name, ignore_permissions=True) + + valid_columns = frappe.get_meta(document_type).get_valid_columns() + + filters = document + for key in list(filters): + if key not in valid_columns: + filters.pop(key, None) + + doc = frappe.get_doc(document_type, filters) + doc.delete(ignore_permissions=True) def delete_company(company): From 1ff80fcbee24b709733b3561514bcefc7dae0259 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 16:25:38 +0530 Subject: [PATCH 05/13] fix: Button Alignment center in hero slider (backport #36607) (#36638) fix: Button Alignment center in hero slider (#36607) fix: speling in CSS (Button alignment center is not working on hero slider)#36561 (cherry picked from commit b131f70ed6a8e3b763d59106084440e08a73b815) Co-authored-by: ViralKansodiya <141210323+viralkansodiya@users.noreply.github.com> --- erpnext/e_commerce/web_template/hero_slider/hero_slider.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/e_commerce/web_template/hero_slider/hero_slider.html b/erpnext/e_commerce/web_template/hero_slider/hero_slider.html index e560f4ad7de..fe4fee375bd 100644 --- a/erpnext/e_commerce/web_template/hero_slider/hero_slider.html +++ b/erpnext/e_commerce/web_template/hero_slider/hero_slider.html @@ -1,7 +1,7 @@ {%- macro slide(image, title, subtitle, action, label, index, align="Left", theme="Dark") -%} {%- set align_class = resolve_class({ 'text-right': align == 'Right', - 'text-centre': align == 'Centre', + 'text-center': align == 'Centre', 'text-left': align == 'Left', }) -%} From 12a6f3b997ae8f598c90cb2296d2ab9ea89c1226 Mon Sep 17 00:00:00 2001 From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:15:47 +0530 Subject: [PATCH 06/13] feat: add invoice totals in tax withholding report (#36567) * fix: add invoice totals in tax withholding report * fix: naming series col in tax withholding report * fix: tds computation summary cols --- .../tax_withholding_details.js | 9 +- .../tax_withholding_details.py | 114 ++++++++++++------ .../tds_computation_summary.js | 28 ++++- .../tds_computation_summary.py | 87 +++++++------ 4 files changed, 162 insertions(+), 76 deletions(-) diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js index b66a5555228..8808165919a 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.js @@ -33,7 +33,14 @@ frappe.query_reports["Tax Withholding Details"] = { frappe.throw(__("Please select Party Type first")); } return party_type; - } + }, + "get_query": function() { + return { + "filters": { + "tax_withholding_category": ["!=",""], + } + } + }, }, { "fieldname":"from_date", diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py index ddd049a1151..7d166614722 100644 --- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py +++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py @@ -7,19 +7,26 @@ from frappe import _ def execute(filters=None): + if filters.get("party_type") == "Customer": + party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name") + else: + party_naming_by = frappe.db.get_single_value("Buying Settings", "supp_master_name") + + filters.update({"naming_series": party_naming_by}) + validate_filters(filters) ( tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, - invoice_net_total_map, + net_total_map, ) = get_tds_docs(filters) columns = get_columns(filters) res = get_result( - filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map + filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map ) return columns, res @@ -31,7 +38,7 @@ def validate_filters(filters): def get_result( - filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_net_total_map + filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map ): party_map = get_party_pan_map(filters.get("party_type")) tax_rate_map = get_tax_rate_map(filters) @@ -39,7 +46,7 @@ def get_result( out = [] for name, details in gle_map.items(): - tax_amount, total_amount = 0, 0 + tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0 tax_withholding_category = tax_category_map.get(name) rate = tax_rate_map.get(tax_withholding_category) @@ -60,8 +67,8 @@ def get_result( if entry.account in tds_accounts: tax_amount += entry.credit - entry.debit - if invoice_net_total_map.get(name): - total_amount = invoice_net_total_map.get(name) + if net_total_map.get(name): + total_amount, grand_total, base_total = net_total_map.get(name) else: total_amount += entry.credit @@ -69,15 +76,13 @@ def get_result( if party_map.get(party, {}).get("party_type") == "Supplier": party_name = "supplier_name" party_type = "supplier_type" - table_name = "Supplier" else: party_name = "customer_name" party_type = "customer_type" - table_name = "Customer" row = { "pan" - if frappe.db.has_column(table_name, "pan") + if frappe.db.has_column(filters.party_type, "pan") else "tax_id": party_map.get(party, {}).get("pan"), "party": party_map.get(party, {}).get("name"), } @@ -91,6 +96,8 @@ def get_result( "entity_type": party_map.get(party, {}).get(party_type), "rate": rate, "total_amount": total_amount, + "grand_total": grand_total, + "base_total": base_total, "tax_amount": tax_amount, "transaction_date": posting_date, "transaction_type": voucher_type, @@ -144,9 +151,9 @@ def get_gle_map(documents): def get_columns(filters): - pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" + pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id" columns = [ - {"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90}, + {"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60}, { "label": _(filters.get("party_type")), "fieldname": "party", @@ -158,25 +165,30 @@ def get_columns(filters): if filters.naming_series == "Naming Series": columns.append( - {"label": _("Party Name"), "fieldname": "party_name", "fieldtype": "Data", "width": 180} + { + "label": _(filters.party_type + " Name"), + "fieldname": "party_name", + "fieldtype": "Data", + "width": 180, + } ) columns.extend( [ + { + "label": _("Date of Transaction"), + "fieldname": "transaction_date", + "fieldtype": "Date", + "width": 100, + }, { "label": _("Section Code"), "options": "Tax Withholding Category", "fieldname": "section_code", "fieldtype": "Link", - "width": 180, - }, - {"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 120}, - { - "label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"), - "fieldname": "rate", - "fieldtype": "Percent", "width": 90, }, + {"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100}, { "label": _("Total Amount"), "fieldname": "total_amount", @@ -184,15 +196,27 @@ def get_columns(filters): "width": 90, }, { - "label": _("TDS Amount") if filters.get("party_type") == "Supplier" else _("TCS Amount"), + "label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"), + "fieldname": "rate", + "fieldtype": "Percent", + "width": 90, + }, + { + "label": _("Tax Amount"), "fieldname": "tax_amount", "fieldtype": "Float", "width": 90, }, { - "label": _("Date of Transaction"), - "fieldname": "transaction_date", - "fieldtype": "Date", + "label": _("Grand Total"), + "fieldname": "grand_total", + "fieldtype": "Float", + "width": 90, + }, + { + "label": _("Base Total"), + "fieldname": "base_total", + "fieldtype": "Float", "width": 90, }, {"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 100}, @@ -216,7 +240,7 @@ def get_tds_docs(filters): payment_entries = [] journal_entries = [] tax_category_map = frappe._dict() - invoice_net_total_map = frappe._dict() + net_total_map = frappe._dict() or_filters = frappe._dict() journal_entry_party_map = frappe._dict() bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name") @@ -260,13 +284,13 @@ def get_tds_docs(filters): tds_documents.append(d.voucher_no) if purchase_invoices: - get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, invoice_net_total_map) + get_doc_info(purchase_invoices, "Purchase Invoice", tax_category_map, net_total_map) if sales_invoices: - get_doc_info(sales_invoices, "Sales Invoice", tax_category_map, invoice_net_total_map) + get_doc_info(sales_invoices, "Sales Invoice", tax_category_map, net_total_map) if payment_entries: - get_doc_info(payment_entries, "Payment Entry", tax_category_map) + get_doc_info(payment_entries, "Payment Entry", tax_category_map, net_total_map) if journal_entries: journal_entry_party_map = get_journal_entry_party_map(journal_entries) @@ -277,7 +301,7 @@ def get_tds_docs(filters): tds_accounts, tax_category_map, journal_entry_party_map, - invoice_net_total_map, + net_total_map, ) @@ -295,11 +319,25 @@ def get_journal_entry_party_map(journal_entries): return journal_entry_party_map -def get_doc_info(vouchers, doctype, tax_category_map, invoice_net_total_map=None): +def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None): if doctype == "Purchase Invoice": - fields = ["name", "tax_withholding_category", "base_tax_withholding_net_total"] - if doctype == "Sales Invoice": - fields = ["name", "base_net_total"] + fields = [ + "name", + "tax_withholding_category", + "base_tax_withholding_net_total", + "grand_total", + "base_total", + ] + elif doctype == "Sales Invoice": + fields = ["name", "base_net_total", "grand_total", "base_total"] + elif doctype == "Payment Entry": + fields = [ + "name", + "tax_withholding_category", + "paid_amount", + "paid_amount_after_tax", + "base_paid_amount", + ] else: fields = ["name", "tax_withholding_category"] @@ -308,9 +346,15 @@ def get_doc_info(vouchers, doctype, tax_category_map, invoice_net_total_map=None for entry in entries: tax_category_map.update({entry.name: entry.tax_withholding_category}) if doctype == "Purchase Invoice": - invoice_net_total_map.update({entry.name: entry.base_tax_withholding_net_total}) - if doctype == "Sales Invoice": - invoice_net_total_map.update({entry.name: entry.base_net_total}) + net_total_map.update( + {entry.name: [entry.base_tax_withholding_net_total, entry.grand_total, entry.base_total]} + ) + elif doctype == "Sales Invoice": + net_total_map.update({entry.name: [entry.base_net_total, entry.grand_total, entry.base_total]}) + elif doctype == "Payment Entry": + net_total_map.update( + {entry.name: [entry.paid_amount, entry.paid_amount_after_tax, entry.base_paid_amount]} + ) def get_tax_rate_map(filters): diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js index d3348460a88..a0be1b5abdb 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.js @@ -12,17 +12,35 @@ frappe.query_reports["TDS Computation Summary"] = { "default": frappe.defaults.get_default('company') }, { - "fieldname":"supplier", - "label": __("Supplier"), - "fieldtype": "Link", - "options": "Supplier", + "fieldname":"party_type", + "label": __("Party Type"), + "fieldtype": "Select", + "options": ["Supplier", "Customer"], + "reqd": 1, + "default": "Supplier", + "on_change": function(){ + frappe.query_report.set_filter_value("party", ""); + } + }, + { + "fieldname":"party", + "label": __("Party"), + "fieldtype": "Dynamic Link", + "get_options": function() { + var party_type = frappe.query_report.get_filter_value('party_type'); + var party = frappe.query_report.get_filter_value('party'); + if(party && !party_type) { + frappe.throw(__("Please select Party Type first")); + } + return party_type; + }, "get_query": function() { return { "filters": { "tax_withholding_category": ["!=",""], } } - } + }, }, { "fieldname":"from_date", diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py index c6aa21cc862..82f97f18941 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -9,9 +9,14 @@ from erpnext.accounts.utils import get_fiscal_year def execute(filters=None): - validate_filters(filters) + if filters.get("party_type") == "Customer": + party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name") + else: + party_naming_by = frappe.db.get_single_value("Buying Settings", "supp_master_name") - filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name") + filters.update({"naming_series": party_naming_by}) + + validate_filters(filters) columns = get_columns(filters) ( @@ -25,7 +30,7 @@ def execute(filters=None): res = get_result( filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_total_map ) - final_result = group_by_supplier_and_category(res) + final_result = group_by_party_and_category(res, filters) return columns, final_result @@ -43,60 +48,67 @@ def validate_filters(filters): filters["fiscal_year"] = from_year -def group_by_supplier_and_category(data): - supplier_category_wise_map = {} +def group_by_party_and_category(data, filters): + party_category_wise_map = {} for row in data: - supplier_category_wise_map.setdefault( - (row.get("supplier"), row.get("section_code")), + party_category_wise_map.setdefault( + (row.get("party"), row.get("section_code")), { "pan": row.get("pan"), - "supplier": row.get("supplier"), - "supplier_name": row.get("supplier_name"), + "tax_id": row.get("tax_id"), + "party": row.get("party"), + "party_name": row.get("party_name"), "section_code": row.get("section_code"), "entity_type": row.get("entity_type"), - "tds_rate": row.get("tds_rate"), - "total_amount_credited": 0.0, - "tds_deducted": 0.0, + "rate": row.get("rate"), + "total_amount": 0.0, + "tax_amount": 0.0, }, ) - supplier_category_wise_map.get((row.get("supplier"), row.get("section_code")))[ - "total_amount_credited" - ] += row.get("total_amount_credited", 0.0) + party_category_wise_map.get((row.get("party"), row.get("section_code")))[ + "total_amount" + ] += row.get("total_amount", 0.0) - supplier_category_wise_map.get((row.get("supplier"), row.get("section_code")))[ - "tds_deducted" - ] += row.get("tds_deducted", 0.0) + party_category_wise_map.get((row.get("party"), row.get("section_code")))[ + "tax_amount" + ] += row.get("tax_amount", 0.0) - final_result = get_final_result(supplier_category_wise_map) + final_result = get_final_result(party_category_wise_map) return final_result -def get_final_result(supplier_category_wise_map): +def get_final_result(party_category_wise_map): out = [] - for key, value in supplier_category_wise_map.items(): + for key, value in party_category_wise_map.items(): out.append(value) return out def get_columns(filters): + pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id" columns = [ - {"label": _("PAN"), "fieldname": "pan", "fieldtype": "Data", "width": 90}, + {"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90}, { - "label": _("Supplier"), - "options": "Supplier", - "fieldname": "supplier", - "fieldtype": "Link", + "label": _(filters.get("party_type")), + "fieldname": "party", + "fieldtype": "Dynamic Link", + "options": "party_type", "width": 180, }, ] if filters.naming_series == "Naming Series": columns.append( - {"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 180} + { + "label": _(filters.party_type + " Name"), + "fieldname": "party_name", + "fieldtype": "Data", + "width": 180, + } ) columns.extend( @@ -109,18 +121,23 @@ def get_columns(filters): "width": 180, }, {"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180}, - {"label": _("TDS Rate %"), "fieldname": "tds_rate", "fieldtype": "Percent", "width": 90}, { - "label": _("Total Amount Credited"), - "fieldname": "total_amount_credited", - "fieldtype": "Float", - "width": 90, + "label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"), + "fieldname": "rate", + "fieldtype": "Percent", + "width": 120, }, { - "label": _("Amount of TDS Deducted"), - "fieldname": "tds_deducted", + "label": _("Total Amount"), + "fieldname": "total_amount", "fieldtype": "Float", - "width": 90, + "width": 120, + }, + { + "label": _("Tax Amount"), + "fieldname": "tax_amount", + "fieldtype": "Float", + "width": 120, }, ] ) From 985ff9781b9f18f7d2da55acaecab8ebf3c51bb7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 14 Aug 2023 18:19:15 +0530 Subject: [PATCH 07/13] fix: Tax withholding post LDC limit consumed (#36611) * fix: Tax withholding post LDC limit consumed * fix: LDC condition check --- .../tax_withholding_category.py | 62 +++++++--------- .../test_tax_withholding_category.py | 73 +++++++++++++++++++ 2 files changed, 100 insertions(+), 35 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index e66a886bf9a..d17ca08c408 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -262,14 +262,20 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N if tax_deducted: net_total = inv.tax_withholding_net_total if ldc: - tax_amount = get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total) + limit_consumed = get_limit_consumed(ldc, parties) + if is_valid_certificate(ldc, posting_date, limit_consumed): + tax_amount = get_lower_deduction_amount( + net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details + ) + else: + tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 else: tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 # once tds is deducted, not need to add vouchers in the invoice voucher_wise_amount = {} else: - tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers) + tax_amount = get_tds_amount(ldc, parties, inv, tax_details, vouchers) elif party_type == "Customer": if tax_deducted: @@ -416,7 +422,7 @@ def get_deducted_tax(taxable_vouchers, tax_details): return sum(entries) -def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): +def get_tds_amount(ldc, parties, inv, tax_details, vouchers): tds_amount = 0 invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1} @@ -496,15 +502,10 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): net_total += inv.tax_withholding_net_total supp_credit_amt = net_total - cumulative_threshold - if ldc and is_valid_certificate( - ldc.valid_from, - ldc.valid_upto, - inv.get("posting_date") or inv.get("transaction_date"), - tax_deducted, - inv.tax_withholding_net_total, - ldc.certificate_limit, - ): - tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details) + if ldc and is_valid_certificate(ldc, inv.get("posting_date") or inv.get("transaction_date"), 0): + tds_amount = get_lower_deduction_amount( + supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details + ) else: tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0 @@ -582,8 +583,7 @@ def get_invoice_total_without_tcs(inv, tax_details): return inv.grand_total - tcs_tax_row_amount -def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total): - tds_amount = 0 +def get_limit_consumed(ldc, parties): limit_consumed = frappe.db.get_value( "Purchase Invoice", { @@ -597,37 +597,29 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total): "sum(tax_withholding_net_total)", ) - if is_valid_certificate( - ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total, ldc.certificate_limit - ): - tds_amount = get_ltds_amount( - net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details - ) - - return tds_amount + return limit_consumed -def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details): - if certificate_limit - flt(deducted_amount) - flt(current_amount) >= 0: +def get_lower_deduction_amount( + current_amount, limit_consumed, certificate_limit, rate, tax_details +): + if certificate_limit - flt(limit_consumed) - flt(current_amount) >= 0: return current_amount * rate / 100 else: - ltds_amount = certificate_limit - flt(deducted_amount) + ltds_amount = certificate_limit - flt(limit_consumed) tds_amount = current_amount - ltds_amount return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100 -def is_valid_certificate( - valid_from, valid_upto, posting_date, deducted_amount, current_amount, certificate_limit -): - valid = False +def is_valid_certificate(ldc, posting_date, limit_consumed): + available_amount = flt(ldc.certificate_limit) - flt(limit_consumed) + if ( + getdate(ldc.valid_from) <= getdate(posting_date) <= getdate(ldc.valid_upto) + ) and available_amount > 0: + return True - available_amount = flt(certificate_limit) - flt(deducted_amount) - - if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0: - valid = True - - return valid + return False def normal_round(number): diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 80220e4e541..0fbaf23c3c8 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -4,6 +4,7 @@ import unittest import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.utils import today from erpnext.accounts.utils import get_fiscal_year @@ -17,6 +18,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): # create relevant supplier, etc create_records() create_tax_withholding_category_records() + make_pan_no_field() def tearDown(self): cancel_invoices() @@ -451,6 +453,40 @@ class TestTaxWithholdingCategory(unittest.TestCase): pe2.cancel() pe3.cancel() + def test_lower_deduction_certificate_application(self): + frappe.db.set_value( + "Supplier", + "Test LDC Supplier", + { + "tax_withholding_category": "Test Service Category", + "pan": "ABCTY1234D", + }, + ) + + create_lower_deduction_certificate( + supplier="Test LDC Supplier", + certificate_no="1AE0423AAJ", + tax_withholding_category="Test Service Category", + tax_rate=2, + limit=50000, + ) + + pi1 = create_purchase_invoice(supplier="Test LDC Supplier", rate=35000) + pi1.submit() + self.assertEqual(pi1.taxes[0].tax_amount, 700) + + pi2 = create_purchase_invoice(supplier="Test LDC Supplier", rate=35000) + pi2.submit() + self.assertEqual(pi2.taxes[0].tax_amount, 2300) + + pi3 = create_purchase_invoice(supplier="Test LDC Supplier", rate=35000) + pi3.submit() + self.assertEqual(pi3.taxes[0].tax_amount, 3500) + + pi1.cancel() + pi2.cancel() + pi3.cancel() + def cancel_invoices(): purchase_invoices = frappe.get_all( @@ -610,6 +646,7 @@ def create_records(): "Test TDS Supplier6", "Test TDS Supplier7", "Test TDS Supplier8", + "Test LDC Supplier", ]: if frappe.db.exists("Supplier", name): continue @@ -806,3 +843,39 @@ def create_tax_withholding_category( "accounts": [{"company": "_Test Company", "account": account}], } ).insert() + + +def create_lower_deduction_certificate( + supplier, tax_withholding_category, tax_rate, certificate_no, limit +): + fiscal_year = get_fiscal_year(today(), company="_Test Company") + if not frappe.db.exists("Lower Deduction Certificate", certificate_no): + frappe.get_doc( + { + "doctype": "Lower Deduction Certificate", + "company": "_Test Company", + "supplier": supplier, + "certificate_no": certificate_no, + "tax_withholding_category": tax_withholding_category, + "fiscal_year": fiscal_year[0], + "valid_from": fiscal_year[1], + "valid_upto": fiscal_year[2], + "rate": tax_rate, + "certificate_limit": limit, + } + ).insert() + + +def make_pan_no_field(): + pan_field = { + "Supplier": [ + { + "fieldname": "pan", + "label": "PAN", + "fieldtype": "Data", + "translatable": 0, + } + ] + } + + create_custom_fields(pan_field, update=1) From 843e77e72d17320fbf0f10bde4920ae600f62a40 Mon Sep 17 00:00:00 2001 From: HarryPaulo Date: Mon, 14 Aug 2023 10:21:30 -0300 Subject: [PATCH 08/13] fix: standard formula to calculate the "difference" (#36612) --- erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index a6c0102a7f9..91e71e90dd8 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -153,7 +153,7 @@ frappe.ui.form.on('POS Closing Entry', { frappe.ui.form.on('POS Closing Entry Detail', { closing_amount: (frm, cdt, cdn) => { const row = locals[cdt][cdn]; - frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount)); + frappe.model.set_value(cdt, cdn, "difference", flt(row.closing_amount - row.expected_amount)); } }) From 670d9e5556e2e43ec861e4e0d4669076547392bb Mon Sep 17 00:00:00 2001 From: abdosaeed95 <118386543+abdosaeed95@users.noreply.github.com> Date: Mon, 14 Aug 2023 16:24:01 +0300 Subject: [PATCH 09/13] fix: validate loyalty_amount against rounded_total instead of grand_total (#36466) --- erpnext/accounts/doctype/loyalty_program/loyalty_program.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py index a134f746635..4f58579a52d 100644 --- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py @@ -145,8 +145,8 @@ def validate_loyalty_points(ref_doc, points_to_redeem): loyalty_amount = flt(points_to_redeem * loyalty_program_details.conversion_factor) - if loyalty_amount > ref_doc.grand_total: - frappe.throw(_("You can't redeem Loyalty Points having more value than the Grand Total.")) + if loyalty_amount > ref_doc.rounded_total: + frappe.throw(_("You can't redeem Loyalty Points having more value than the Rounded Total.")) if not ref_doc.loyalty_amount and ref_doc.loyalty_amount != loyalty_amount: ref_doc.loyalty_amount = loyalty_amount From 7ab55b1bb293517aaac11193f25efdf6ccbc7bb4 Mon Sep 17 00:00:00 2001 From: Kevin Shenk Date: Mon, 14 Aug 2023 09:33:01 -0400 Subject: [PATCH 10/13] fix: Document Name link validation in Bank Reconciliation Tool (#36495) fix: format_row broke Document Name link validation #35540 broke Voucher Matching, leading to an invalid link exception on submission. This is because the format_row() function overwrites the row data instead of just providing a formatter on the DataTable column, and therefore passes through the formatted (linked) column data instead of the Document Name only. This patch moves the appropriate frappe.form.formatters.Link function to a dedicated format hook on the DataTable columns definition, both fixing the error and retaining the functionality of #35540. --- erpnext/public/js/bank_reconciliation_tool/dialog_manager.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js index cbb64ca61b2..52fa8ab0f3c 100644 --- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js @@ -104,6 +104,9 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { name: __("Document Name"), editable: false, width: 1, + format: (value, row) => { + return frappe.form.formatters.Link(value, {options: row[2].content}); + }, }, { name: __("Reference Date"), @@ -132,7 +135,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { format_row(row) { return [ row[1], // Document Type - frappe.form.formatters.Link(row[2], {options: row[1]}), // Document Name + row[2], // Document Name row[5] || row[8], // Reference Date format_currency(row[3], row[9]), // Remaining row[4], // Reference Number From a0fc68538fbfd941f2d26741770b037b89dea36a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 14 Aug 2023 17:38:44 +0530 Subject: [PATCH 11/13] refactor: toggle for negative rates in Selling Settings --- .../doctype/sales_invoice/test_sales_invoice.py | 1 + erpnext/controllers/status_updater.py | 15 ++++++++++++--- erpnext/patches.txt | 5 +++-- .../selling_settings/selling_settings.json | 9 ++++++++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 63c0c453045..f9cfe5a9200 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -3376,6 +3376,7 @@ class TestSalesInvoice(unittest.TestCase): set_advance_flag(company="_Test Company", flag=0, default_account="") + @change_settings("Selling Settings", {"allow_negative_rates_for_items": 0}) def test_sales_return_negative_rate(self): si = create_sales_invoice(is_return=1, qty=-2, rate=-10, do_not_save=True) self.assertRaises(frappe.ValidationError, si.save) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index f3663cc5271..73a248fb531 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -5,7 +5,7 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import comma_or, flt, getdate, now, nowdate +from frappe.utils import comma_or, flt, get_link_to_form, getdate, now, nowdate class OverAllowanceError(frappe.ValidationError): @@ -233,8 +233,17 @@ class StatusUpdater(Document): if hasattr(d, "qty") and d.qty > 0 and self.get("is_return"): frappe.throw(_("For an item {0}, quantity must be negative number").format(d.item_code)) - if hasattr(d, "item_code") and hasattr(d, "rate") and flt(d.rate) < 0: - frappe.throw(_("For an item {0}, rate must be a positive number").format(d.item_code)) + if not frappe.db.get_single_value("Selling Settings", "allow_negative_rates_for_items"): + if hasattr(d, "item_code") and hasattr(d, "rate") and flt(d.rate) < 0: + frappe.throw( + _( + "For item {0}, rate must be a positive number. To Allow negative rates, enable {1} in {2}" + ).format( + frappe.bold(d.item_code), + frappe.bold(_("`Allow Negative rates for Items`")), + get_link_to_form("Selling Settings", "Selling Settings"), + ), + ) if d.doctype == args["source_dt"] and d.get(args["join_field"]): args["name"] = d.get(args["join_field"]) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d035ad6fe7c..a25c7c22ade 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -322,8 +322,6 @@ erpnext.patches.v14_0.update_closing_balances #14-07-2023 execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0) erpnext.patches.v14_0.update_reference_type_in_journal_entry_accounts erpnext.patches.v14_0.update_subscription_details -# below migration patches should always run last -erpnext.patches.v14_0.migrate_gl_to_payment_ledger execute:frappe.delete_doc_if_exists("Report", "Tax Detail") erpnext.patches.v15_0.enable_all_leads erpnext.patches.v14_0.update_company_in_ldc @@ -340,3 +338,6 @@ erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users execute:frappe.defaults.clear_default("fiscal_year") erpnext.patches.v15_0.remove_exotel_integration erpnext.patches.v14_0.single_to_multi_dunning +execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0) +# below migration patch should always run last +erpnext.patches.v14_0.migrate_gl_to_payment_ledger diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index f3b9f6f3b06..6855012d5f7 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -20,6 +20,7 @@ "editable_price_list_rate", "validate_selling_price", "editable_bundle_item_rates", + "allow_negative_rates_for_items", "sales_transactions_settings_section", "so_required", "dn_required", @@ -193,6 +194,12 @@ "fieldname": "dont_reserve_sales_order_qty_on_sales_return", "fieldtype": "Check", "label": "Don't Reserve Sales Order Qty on Sales Return" + }, + { + "default": "0", + "fieldname": "allow_negative_rates_for_items", + "fieldtype": "Check", + "label": "Allow Negative rates for Items" } ], "icon": "fa fa-cog", @@ -200,7 +207,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-08-09 15:35:42.914354", + "modified": "2023-08-14 20:33:05.693667", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", From 89e89de9616e4e4224e532d9e63933b18577c3a4 Mon Sep 17 00:00:00 2001 From: Babuuu <59009890+aynugek@users.noreply.github.com> Date: Tue, 15 Aug 2023 06:24:06 +0300 Subject: [PATCH 12/13] Fix tax amount in customer portal (#36453) The net total is the value that is currently being displayed instead of the tax amount. --- erpnext/templates/includes/order/order_taxes.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html index 0060ab39cc9..d7b9620fa0a 100644 --- a/erpnext/templates/includes/order/order_taxes.html +++ b/erpnext/templates/includes/order/order_taxes.html @@ -19,7 +19,7 @@ {{ d.description }}
- {{ doc.get_formatted("net_total") }} + {{ d.get_formatted("base_tax_amount") }}
From 1e07f6eef97be0f0c43db8804069574b1f06dc46 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 15 Aug 2023 10:45:36 +0530 Subject: [PATCH 13/13] fix(demo): More exception handling on demo data generation (#36652) --- erpnext/setup/demo.py | 2 +- erpnext/setup/demo_data/purchase_order.json | 10 ++++++++++ erpnext/setup/demo_data/sales_order.json | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py index a6df419b4df..926283ff1c6 100644 --- a/erpnext/setup/demo.py +++ b/erpnext/setup/demo.py @@ -114,7 +114,7 @@ def create_transaction(doctype, company, start_date): if document_type == "Purchase Order": posting_date = get_random_date(start_date, 1, 30) else: - posting_date = get_random_date(start_date, 31, 365) + posting_date = get_random_date(start_date, 31, 364) doctype.update( { diff --git a/erpnext/setup/demo_data/purchase_order.json b/erpnext/setup/demo_data/purchase_order.json index 42ffa8862a9..318a8658f04 100644 --- a/erpnext/setup/demo_data/purchase_order.json +++ b/erpnext/setup/demo_data/purchase_order.json @@ -4,6 +4,7 @@ "supplier": "Zuckerman Security Ltd.", "doctype": "Purchase Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Purchase Order Item", @@ -20,6 +21,7 @@ "supplier": "MA Inc.", "doctype": "Purchase Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Purchase Order Item", @@ -36,6 +38,7 @@ "supplier": "Summit Traders Ltd.", "doctype": "Purchase Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Purchase Order Item", @@ -52,6 +55,7 @@ "supplier": "Zuckerman Security Ltd.", "doctype": "Purchase Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Purchase Order Item", @@ -68,6 +72,7 @@ "supplier": "MA Inc.", "doctype": "Purchase Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Purchase Order Item", @@ -84,6 +89,7 @@ "supplier": "Summit Traders Ltd.", "doctype": "Purchase Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Purchase Order Item", @@ -100,6 +106,7 @@ "supplier": "Zuckerman Security Ltd.", "doctype": "Purchase Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Purchase Order Item", @@ -116,6 +123,7 @@ "supplier": "MA Inc.", "doctype": "Purchase Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Purchase Order Item", @@ -132,6 +140,7 @@ "supplier": "Summit Traders Ltd.", "doctype": "Purchase Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Purchase Order Item", @@ -148,6 +157,7 @@ "supplier": "Zuckerman Security Ltd.", "doctype": "Purchase Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Purchase Order Item", diff --git a/erpnext/setup/demo_data/sales_order.json b/erpnext/setup/demo_data/sales_order.json index d39063723ee..29bffc34ff0 100644 --- a/erpnext/setup/demo_data/sales_order.json +++ b/erpnext/setup/demo_data/sales_order.json @@ -4,6 +4,7 @@ "customer": "Grant Plastics Ltd.", "doctype": "Sales Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Sales Order Item", @@ -20,6 +21,7 @@ "customer": "West View Software Ltd.", "doctype": "Sales Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Sales Order Item", @@ -44,6 +46,7 @@ "customer": "West View Software Ltd.", "doctype": "Sales Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Sales Order Item", @@ -76,6 +79,7 @@ "customer": "Palmer Productions Ltd.", "doctype": "Sales Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Sales Order Item", @@ -92,6 +96,7 @@ "customer": "Grant Plastics Ltd.", "doctype": "Sales Order", "update_stock": 1, + "disable_rounded_total": 1, "items": [ { "doctype": "Sales Order Item",