diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 42e54e0ccf0..82825e46729 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1803,13 +1803,13 @@ class PurchaseInvoice(BuyingController): self.remove(d) ## Add pending vouchers on which tax was withheld - for voucher_no, voucher_details in voucher_wise_amount.items(): + for row in voucher_wise_amount: self.append( "tax_withheld_vouchers", { - "voucher_name": voucher_no, - "voucher_type": voucher_details.get("voucher_type"), - "taxable_amount": voucher_details.get("amount"), + "voucher_name": row.voucher_name, + "voucher_type": row.voucher_type, + "taxable_amount": row.taxable_amount, }, ) 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 53a2e279a4d..3215b93a496 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -87,6 +87,7 @@ def get_party_details(inv): def get_party_tax_withholding_details(inv, tax_withholding_category=None): if inv.doctype == "Payment Entry": inv.tax_withholding_net_total = inv.net_total + inv.base_tax_withholding_net_total = inv.net_total pan_no = "" parties = [] @@ -326,7 +327,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N # 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, vouchers) + tax_amount = get_tds_amount(ldc, parties, inv, tax_details, voucher_wise_amount) elif party_type == "Customer": if tax_deducted: @@ -356,13 +357,16 @@ def is_tax_deducted_on_the_basis_of_inv(vouchers): def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): - doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" - field = ( - "base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total" - ) - voucher_wise_amount = {} + voucher_wise_amount = [] vouchers = [] + doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" + field = [ + "base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total", + "name", + "grand_total", + ] + filters = { "company": company, frappe.scrub(party_type): ["in", parties], @@ -376,15 +380,24 @@ 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", field]) + invoices_details = frappe.get_all(doctype, filters=filters, fields=field) for d in invoices_details: vouchers.append(d.name) - voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}}) + voucher_wise_amount.append( + frappe._dict( + { + "voucher_name": d.name, + "voucher_type": doctype, + "taxable_amount": d.base_net_total, + "grand_total": d.grand_total, + } + ) + ) journal_entries_details = frappe.db.sql( """ - SELECT j.name, ja.credit - ja.debit AS amount + SELECT j.name, ja.credit - ja.debit AS amount, ja.reference_type FROM `tabJournal Entry` j, `tabJournal Entry Account` ja WHERE j.name = ja.parent @@ -403,13 +416,20 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): tax_details.get("tax_withholding_category"), company, ), - as_dict=1, ) - 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"}}) + for d in journal_entries_details: + vouchers.append(d.name) + voucher_wise_amount.append( + frappe._dict( + { + "voucher_name": d.name, + "voucher_type": "Journal Entry", + "taxable_amount": d.amount, + "reference_type": d.reference_type, + } + ) + ) return vouchers, voucher_wise_amount @@ -508,12 +528,24 @@ def get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details): return advance_tax_from_across_fiscal_year -def get_tds_amount(ldc, parties, inv, tax_details, vouchers): +def get_tds_amount(ldc, parties, inv, tax_details, voucher_wise_amount): tds_amount = 0 - invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1} + + pi_grand_total = 0 + pi_base_net_total = 0 + jv_credit_amt = 0 + pe_credit_amt = 0 + + for row in voucher_wise_amount: + if row.voucher_type == "Purchase Invoice": + pi_grand_total += row.get("grand_total", 0) + pi_base_net_total += row.get("taxable_amount", 0) + + if row.voucher_type == "Journal Entry" and row.reference_type != "Purchase Invoice": + jv_credit_amt += row.get("taxable_amount", 0) ## for TDS to be deducted on advances - payment_entry_filters = { + pe_filters = { "party_type": "Supplier", "party": ("in", parties), "docstatus": 1, @@ -524,70 +556,49 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers): "company": inv.company, } - field = "sum(tax_withholding_net_total)" + consider_party_ledger_amt = cint(tax_details.consider_party_ledger_amount) - if cint(tax_details.consider_party_ledger_amount): - invoice_filters.pop("apply_tds", None) - field = "sum(grand_total)" - - payment_entry_filters.pop("apply_tax_withholding_amount", None) - payment_entry_filters.pop("tax_withholding_category", None) - - supp_inv_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 - debit_in_account_currency)", - ) - or 0.0 - ) + if consider_party_ledger_amt: + pe_filters.pop("apply_tax_withholding_amount", None) + pe_filters.pop("tax_withholding_category", None) # Get Amount via payment entry - payment_entry_amounts = frappe.db.get_all( + payment_entries = frappe.db.get_all( "Payment Entry", - filters=payment_entry_filters, - fields=["sum(unallocated_amount) as amount", "payment_type"], - group_by="payment_type", + filters=pe_filters, + fields=["name", "unallocated_amount as taxable_amount", "payment_type"], ) - supp_credit_amt = supp_jv_credit_amt - supp_credit_amt += inv.get("tax_withholding_net_total", 0) - - for type in payment_entry_amounts: - if type.payment_type == "Pay": - supp_credit_amt += type.amount - else: - supp_credit_amt -= type.amount + for row in payment_entries: + value = row.taxable_amount if row.payment_type == "Pay" else -1 * row.taxable_amount + pe_credit_amt += value + voucher_wise_amount.append( + frappe._dict( + { + "voucher_name": row.name, + "voucher_type": "Payment Entry", + "taxable_amount": value, + } + ) + ) threshold = tax_details.get("threshold", 0) cumulative_threshold = tax_details.get("cumulative_threshold", 0) + supp_credit_amt = jv_credit_amt + pe_credit_amt + inv.get("tax_withholding_net_total", 0) + tax_withholding_net_total = inv.get("base_tax_withholding_net_total", 0) - if inv.doctype != "Payment Entry": - tax_withholding_net_total = inv.get("base_tax_withholding_net_total", 0) - else: - tax_withholding_net_total = inv.get("tax_withholding_net_total", 0) + # if consider_party_ledger_amount is checked, then threshold will be based on grand total + amt_for_threshold = pi_grand_total if consider_party_ledger_amt else pi_base_net_total - has_cumulative_threshold_breached = ( - cumulative_threshold and (supp_credit_amt + supp_inv_credit_amt) >= cumulative_threshold + cumulative_threshold_breached = ( + cumulative_threshold and (supp_credit_amt + amt_for_threshold) >= cumulative_threshold ) - if (threshold and tax_withholding_net_total >= threshold) or (has_cumulative_threshold_breached): - # Get net total again as TDS is calculated on net total - # Grand is used to just check for threshold breach - net_total = ( - frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(tax_withholding_net_total)") or 0.0 - ) - supp_credit_amt += net_total + if (threshold and tax_withholding_net_total >= threshold) or (cumulative_threshold_breached): + supp_credit_amt += pi_base_net_total - if has_cumulative_threshold_breached and cint(tax_details.tax_on_excess_amount): - supp_credit_amt = net_total + tax_withholding_net_total - cumulative_threshold + if cumulative_threshold_breached and cint(tax_details.tax_on_excess_amount): + supp_credit_amt = pi_base_net_total + tax_withholding_net_total - cumulative_threshold if ldc and is_valid_certificate(ldc, inv.get("posting_date") or inv.get("transaction_date"), 0): tds_amount = get_lower_deduction_amount( 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 e9540bb04be..109a15a968e 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 @@ -579,6 +579,15 @@ class TestTaxWithholdingCategory(IntegrationTestCase): pi1.submit() invoices.append(pi1) + pe = create_payment_entry( + payment_type="Pay", party_type="Supplier", party="Test TDS Supplier6", paid_amount=1000 + ) + pe.apply_tax_withholding_amount = 1 + pe.tax_withholding_category = "Test Multi Invoice Category" + pe.save() + pe.submit() + invoices.append(pe) + 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" @@ -594,6 +603,8 @@ class TestTaxWithholdingCategory(IntegrationTestCase): 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) + self.assertTrue(pi2.tax_withheld_vouchers[2].voucher_name == pe.name) + self.assertTrue(pi2.tax_withheld_vouchers[2].taxable_amount == pe.paid_amount) # cancel invoices to avoid clashing for d in reversed(invoices):