From 78b5c735dcc9d5fafb8798875aca9439325a6e79 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 13 Aug 2024 15:10:36 +0530 Subject: [PATCH 1/4] fix: better gls for purchases with tax witholding --- .../purchase_invoice/purchase_invoice.py | 79 +++++++++++++------ erpnext/accounts/general_ledger.py | 1 + .../report/general_ledger/general_ledger.py | 1 + 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 5fb7073c7662..92daa265eb7b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -877,6 +877,7 @@ def get_gl_entries(self, warehouse_account=None): self.make_tax_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) + self.make_gl_entries_for_tax_withholding(gl_entries) gl_entries = make_regional_gl_entries(gl_entries, self) @@ -910,32 +911,36 @@ def make_supplier_gl_entry(self, gl_entries): ) if grand_total and not self.is_internal_transfer(): - against_voucher = self.name - if self.is_return and self.return_against and not self.update_outstanding_for_self: - against_voucher = self.return_against + self.add_supplier_gl_entry(gl_entries, base_grand_total, grand_total) + + def add_supplier_gl_entry( + self, gl_entries, base_grand_total, grand_total, against_account=None, remarks=None + ): + against_voucher = self.name + if self.is_return and self.return_against and not self.update_outstanding_for_self: + against_voucher = self.return_against + + # Did not use base_grand_total to book rounding loss gle + gl = { + "account": self.credit_to, + "party_type": "Supplier", + "party": self.supplier, + "due_date": self.due_date, + "against": against_account or self.against_expense_account, + "credit": base_grand_total, + "credit_in_account_currency": base_grand_total + if self.party_account_currency == self.company_currency + else grand_total, + "against_voucher": against_voucher, + "against_voucher_type": self.doctype, + "project": self.project, + "cost_center": self.cost_center, + } - # Did not use base_grand_total to book rounding loss gle - gl_entries.append( - self.get_gl_dict( - { - "account": self.credit_to, - "party_type": "Supplier", - "party": self.supplier, - "due_date": self.due_date, - "against": self.against_expense_account, - "credit": base_grand_total, - "credit_in_account_currency": base_grand_total - if self.party_account_currency == self.company_currency - else grand_total, - "against_voucher": against_voucher, - "against_voucher_type": self.doctype, - "project": self.project, - "cost_center": self.cost_center, - }, - self.party_account_currency, - item=self, - ) - ) + if remarks: + gl["remarks"] = remarks + + gl_entries.append(self.get_gl_dict(gl, self.party_account_currency, item=self)) def make_item_gl_entries(self, gl_entries): # item gl entries @@ -1427,6 +1432,30 @@ def make_internal_transfer_gl_entries(self, gl_entries): ) ) + def make_gl_entries_for_tax_withholding(self, gl_entries): + """ + Tax withholding amount is not part of supplier invoice. + Separate supplier GL Entry for correct reporting. + """ + if not self.apply_tds: + return + + for row in self.get("taxes"): + if not row.is_tax_withholding_account or not row.tax_amount: + continue + + base_tds_amount = row.base_tax_amount_after_discount_amount + tds_amount = row.tax_amount_after_discount_amount + + self.add_supplier_gl_entry(gl_entries, base_tds_amount, tds_amount) + self.add_supplier_gl_entry( + gl_entries, + -base_tds_amount, + -tds_amount, + against_account=row.account_head, + remarks=_("TDS Deducted"), + ) + def make_payment_gl_entries(self, gl_entries): # Make Cash GL Entries if cint(self.is_paid) and self.cash_bank_account and self.paid_amount: diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 4fba10955468..d4318ff07f66 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -288,6 +288,7 @@ def get_merge_properties(dimensions=None): "project", "finance_book", "voucher_no", + "against", ] if dimensions: merge_properties.extend(dimensions) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 9c381db2f1d7..eb53c1a3434f 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -505,6 +505,7 @@ def update_value_in_dict(data, key, gle): gle.get("account"), gle.get("party_type"), gle.get("party"), + gle.get("against"), ] if immutable_ledger: From fc673746eab2f4b906aaed9bb18c2b4219095edb Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 8 Nov 2024 14:39:59 +0530 Subject: [PATCH 2/4] test: test case for purchase invoice gl entries with tax witholding --- .../purchase_invoice/test_purchase_invoice.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index b020b4fd9050..06f93997b210 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1551,6 +1551,61 @@ def test_purchase_invoice_advance_taxes(self): payment_entry.load_from_db() self.assertEqual(payment_entry.taxes[0].allocated_amount, 0) + def test_purchase_gl_with_tax_withholding_tax(self): + company = "_Test Company" + + tds_account_args = { + "doctype": "Account", + "account_name": "TDS Payable", + "account_type": "Tax", + "parent_account": frappe.db.get_value( + "Account", {"account_name": "Duties and Taxes", "company": company} + ), + "company": company, + } + + tds_account = create_account(**tds_account_args) + tax_withholding_category = "Test TDS - 194 - Dividends - Individual" + + # Update tax withholding category with current fiscal year and rate details + create_tax_witholding_category(tax_withholding_category, company, tds_account) + + # create a new supplier to test + supplier = create_supplier( + supplier_name="_Test TDS Advance Supplier", + tax_withholding_category=tax_withholding_category, + ) + + pi = make_purchase_invoice( + supplier=supplier.name, + rate=3000, + qty=1, + item="_Test Non Stock Item", + do_not_submit=1, + ) + pi.apply_tds = 1 + pi.tax_withholding_category = tax_withholding_category + pi.save() + pi.submit() + + self.assertEqual(pi.taxes[0].tax_amount, 300) + self.assertEqual(pi.taxes[0].account_head, tds_account) + + gl_entries = frappe.get_all( + "GL Entry", + filters={"voucher_no": pi.name, "voucher_type": "Purchase Invoice", "account": "Creditors - _TC"}, + fields=["account", "against", "debit", "credit"], + ) + + for gle in gl_entries: + if gle.debit: + # GL Entry with TDS Amount + self.assertEqual(gle.against, tds_account) + self.assertEqual(gle.debit, 300) + else: + # GL Entry with Purchase Invoice Amount + self.assertEqual(gle.credit, 3000) + def test_provisional_accounting_entry(self): setup_provisional_accounting() From 330edf533a53ff88fb7ab8d17324e455d7a7c1d6 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 9 Nov 2024 14:06:11 +0530 Subject: [PATCH 3/4] fix: use flag `_skip_merge` instead of skipping merge based on against account --- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 4 +++- erpnext/accounts/general_ledger.py | 5 ++++- erpnext/accounts/report/general_ledger/general_ledger.py | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f798fabe3952..dd1b085c1cc1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -902,7 +902,7 @@ def make_supplier_gl_entry(self, gl_entries): self.add_supplier_gl_entry(gl_entries, base_grand_total, grand_total) def add_supplier_gl_entry( - self, gl_entries, base_grand_total, grand_total, against_account=None, remarks=None + self, gl_entries, base_grand_total, grand_total, against_account=None, remarks=None, skip_merge=False ): against_voucher = self.name if self.is_return and self.return_against and not self.update_outstanding_for_self: @@ -923,6 +923,7 @@ def add_supplier_gl_entry( "against_voucher_type": self.doctype, "project": self.project, "cost_center": self.cost_center, + "_skip_merge": skip_merge, } if remarks: @@ -1446,6 +1447,7 @@ def make_gl_entries_for_tax_withholding(self, gl_entries): -tds_amount, against_account=row.account_head, remarks=_("TDS Deducted"), + skip_merge=True, ) def make_payment_gl_entries(self, gl_entries): diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 4ad0dc3e587a..37d9cfafa7ce 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -235,6 +235,10 @@ def merge_similar_entries(gl_map, precision=None): merge_properties = get_merge_properties(accounting_dimensions) for entry in gl_map: + if entry._skip_merge: + merged_gl_map.append(entry) + continue + entry.merge_key = get_merge_key(entry, merge_properties) # if there is already an entry in this account then just add it # to that entry @@ -291,7 +295,6 @@ def get_merge_properties(dimensions=None): "project", "finance_book", "voucher_no", - "against", ] if dimensions: merge_properties.extend(dimensions) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 204de6ea15bd..fdaf3fe2a598 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -516,7 +516,6 @@ def update_value_in_dict(data, key, gle): gle.get("account"), gle.get("party_type"), gle.get("party"), - gle.get("against"), ] if immutable_ledger: From c5e31e8e0c2f21f8da255db8fa4d89c4ccbc56f0 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 9 Nov 2024 14:50:17 +0530 Subject: [PATCH 4/4] test: fix test `test_single_threshold_tds` for newer implementation --- .../test_tax_withholding_category.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 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 984a081fc8ca..761969470e08 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 @@ -84,11 +84,17 @@ def test_single_threshold_tds(self): self.assertEqual(pi.grand_total, 18000) # check gl entry for the purchase invoice - gl_entries = frappe.db.get_all("GL Entry", filters={"voucher_no": pi.name}, fields=["*"]) + gl_entries = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": pi.name}, + fields=["account", "sum(debit) as debit", "sum(credit) as credit"], + group_by="account", + ) self.assertEqual(len(gl_entries), 3) for d in gl_entries: if d.account == pi.credit_to: - self.assertEqual(d.credit, 18000) + self.assertEqual(d.credit, 20000) + self.assertEqual(d.debit, 2000) elif d.account == pi.items[0].get("expense_account"): self.assertEqual(d.debit, 20000) elif d.account == pi.taxes[0].get("account_head"):