From 4165fee6a7f09aecc5a2a3e6767732ae773bcbe7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Sep 2022 20:05:20 +0530 Subject: [PATCH 01/13] fix: Add child table for tax withheld vouchers (cherry picked from commit 246c1a9380238212b821951db6ed3e8ef88d9922) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json --- .../purchase_invoice/purchase_invoice.json | 34 +++++++++++++ .../doctype/tax_withheld_vouchers/__init__.py | 0 .../tax_withheld_vouchers.json | 48 +++++++++++++++++++ .../tax_withheld_vouchers.py | 9 ++++ 4 files changed, 91 insertions(+) create mode 100644 erpnext/accounts/doctype/tax_withheld_vouchers/__init__.py create mode 100644 erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json create mode 100644 erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index bd0116443ff..3c67cf38eaa 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -93,6 +93,8 @@ "taxes_and_charges_added", "taxes_and_charges_deducted", "total_taxes_and_charges", + "tax_withheld_vouchers_section", + "tax_withheld_vouchers", "section_break_44", "apply_discount_on", "base_discount_amount", @@ -1366,7 +1368,11 @@ "width": "50px" }, { +<<<<<<< HEAD "depends_on": "eval:doc.update_stock && doc.is_subcontracted==\"Yes\"", +======= + "depends_on": "eval:doc.is_subcontracted", +>>>>>>> 246c1a9380 (fix: Add child table for tax withheld vouchers) "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", @@ -1417,13 +1423,40 @@ "label": "Advance Tax", "options": "Advance Tax", "read_only": 1 +<<<<<<< HEAD +======= + }, + { + "default": "0", + "fieldname": "is_old_subcontracting_flow", + "fieldtype": "Check", + "hidden": 1, + "label": "Is Old Subcontracting Flow", + "read_only": 1 + }, + { + "fieldname": "tax_withheld_vouchers_section", + "fieldtype": "Section Break", + "label": "Tax Withheld Vouchers" + }, + { + "fieldname": "tax_withheld_vouchers", + "fieldtype": "Table", + "label": "Tax Withheld Vouchers", + "options": "Tax Withheld Vouchers", + "read_only": 1 +>>>>>>> 246c1a9380 (fix: Add child table for tax withheld vouchers) } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2021-11-25 13:31:02.716727", +======= + "modified": "2022-09-13 16:22:04.103982", +>>>>>>> 246c1a9380 (fix: Add child table for tax withheld vouchers) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", @@ -1483,6 +1516,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "timeline_field": "supplier", "title_field": "title", "track_changes": 1 diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/__init__.py b/erpnext/accounts/doctype/tax_withheld_vouchers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json new file mode 100644 index 00000000000..cecc6fb20ab --- /dev/null +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json @@ -0,0 +1,48 @@ +{ + "actions": [], + "autoname": "autoincrement", + "creation": "2022-09-13 16:18:59.404842", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "voucher_type", + "voucher_name", + "taxable_amount" + ], + "fields": [ + { + "fieldname": "voucher_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Voucher Type", + "options": "DocType" + }, + { + "fieldname": "voucher_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Voucher Name", + "options": "voucher_type" + }, + { + "fieldname": "taxable_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Taxable Amount" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2022-09-13 17:31:52.321034", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Tax Withheld Vouchers", + "naming_rule": "Autoincrement", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py new file mode 100644 index 00000000000..ea54c5403a8 --- /dev/null +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.py @@ -0,0 +1,9 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class TaxWithheldVouchers(Document): + pass From 51d7c0bfe4f52417a17df4e934e5a62ebfe5253d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Sep 2022 20:31:31 +0530 Subject: [PATCH 02/13] fix: Fetch vouchers to show in Invoice (cherry picked from commit 3fb1595a4ecdaeac4a6d85be1d8bfa4a62f181d8) --- .../purchase_invoice/purchase_invoice.py | 15 +++- .../tax_withholding_category.py | 72 +++++++++---------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 75508be8d09..6476845de78 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1457,7 +1457,7 @@ class PurchaseInvoice(BuyingController): if not self.tax_withholding_category: return - tax_withholding_details, advance_taxes = get_party_tax_withholding_details( + tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details( self, self.tax_withholding_category ) @@ -1486,6 +1486,19 @@ class PurchaseInvoice(BuyingController): for d in to_remove: self.remove(d) + ## Add pending vouchers on which tax was withheld + self.set("tax_withheld_vouchers", []) + + for voucher_no, voucher_details in voucher_wise_amount.items(): + self.append( + "tax_withheld_vouchers", + { + "voucher_name": voucher_no, + "voucher_type": voucher_details.get("voucher_type"), + "taxable_amount": voucher_details.get("amount"), + }, + ) + # calculate totals again after applying TDS self.calculate_taxes_and_totals() 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 3db21dc5a41..35ce4a92e10 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -100,7 +100,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): ).format(tax_withholding_category, inv.company, party) ) - tax_amount, tax_deducted, tax_deducted_on_advances = get_tax_amount( + tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount = get_tax_amount( party_type, parties, inv, tax_details, posting_date, pan_no ) @@ -110,7 +110,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted) if inv.doctype == "Purchase Invoice": - return tax_row, tax_deducted_on_advances + return tax_row, tax_deducted_on_advances, voucher_wise_amount else: return tax_row @@ -208,7 +208,9 @@ def get_lower_deduction_certificate(tax_details, pan_no): def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None): - vouchers = get_invoice_vouchers(parties, tax_details, inv.company, party_type=party_type) + vouchers, voucher_wise_amount = get_invoice_vouchers( + parties, tax_details, inv.company, party_type=party_type + ) advance_vouchers = get_advance_vouchers( parties, company=inv.company, @@ -227,6 +229,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N tax_deducted = get_deducted_tax(taxable_vouchers, tax_details) tax_amount = 0 + if party_type == "Supplier": ldc = get_lower_deduction_certificate(tax_details, pan_no) if tax_deducted: @@ -252,12 +255,13 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N if cint(tax_details.round_off_tax_amount): tax_amount = round(tax_amount) - return tax_amount, tax_deducted, tax_deducted_on_advances + return tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): - dr_or_cr = "credit" if party_type == "Supplier" else "debit" doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" + voucher_wise_amount = {} + vouchers = [] filters = { "company": company, @@ -272,29 +276,42 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): {"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")} ) - invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""] + invoices_details = frappe.get_all( + doctype, filters=filters, fields=["name", "base_net_total"] + ) or [""] - journal_entries = frappe.db.sql( + for d in invoices_details: + vouchers.append(d.name) + voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}}) + + journal_entries_details = frappe.db.sql( """ - SELECT j.name + SELECT j.name, ja.credit - ja.debit AS amount FROM `tabJournal Entry` j, `tabJournal Entry Account` ja WHERE - j.docstatus = 1 + j.name = ja.parent + AND j.docstatus = 1 AND j.is_opening = 'No' AND j.posting_date between %s and %s - AND ja.{dr_or_cr} > 0 AND ja.party in %s - """.format( - dr_or_cr=dr_or_cr + AND j.apply_tds = 1 + AND j.tax_withholding_category = %s + """, + ( + tax_details.from_date, + tax_details.to_date, + tuple(parties), + tax_details.get("tax_withholding_category"), ), - (tax_details.from_date, tax_details.to_date, tuple(parties)), - as_list=1, + as_dict=1, ) - if journal_entries: - journal_entries = journal_entries[0] + if journal_entries_details: + for d in journal_entries_details: + vouchers.append(d.name) + voucher_wise_amount.update({d.name: {"amount": d.amount, "voucher_type": "Journal Entry"}}) - return invoices + journal_entries + return vouchers, voucher_wise_amount def get_advance_vouchers( @@ -385,11 +402,6 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): supp_credit_amt += supp_jv_credit_amt supp_credit_amt += inv.net_total - debit_note_amount = get_debit_note_amount( - parties, tax_details.from_date, tax_details.to_date, inv.company - ) - supp_credit_amt -= debit_note_amount - threshold = tax_details.get("threshold", 0) cumulative_threshold = tax_details.get("cumulative_threshold", 0) @@ -506,22 +518,6 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net return tds_amount -def get_debit_note_amount(suppliers, from_date, to_date, company=None): - - filters = { - "supplier": ["in", suppliers], - "is_return": 1, - "docstatus": 1, - "posting_date": ["between", (from_date, to_date)], - } - fields = ["abs(sum(net_total)) as net_total"] - - if company: - filters["company"] = company - - return frappe.get_all("Purchase Invoice", filters, fields)[0].get("net_total") or 0.0 - - def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details): if current_amount < (certificate_limit - deducted_amount): return current_amount * rate / 100 From e07fd46a461f4a9fb2748c165fc1eea5385201ea Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 14 Sep 2022 09:13:02 +0530 Subject: [PATCH 03/13] test: Add tests (cherry picked from commit b6184ce4715111add6dad3acf839097639cb4b51) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json --- .../purchase_invoice/purchase_invoice.json | 8 ++- .../tax_withheld_vouchers.json | 5 +- .../tax_withholding_category.py | 4 +- .../test_tax_withholding_category.py | 68 ++++++++++++++++++- 4 files changed, 75 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 3c67cf38eaa..c6996b9aa88 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -83,6 +83,8 @@ "section_break_51", "taxes_and_charges", "taxes", + "tax_withheld_vouchers_section", + "tax_withheld_vouchers", "sec_tax_breakup", "other_charges_calculation", "totals", @@ -93,8 +95,6 @@ "taxes_and_charges_added", "taxes_and_charges_deducted", "total_taxes_and_charges", - "tax_withheld_vouchers_section", - "tax_withheld_vouchers", "section_break_44", "apply_discount_on", "base_discount_amount", @@ -1452,11 +1452,15 @@ "idx": 204, "is_submittable": 1, "links": [], +<<<<<<< HEAD <<<<<<< HEAD "modified": "2021-11-25 13:31:02.716727", ======= "modified": "2022-09-13 16:22:04.103982", >>>>>>> 246c1a9380 (fix: Add child table for tax withheld vouchers) +======= + "modified": "2022-09-13 23:39:54.525037", +>>>>>>> b6184ce471 (test: Add tests) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json index cecc6fb20ab..ce8c0c37086 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json @@ -29,13 +29,14 @@ "fieldname": "taxable_amount", "fieldtype": "Currency", "in_list_view": 1, - "label": "Taxable Amount" + "label": "Taxable Amount", + "options": "Company:company:default_currency" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-09-13 17:31:52.321034", + "modified": "2022-09-13 23:40:41.479208", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withheld Vouchers", 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 35ce4a92e10..179557f751a 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -276,9 +276,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): {"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")} ) - invoices_details = frappe.get_all( - doctype, filters=filters, fields=["name", "base_net_total"] - ) or [""] + invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", "base_net_total"]) for d in invoices_details: vouchers.append(d.name) 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 3059f8d64b8..5c031a99542 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 @@ -148,7 +148,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): self.assertEqual(tcs_charged, 500) invoices.append(si) - # delete invoices to avoid clashing + # cancel invoices to avoid clashing for d in invoices: d.cancel() @@ -182,7 +182,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): self.assertEqual(pi1.taxes[0].tax_amount, 4000) - # delete invoices to avoid clashing + # cancel invoices to avoid clashing for d in invoices: d.cancel() @@ -207,10 +207,52 @@ class TestTaxWithholdingCategory(unittest.TestCase): self.assertEqual(pi1.taxes[0].tax_amount, 250) - # delete invoices to avoid clashing + # cancel invoices to avoid clashing for d in invoices: d.cancel() + def test_tax_withholding_category_voucher_display(self): + frappe.db.set_value( + "Supplier", "Test TDS Supplier6", "tax_withholding_category", "Test Multi Invoice Category" + ) + invoices = [] + + pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=4000, do_not_save=True) + pi.apply_tds = 1 + pi.tax_withholding_category = "Test Multi Invoice Category" + pi.save() + pi.submit() + invoices.append(pi) + + pi1 = create_purchase_invoice(supplier="Test TDS Supplier6", rate=2000, do_not_save=True) + pi1.apply_tds = 1 + pi1.is_return = 1 + pi1.items[0].qty = -1 + pi1.tax_withholding_category = "Test Multi Invoice Category" + pi1.save() + pi1.submit() + invoices.append(pi1) + + pi2 = create_purchase_invoice(supplier="Test TDS Supplier6", rate=9000, do_not_save=True) + pi2.apply_tds = 1 + pi2.tax_withholding_category = "Test Multi Invoice Category" + pi2.save() + pi2.submit() + invoices.append(pi2) + + pi2.load_from_db() + + self.assertTrue(pi2.taxes[0].tax_amount, 1100) + + self.assertTrue(pi2.tax_withheld_vouchers[0].voucher_name == pi1.name) + self.assertTrue(pi2.tax_withheld_vouchers[0].taxable_amount == pi1.net_total) + self.assertTrue(pi2.tax_withheld_vouchers[1].voucher_name == pi.name) + self.assertTrue(pi2.tax_withheld_vouchers[1].taxable_amount == pi.net_total) + + # cancel invoices to avoid clashing + for d in reversed(invoices): + d.cancel() + def cancel_invoices(): purchase_invoices = frappe.get_all( @@ -308,6 +350,7 @@ def create_records(): "Test TDS Supplier3", "Test TDS Supplier4", "Test TDS Supplier5", + "Test TDS Supplier6", ]: if frappe.db.exists("Supplier", name): continue @@ -498,3 +541,22 @@ def create_tax_with_holding_category(): "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}], } ).insert() + + if not frappe.db.exists("Tax Withholding Category", "Test Multi Invoice Category"): + frappe.get_doc( + { + "doctype": "Tax Withholding Category", + "name": "Test Multi Invoice Category", + "category_name": "Test Multi Invoice Category", + "rates": [ + { + "from_date": fiscal_year[1], + "to_date": fiscal_year[2], + "tax_withholding_rate": 10, + "single_threshold": 5000, + "cumulative_threshold": 10000, + } + ], + "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}], + } + ).insert() From ca0cce7599afaa1d24aa6cc5f853eee01005e101 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 16 Sep 2022 13:50:37 +0530 Subject: [PATCH 04/13] fix: TDS deduction via journal entry (cherry picked from commit 36d0906ea26b65b9a4111439e2046bcf16305273) --- .../doctype/journal_entry/journal_entry.py | 4 ++- .../tax_withholding_category.py | 30 ++++++++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 8cc20038770..73e673ea38b 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -194,7 +194,9 @@ class JournalEntry(AccountsController): } ) - tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category) + tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details( + inv, self.tax_withholding_category + ) if not tax_withholding_details: return 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 179557f751a..ebad1e76a30 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -335,23 +335,25 @@ def get_advance_vouchers( def get_taxes_deducted_on_advances_allocated(inv, tax_details): - advances = [d.reference_name for d in inv.get("advances")] tax_info = [] - if advances: - pe = frappe.qb.DocType("Payment Entry").as_("pe") - at = frappe.qb.DocType("Advance Taxes and Charges").as_("at") + if inv.get("advances"): + advances = [d.reference_name for d in inv.get("advances")] - tax_info = ( - frappe.qb.from_(at) - .inner_join(pe) - .on(pe.name == at.parent) - .select(at.parent, at.name, at.tax_amount, at.allocated_amount) - .where(pe.tax_withholding_category == tax_details.get("tax_withholding_category")) - .where(at.parent.isin(advances)) - .where(at.account_head == tax_details.account_head) - .run(as_dict=True) - ) + if advances: + pe = frappe.qb.DocType("Payment Entry").as_("pe") + at = frappe.qb.DocType("Advance Taxes and Charges").as_("at") + + tax_info = ( + frappe.qb.from_(at) + .inner_join(pe) + .on(pe.name == at.parent) + .select(at.parent, at.name, at.tax_amount, at.allocated_amount) + .where(pe.tax_withholding_category == tax_details.get("tax_withholding_category")) + .where(at.parent.isin(advances)) + .where(at.account_head == tax_details.account_head) + .run(as_dict=True) + ) return tax_info From fe252b48f7e11542eb79b3b761a81117f3dba453 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Sep 2022 09:06:18 +0530 Subject: [PATCH 05/13] chore: fix tests (cherry picked from commit 9aa1f84d4578901902bfdb3889c571fced8861c1) --- .../test_tax_withholding_category.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 5c031a99542..e80fe11ab30 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 @@ -52,7 +52,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): invoices.append(pi) # delete invoices to avoid clashing - for d in invoices: + for d in reversed(invoices): d.cancel() def test_single_threshold_tds(self): @@ -88,7 +88,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): self.assertEqual(pi.taxes_and_charges_deducted, 1000) # delete invoices to avoid clashing - for d in invoices: + for d in reversed(invoices): d.cancel() def test_tax_withholding_category_checks(self): @@ -114,7 +114,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): # TDS should be applied only on 1000 self.assertEqual(pi1.taxes[0].tax_amount, 1000) - for d in invoices: + for d in reversed(invoices): d.cancel() def test_cumulative_threshold_tcs(self): @@ -149,7 +149,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): invoices.append(si) # cancel invoices to avoid clashing - for d in invoices: + for d in reversed(invoices): d.cancel() def test_tds_calculation_on_net_total(self): @@ -183,7 +183,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): self.assertEqual(pi1.taxes[0].tax_amount, 4000) # cancel invoices to avoid clashing - for d in invoices: + for d in reversed(invoices): d.cancel() def test_multi_category_single_supplier(self): @@ -208,7 +208,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): self.assertEqual(pi1.taxes[0].tax_amount, 250) # cancel invoices to avoid clashing - for d in invoices: + for d in reversed(invoices): d.cancel() def test_tax_withholding_category_voucher_display(self): From 794eeebabc74e549254ef61a751d9d2b7cd5601a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Sep 2022 14:48:06 +0530 Subject: [PATCH 06/13] chore: resolve conflicts --- .../purchase_invoice/purchase_invoice.json | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index c6996b9aa88..fc5b94284ef 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1368,11 +1368,7 @@ "width": "50px" }, { -<<<<<<< HEAD "depends_on": "eval:doc.update_stock && doc.is_subcontracted==\"Yes\"", -======= - "depends_on": "eval:doc.is_subcontracted", ->>>>>>> 246c1a9380 (fix: Add child table for tax withheld vouchers) "fieldname": "supplier_warehouse", "fieldtype": "Link", "label": "Supplier Warehouse", @@ -1423,16 +1419,6 @@ "label": "Advance Tax", "options": "Advance Tax", "read_only": 1 -<<<<<<< HEAD -======= - }, - { - "default": "0", - "fieldname": "is_old_subcontracting_flow", - "fieldtype": "Check", - "hidden": 1, - "label": "Is Old Subcontracting Flow", - "read_only": 1 }, { "fieldname": "tax_withheld_vouchers_section", @@ -1445,22 +1431,13 @@ "label": "Tax Withheld Vouchers", "options": "Tax Withheld Vouchers", "read_only": 1 ->>>>>>> 246c1a9380 (fix: Add child table for tax withheld vouchers) } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], -<<<<<<< HEAD -<<<<<<< HEAD - "modified": "2021-11-25 13:31:02.716727", -======= - "modified": "2022-09-13 16:22:04.103982", ->>>>>>> 246c1a9380 (fix: Add child table for tax withheld vouchers) -======= "modified": "2022-09-13 23:39:54.525037", ->>>>>>> b6184ce471 (test: Add tests) "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", From e9bf74e5899ba9eebe9fca815e0a65fc1ca60367 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Sep 2022 07:14:06 +0530 Subject: [PATCH 07/13] fix: remove return pos from pos reconciliation tool --- erpnext/accounts/utils.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 47de0eff359..c4fafba2669 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -818,6 +818,31 @@ def get_held_invoices(party_type, party): return held_invoices +def remove_return_pos_invoices(party_type, party, invoice_list): + if invoice_list: + + if party_type == "Customer": + sinv = frappe.qb.DocType("Sales Invoice") + return_pos = ( + frappe.qb.from_(sinv) + .select(sinv.name) + .where((sinv.is_pos == 1) & (sinv.docstatus == 1) & (sinv.is_return == 1)) + .run() + ) + + if return_pos: + return_pos = [x[0] for x in return_pos] + else: + return invoice_list + + # remove pos return invoices from invoice_list + for idx, inv in enumerate(invoice_list, 0): + if inv.voucher_no in return_pos: + del invoice_list[idx] + + return invoice_list + + def get_outstanding_invoices(party_type, party, account, condition=None, filters=None): outstanding_invoices = [] precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2 @@ -868,6 +893,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters as_dict=True, ) + invoice_list = remove_return_pos_invoices(party_type, party, invoice_list) + payment_entries = frappe.db.sql( """ select against_voucher_type, against_voucher, From 21154c8bee22663e833eb38e75ad6a179d3b126c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Sep 2022 17:52:37 +0530 Subject: [PATCH 08/13] chore: fix tests --- .../tax_withholding_category.py | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 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 ebad1e76a30..e4cd39a52a9 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -375,6 +375,9 @@ def get_deducted_tax(taxable_vouchers, tax_details): def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): tds_amount = 0 + supp_credit_amt = 0.0 + supp_jv_credit_amt = 0.0 + invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1} field = "sum(net_total)" @@ -383,21 +386,21 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): invoice_filters.pop("apply_tds", None) field = "sum(grand_total)" - supp_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0 + if vouchers: + supp_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0 - supp_jv_credit_amt = ( - frappe.db.get_value( - "Journal Entry Account", - { - "parent": ("in", vouchers), - "docstatus": 1, - "party": ("in", parties), - "reference_type": ("!=", "Purchase Invoice"), - }, - "sum(credit_in_account_currency)", - ) - or 0.0 - ) + supp_jv_credit_amt = ( + frappe.db.get_value( + "Journal Entry Account", + { + "parent": ("in", vouchers), + "docstatus": 1, + "party": ("in", parties), + "reference_type": ("!=", "Purchase Invoice"), + }, + "sum(credit_in_account_currency)", + ) + ) or 0.0 supp_credit_amt += supp_jv_credit_amt supp_credit_amt += inv.net_total From 0cd1cacc29c96b14352e45e6256db91c2091d3d1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Sep 2022 17:32:12 +0530 Subject: [PATCH 09/13] fix: get amount in words for debit note (cherry picked from commit 70f6484d9d9afc3b89e6df6a18993214584722f8) --- erpnext/controllers/buying_controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index a084ae5fe61..ffd021773c9 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -193,16 +193,16 @@ class BuyingController(StockController, Subcontracting): if self.meta.get_field("base_in_words"): if self.meta.get_field("base_rounded_total") and not self.is_rounded_total_disabled(): - amount = self.base_rounded_total + amount = abs(self.base_rounded_total) else: - amount = self.base_grand_total + amount = abs(self.base_grand_total) self.base_in_words = money_in_words(amount, self.company_currency) if self.meta.get_field("in_words"): if self.meta.get_field("rounded_total") and not self.is_rounded_total_disabled(): - amount = self.rounded_total + amount = abs(self.rounded_total) else: - amount = self.grand_total + amount = abs(self.grand_total) self.in_words = money_in_words(amount, self.currency) From 1f633b293d765aa21a4e26c99bcd12ffa6ea27e1 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 20 Sep 2022 16:18:59 +0530 Subject: [PATCH 10/13] refactor: rewrite `BOM Stock Report` queries in `QB` (cherry picked from commit 8fd7c04920ddfb942451ec9151db36225f86f83b) --- .../bom_stock_report/bom_stock_report.py | 89 +++++++++---------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py index 34e9826305e..1e1b4356008 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py @@ -4,6 +4,8 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Floor, Sum +from pypika.terms import ExistsCriterion def execute(filters=None): @@ -11,7 +13,6 @@ def execute(filters=None): filters = {} columns = get_columns() - data = get_bom_stock(filters) return columns, data @@ -33,59 +34,57 @@ def get_columns(): def get_bom_stock(filters): - conditions = "" - bom = filters.get("bom") - - table = "`tabBOM Item`" - qty_field = "stock_qty" - - qty_to_produce = filters.get("qty_to_produce", 1) - if int(qty_to_produce) <= 0: + qty_to_produce = filters.get("qty_to_produce") or 1 + if int(qty_to_produce) < 0: frappe.throw(_("Quantity to Produce can not be less than Zero")) if filters.get("show_exploded_view"): - table = "`tabBOM Explosion Item`" + bom_item_table = "BOM Explosion Item" + else: + bom_item_table = "BOM Item" + + bin = frappe.qb.DocType("Bin") + bom = frappe.qb.DocType("BOM") + bom_item = frappe.qb.DocType(bom_item_table) + + query = ( + frappe.qb.from_(bom) + .inner_join(bom_item) + .on(bom.name == bom_item.parent) + .left_join(bin) + .on(bom_item.item_code == bin.item_code) + .select( + bom_item.item_code, + bom_item.description, + bom_item.stock_qty, + bom_item.stock_uom, + bom_item.stock_qty * qty_to_produce / bom.quantity, + Sum(bin.actual_qty).as_("actual_qty"), + Sum(Floor(bin.actual_qty / (bom_item.stock_qty * qty_to_produce / bom.quantity))), + ) + .where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM")) + .groupby(bom_item.item_code) + ) if filters.get("warehouse"): warehouse_details = frappe.db.get_value( "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1 ) + if warehouse_details: - conditions += ( - " and exists (select name from `tabWarehouse` wh \ - where wh.lft >= %s and wh.rgt <= %s and ledger.warehouse = wh.name)" - % (warehouse_details.lft, warehouse_details.rgt) + wh = frappe.qb.DocType("Warehouse") + query = query.where( + ExistsCriterion( + frappe.qb.from_(wh) + .select(wh.name) + .where( + (wh.lft >= warehouse_details.lft) + & (wh.rgt <= warehouse_details.rgt) + & (bin.warehouse == wh.name) + ) + ) ) else: - conditions += " and ledger.warehouse = %s" % frappe.db.escape(filters.get("warehouse")) + query = query.where(bin.warehouse == filters.get("warehouse")) - else: - conditions += "" - - return frappe.db.sql( - """ - SELECT - bom_item.item_code, - bom_item.description , - bom_item.{qty_field}, - bom_item.stock_uom, - bom_item.{qty_field} * {qty_to_produce} / bom.quantity, - sum(ledger.actual_qty) as actual_qty, - sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity))) - FROM - `tabBOM` AS bom INNER JOIN {table} AS bom_item - ON bom.name = bom_item.parent - LEFT JOIN `tabBin` AS ledger - ON bom_item.item_code = ledger.item_code - {conditions} - WHERE - bom_item.parent = {bom} and bom_item.parenttype='BOM' - - GROUP BY bom_item.item_code""".format( - qty_field=qty_field, - table=table, - conditions=conditions, - bom=frappe.db.escape(bom), - qty_to_produce=qty_to_produce or 1, - ) - ) + return query.run() From 96bf1e2a0a8d0ef3ef5aa4d71d966062da5f7545 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 20 Sep 2022 16:25:20 +0530 Subject: [PATCH 11/13] fix: warehouse filter in `BOM Stock Calculated Report` (cherry picked from commit 390ce5719d290db38172aef69f542a862a662090) --- .../report/bom_stock_calculated/bom_stock_calculated.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py index ec4b25c859f..550445c1f77 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py @@ -146,7 +146,7 @@ def get_bom_data(filters): ) ) else: - query = query.where(bin.warehouse == frappe.db.escape(filters.get("warehouse"))) + query = query.where(bin.warehouse == filters.get("warehouse")) return query.run(as_dict=True) From e2912caeae1d888f4201b90cef1d60065f2d4948 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Sep 2022 23:50:16 +0530 Subject: [PATCH 12/13] chore: Handle edge cases --- .../tax_withholding_category.py | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 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 e4cd39a52a9..2d169f70aa0 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -437,36 +437,40 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): tcs_amount = 0 + invoiced_amt = 0 + advance_amt = 0 # sum of debit entries made from sales invoices - invoiced_amt = ( - frappe.db.get_value( - "GL Entry", - { - "is_cancelled": 0, - "party": ["in", parties], - "company": inv.company, - "voucher_no": ["in", vouchers], - }, - "sum(debit)", + if vouchers: + invoiced_amt = ( + frappe.db.get_value( + "GL Entry", + { + "is_cancelled": 0, + "party": ["in", parties], + "company": inv.company, + "voucher_no": ["in", vouchers], + }, + "sum(debit)", + ) + or 0.0 ) - or 0.0 - ) # sum of credit entries made from PE / JV with unset 'against voucher' - advance_amt = ( - frappe.db.get_value( - "GL Entry", - { - "is_cancelled": 0, - "party": ["in", parties], - "company": inv.company, - "voucher_no": ["in", adv_vouchers], - }, - "sum(credit)", + if advance_amt: + advance_amt = ( + frappe.db.get_value( + "GL Entry", + { + "is_cancelled": 0, + "party": ["in", parties], + "company": inv.company, + "voucher_no": ["in", adv_vouchers], + }, + "sum(credit)", + ) + or 0.0 ) - or 0.0 - ) # sum of credit entries made from sales invoice credit_note_amt = sum( From cedc8d28e4a2e4c2eb2732e93f6adea094601205 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 20 Sep 2022 17:56:47 +0530 Subject: [PATCH 13/13] refactor: rewrite `BOM Variance Report` queries in `QB` --- .../bom_variance_report.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py index 3fe2198966c..70a1850fd0f 100644 --- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py +++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py @@ -64,22 +64,21 @@ def get_columns(filters): def get_data(filters): - cond = "1=1" + wo = frappe.qb.DocType("Work Order") + query = ( + frappe.qb.from_(wo) + .select(wo.name.as_("work_order"), wo.qty, wo.produced_qty, wo.production_item, wo.bom_no) + .where((wo.produced_qty > wo.qty) & (wo.docstatus == 1)) + ) if filters.get("bom_no") and not filters.get("work_order"): - cond += " and bom_no = '%s'" % filters.get("bom_no") + query = query.where(wo.bom_no == filters.get("bom_no")) if filters.get("work_order"): - cond += " and name = '%s'" % filters.get("work_order") + query = query.where(wo.name == filters.get("work_order")) results = [] - for d in frappe.db.sql( - """ select name as work_order, qty, produced_qty, production_item, bom_no - from `tabWork Order` where produced_qty > qty and docstatus = 1 and {0}""".format( - cond - ), - as_dict=1, - ): + for d in query.run(as_dict=True): results.append(d) for data in frappe.get_all( @@ -95,16 +94,17 @@ def get_data(filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_work_orders(doctype, txt, searchfield, start, page_len, filters): - cond = "1=1" - if filters.get("bom_no"): - cond += " and bom_no = '%s'" % filters.get("bom_no") - - return frappe.db.sql( - """select name from `tabWork Order` - where name like %(name)s and {0} and produced_qty > qty and docstatus = 1 - order by name limit {1}, {2}""".format( - cond, start, page_len - ), - {"name": "%%%s%%" % txt}, - as_list=1, + wo = frappe.qb.DocType("Work Order") + query = ( + frappe.qb.from_(wo) + .select(wo.name) + .where((wo.name.like(f"{txt}%")) & (wo.produced_qty > wo.qty) & (wo.docstatus == 1)) + .orderby(wo.name) + .limit(page_len) + .offset(start) ) + + if filters.get("bom_no"): + query = query.where(wo.bom_no == filters.get("bom_no")) + + return query.run(as_list=True)