mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-27 02:28:30 +00:00
feat: Introduce tax withholding entry
(cherry picked from commit c66f78c784)
# Conflicts:
# erpnext/accounts/doctype/journal_entry/journal_entry.py
# erpnext/accounts/doctype/payment_entry/payment_entry.json
# erpnext/accounts/doctype/sales_invoice/sales_invoice.py
# erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
# erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
# erpnext/patches.txt
This commit is contained in:
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2021-11-25 10:24:39.836195",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_type",
|
||||
"reference_name",
|
||||
"reference_detail",
|
||||
"account_head",
|
||||
"allocated_amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "reference_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Reference Name",
|
||||
"options": "reference_type"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_detail",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference Detail"
|
||||
},
|
||||
{
|
||||
"fieldname": "account_head",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account Head",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Allocated Amount",
|
||||
"options": "party_account_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:05:58.308002",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Tax",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AdvanceTax(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
account_head: DF.Link | None
|
||||
allocated_amount: DF.Currency
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
reference_detail: DF.Data | None
|
||||
reference_name: DF.DynamicLink | None
|
||||
reference_type: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -14,6 +14,7 @@
|
||||
"description",
|
||||
"included_in_paid_amount",
|
||||
"set_by_item_tax_template",
|
||||
"is_tax_withholding_account",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
@@ -25,7 +26,6 @@
|
||||
"net_amount",
|
||||
"tax_amount",
|
||||
"total",
|
||||
"allocated_amount",
|
||||
"column_break_13",
|
||||
"base_tax_amount",
|
||||
"base_net_amount",
|
||||
@@ -97,11 +97,11 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
@@ -172,12 +172,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Considered In Paid Amount"
|
||||
},
|
||||
{
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Allocated Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fetch_from": "account_head.account_currency",
|
||||
"fieldname": "currency",
|
||||
@@ -213,18 +207,26 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_tax_withholding_account",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Tax Withholding Account",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-22 19:16:22.346267",
|
||||
"modified": "2025-12-15 06:42:18.707671",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Taxes and Charges",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ class AdvanceTaxesandCharges(Document):
|
||||
|
||||
account_head: DF.Link
|
||||
add_deduct_tax: DF.Literal["Add", "Deduct"]
|
||||
allocated_amount: DF.Currency
|
||||
base_net_amount: DF.Currency
|
||||
base_tax_amount: DF.Currency
|
||||
base_total: DF.Currency
|
||||
@@ -28,10 +27,12 @@ class AdvanceTaxesandCharges(Document):
|
||||
currency: DF.Link | None
|
||||
description: DF.SmallText
|
||||
included_in_paid_amount: DF.Check
|
||||
is_tax_withholding_account: DF.Check
|
||||
net_amount: DF.Currency
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
project: DF.Link | None
|
||||
rate: DF.Float
|
||||
row_id: DF.Data | None
|
||||
set_by_item_tax_template: DF.Check
|
||||
|
||||
@@ -201,6 +201,7 @@ frappe.ui.form.on("Journal Entry", {
|
||||
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
erpnext.utils.set_letter_head(frm);
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
},
|
||||
|
||||
voucher_type: function (frm) {
|
||||
@@ -251,6 +252,10 @@ frappe.ui.form.on("Journal Entry", {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
apply_tds: function (frm) {
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
},
|
||||
});
|
||||
|
||||
var update_jv_details = function (doc, r) {
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
"total_amount_currency",
|
||||
"total_amount",
|
||||
"total_amount_in_words",
|
||||
"section_tax_withholding_entry",
|
||||
"tax_withholding_group",
|
||||
"ignore_tax_withholding_threshold",
|
||||
"override_tax_withholding_entries",
|
||||
"tax_withholding_entries",
|
||||
"reference",
|
||||
"clearance_date",
|
||||
"remark",
|
||||
@@ -517,7 +522,7 @@
|
||||
"depends_on": "eval:['Credit Note', 'Debit Note'].includes(doc.voucher_type)",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Tax Withholding Amount "
|
||||
"label": "Consider for Tax Withholding "
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus",
|
||||
@@ -586,6 +591,39 @@
|
||||
"hidden": 1,
|
||||
"label": "Party Not Required",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.apply_tds && doc.docstatus == 0",
|
||||
"depends_on": "eval: doc.apply_tds",
|
||||
"fieldname": "section_tax_withholding_entry",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Withholding Entry"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Group",
|
||||
"options": "Tax Withholding Group"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_tax_withholding_threshold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Tax Withholding Threshold"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "override_tax_withholding_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Edit Tax Withholding Entries"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_entries",
|
||||
"fieldtype": "Table",
|
||||
"label": "Tax Withholding Entries",
|
||||
"options": "Tax Withholding Entry",
|
||||
"read_only_depends_on": "eval: !doc.override_tax_withholding_entries"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -600,7 +638,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2025-09-29 13:05:46.982277",
|
||||
"modified": "2025-11-13 17:54:14.542903",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
||||
@@ -17,9 +17,7 @@ from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger
|
||||
validate_docs_for_deferred_accounting,
|
||||
validate_docs_for_voucher_types,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import JournalTaxWithholding
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.utils import (
|
||||
cancel_exchange_gain_loss_journal,
|
||||
@@ -49,6 +47,7 @@ class JournalEntry(AccountsController):
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.journal_entry_account.journal_entry_account import JournalEntryAccount
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import TaxWithholdingEntry
|
||||
|
||||
accounts: DF.Table[JournalEntryAccount]
|
||||
amended_from: DF.Link | None
|
||||
@@ -65,6 +64,7 @@ class JournalEntry(AccountsController):
|
||||
finance_book: DF.Link | None
|
||||
for_all_stock_asset_accounts: DF.Check
|
||||
from_template: DF.Link | None
|
||||
ignore_tax_withholding_threshold: DF.Check
|
||||
inter_company_journal_entry_reference: DF.Link | None
|
||||
is_opening: DF.Literal["No", "Yes"]
|
||||
is_system_generated: DF.Check
|
||||
@@ -73,6 +73,7 @@ class JournalEntry(AccountsController):
|
||||
multi_currency: DF.Check
|
||||
naming_series: DF.Literal["ACC-JV-.YYYY.-"]
|
||||
party_not_required: DF.Check
|
||||
override_tax_withholding_entries: DF.Check
|
||||
pay_to_recd_from: DF.Data | None
|
||||
payment_order: DF.Link | None
|
||||
periodic_entry_difference_account: DF.Link | None
|
||||
@@ -84,6 +85,8 @@ class JournalEntry(AccountsController):
|
||||
stock_asset_account: DF.Link | None
|
||||
stock_entry: DF.Link | None
|
||||
tax_withholding_category: DF.Link | None
|
||||
tax_withholding_entries: DF.Table[TaxWithholdingEntry]
|
||||
tax_withholding_group: DF.Link | None
|
||||
title: DF.Data | None
|
||||
total_amount: DF.Currency
|
||||
total_amount_currency: DF.Link | None
|
||||
@@ -150,8 +153,8 @@ class JournalEntry(AccountsController):
|
||||
self.validate_company_in_accounting_dimension()
|
||||
self.validate_advance_accounts()
|
||||
|
||||
if self.docstatus == 0:
|
||||
self.apply_tax_withholding()
|
||||
JournalTaxWithholding(self).on_validate()
|
||||
|
||||
if self.is_new() or not self.title:
|
||||
self.title = self.get_title()
|
||||
|
||||
@@ -199,6 +202,7 @@ class JournalEntry(AccountsController):
|
||||
self.update_asset_value()
|
||||
self.update_inter_company_jv()
|
||||
self.update_invoice_discounting()
|
||||
JournalTaxWithholding(self).on_submit()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_balance_for_periodic_accounting(self):
|
||||
@@ -282,6 +286,8 @@ class JournalEntry(AccountsController):
|
||||
self.repost_accounting_entries()
|
||||
|
||||
def on_cancel(self):
|
||||
# Cancel tax withholding entries
|
||||
|
||||
# References for this Journal are removed on the `on_cancel` event in accounts_controller
|
||||
super().on_cancel()
|
||||
self.ignore_linked_doctypes = (
|
||||
@@ -295,8 +301,10 @@ class JournalEntry(AccountsController):
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Advance Payment Ledger Entry",
|
||||
"Tax Withholding Entry",
|
||||
)
|
||||
self.make_gl_entries(1)
|
||||
JournalTaxWithholding(self).on_cancel()
|
||||
self.unlink_advance_entry_reference()
|
||||
self.unlink_asset_reference()
|
||||
self.unlink_inter_company_jv()
|
||||
@@ -352,6 +360,7 @@ class JournalEntry(AccountsController):
|
||||
StockAccountInvalidTransaction,
|
||||
)
|
||||
|
||||
<<<<<<< HEAD
|
||||
def apply_tax_withholding(self):
|
||||
from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map
|
||||
|
||||
@@ -441,6 +450,8 @@ class JournalEntry(AccountsController):
|
||||
for d in to_remove:
|
||||
self.remove(d)
|
||||
|
||||
=======
|
||||
>>>>>>> c66f78c784 (feat: Introduce tax withholding entry)
|
||||
def update_asset_value(self):
|
||||
self.update_asset_on_depreciation()
|
||||
self.update_asset_on_disposal()
|
||||
|
||||
@@ -41,6 +41,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
if (frm.is_new()) {
|
||||
set_default_party_type(frm);
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -532,6 +533,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
},
|
||||
() => frm.set_value("party_name", r.message.party_name),
|
||||
() => frm.clear_table("references"),
|
||||
() => frm.clear_table("tax_withholding_entries"),
|
||||
() => frm.events.hide_unhide_fields(frm),
|
||||
() => frm.events.set_dynamic_labels(frm),
|
||||
() => {
|
||||
@@ -564,14 +566,15 @@ frappe.ui.form.on("Payment Entry", {
|
||||
}
|
||||
},
|
||||
|
||||
apply_tax_withholding_amount: function (frm) {
|
||||
if (!frm.doc.apply_tax_withholding_amount) {
|
||||
apply_tds: function (frm) {
|
||||
if (!frm.doc.apply_tds) {
|
||||
frm.set_value("tax_withholding_category", "");
|
||||
} else {
|
||||
frappe.db.get_value("Supplier", frm.doc.party, "tax_withholding_category", (values) => {
|
||||
} else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
|
||||
frappe.db.get_value(frm.doc.party_type, frm.doc.party, "tax_withholding_category", (values) => {
|
||||
frm.set_value("tax_withholding_category", values.tax_withholding_category);
|
||||
});
|
||||
}
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
},
|
||||
|
||||
paid_from: function (frm) {
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
"party_name",
|
||||
"book_advance_payments_in_separate_party_account",
|
||||
"reconcile_on_advance_payment_date",
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"column_break_11",
|
||||
"bank_account",
|
||||
"party_bank_account",
|
||||
@@ -60,10 +62,6 @@
|
||||
"taxes_and_charges_section",
|
||||
"purchase_taxes_and_charges_template",
|
||||
"sales_taxes_and_charges_template",
|
||||
"column_break_55",
|
||||
"apply_tax_withholding_amount",
|
||||
"tax_withholding_category",
|
||||
"section_break_56",
|
||||
"taxes",
|
||||
"section_break_60",
|
||||
"base_total_taxes_and_charges",
|
||||
@@ -71,6 +69,11 @@
|
||||
"total_taxes_and_charges",
|
||||
"deductions_or_loss_section",
|
||||
"deductions",
|
||||
"section_tax_withholding_entry",
|
||||
"tax_withholding_group",
|
||||
"ignore_tax_withholding_threshold",
|
||||
"override_tax_withholding_entries",
|
||||
"tax_withholding_entries",
|
||||
"transaction_references",
|
||||
"reference_no",
|
||||
"column_break_23",
|
||||
@@ -578,14 +581,15 @@
|
||||
"label": "Custom Remarks"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||
"depends_on": "eval:doc.apply_tds",
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Category",
|
||||
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||
"mandatory_depends_on": "eval:doc.apply_tds",
|
||||
"options": "Tax Withholding Category"
|
||||
},
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||
"fieldname": "apply_tax_withholding_amount",
|
||||
@@ -594,6 +598,8 @@
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
=======
|
||||
>>>>>>> c66f78c784 (feat: Introduce tax withholding entry)
|
||||
"fieldname": "taxes_and_charges_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Taxes and Charges"
|
||||
@@ -648,15 +654,6 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_55",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_56",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
|
||||
"fieldname": "received_amount_after_tax",
|
||||
@@ -753,6 +750,46 @@
|
||||
"options": "No\nYes",
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider for Tax Withholding"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.apply_tds && doc.docstatus == 0",
|
||||
"depends_on": "eval: doc.apply_tds",
|
||||
"fieldname": "section_tax_withholding_entry",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Withholding Entry"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Group",
|
||||
"options": "Tax Withholding Group"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_tax_withholding_threshold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Tax Withholding Threshold"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_entries",
|
||||
"fieldtype": "Table",
|
||||
"label": "Tax Withholding Entries",
|
||||
"options": "Tax Withholding Entry",
|
||||
"read_only_depends_on": "eval: !doc.override_tax_withholding_entries"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "override_tax_withholding_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Edit Tax Withholding Entries"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
|
||||
@@ -30,9 +30,7 @@ from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger
|
||||
validate_docs_for_deferred_accounting,
|
||||
validate_docs_for_voucher_types,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import PaymentTaxWithholding
|
||||
from erpnext.accounts.general_ledger import (
|
||||
make_gl_entries,
|
||||
make_reverse_gl_entries,
|
||||
@@ -80,9 +78,10 @@ class PaymentEntry(AccountsController):
|
||||
from erpnext.accounts.doctype.payment_entry_reference.payment_entry_reference import (
|
||||
PaymentEntryReference,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import TaxWithholdingEntry
|
||||
|
||||
amended_from: DF.Link | None
|
||||
apply_tax_withholding_amount: DF.Check
|
||||
apply_tds: DF.Check
|
||||
auto_repeat: DF.Link | None
|
||||
bank: DF.ReadOnly | None
|
||||
bank_account: DF.Link | None
|
||||
@@ -103,11 +102,13 @@ class PaymentEntry(AccountsController):
|
||||
custom_remarks: DF.Check
|
||||
deductions: DF.Table[PaymentEntryDeduction]
|
||||
difference_amount: DF.Currency
|
||||
ignore_tax_withholding_threshold: DF.Check
|
||||
in_words: DF.SmallText | None
|
||||
is_opening: DF.Literal["No", "Yes"]
|
||||
letter_head: DF.Link | None
|
||||
mode_of_payment: DF.Link | None
|
||||
naming_series: DF.Literal["ACC-PAY-.YYYY.-"]
|
||||
override_tax_withholding_entries: DF.Check
|
||||
paid_amount: DF.Currency
|
||||
paid_amount_after_tax: DF.Currency
|
||||
paid_from: DF.Link
|
||||
@@ -139,6 +140,8 @@ class PaymentEntry(AccountsController):
|
||||
status: DF.Literal["", "Draft", "Submitted", "Cancelled"]
|
||||
target_exchange_rate: DF.Float
|
||||
tax_withholding_category: DF.Link | None
|
||||
tax_withholding_entries: DF.Table[TaxWithholdingEntry]
|
||||
tax_withholding_group: DF.Link | None
|
||||
taxes: DF.Table[AdvanceTaxesandCharges]
|
||||
title: DF.Data | None
|
||||
total_allocated_amount: DF.Currency
|
||||
@@ -189,7 +192,7 @@ class PaymentEntry(AccountsController):
|
||||
self.validate_allocated_amount()
|
||||
self.validate_paid_invoices()
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
self.set_tax_withholding()
|
||||
PaymentTaxWithholding(self).on_validate()
|
||||
self.set_status()
|
||||
self.set_total_in_words()
|
||||
|
||||
@@ -199,6 +202,7 @@ class PaymentEntry(AccountsController):
|
||||
def on_submit(self):
|
||||
if self.difference_amount:
|
||||
frappe.throw(_("Difference Amount must be zero"))
|
||||
PaymentTaxWithholding(self).on_submit()
|
||||
self.update_payment_requests()
|
||||
self.update_payment_schedule()
|
||||
self.make_gl_entries()
|
||||
@@ -300,8 +304,10 @@ class PaymentEntry(AccountsController):
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Advance Payment Ledger Entry",
|
||||
"Tax Withholding Entry",
|
||||
)
|
||||
super().on_cancel()
|
||||
PaymentTaxWithholding(self).on_cancel()
|
||||
self.update_payment_requests(cancel=True)
|
||||
self.update_payment_schedule(cancel=1)
|
||||
self.make_gl_entries(cancel=1)
|
||||
@@ -937,93 +943,6 @@ class PaymentEntry(AccountsController):
|
||||
self.base_in_words = money_in_words(base_amount, self.company_currency)
|
||||
self.in_words = money_in_words(amount, currency)
|
||||
|
||||
def set_tax_withholding(self):
|
||||
if self.party_type != "Supplier":
|
||||
return
|
||||
|
||||
if not self.apply_tax_withholding_amount:
|
||||
return
|
||||
|
||||
net_total = self.calculate_tax_withholding_net_total()
|
||||
|
||||
# Adding args as purchase invoice to get TDS amount
|
||||
args = frappe._dict(
|
||||
{
|
||||
"company": self.company,
|
||||
"doctype": "Payment Entry",
|
||||
"supplier": self.party,
|
||||
"posting_date": self.posting_date,
|
||||
"net_total": net_total,
|
||||
}
|
||||
)
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
|
||||
tax_withholding_details.update(
|
||||
{"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company)}
|
||||
)
|
||||
|
||||
accounts = []
|
||||
for d in self.taxes:
|
||||
if d.account_head == tax_withholding_details.get("account_head"):
|
||||
# Preserve user updated included in paid amount
|
||||
if d.included_in_paid_amount:
|
||||
tax_withholding_details.update({"included_in_paid_amount": d.included_in_paid_amount})
|
||||
|
||||
d.update(tax_withholding_details)
|
||||
accounts.append(d.account_head)
|
||||
|
||||
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||
self.append("taxes", tax_withholding_details)
|
||||
|
||||
to_remove = [
|
||||
d
|
||||
for d in self.taxes
|
||||
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")
|
||||
]
|
||||
|
||||
for d in to_remove:
|
||||
self.remove(d)
|
||||
|
||||
def calculate_tax_withholding_net_total(self):
|
||||
net_total = 0
|
||||
order_details = self.get_order_wise_tax_withholding_net_total()
|
||||
|
||||
for d in self.references:
|
||||
tax_withholding_net_total = order_details.get(d.reference_name)
|
||||
if not tax_withholding_net_total:
|
||||
continue
|
||||
|
||||
net_taxable_outstanding = max(
|
||||
0, d.outstanding_amount - (d.total_amount - tax_withholding_net_total)
|
||||
)
|
||||
|
||||
net_total += min(net_taxable_outstanding, d.allocated_amount)
|
||||
|
||||
net_total += self.unallocated_amount
|
||||
|
||||
return net_total
|
||||
|
||||
def get_order_wise_tax_withholding_net_total(self):
|
||||
if self.party_type == "Supplier":
|
||||
doctype = "Purchase Order"
|
||||
else:
|
||||
doctype = "Sales Order"
|
||||
|
||||
docnames = [d.reference_name for d in self.references if d.reference_doctype == doctype]
|
||||
|
||||
return frappe._dict(
|
||||
frappe.db.get_all(
|
||||
doctype,
|
||||
filters={"name": ["in", docnames]},
|
||||
fields=["name", "base_tax_withholding_net_total"],
|
||||
as_list=True,
|
||||
)
|
||||
)
|
||||
|
||||
def apply_taxes(self):
|
||||
self.initialize_taxes()
|
||||
self.determine_exclusive_rate()
|
||||
|
||||
@@ -59,14 +59,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-05 16:07:47.307971",
|
||||
"modified": "2025-08-13 06:52:46.130142",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Deduction",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +223,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
});
|
||||
}
|
||||
|
||||
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
|
||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
|
||||
}
|
||||
|
||||
@@ -363,10 +362,9 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
},
|
||||
function () {
|
||||
me.apply_pricing_rule();
|
||||
me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0;
|
||||
me.frm.doc.tax_withholding_category = me.frm.supplier_tds;
|
||||
me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
|
||||
me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1);
|
||||
me.frm.doc.apply_tds =
|
||||
me.frm.tax_withholding_category || me.frm.tax_withholding_group ? 1 : 0;
|
||||
me.frm.clear_table("tax_withholding_entries");
|
||||
|
||||
// while duplicating, don't change payment terms
|
||||
if (me.frm.doc.__run_link_triggers === false) {
|
||||
@@ -379,26 +377,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
|
||||
apply_tds(frm) {
|
||||
var me = this;
|
||||
me.frm.set_value("tax_withheld_vouchers", []);
|
||||
if (!me.frm.doc.apply_tds) {
|
||||
me.frm.set_value("tax_withholding_category", "");
|
||||
me.frm.set_df_property("tax_withholding_category", "hidden", 1);
|
||||
} else {
|
||||
me.frm.set_value("tax_withholding_category", me.frm.supplier_tds);
|
||||
me.frm.set_df_property("tax_withholding_category", "hidden", 0);
|
||||
}
|
||||
}
|
||||
|
||||
tax_withholding_category(frm) {
|
||||
var me = this;
|
||||
let filtered_taxes = (me.frm.doc.taxes || []).filter((row) => !row.is_tax_withholding_account);
|
||||
me.frm.clear_table("taxes");
|
||||
|
||||
filtered_taxes.forEach((row) => {
|
||||
me.frm.add_child("taxes", row);
|
||||
});
|
||||
|
||||
me.frm.refresh_field("taxes");
|
||||
me.frm.clear_table("tax_withholding_entries");
|
||||
}
|
||||
|
||||
credit_to() {
|
||||
@@ -702,10 +681,7 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
onload: function (frm) {
|
||||
if (frm.doc.__onload && frm.doc.supplier) {
|
||||
if (frm.is_new()) {
|
||||
frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
|
||||
}
|
||||
if (!frm.doc.__onload.supplier_tds) {
|
||||
frm.set_df_property("apply_tds", "read_only", 1);
|
||||
frm.doc.apply_tds = frm.doc.__onload.apply_tds ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,7 +690,7 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
});
|
||||
|
||||
if (frm.is_new()) {
|
||||
frm.clear_table("tax_withheld_vouchers");
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -741,6 +717,7 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
|
||||
company: function (frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
|
||||
if (frm.doc.company) {
|
||||
frappe.call({
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
"update_billed_amount_in_purchase_order",
|
||||
"update_billed_amount_in_purchase_receipt",
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"amended_from",
|
||||
"supplier_invoice_details",
|
||||
"bill_no",
|
||||
@@ -68,8 +67,6 @@
|
||||
"column_break_28",
|
||||
"total",
|
||||
"net_total",
|
||||
"tax_withholding_net_total",
|
||||
"base_tax_withholding_net_total",
|
||||
"taxes_section",
|
||||
"tax_category",
|
||||
"taxes_and_charges",
|
||||
@@ -102,14 +99,17 @@
|
||||
"total_advance",
|
||||
"outstanding_amount",
|
||||
"disable_rounded_total",
|
||||
"section_tax_withholding_entry",
|
||||
"tax_withholding_group",
|
||||
"ignore_tax_withholding_threshold",
|
||||
"override_tax_withholding_entries",
|
||||
"tax_withholding_entries",
|
||||
"section_break_44",
|
||||
"apply_discount_on",
|
||||
"base_discount_amount",
|
||||
"column_break_46",
|
||||
"additional_discount_percentage",
|
||||
"discount_amount",
|
||||
"tax_withheld_vouchers_section",
|
||||
"tax_withheld_vouchers",
|
||||
"sec_tax_breakup",
|
||||
"other_charges_calculation",
|
||||
"item_wise_tax_details",
|
||||
@@ -130,7 +130,6 @@
|
||||
"only_include_allocated_payments",
|
||||
"get_advances",
|
||||
"advances",
|
||||
"advance_tax",
|
||||
"write_off",
|
||||
"write_off_amount",
|
||||
"base_write_off_amount",
|
||||
@@ -286,7 +285,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Tax Withholding Amount",
|
||||
"label": "Consider for Tax Withholding",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@@ -1358,14 +1357,6 @@
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Tax Withholding Category",
|
||||
"options": "Tax Withholding Category",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "billing_address",
|
||||
"fieldtype": "Link",
|
||||
@@ -1455,14 +1446,6 @@
|
||||
"fieldname": "column_break_147",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "advance_tax",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 1,
|
||||
"label": "Advance Tax",
|
||||
"options": "Advance Tax",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "subscription",
|
||||
"fieldtype": "Link",
|
||||
@@ -1477,42 +1460,6 @@
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "apply_tds",
|
||||
"fieldname": "tax_withholding_net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Tax Withholding Net Total",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "apply_tds",
|
||||
"fieldname": "base_tax_withholding_net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Base Tax Withholding Net Total",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible_depends_on": "tax_withheld_vouchers",
|
||||
"fieldname": "tax_withheld_vouchers_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Withheld Vouchers"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withheld_vouchers",
|
||||
"fieldtype": "Table",
|
||||
"label": "Tax Withheld Vouchers",
|
||||
"no_copy": 1,
|
||||
"options": "Tax Withheld Vouchers",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "payments_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
@@ -1662,7 +1609,7 @@
|
||||
"fieldtype": "Data",
|
||||
"is_virtual": 1,
|
||||
"label": "Last Scanned Warehouse"
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "claimed_landed_cost_amount",
|
||||
"fieldtype": "Currency",
|
||||
@@ -1679,6 +1626,40 @@
|
||||
"label": "Item Wise Tax Details",
|
||||
"no_copy": 1,
|
||||
"options": "Item Wise Tax Detail"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.apply_tds && doc.docstatus == 0",
|
||||
"depends_on": "eval: doc.apply_tds",
|
||||
"fieldname": "section_tax_withholding_entry",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Withholding Entry"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Group",
|
||||
"options": "Tax Withholding Group",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_entries",
|
||||
"fieldtype": "Table",
|
||||
"label": "Tax Withholding Entries",
|
||||
"options": "Tax Withholding Entry",
|
||||
"read_only_depends_on": "eval: !doc.override_tax_withholding_entries"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_tax_withholding_threshold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Tax Withholding Threshold"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "override_tax_withholding_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Edit Tax Withholding Entries"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1686,7 +1667,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-04 19:19:11.380664",
|
||||
"modified": "2025-12-15 06:41:38.237728",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -24,9 +24,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
update_linked_doc,
|
||||
validate_inter_company_party,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import PurchaseTaxWithholding
|
||||
from erpnext.accounts.general_ledger import (
|
||||
get_round_off_account_and_cost_center,
|
||||
make_gl_entries,
|
||||
@@ -61,7 +59,6 @@ class PurchaseInvoice(BuyingController):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.advance_tax.advance_tax import AdvanceTax
|
||||
from erpnext.accounts.doctype.item_wise_tax_detail.item_wise_tax_detail import ItemWiseTaxDetail
|
||||
from erpnext.accounts.doctype.payment_schedule.payment_schedule import PaymentSchedule
|
||||
from erpnext.accounts.doctype.pricing_rule_detail.pricing_rule_detail import PricingRuleDetail
|
||||
@@ -72,14 +69,13 @@ class PurchaseInvoice(BuyingController):
|
||||
from erpnext.accounts.doctype.purchase_taxes_and_charges.purchase_taxes_and_charges import (
|
||||
PurchaseTaxesandCharges,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withheld_vouchers.tax_withheld_vouchers import TaxWithheldVouchers
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import TaxWithholdingEntry
|
||||
from erpnext.buying.doctype.purchase_receipt_item_supplied.purchase_receipt_item_supplied import (
|
||||
PurchaseReceiptItemSupplied,
|
||||
)
|
||||
|
||||
additional_discount_percentage: DF.Float
|
||||
address_display: DF.TextEditor | None
|
||||
advance_tax: DF.Table[AdvanceTax]
|
||||
advances: DF.Table[PurchaseInvoiceAdvance]
|
||||
against_expense_account: DF.SmallText | None
|
||||
allocate_advances_automatically: DF.Check
|
||||
@@ -94,7 +90,6 @@ class PurchaseInvoice(BuyingController):
|
||||
base_paid_amount: DF.Currency
|
||||
base_rounded_total: DF.Currency
|
||||
base_rounding_adjustment: DF.Currency
|
||||
base_tax_withholding_net_total: DF.Currency
|
||||
base_taxes_and_charges_added: DF.Currency
|
||||
base_taxes_and_charges_deducted: DF.Currency
|
||||
base_total: DF.Currency
|
||||
@@ -128,6 +123,7 @@ class PurchaseInvoice(BuyingController):
|
||||
hold_comment: DF.SmallText | None
|
||||
ignore_default_payment_terms_template: DF.Check
|
||||
ignore_pricing_rule: DF.Check
|
||||
ignore_tax_withholding_threshold: DF.Check
|
||||
in_words: DF.Data | None
|
||||
incoterm: DF.Link | None
|
||||
inter_company_invoice_reference: DF.Link | None
|
||||
@@ -149,6 +145,7 @@ class PurchaseInvoice(BuyingController):
|
||||
only_include_allocated_payments: DF.Check
|
||||
other_charges_calculation: DF.TextEditor | None
|
||||
outstanding_amount: DF.Currency
|
||||
override_tax_withholding_entries: DF.Check
|
||||
paid_amount: DF.Currency
|
||||
party_account_currency: DF.Link | None
|
||||
payment_schedule: DF.Table[PaymentSchedule]
|
||||
@@ -198,9 +195,8 @@ class PurchaseInvoice(BuyingController):
|
||||
supplier_warehouse: DF.Link | None
|
||||
tax_category: DF.Link | None
|
||||
tax_id: DF.ReadOnly | None
|
||||
tax_withheld_vouchers: DF.Table[TaxWithheldVouchers]
|
||||
tax_withholding_category: DF.Link | None
|
||||
tax_withholding_net_total: DF.Currency
|
||||
tax_withholding_entries: DF.Table[TaxWithholdingEntry]
|
||||
tax_withholding_group: DF.Link | None
|
||||
taxes: DF.Table[PurchaseTaxesandCharges]
|
||||
taxes_and_charges: DF.Link | None
|
||||
taxes_and_charges_added: DF.Currency
|
||||
@@ -245,11 +241,14 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
def onload(self):
|
||||
super().onload()
|
||||
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||
self.set_onload("supplier_tds", supplier_tds)
|
||||
if self.supplier:
|
||||
tax_withholding_category, tax_withholding_group = frappe.get_cached_value(
|
||||
"Supplier", self.supplier, ["tax_withholding_category", "tax_withholding_group"]
|
||||
)
|
||||
self.set_onload("apply_tds", tax_withholding_category or tax_withholding_group)
|
||||
|
||||
if self.is_new():
|
||||
self.set("tax_withheld_vouchers", [])
|
||||
self.set("tax_withholding_entries", [])
|
||||
|
||||
def before_save(self):
|
||||
if not self.on_hold:
|
||||
@@ -300,6 +299,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
|
||||
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
|
||||
PurchaseTaxWithholding(self).on_validate()
|
||||
self.set_percentage_received()
|
||||
|
||||
def set_percentage_received(self):
|
||||
@@ -352,11 +352,13 @@ class PurchaseInvoice(BuyingController):
|
||||
template_name=self.payment_terms_template,
|
||||
)
|
||||
|
||||
tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||
if tds_category and not for_validate:
|
||||
self.apply_tds = 1
|
||||
self.tax_withholding_category = tds_category
|
||||
self.set_onload("supplier_tds", tds_category)
|
||||
if self.supplier:
|
||||
tax_withholding_category, tax_withholding_group = frappe.get_cached_value(
|
||||
"Supplier", self.supplier, ["tax_withholding_category", "tax_withholding_group"]
|
||||
)
|
||||
if not for_validate:
|
||||
if tax_withholding_category or tax_withholding_group:
|
||||
self.apply_tds = 1
|
||||
|
||||
super().set_missing_values(for_validate)
|
||||
|
||||
@@ -747,6 +749,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
def on_submit(self):
|
||||
super().on_submit()
|
||||
PurchaseTaxWithholding(self).on_submit()
|
||||
|
||||
self.check_prev_docstatus()
|
||||
|
||||
@@ -788,7 +791,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.update_project()
|
||||
|
||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
self.update_advance_tax_references()
|
||||
|
||||
self.process_common_party_accounting()
|
||||
|
||||
@@ -1672,6 +1674,7 @@ class PurchaseInvoice(BuyingController):
|
||||
check_if_return_invoice_linked_with_payment_entry(self)
|
||||
|
||||
super().on_cancel()
|
||||
PurchaseTaxWithholding(self).on_cancel()
|
||||
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
@@ -1718,10 +1721,9 @@ class PurchaseInvoice(BuyingController):
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Payment Ledger Entry",
|
||||
"Tax Withheld Vouchers",
|
||||
"Serial and Batch Bundle",
|
||||
"Tax Withholding Entry",
|
||||
)
|
||||
self.update_advance_tax_references(cancel=1)
|
||||
|
||||
def update_project(self):
|
||||
projects = frappe._dict()
|
||||
@@ -1844,102 +1846,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.db_set("on_hold", 0)
|
||||
self.db_set("release_date", None)
|
||||
|
||||
def set_tax_withholding(self):
|
||||
self.set("advance_tax", [])
|
||||
self.set("tax_withheld_vouchers", [])
|
||||
|
||||
if not self.apply_tds:
|
||||
return
|
||||
|
||||
if self.apply_tds and not self.get("tax_withholding_category"):
|
||||
self.tax_withholding_category = frappe.db.get_value(
|
||||
"Supplier", self.supplier, "tax_withholding_category"
|
||||
)
|
||||
|
||||
if not self.tax_withholding_category:
|
||||
return
|
||||
|
||||
tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details(
|
||||
self, self.tax_withholding_category
|
||||
)
|
||||
|
||||
# Adjust TDS paid on advances
|
||||
self.allocate_advance_tds(tax_withholding_details, advance_taxes)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
|
||||
accounts = []
|
||||
for d in self.taxes:
|
||||
if d.account_head == tax_withholding_details.get("account_head"):
|
||||
d.update(tax_withholding_details)
|
||||
|
||||
accounts.append(d.account_head)
|
||||
|
||||
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||
self.append("taxes", tax_withholding_details)
|
||||
|
||||
to_remove = [
|
||||
d
|
||||
for d in self.taxes
|
||||
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")
|
||||
]
|
||||
|
||||
for d in to_remove:
|
||||
self.remove(d)
|
||||
|
||||
## Add pending vouchers on which tax was withheld
|
||||
for row in voucher_wise_amount:
|
||||
self.append(
|
||||
"tax_withheld_vouchers",
|
||||
{
|
||||
"voucher_name": row.voucher_name,
|
||||
"voucher_type": row.voucher_type,
|
||||
"taxable_amount": row.taxable_amount,
|
||||
},
|
||||
)
|
||||
|
||||
# calculate totals again after applying TDS
|
||||
self.calculate_taxes_and_totals()
|
||||
|
||||
def allocate_advance_tds(self, tax_withholding_details, advance_taxes):
|
||||
for tax in advance_taxes:
|
||||
allocated_amount = 0
|
||||
pending_amount = flt(tax.tax_amount - tax.allocated_amount)
|
||||
if flt(tax_withholding_details.get("tax_amount")) >= pending_amount:
|
||||
tax_withholding_details["tax_amount"] -= pending_amount
|
||||
allocated_amount = pending_amount
|
||||
elif (
|
||||
flt(tax_withholding_details.get("tax_amount"))
|
||||
and flt(tax_withholding_details.get("tax_amount")) < pending_amount
|
||||
):
|
||||
allocated_amount = tax_withholding_details["tax_amount"]
|
||||
tax_withholding_details["tax_amount"] = 0
|
||||
|
||||
self.append(
|
||||
"advance_tax",
|
||||
{
|
||||
"reference_type": "Payment Entry",
|
||||
"reference_name": tax.parent,
|
||||
"reference_detail": tax.name,
|
||||
"account_head": tax.account_head,
|
||||
"allocated_amount": allocated_amount,
|
||||
},
|
||||
)
|
||||
|
||||
def update_advance_tax_references(self, cancel=0):
|
||||
for tax in self.get("advance_tax"):
|
||||
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
|
||||
|
||||
if cancel:
|
||||
frappe.qb.update(at).set(
|
||||
at.allocated_amount, at.allocated_amount - tax.allocated_amount
|
||||
).where(at.name == tax.reference_detail).run()
|
||||
else:
|
||||
frappe.qb.update(at).set(
|
||||
at.allocated_amount, at.allocated_amount + tax.allocated_amount
|
||||
).where(at.name == tax.reference_detail).run()
|
||||
|
||||
def set_status(self, update=False, status=None, update_modified=True):
|
||||
if self.is_new():
|
||||
if self.get("amended_from"):
|
||||
|
||||
@@ -1538,7 +1538,7 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
|
||||
# Create Payment Entry Against the order
|
||||
payment_entry = get_payment_entry(dt="Purchase Order", dn=po.name)
|
||||
payment_entry.paid_from = "Cash - _TC"
|
||||
payment_entry.apply_tax_withholding_amount = 1
|
||||
payment_entry.apply_tds = 1
|
||||
payment_entry.tax_withholding_category = tax_withholding_category
|
||||
payment_entry.save()
|
||||
payment_entry.submit()
|
||||
@@ -1591,12 +1591,26 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
|
||||
self.assertEqual(expected_gle[i][1], gle.amount)
|
||||
|
||||
payment_entry.load_from_db()
|
||||
self.assertEqual(payment_entry.taxes[0].allocated_amount, 3000)
|
||||
tax_allocated = sum(
|
||||
[
|
||||
entry.withholding_amount
|
||||
for entry in payment_entry.get("tax_withholding_entries", [])
|
||||
if entry.taxable_name
|
||||
]
|
||||
)
|
||||
self.assertEqual(tax_allocated, 3000)
|
||||
|
||||
purchase_invoice.cancel()
|
||||
|
||||
payment_entry.load_from_db()
|
||||
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
|
||||
tax_allocated = sum(
|
||||
[
|
||||
entry.withholding_amount
|
||||
for entry in payment_entry.get("tax_withholding_entries", [])
|
||||
if entry.taxable_name
|
||||
]
|
||||
)
|
||||
self.assertEqual(tax_allocated, 0)
|
||||
|
||||
def test_purchase_gl_with_tax_withholding_tax(self):
|
||||
company = "_Test Company"
|
||||
@@ -1631,7 +1645,6 @@ class TestPurchaseInvoice(IntegrationTestCase, StockTestMixin):
|
||||
do_not_submit=1,
|
||||
)
|
||||
pi.apply_tds = 1
|
||||
pi.tax_withholding_category = tax_withholding_category
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"rate",
|
||||
"amount",
|
||||
"item_tax_template",
|
||||
"tax_withholding_category",
|
||||
"col_break4",
|
||||
"base_rate",
|
||||
"base_amount",
|
||||
@@ -893,7 +894,7 @@
|
||||
"default": "1",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply TDS"
|
||||
"label": "Consider for Tax Withholding"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
@@ -979,6 +980,13 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Distributed Discount Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Category",
|
||||
"options": "Tax Withholding Category",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
|
||||
@@ -90,6 +90,7 @@ class PurchaseInvoiceItem(Document):
|
||||
stock_qty: DF.Float
|
||||
stock_uom: DF.Link | None
|
||||
stock_uom_rate: DF.Currency
|
||||
tax_withholding_category: DF.Link | None
|
||||
total_weight: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
"base_net_amount",
|
||||
"base_tax_amount",
|
||||
"base_total",
|
||||
"base_tax_amount_after_discount_amount"
|
||||
"base_tax_amount_after_discount_amount",
|
||||
"dont_recompute_tax"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -205,11 +206,11 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -262,13 +263,22 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "dont_recompute_tax",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Don't Recompute Tax",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-07-24 15:08:44.433022",
|
||||
"modified": "2025-11-24 18:22:56.886010",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Taxes and Charges",
|
||||
|
||||
@@ -32,6 +32,7 @@ class PurchaseTaxesandCharges(Document):
|
||||
]
|
||||
cost_center: DF.Link | None
|
||||
description: DF.SmallText
|
||||
dont_recompute_tax: DF.Check
|
||||
included_in_paid_amount: DF.Check
|
||||
included_in_print_rate: DF.Check
|
||||
is_tax_withholding_account: DF.Check
|
||||
@@ -39,6 +40,7 @@ class PurchaseTaxesandCharges(Document):
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
project: DF.Link | None
|
||||
rate: DF.Float
|
||||
row_id: DF.Data | None
|
||||
set_by_item_tax_template: DF.Check
|
||||
|
||||
@@ -24,6 +24,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
company() {
|
||||
super.company();
|
||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||
this.frm.clear_table("tax_withholding_entries");
|
||||
}
|
||||
onload() {
|
||||
var me = this;
|
||||
@@ -381,6 +382,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
),
|
||||
},
|
||||
function () {
|
||||
me.frm.doc.apply_tds =
|
||||
me.frm.tax_withholding_category || me.frm.tax_withholding_group ? 1 : 0;
|
||||
me.frm.clear_table("tax_withholding_entries");
|
||||
me.apply_pricing_rule();
|
||||
}
|
||||
);
|
||||
@@ -597,6 +601,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
|
||||
this.calculate_taxes_and_totals();
|
||||
}
|
||||
|
||||
apply_tds(frm) {
|
||||
this.frm.clear_table("tax_withholding_entries");
|
||||
}
|
||||
};
|
||||
|
||||
// for backward compatibility: combine new and previous states
|
||||
@@ -817,6 +825,16 @@ frappe.ui.form.on("Sales Invoice", {
|
||||
},
|
||||
onload: function (frm) {
|
||||
frm.redemption_conversion_factor = null;
|
||||
|
||||
if (frm.doc.__onload && frm.doc.customer) {
|
||||
if (frm.is_new()) {
|
||||
frm.doc.apply_tds = frm.doc.__onload.apply_tds ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (frm.is_new()) {
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
}
|
||||
},
|
||||
|
||||
update_stock: function (frm, dt, dn) {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"update_billed_amount_in_sales_order",
|
||||
"update_billed_amount_in_delivery_note",
|
||||
"is_debit_note",
|
||||
"apply_tds",
|
||||
"amended_from",
|
||||
"is_created_using_pos",
|
||||
"pos_closing_entry",
|
||||
@@ -90,6 +91,11 @@
|
||||
"total_advance",
|
||||
"outstanding_amount",
|
||||
"disable_rounded_total",
|
||||
"section_tax_withholding_entry",
|
||||
"tax_withholding_group",
|
||||
"ignore_tax_withholding_threshold",
|
||||
"override_tax_withholding_entries",
|
||||
"tax_withholding_entries",
|
||||
"section_break_49",
|
||||
"apply_discount_on",
|
||||
"base_discount_amount",
|
||||
@@ -2247,6 +2253,46 @@
|
||||
"label": "Item Wise Tax Details",
|
||||
"no_copy": 1,
|
||||
"options": "Item Wise Tax Detail"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider for Tax Withholding",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.apply_tds && doc.docstatus == 0",
|
||||
"depends_on": "eval: doc.apply_tds",
|
||||
"fieldname": "section_tax_withholding_entry",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Withholding Entry"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Group",
|
||||
"options": "Tax Withholding Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_entries",
|
||||
"fieldtype": "Table",
|
||||
"label": "Tax Withholding Entries",
|
||||
"options": "Tax Withholding Entry",
|
||||
"read_only_depends_on": "eval: !doc.override_tax_withholding_entries"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_tax_withholding_threshold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Tax Withholding Threshold"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "override_tax_withholding_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Edit Tax Withholding Entries"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
|
||||
@@ -26,9 +26,7 @@ from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger
|
||||
validate_docs_for_deferred_accounting,
|
||||
validate_docs_for_voucher_types,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import SalesTaxWithholding
|
||||
from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center
|
||||
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
|
||||
from erpnext.accounts.utils import (
|
||||
@@ -77,6 +75,7 @@ class SalesInvoice(SellingController):
|
||||
from erpnext.accounts.doctype.sales_taxes_and_charges.sales_taxes_and_charges import (
|
||||
SalesTaxesandCharges,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import TaxWithholdingEntry
|
||||
from erpnext.selling.doctype.sales_team.sales_team import SalesTeam
|
||||
from erpnext.stock.doctype.packed_item.packed_item import PackedItem
|
||||
|
||||
@@ -90,6 +89,7 @@ class SalesInvoice(SellingController):
|
||||
amended_from: DF.Link | None
|
||||
amount_eligible_for_commission: DF.Currency
|
||||
apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
|
||||
apply_tds: DF.Check
|
||||
auto_repeat: DF.Link | None
|
||||
base_change_amount: DF.Currency
|
||||
base_discount_amount: DF.Currency
|
||||
@@ -135,6 +135,7 @@ class SalesInvoice(SellingController):
|
||||
has_subcontracted: DF.Check
|
||||
ignore_default_payment_terms_template: DF.Check
|
||||
ignore_pricing_rule: DF.Check
|
||||
ignore_tax_withholding_threshold: DF.Check
|
||||
in_words: DF.SmallText | None
|
||||
incoterm: DF.Link | None
|
||||
inter_company_invoice_reference: DF.Link | None
|
||||
@@ -162,6 +163,7 @@ class SalesInvoice(SellingController):
|
||||
only_include_allocated_payments: DF.Check
|
||||
other_charges_calculation: DF.TextEditor | None
|
||||
outstanding_amount: DF.Currency
|
||||
override_tax_withholding_entries: DF.Check
|
||||
packed_items: DF.Table[PackedItem]
|
||||
paid_amount: DF.Currency
|
||||
party_account_currency: DF.Link | None
|
||||
@@ -214,6 +216,8 @@ class SalesInvoice(SellingController):
|
||||
subscription: DF.Link | None
|
||||
tax_category: DF.Link | None
|
||||
tax_id: DF.Data | None
|
||||
tax_withholding_entries: DF.Table[TaxWithholdingEntry]
|
||||
tax_withholding_group: DF.Link | None
|
||||
taxes: DF.Table[SalesTaxesandCharges]
|
||||
taxes_and_charges: DF.Link | None
|
||||
tc_name: DF.Link | None
|
||||
@@ -282,6 +286,7 @@ class SalesInvoice(SellingController):
|
||||
self.indicator_color = "green"
|
||||
self.indicator_title = _("Paid")
|
||||
|
||||
<<<<<<< HEAD
|
||||
def before_print(self, settings=None):
|
||||
from frappe.contacts.doctype.address.address import get_address_display_list
|
||||
|
||||
@@ -334,6 +339,15 @@ class SalesInvoice(SellingController):
|
||||
},
|
||||
user=frappe.session.user,
|
||||
)
|
||||
=======
|
||||
def onload(self):
|
||||
super().onload()
|
||||
if self.customer:
|
||||
tax_withholding_category, tax_withholding_group = frappe.get_cached_value(
|
||||
"Customer", self.customer, ["tax_withholding_category", "tax_withholding_group"]
|
||||
)
|
||||
self.set_onload("apply_tds", tax_withholding_category or tax_withholding_group)
|
||||
>>>>>>> c66f78c784 (feat: Introduce tax withholding entry)
|
||||
|
||||
def validate(self):
|
||||
self.validate_auto_set_posting_time()
|
||||
@@ -344,7 +358,7 @@ class SalesInvoice(SellingController):
|
||||
if not (self.is_pos or self.is_debit_note):
|
||||
self.so_dn_required()
|
||||
|
||||
self.set_tax_withholding()
|
||||
SalesTaxWithholding(self).on_validate()
|
||||
|
||||
self.validate_proj_cust()
|
||||
self.validate_pos_return()
|
||||
@@ -466,38 +480,6 @@ class SalesInvoice(SellingController):
|
||||
for item in self.get("items"):
|
||||
validate_account_head(item.idx, item.income_account, self.company, _("Income"))
|
||||
|
||||
def set_tax_withholding(self):
|
||||
if self.get("is_opening") == "Yes":
|
||||
return
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(self)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
|
||||
accounts = []
|
||||
tax_withholding_account = tax_withholding_details.get("account_head")
|
||||
|
||||
for d in self.taxes:
|
||||
if d.account_head == tax_withholding_account:
|
||||
d.update(tax_withholding_details)
|
||||
accounts.append(d.account_head)
|
||||
|
||||
if not accounts or tax_withholding_account not in accounts:
|
||||
self.append("taxes", tax_withholding_details)
|
||||
|
||||
to_remove = [
|
||||
d
|
||||
for d in self.taxes
|
||||
if not d.tax_amount and d.charge_type == "Actual" and d.account_head == tax_withholding_account
|
||||
]
|
||||
|
||||
for d in to_remove:
|
||||
self.remove(d)
|
||||
|
||||
# calculate totals again after applying TDS
|
||||
self.calculate_taxes_and_totals()
|
||||
|
||||
def before_save(self):
|
||||
self.set_account_for_mode_of_payment()
|
||||
self.set_paid_amount()
|
||||
@@ -519,6 +501,8 @@ class SalesInvoice(SellingController):
|
||||
# NOTE status updating bypassed for is_return
|
||||
self.status_updater = []
|
||||
|
||||
SalesTaxWithholding(self).on_submit()
|
||||
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
|
||||
@@ -658,6 +642,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
# Updating stock ledger should always be called after updating prevdoc status,
|
||||
# because updating reserved qty in bin depends upon updated delivered qty in SO
|
||||
SalesTaxWithholding(self).on_cancel()
|
||||
if self.update_stock == 1:
|
||||
self.update_stock_ledger()
|
||||
|
||||
@@ -699,6 +684,7 @@ class SalesInvoice(SellingController):
|
||||
"Unreconcile Payment Entries",
|
||||
"Payment Ledger Entry",
|
||||
"Serial and Batch Bundle",
|
||||
"Tax Withholding Entry",
|
||||
)
|
||||
|
||||
self.delete_auto_created_batches()
|
||||
|
||||
@@ -43,12 +43,14 @@
|
||||
"rate",
|
||||
"amount",
|
||||
"item_tax_template",
|
||||
"tax_withholding_category",
|
||||
"col_break3",
|
||||
"base_rate",
|
||||
"base_amount",
|
||||
"pricing_rules",
|
||||
"stock_uom_rate",
|
||||
"is_free_item",
|
||||
"apply_tds",
|
||||
"grant_commission",
|
||||
"section_break_21",
|
||||
"net_rate",
|
||||
@@ -986,6 +988,21 @@
|
||||
"hidden": 1,
|
||||
"label": "SCIO Detail",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Category",
|
||||
"options": "Tax Withholding Category",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider for Tax Withholding",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
|
||||
@@ -24,6 +24,7 @@ class SalesInvoiceItem(Document):
|
||||
actual_qty: DF.Float
|
||||
allow_zero_valuation_rate: DF.Check
|
||||
amount: DF.Currency
|
||||
apply_tds: DF.Check
|
||||
asset: DF.Link | None
|
||||
barcode: DF.Data | None
|
||||
base_amount: DF.Currency
|
||||
@@ -95,6 +96,7 @@ class SalesInvoiceItem(Document):
|
||||
stock_uom: DF.Link | None
|
||||
stock_uom_rate: DF.Currency
|
||||
target_warehouse: DF.Link | None
|
||||
tax_withholding_category: DF.Link | None
|
||||
total_weight: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"included_in_print_rate",
|
||||
"included_in_paid_amount",
|
||||
"set_by_item_tax_template",
|
||||
"is_tax_withholding_account",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
@@ -202,7 +203,7 @@
|
||||
"fieldname": "dont_recompute_tax",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Dont Recompute tax",
|
||||
"label": "Don't Recompute Tax",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -241,6 +242,13 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_tax_withholding_account",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Tax Withholding Account",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
|
||||
@@ -33,6 +33,8 @@ class SalesTaxesandCharges(Document):
|
||||
dont_recompute_tax: DF.Check
|
||||
included_in_paid_amount: DF.Check
|
||||
included_in_print_rate: DF.Check
|
||||
is_tax_withholding_account: DF.Check
|
||||
item_wise_tax_detail: DF.Code | None
|
||||
net_amount: DF.Currency
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
|
||||
@@ -411,7 +411,10 @@ class Subscription(Document):
|
||||
invoice.customer = self.party
|
||||
else:
|
||||
invoice.supplier = self.party
|
||||
if frappe.db.get_value("Supplier", self.party, "tax_withholding_category"):
|
||||
tax_withholding_category, tax_withholding_group = frappe.get_cached_value(
|
||||
"Supplier", self.party, ["tax_withholding_category", "tax_withholding_group"]
|
||||
)
|
||||
if tax_withholding_category or tax_withholding_group:
|
||||
invoice.apply_tds = 1
|
||||
|
||||
# Add currency to invoice
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"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": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Voucher Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Voucher Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "taxable_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Taxable Amount",
|
||||
"options": "Company:company:default_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-02-05 16:39:14.863698",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withheld Vouchers",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -16,4 +16,54 @@ frappe.ui.form.on("Tax Withholding Category", {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
update_rates_read_only_state(frm);
|
||||
},
|
||||
|
||||
disable_cumulative_threshold: function (frm) {
|
||||
toggle_threshold_settings(frm, "disable_cumulative_threshold");
|
||||
if (frm.doc.disable_cumulative_threshold) {
|
||||
reset_rates_column(frm, "cumulative_threshold");
|
||||
}
|
||||
update_rates_read_only_state(frm);
|
||||
},
|
||||
|
||||
disable_transaction_threshold: function (frm) {
|
||||
toggle_threshold_settings(frm, "disable_transaction_threshold");
|
||||
if (frm.doc.disable_transaction_threshold) {
|
||||
reset_rates_column(frm, "single_threshold");
|
||||
}
|
||||
update_rates_read_only_state(frm);
|
||||
},
|
||||
});
|
||||
|
||||
function toggle_threshold_settings(frm, field_name) {
|
||||
if (frm.doc[field_name]) {
|
||||
const other_field =
|
||||
field_name === "disable_cumulative_threshold"
|
||||
? "disable_transaction_threshold"
|
||||
: "disable_cumulative_threshold";
|
||||
frm.set_value(other_field, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function update_rates_read_only_state(frm) {
|
||||
frm.fields_dict["rates"].grid.update_docfield_property(
|
||||
"cumulative_threshold",
|
||||
"read_only",
|
||||
frm.doc.disable_cumulative_threshold
|
||||
);
|
||||
frm.fields_dict["rates"].grid.update_docfield_property(
|
||||
"single_threshold",
|
||||
"read_only",
|
||||
frm.doc.disable_transaction_threshold
|
||||
);
|
||||
}
|
||||
|
||||
function reset_rates_column(frm, field_name) {
|
||||
$.each(frm.doc.rates || [], function (i, row) {
|
||||
row[field_name] = 0;
|
||||
});
|
||||
frm.refresh_field("rates");
|
||||
}
|
||||
|
||||
@@ -10,10 +10,12 @@
|
||||
"field_order": [
|
||||
"category_details_section",
|
||||
"category_name",
|
||||
"round_off_tax_amount",
|
||||
"tax_deduction_basis",
|
||||
"column_break_2",
|
||||
"consider_party_ledger_amount",
|
||||
"round_off_tax_amount",
|
||||
"tax_on_excess_amount",
|
||||
"disable_cumulative_threshold",
|
||||
"disable_transaction_threshold",
|
||||
"section_break_8",
|
||||
"rates",
|
||||
"section_break_7",
|
||||
@@ -61,14 +63,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Only payment entries with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
|
||||
"fieldname": "consider_party_ledger_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider Entire Party Ledger Amount"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Tax will be withheld only for amount exceeding the cumulative threshold",
|
||||
"description": "Tax withheld only for amount exceeding cumulative threshold",
|
||||
"fieldname": "tax_on_excess_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Only Deduct Tax On Excess Amount "
|
||||
@@ -79,6 +74,28 @@
|
||||
"fieldname": "round_off_tax_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Round Off Tax Amount"
|
||||
},
|
||||
{
|
||||
"default": "Net Total",
|
||||
"fieldname": "tax_deduction_basis",
|
||||
"fieldtype": "Select",
|
||||
"label": "Deduct Tax On Basis",
|
||||
"options": "\nGross Total\nNet Total",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "When checked, only transaction threshold will be applied for transaction individually",
|
||||
"fieldname": "disable_cumulative_threshold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Cumulative Threshold"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "When checked, only cumulative threshold will be applied",
|
||||
"fieldname": "disable_transaction_threshold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Transaction Threshold"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.functions import Abs, Sum
|
||||
from frappe.utils import cint, flt, getdate
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import getdate
|
||||
|
||||
from erpnext import allow_regional
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
|
||||
|
||||
@@ -28,30 +29,41 @@ class TaxWithholdingCategory(Document):
|
||||
|
||||
accounts: DF.Table[TaxWithholdingAccount]
|
||||
category_name: DF.Data | None
|
||||
consider_party_ledger_amount: DF.Check
|
||||
disable_cumulative_threshold: DF.Check
|
||||
disable_transaction_threshold: DF.Check
|
||||
rates: DF.Table[TaxWithholdingRate]
|
||||
round_off_tax_amount: DF.Check
|
||||
tax_deduction_basis: DF.Literal["", "Gross Total", "Net Total"]
|
||||
tax_on_excess_amount: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
# TODO: Disable single threshold if tax on excess is enabled
|
||||
self.validate_dates()
|
||||
self.validate_companies_and_accounts()
|
||||
self.validate_thresholds()
|
||||
|
||||
def validate_dates(self):
|
||||
last_to_date = None
|
||||
rates = sorted(self.get("rates"), key=lambda d: getdate(d.from_date))
|
||||
|
||||
for d in rates:
|
||||
group_rates = defaultdict(list)
|
||||
for d in self.get("rates"):
|
||||
if getdate(d.from_date) >= getdate(d.to_date):
|
||||
frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx))
|
||||
group_rates[d.tax_withholding_group].append(d)
|
||||
|
||||
# validate overlapping of dates
|
||||
if last_to_date and getdate(d.from_date) < getdate(last_to_date):
|
||||
frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx))
|
||||
# Validate overlapping dates within each group
|
||||
for group, rates in group_rates.items():
|
||||
rates = sorted(rates, key=lambda d: getdate(d.from_date))
|
||||
last_to_date = None
|
||||
|
||||
last_to_date = d.to_date
|
||||
for d in rates:
|
||||
if last_to_date and getdate(d.from_date) < getdate(last_to_date):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Dates overlapping with other row in group {1}").format(
|
||||
d.idx, group or "Default"
|
||||
)
|
||||
)
|
||||
|
||||
last_to_date = d.to_date
|
||||
|
||||
def validate_companies_and_accounts(self):
|
||||
existing_accounts = set()
|
||||
@@ -78,74 +90,32 @@ class TaxWithholdingCategory(Document):
|
||||
).format(d.idx)
|
||||
)
|
||||
|
||||
def get_applicable_tax_row(self, posting_date, tax_withholding_group):
|
||||
for row in self.rates:
|
||||
if (
|
||||
getdate(row.from_date) <= getdate(posting_date) <= getdate(row.to_date)
|
||||
and row.tax_withholding_group == tax_withholding_group
|
||||
):
|
||||
return row
|
||||
|
||||
def get_party_details(inv):
|
||||
party_type, party = "", ""
|
||||
frappe.throw(_("No Tax Withholding data found for the current posting date."))
|
||||
|
||||
<<<<<<< HEAD
|
||||
if inv.doctype == "Sales Invoice":
|
||||
party_type = "Customer"
|
||||
party = inv.customer
|
||||
else:
|
||||
party_type = "Supplier"
|
||||
party = inv.supplier
|
||||
=======
|
||||
def get_company_account(self, company):
|
||||
for row in self.accounts:
|
||||
if company == row.company:
|
||||
return row.account
|
||||
>>>>>>> c66f78c784 (feat: Introduce tax withholding entry)
|
||||
|
||||
if not party:
|
||||
frappe.throw(_("Please select {0} first").format(party_type))
|
||||
|
||||
return party_type, party
|
||||
|
||||
|
||||
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 = []
|
||||
party_type, party = get_party_details(inv)
|
||||
has_pan_field = frappe.get_meta(party_type).has_field("pan")
|
||||
|
||||
if not tax_withholding_category:
|
||||
if has_pan_field:
|
||||
fields = ["tax_withholding_category", "pan"]
|
||||
else:
|
||||
fields = ["tax_withholding_category"]
|
||||
|
||||
tax_withholding_details = frappe.db.get_value(party_type, party, fields, as_dict=1)
|
||||
|
||||
tax_withholding_category = tax_withholding_details.get("tax_withholding_category")
|
||||
pan_no = tax_withholding_details.get("pan")
|
||||
|
||||
if not tax_withholding_category:
|
||||
return
|
||||
|
||||
# if tax_withholding_category passed as an argument but not pan_no
|
||||
if not pan_no and has_pan_field:
|
||||
pan_no = frappe.db.get_value(party_type, party, "pan")
|
||||
|
||||
# Get others suppliers with the same PAN No
|
||||
if pan_no:
|
||||
parties = frappe.get_all(party_type, filters={"pan": pan_no}, pluck="name")
|
||||
|
||||
if not parties:
|
||||
parties.append(party)
|
||||
|
||||
posting_date = inv.get("posting_date") or inv.get("transaction_date")
|
||||
tax_details = get_tax_withholding_details(tax_withholding_category, posting_date, inv.company)
|
||||
|
||||
if not tax_details:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Skipping Tax Withholding Category {0} as there is no associated account set for Company {1} in it."
|
||||
).format(tax_withholding_category, inv.company)
|
||||
)
|
||||
if inv.doctype == "Purchase Invoice":
|
||||
return {}, [], {}
|
||||
return {}
|
||||
|
||||
if party_type == "Customer" and not tax_details.cumulative_threshold:
|
||||
# TCS is only chargeable on sum of invoiced value
|
||||
frappe.throw(
|
||||
<<<<<<< HEAD
|
||||
_(
|
||||
"Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value."
|
||||
).format(tax_withholding_category, inv.company, party)
|
||||
@@ -404,244 +374,155 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
if doctype != "Sales Invoice":
|
||||
filters.update(
|
||||
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
|
||||
=======
|
||||
_("No Tax withholding account set for Company {0} in Tax Withholding Category {1}.").format(
|
||||
frappe.bold(company), frappe.bold(self.name)
|
||||
)
|
||||
>>>>>>> c66f78c784 (feat: Introduce tax withholding entry)
|
||||
)
|
||||
|
||||
invoices_details = frappe.get_all(doctype, filters=filters, fields=field)
|
||||
|
||||
for d in invoices_details:
|
||||
d = frappe._dict(
|
||||
{
|
||||
"voucher_name": d.name,
|
||||
"voucher_type": doctype,
|
||||
"taxable_amount": d.base_net_total,
|
||||
"grand_total": d.grand_total,
|
||||
"posting_date": d.posting_date,
|
||||
}
|
||||
)
|
||||
class TaxWithholdingDetails:
|
||||
def __init__(
|
||||
self,
|
||||
tax_withholding_categories: list[str],
|
||||
tax_withholding_group: str,
|
||||
posting_date: str,
|
||||
party_type: str,
|
||||
party: str,
|
||||
company: str,
|
||||
):
|
||||
self.tax_withholding_categories = tax_withholding_categories
|
||||
self.tax_withholding_group = tax_withholding_group
|
||||
self.posting_date = posting_date
|
||||
self.party_type = party_type
|
||||
self.party = party
|
||||
self.company = company
|
||||
|
||||
if ldc := [x for x in ldcs if d.posting_date >= x.valid_from and d.posting_date <= x.valid_upto]:
|
||||
if ldc[0].supplier in parties and ldc[0].rate == 0:
|
||||
d.update({"taxable_amount": 0})
|
||||
|
||||
vouchers.append(d.voucher_name)
|
||||
voucher_wise_amount.append(d)
|
||||
|
||||
journal_entries_details = frappe.db.sql(
|
||||
def get(self) -> list:
|
||||
"""
|
||||
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
|
||||
AND j.docstatus = 1
|
||||
AND j.is_opening = 'No'
|
||||
AND j.posting_date between %s and %s
|
||||
AND ja.party in %s
|
||||
AND j.apply_tds = 1
|
||||
AND j.tax_withholding_category = %s
|
||||
AND j.company = %s
|
||||
""",
|
||||
(
|
||||
tax_details.from_date,
|
||||
tax_details.to_date,
|
||||
tuple(parties),
|
||||
tax_details.get("tax_withholding_category"),
|
||||
company,
|
||||
),
|
||||
as_dict=1,
|
||||
)
|
||||
Fetches tax withholding categories based on the provided parameters.
|
||||
"""
|
||||
category_details = frappe._dict()
|
||||
if not self.tax_withholding_categories:
|
||||
return category_details
|
||||
|
||||
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,
|
||||
}
|
||||
ldc_details = self.get_ldc_details()
|
||||
|
||||
for category_name in self.tax_withholding_categories:
|
||||
doc: TaxWithholdingCategory = frappe.get_cached_doc("Tax Withholding Category", category_name)
|
||||
row = doc.get_applicable_tax_row(self.posting_date, self.tax_withholding_group)
|
||||
account_head = doc.get_company_account(self.company)
|
||||
|
||||
category_detail = frappe._dict(
|
||||
name=category_name,
|
||||
description=doc.category_name,
|
||||
account_head=account_head,
|
||||
# rates
|
||||
tax_rate=row.tax_withholding_rate,
|
||||
from_date=row.from_date,
|
||||
to_date=row.to_date,
|
||||
single_threshold=row.single_threshold,
|
||||
cumulative_threshold=row.cumulative_threshold,
|
||||
# settings
|
||||
tax_deduction_basis=doc.tax_deduction_basis,
|
||||
round_off_tax_amount=doc.round_off_tax_amount,
|
||||
tax_on_excess_amount=doc.tax_on_excess_amount,
|
||||
disable_cumulative_threshold=doc.disable_cumulative_threshold,
|
||||
disable_transaction_threshold=doc.disable_transaction_threshold,
|
||||
taxable_amount=0,
|
||||
)
|
||||
|
||||
# ldc (only if valid based on posting date)
|
||||
if ldc_detail := ldc_details.get(category_name):
|
||||
category_detail.update(ldc_detail)
|
||||
|
||||
category_details[category_name] = category_detail
|
||||
|
||||
return category_details
|
||||
|
||||
def get_ldc_details(self):
|
||||
"""
|
||||
Fetches the Lower Deduction Certificate (LDC) details for the given party.
|
||||
Assumes that only one LDC per category can be valid at a time.
|
||||
"""
|
||||
ldc_details = {}
|
||||
|
||||
if self.party_type != "Supplier":
|
||||
return ldc_details
|
||||
|
||||
# NOTE: This can be a configurable option
|
||||
# To check if filter by tax_id is needed
|
||||
tax_id = get_tax_id_for_party(self.party_type, self.party)
|
||||
|
||||
# ldc details
|
||||
ldc_records = self.get_valid_ldc_records(tax_id)
|
||||
if not ldc_records:
|
||||
return ldc_details
|
||||
|
||||
ldc_names = [ldc.name for ldc in ldc_records]
|
||||
ldc_utilization_map = self.get_ldc_utilization_by_category(ldc_names, tax_id)
|
||||
|
||||
# map
|
||||
for ldc in ldc_records:
|
||||
category_name = ldc.tax_withholding_category
|
||||
|
||||
unutilized_amount = ldc.certificate_limit - (ldc_utilization_map.get(ldc.name) or 0)
|
||||
if not unutilized_amount:
|
||||
continue
|
||||
|
||||
ldc_details[category_name] = dict(
|
||||
ldc_certificate=ldc.name,
|
||||
ldc_unutilized_amount=unutilized_amount,
|
||||
ldc_rate=ldc.rate,
|
||||
)
|
||||
|
||||
return ldc_details
|
||||
|
||||
def get_valid_ldc_records(self, tax_id):
|
||||
ldc = frappe.qb.DocType("Lower Deduction Certificate")
|
||||
query = (
|
||||
frappe.qb.from_(ldc)
|
||||
.select(
|
||||
ldc.name,
|
||||
ldc.tax_withholding_category,
|
||||
ldc.rate,
|
||||
ldc.certificate_limit,
|
||||
)
|
||||
.where(
|
||||
(ldc.valid_from <= self.posting_date)
|
||||
& (ldc.valid_upto >= self.posting_date)
|
||||
& (ldc.company == self.company)
|
||||
& ldc.tax_withholding_category.isin(self.tax_withholding_categories)
|
||||
)
|
||||
)
|
||||
|
||||
return vouchers, voucher_wise_amount
|
||||
query = query.where(ldc.pan_no == tax_id) if tax_id else query.where(ldc.supplier == self.party)
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
||||
def get_payment_entry_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
payment_entry_filters = {
|
||||
"party_type": party_type,
|
||||
"party": ("in", parties),
|
||||
"docstatus": 1,
|
||||
"apply_tax_withholding_amount": 1,
|
||||
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
|
||||
"tax_withholding_category": tax_details.get("tax_withholding_category"),
|
||||
"company": company,
|
||||
}
|
||||
|
||||
return frappe.db.get_all("Payment Entry", filters=payment_entry_filters, pluck="name")
|
||||
|
||||
|
||||
def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type="Supplier"):
|
||||
"""
|
||||
Use Payment Ledger to fetch unallocated Advance Payments
|
||||
"""
|
||||
|
||||
if party_type == "Supplier":
|
||||
return []
|
||||
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
conditions = []
|
||||
|
||||
conditions.append(ple.amount.lt(0))
|
||||
conditions.append(ple.delinked == 0)
|
||||
conditions.append(ple.party_type == party_type)
|
||||
conditions.append(ple.party.isin(parties))
|
||||
conditions.append(ple.voucher_no == ple.against_voucher_no)
|
||||
|
||||
if company:
|
||||
conditions.append(ple.company == company)
|
||||
|
||||
if from_date and to_date:
|
||||
conditions.append(ple.posting_date[from_date:to_date])
|
||||
|
||||
advances = qb.from_(ple).select(ple.voucher_no).distinct().where(Criterion.all(conditions)).run(as_list=1)
|
||||
if advances:
|
||||
advances = [x[0] for x in advances]
|
||||
|
||||
return advances
|
||||
|
||||
|
||||
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
|
||||
tax_info = []
|
||||
|
||||
if inv.get("advances"):
|
||||
advances = [d.reference_name for d in inv.get("advances")]
|
||||
|
||||
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(pe.posting_date, 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
|
||||
|
||||
|
||||
def get_deducted_tax(taxable_vouchers, tax_details):
|
||||
# check if TDS / TCS account is already charged on taxable vouchers
|
||||
filters = {
|
||||
"is_cancelled": 0,
|
||||
"credit": [">", 0],
|
||||
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
|
||||
"account": tax_details.account_head,
|
||||
"voucher_no": ["in", taxable_vouchers],
|
||||
}
|
||||
field = "credit"
|
||||
|
||||
entries = frappe.db.get_all("GL Entry", filters, pluck=field)
|
||||
return sum(entries)
|
||||
|
||||
|
||||
def get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details):
|
||||
"""
|
||||
Only applies for Taxes deducted on Advance Payments
|
||||
"""
|
||||
advance_tax_from_across_fiscal_year = sum(
|
||||
[adv.tax_amount for adv in tax_deducted_on_advances if adv.posting_date < tax_details.from_date]
|
||||
)
|
||||
return advance_tax_from_across_fiscal_year
|
||||
|
||||
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, voucher_wise_amount):
|
||||
tds_amount = 0
|
||||
|
||||
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
|
||||
pe_filters = {
|
||||
"party_type": "Supplier",
|
||||
"party": ("in", parties),
|
||||
"docstatus": 1,
|
||||
"apply_tax_withholding_amount": 1,
|
||||
"unallocated_amount": (">", 0),
|
||||
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
|
||||
"tax_withholding_category": tax_details.get("tax_withholding_category"),
|
||||
"company": inv.company,
|
||||
}
|
||||
|
||||
consider_party_ledger_amt = cint(tax_details.consider_party_ledger_amount)
|
||||
|
||||
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_entries = frappe.db.get_all(
|
||||
"Payment Entry",
|
||||
filters=pe_filters,
|
||||
fields=["name", "unallocated_amount as taxable_amount", "payment_type"],
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
def get_ldc_utilization_by_category(self, ldc_names, tax_id):
|
||||
twe = frappe.qb.DocType("Tax Withholding Entry")
|
||||
query = (
|
||||
frappe.qb.from_(twe)
|
||||
.select(twe.lower_deduction_certificate, Sum(twe.taxable_amount).as_("limit_consumed"))
|
||||
.where(
|
||||
(twe.company == self.company)
|
||||
& (twe.party_type == self.party_type)
|
||||
& (twe.tax_withholding_category.isin(self.tax_withholding_categories))
|
||||
& (twe.lower_deduction_certificate.isin(ldc_names))
|
||||
& (twe.docstatus == 1)
|
||||
& (twe.status.isin(["Settled", "Over Withheld"]))
|
||||
)
|
||||
.groupby(twe.lower_deduction_certificate)
|
||||
)
|
||||
|
||||
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)
|
||||
query = query.where(twe.tax_id == tax_id) if tax_id else query.where(twe.party == self.party)
|
||||
|
||||
# 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
|
||||
|
||||
cumulative_threshold_breached = (
|
||||
cumulative_threshold and (supp_credit_amt + amt_for_threshold) >= cumulative_threshold
|
||||
)
|
||||
|
||||
if (threshold and tax_withholding_net_total >= threshold) or (cumulative_threshold_breached):
|
||||
supp_credit_amt += pi_base_net_total
|
||||
|
||||
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(
|
||||
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
|
||||
|
||||
return tds_amount
|
||||
return frappe._dict(query.run())
|
||||
|
||||
|
||||
<<<<<<< HEAD
|
||||
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
||||
tcs_amount = 0
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
@@ -774,3 +655,8 @@ def normal_round(number):
|
||||
number = int(number) + decimal_part
|
||||
|
||||
return number
|
||||
=======
|
||||
@allow_regional
|
||||
def get_tax_id_for_party(party_type, party):
|
||||
return None
|
||||
>>>>>>> c66f78c784 (feat: Introduce tax withholding entry)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,237 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-06-20 04:55:28.583171",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"section_break_krko",
|
||||
"company",
|
||||
"party_type",
|
||||
"party",
|
||||
"tax_id",
|
||||
"column_break_egzm",
|
||||
"tax_withholding_category",
|
||||
"tax_withholding_group",
|
||||
"taxable_amount",
|
||||
"tax_rate",
|
||||
"withholding_amount",
|
||||
"target_section",
|
||||
"taxable_doctype",
|
||||
"taxable_name",
|
||||
"taxable_date",
|
||||
"currency",
|
||||
"conversion_rate",
|
||||
"column_break_fqoe",
|
||||
"under_withheld_reason",
|
||||
"lower_deduction_certificate",
|
||||
"source_section",
|
||||
"withholding_doctype",
|
||||
"withholding_name",
|
||||
"withholding_date",
|
||||
"column_break_dahw",
|
||||
"section_break_ggna",
|
||||
"status",
|
||||
"column_break_jfjf",
|
||||
"created_by_migration"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "section_break_krko",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Party Type",
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Party",
|
||||
"options": "party_type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tax ID",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Tax Withholding Category",
|
||||
"options": "Tax Withholding Category",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_egzm",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"fieldname": "tax_rate",
|
||||
"fieldtype": "Percent",
|
||||
"in_list_view": 1,
|
||||
"label": "Tax Rate"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"fieldname": "taxable_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Base Taxable Amount",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"description": "Transaction from which tax is withheld",
|
||||
"fieldname": "source_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Deducted From"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_dahw",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Transaction for which tax is withheld",
|
||||
"fieldname": "target_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Applicable For"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fqoe",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ggna",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_jfjf",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "lower_deduction_certificate",
|
||||
"fieldtype": "Link",
|
||||
"label": "Lower Deduction Certificate",
|
||||
"options": "Lower Deduction Certificate",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "\nSettled\nUnder Withheld\nOver Withheld\nDuplicate\nCancelled",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "conversion_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Exchange Rate",
|
||||
"precision": "9",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "withholding_doctype",
|
||||
"fieldtype": "Link",
|
||||
"label": "Withholding Document Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "withholding_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Withholding Document Name",
|
||||
"options": "withholding_doctype"
|
||||
},
|
||||
{
|
||||
"fieldname": "taxable_doctype",
|
||||
"fieldtype": "Link",
|
||||
"label": "Taxable Document Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "taxable_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Taxable Document Name",
|
||||
"options": "taxable_doctype"
|
||||
},
|
||||
{
|
||||
"fieldname": "taxable_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Taxable Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "withholding_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Withholding Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "under_withheld_reason",
|
||||
"fieldtype": "Select",
|
||||
"label": "Under Withheld Reason",
|
||||
"options": "\nThreshold Exemption\nLower Deduction Certificate",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"fieldname": "withholding_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Base Tax Withheld",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"fieldname": "tax_withholding_group",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Tax Withholding Group",
|
||||
"options": "Tax Withholding Group",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "created_by_migration",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Created By Migration",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-22 09:07:26.701207",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Entry",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestTaxWithholdingEntry(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for TaxWithholdingEntry.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Tax Withholding Group", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:group_name",
|
||||
"creation": "2025-06-29 05:24:51.819891",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"group_name"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "group_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Group Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-06-29 05:25:50.243710",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Group",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class TaxWithheldVouchers(Document):
|
||||
class TaxWithholdingGroup(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
@@ -14,12 +14,7 @@ class TaxWithheldVouchers(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
taxable_amount: DF.Currency
|
||||
voucher_name: DF.Data | None
|
||||
voucher_type: DF.Data | None
|
||||
group_name: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
# Use these module variables to add/remove to/from that list
|
||||
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
|
||||
|
||||
|
||||
class IntegrationTestTaxWithholdingGroup(IntegrationTestCase):
|
||||
"""
|
||||
Integration tests for TaxWithholdingGroup.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -7,10 +7,11 @@
|
||||
"field_order": [
|
||||
"from_date",
|
||||
"to_date",
|
||||
"tax_withholding_rate",
|
||||
"tax_withholding_group",
|
||||
"column_break_3",
|
||||
"single_threshold",
|
||||
"cumulative_threshold"
|
||||
"tax_withholding_rate",
|
||||
"cumulative_threshold",
|
||||
"single_threshold"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -30,14 +31,14 @@
|
||||
"fieldname": "single_threshold",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Single Transaction Threshold"
|
||||
"label": "Transaction Threshold"
|
||||
},
|
||||
{
|
||||
"columns": 3,
|
||||
"fieldname": "cumulative_threshold",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Cumulative Transaction Threshold"
|
||||
"label": "Cumulative Threshold"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
@@ -54,20 +55,28 @@
|
||||
"in_list_view": 1,
|
||||
"label": "To Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_group",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Tax Withholding Group",
|
||||
"options": "Tax Withholding Group"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:52.708165",
|
||||
"modified": "2025-06-29 05:31:05.120377",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Rate",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class TaxWithholdingRate(Document):
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
single_threshold: DF.Float
|
||||
tax_withholding_group: DF.Link | None
|
||||
tax_withholding_rate: DF.Float
|
||||
to_date: DF.Date
|
||||
# end: auto-generated types
|
||||
|
||||
@@ -176,10 +176,6 @@ def _get_party_details(
|
||||
for d in party.get("sales_team")
|
||||
]
|
||||
|
||||
# supplier tax withholding category
|
||||
if party_type == "Supplier" and party:
|
||||
party_details["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category")
|
||||
|
||||
if not party_details.get("tax_category") and pos_profile:
|
||||
party_details["tax_category"] = frappe.get_value("POS Profile", pos_profile, "tax_category")
|
||||
|
||||
@@ -352,10 +348,13 @@ def set_contact_details(party_details, party, party_type):
|
||||
|
||||
def set_other_values(party_details, party, party_type):
|
||||
# copy
|
||||
to_copy = ["tax_withholding_category", "tax_withholding_group", "language"]
|
||||
|
||||
if party_type == "Customer":
|
||||
to_copy = ["customer_name", "customer_group", "territory", "language"]
|
||||
to_copy.extend(["customer_name", "customer_group", "territory"])
|
||||
else:
|
||||
to_copy = ["supplier_name", "supplier_group", "language"]
|
||||
to_copy.extend(["supplier_name", "supplier_group"])
|
||||
|
||||
for f in to_copy:
|
||||
party_details[f] = party.get(f)
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@ frappe.query_reports["Tax Withholding Details"] = {
|
||||
fieldname: "party_type",
|
||||
label: __("Party Type"),
|
||||
fieldtype: "Select",
|
||||
options: ["Supplier", "Customer"],
|
||||
reqd: 1,
|
||||
default: "Supplier",
|
||||
options: ["", "Supplier", "Customer"],
|
||||
default: "",
|
||||
on_change: function () {
|
||||
frappe.query_report.set_filter_value("party", "");
|
||||
},
|
||||
|
||||
@@ -1,194 +1,112 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, getdate
|
||||
|
||||
from erpnext.accounts.utils import get_currency_precision
|
||||
from frappe.query_builder.functions import IfNull
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if filters.get("party_type") == "Customer":
|
||||
party_naming_by = frappe.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"] = party_naming_by
|
||||
|
||||
"""Generate Tax Withholding Details report"""
|
||||
validate_filters(filters)
|
||||
(
|
||||
tds_docs,
|
||||
tds_accounts,
|
||||
tax_category_map,
|
||||
journal_entry_party_map,
|
||||
net_total_map,
|
||||
) = get_tds_docs(filters)
|
||||
|
||||
# Process and format data
|
||||
data = get_tax_withholding_data(filters)
|
||||
columns = get_columns(filters)
|
||||
|
||||
res = get_result(
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, net_total_map
|
||||
)
|
||||
return columns, res
|
||||
return columns, data
|
||||
|
||||
|
||||
def validate_filters(filters):
|
||||
"""Validate if dates are properly set"""
|
||||
"""Validate report filters"""
|
||||
filters = frappe._dict(filters or {})
|
||||
|
||||
if not filters.from_date or not filters.to_date:
|
||||
frappe.throw(_("From Date and To Date are required"))
|
||||
|
||||
if filters.from_date > filters.to_date:
|
||||
frappe.throw(_("From Date must be before To Date"))
|
||||
|
||||
|
||||
def get_result(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)
|
||||
gle_map = get_gle_map(tds_docs)
|
||||
precision = get_currency_precision()
|
||||
def get_tax_withholding_data(filters):
|
||||
"""Process entries into final report format"""
|
||||
data = []
|
||||
entries = get_tax_withholding_entries(filters)
|
||||
if not entries:
|
||||
return data
|
||||
|
||||
out = []
|
||||
entries = {}
|
||||
for name, details in gle_map.items():
|
||||
for entry in details:
|
||||
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
|
||||
tax_withholding_category, rate = None, None
|
||||
bill_no, bill_date = "", ""
|
||||
party = entry.party or entry.against
|
||||
posting_date = entry.posting_date
|
||||
voucher_type = entry.voucher_type
|
||||
doc_info = get_additional_doc_info(entries)
|
||||
party_details = get_party_details(entries)
|
||||
|
||||
if voucher_type == "Journal Entry":
|
||||
party_list = journal_entry_party_map.get(name)
|
||||
if party_list:
|
||||
party = party_list[0]
|
||||
for entry in entries:
|
||||
doc_details = frappe._dict()
|
||||
if entry.taxable_name:
|
||||
doc_details = doc_info.get((entry.taxable_doctype, entry.taxable_name), {})
|
||||
|
||||
if entry.account in tds_accounts.keys():
|
||||
tax_amount += entry.credit - entry.debit
|
||||
# infer tax withholding category from the account if it's the single account for this category
|
||||
tax_withholding_category = tds_accounts.get(entry.account)
|
||||
# or else the consolidated value from the voucher document
|
||||
if not tax_withholding_category:
|
||||
tax_withholding_category = tax_category_map.get((voucher_type, name))
|
||||
# or else from the party default
|
||||
if not tax_withholding_category:
|
||||
tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
|
||||
party_info = party_details.get((entry.party_type, entry.party), {})
|
||||
|
||||
rate = get_tax_withholding_rates(tax_rate_map.get(tax_withholding_category, []), posting_date)
|
||||
row = {
|
||||
"section_code": entry.tax_withholding_category,
|
||||
"entity_type": party_info.get("entity_type"),
|
||||
"rate": entry.tax_rate,
|
||||
"total_amount": entry.taxable_amount,
|
||||
"grand_total": doc_details.get("grand_total", 0),
|
||||
"base_total": doc_details.get("base_total", 0),
|
||||
"tax_amount": entry.withholding_amount,
|
||||
"transaction_date": entry.withholding_date,
|
||||
"transaction_type": entry.taxable_doctype,
|
||||
"ref_no": entry.taxable_name,
|
||||
"taxable_date": entry.taxable_date,
|
||||
"supplier_invoice_no": doc_details.get("bill_no"),
|
||||
"supplier_invoice_date": doc_details.get("bill_date"),
|
||||
"withholding_doctype": entry.withholding_doctype,
|
||||
"withholding_name": entry.withholding_name,
|
||||
"party_name": party_info.get("party_name"),
|
||||
"tax_id": entry.tax_id,
|
||||
"party": entry.party,
|
||||
"party_type": entry.party_type,
|
||||
}
|
||||
data.append(row)
|
||||
|
||||
values = net_total_map.get((voucher_type, name))
|
||||
|
||||
if values:
|
||||
if voucher_type == "Journal Entry" and tax_amount and rate:
|
||||
# back calculate total amount from rate and tax_amount
|
||||
base_total = min(flt(tax_amount / (rate / 100), precision=precision), values[0])
|
||||
total_amount = grand_total = base_total
|
||||
|
||||
else:
|
||||
if tax_amount and rate:
|
||||
# back calculate total amount from rate and tax_amount
|
||||
total_amount = flt((tax_amount * 100) / rate, precision=precision)
|
||||
else:
|
||||
total_amount = values[0]
|
||||
|
||||
grand_total = values[1]
|
||||
base_total = values[2]
|
||||
|
||||
if voucher_type == "Purchase Invoice":
|
||||
bill_no = values[3]
|
||||
bill_date = values[4]
|
||||
else:
|
||||
total_amount += entry.credit
|
||||
|
||||
if tax_amount:
|
||||
if party_map.get(party, {}).get("party_type") == "Supplier":
|
||||
party_name = "supplier_name"
|
||||
party_type = "supplier_type"
|
||||
else:
|
||||
party_name = "customer_name"
|
||||
party_type = "customer_type"
|
||||
|
||||
row = {
|
||||
"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"),
|
||||
}
|
||||
|
||||
if filters.naming_series == "Naming Series":
|
||||
row["party_name"] = party_map.get(party, {}).get(party_name)
|
||||
|
||||
row.update(
|
||||
{
|
||||
"section_code": tax_withholding_category or "",
|
||||
"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,
|
||||
"ref_no": name,
|
||||
"supplier_invoice_no": bill_no,
|
||||
"supplier_invoice_date": bill_date,
|
||||
}
|
||||
)
|
||||
|
||||
key = entry.voucher_no
|
||||
if key in entries:
|
||||
entries[key]["tax_amount"] += tax_amount
|
||||
else:
|
||||
entries[key] = row
|
||||
out = list(entries.values())
|
||||
out.sort(key=lambda x: (x["section_code"], x["transaction_date"]))
|
||||
|
||||
return out
|
||||
# Sort by section code and transaction date
|
||||
data.sort(key=lambda x: (x["section_code"] or "", x["transaction_date"] or ""))
|
||||
return data
|
||||
|
||||
|
||||
def get_party_pan_map(party_type):
|
||||
def get_party_details(entries):
|
||||
"""Fetch party details in batch for all entries"""
|
||||
party_map = frappe._dict()
|
||||
parties_by_type = {"Customer": set(), "Supplier": set()}
|
||||
|
||||
fields = ["name", "tax_withholding_category"]
|
||||
if party_type == "Supplier":
|
||||
fields += ["supplier_type", "supplier_name"]
|
||||
else:
|
||||
fields += ["customer_type", "customer_name"]
|
||||
# Group parties by type
|
||||
for entry in entries:
|
||||
if entry.party_type in parties_by_type and entry.party:
|
||||
parties_by_type[entry.party_type].add(entry.party)
|
||||
|
||||
if frappe.db.has_column(party_type, "pan"):
|
||||
fields.append("pan")
|
||||
# Batch fetch for each party type
|
||||
for party_type, party_set in parties_by_type.items():
|
||||
if not party_type or not party_set:
|
||||
continue
|
||||
|
||||
party_details = frappe.db.get_all(party_type, fields=fields)
|
||||
doctype = frappe.qb.DocType(party_type)
|
||||
fields = [doctype.name]
|
||||
|
||||
for party in party_details:
|
||||
party.party_type = party_type
|
||||
party_map[party.name] = party
|
||||
if party_type == "Supplier":
|
||||
fields.extend([doctype.supplier_type.as_("entity_type"), doctype.supplier_name.as_("party_name")])
|
||||
elif party_type == "Customer":
|
||||
fields.extend([doctype.customer_type.as_("entity_type"), doctype.customer_name.as_("party_name")])
|
||||
|
||||
query = frappe.qb.from_(doctype).select(*fields).where(doctype.name.isin(party_set))
|
||||
party_details = query.run(as_dict=True)
|
||||
|
||||
for party in party_details:
|
||||
party_map[(party_type, party.name)] = party
|
||||
|
||||
return party_map
|
||||
|
||||
|
||||
def get_gle_map(documents):
|
||||
# create gle_map of the form
|
||||
# {"purchase_invoice": list of dict of all gle created for this invoice}
|
||||
gle_map = {}
|
||||
|
||||
gle = frappe.db.get_all(
|
||||
"GL Entry",
|
||||
{"voucher_no": ["in", documents], "is_cancelled": 0},
|
||||
["credit", "debit", "account", "voucher_no", "posting_date", "voucher_type", "against", "party"],
|
||||
)
|
||||
|
||||
for d in gle:
|
||||
if d.voucher_no not in gle_map:
|
||||
gle_map[d.voucher_no] = [d]
|
||||
else:
|
||||
gle_map[d.voucher_no].append(d)
|
||||
|
||||
return gle_map
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
pan = "pan" if frappe.db.has_column(filters.party_type, "pan") else "tax_id"
|
||||
"""Generate report columns based on filters"""
|
||||
columns = [
|
||||
{
|
||||
"label": _("Section Code"),
|
||||
@@ -197,286 +115,190 @@ def get_columns(filters):
|
||||
"fieldtype": "Link",
|
||||
"width": 90,
|
||||
},
|
||||
{"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 60},
|
||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 60},
|
||||
{
|
||||
"label": _(f"{filters.get('party_type', 'Party')} Name"),
|
||||
"fieldname": "party_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _(filters.get("party_type", "Party")),
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("Entity Type"),
|
||||
"fieldname": "entity_type",
|
||||
"fieldtype": "Data",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Supplier Invoice No"),
|
||||
"fieldname": "supplier_invoice_no",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Supplier Invoice Date"),
|
||||
"fieldname": "supplier_invoice_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Tax Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 60,
|
||||
},
|
||||
{
|
||||
"label": _("Total Amount"),
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Base Total"),
|
||||
"fieldname": "base_total",
|
||||
"fieldtype": "Currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Tax Amount"),
|
||||
"fieldname": "tax_amount",
|
||||
"fieldtype": "Currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Reference Date"),
|
||||
"fieldname": "taxable_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Transaction Type"),
|
||||
"fieldname": "transaction_type",
|
||||
"fieldtype": "Data",
|
||||
"width": 130,
|
||||
},
|
||||
{
|
||||
"label": _("Reference No."),
|
||||
"fieldname": "ref_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "transaction_type",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("Date of Transaction"),
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Withholding Document"),
|
||||
"fieldname": "withholding_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "withholding_doctype",
|
||||
"width": 150,
|
||||
},
|
||||
]
|
||||
|
||||
if filters.naming_series == "Naming Series":
|
||||
columns.append(
|
||||
{
|
||||
"label": _(filters.party_type + " Name"),
|
||||
"fieldname": "party_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
else:
|
||||
columns.append(
|
||||
{
|
||||
"label": _(filters.get("party_type")),
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 100},
|
||||
]
|
||||
)
|
||||
if filters.party_type == "Supplier":
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("Supplier Invoice No"),
|
||||
"fieldname": "supplier_invoice_no",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Supplier Invoice Date"),
|
||||
"fieldname": "supplier_invoice_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 60,
|
||||
},
|
||||
{
|
||||
"label": _("Total Amount"),
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Base Total"),
|
||||
"fieldname": "base_total",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("TDS Amount") if filters.get("party_type") == "Supplier" else _("TCS Amount"),
|
||||
"fieldname": "tax_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 130},
|
||||
{
|
||||
"label": _("Reference No."),
|
||||
"fieldname": "ref_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "transaction_type",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("Date of Transaction"),
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"width": 100,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
return columns
|
||||
|
||||
|
||||
def get_tds_docs(filters):
|
||||
tds_documents = []
|
||||
purchase_invoices = []
|
||||
sales_invoices = []
|
||||
payment_entries = []
|
||||
journal_entries = []
|
||||
tax_category_map = frappe._dict()
|
||||
net_total_map = frappe._dict()
|
||||
journal_entry_party_map = frappe._dict()
|
||||
bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
|
||||
|
||||
_tds_accounts = frappe.get_all(
|
||||
"Tax Withholding Account",
|
||||
{"company": filters.get("company")},
|
||||
["account", "parent"],
|
||||
)
|
||||
tds_accounts = {}
|
||||
for tds_acc in _tds_accounts:
|
||||
# if it turns out not to be the only tax withholding category, then don't include in the map
|
||||
if tds_acc["account"] in tds_accounts:
|
||||
tds_accounts[tds_acc["account"]] = None
|
||||
else:
|
||||
tds_accounts[tds_acc["account"]] = tds_acc["parent"]
|
||||
|
||||
tds_docs = get_tds_docs_query(filters, bank_accounts, list(tds_accounts.keys())).run(as_dict=True)
|
||||
|
||||
for d in tds_docs:
|
||||
if d.voucher_type == "Purchase Invoice":
|
||||
purchase_invoices.append(d.voucher_no)
|
||||
if d.voucher_type == "Sales Invoice":
|
||||
sales_invoices.append(d.voucher_no)
|
||||
elif d.voucher_type == "Payment Entry":
|
||||
payment_entries.append(d.voucher_no)
|
||||
elif d.voucher_type == "Journal Entry":
|
||||
journal_entries.append(d.voucher_no)
|
||||
|
||||
tds_documents.append(d.voucher_no)
|
||||
|
||||
if purchase_invoices:
|
||||
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, net_total_map)
|
||||
|
||||
if payment_entries:
|
||||
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)
|
||||
get_doc_info(journal_entries, "Journal Entry", tax_category_map, net_total_map)
|
||||
|
||||
return (
|
||||
tds_documents,
|
||||
tds_accounts,
|
||||
tax_category_map,
|
||||
journal_entry_party_map,
|
||||
net_total_map,
|
||||
)
|
||||
|
||||
|
||||
def get_tds_docs_query(filters, bank_accounts, tds_accounts):
|
||||
if not tds_accounts:
|
||||
frappe.throw(
|
||||
_("No {0} Accounts found for this company.").format(frappe.bold(_("Tax Withholding"))),
|
||||
title=_("Accounts Missing Error"),
|
||||
)
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
def get_tax_withholding_entries(filters):
|
||||
twe = frappe.qb.DocType("Tax Withholding Entry")
|
||||
query = (
|
||||
frappe.qb.from_(gle)
|
||||
.select("voucher_no", "voucher_type", "against", "party")
|
||||
.where(gle.is_cancelled == 0)
|
||||
frappe.qb.from_(twe)
|
||||
.select(
|
||||
twe.company,
|
||||
twe.party_type,
|
||||
twe.party,
|
||||
IfNull(twe.tax_id, "").as_("tax_id"),
|
||||
twe.tax_withholding_category,
|
||||
IfNull(twe.tax_withholding_group, "").as_("tax_withholding_group"),
|
||||
twe.taxable_amount,
|
||||
twe.tax_rate,
|
||||
twe.withholding_amount,
|
||||
IfNull(twe.taxable_doctype, "").as_("taxable_doctype"),
|
||||
IfNull(twe.taxable_name, "").as_("taxable_name"),
|
||||
twe.taxable_date,
|
||||
IfNull(twe.under_withheld_reason, "").as_("under_withheld_reason"),
|
||||
IfNull(twe.lower_deduction_certificate, "").as_("lower_deduction_certificate"),
|
||||
IfNull(twe.withholding_doctype, "").as_("withholding_doctype"),
|
||||
IfNull(twe.withholding_name, "").as_("withholding_name"),
|
||||
twe.withholding_date,
|
||||
twe.status,
|
||||
)
|
||||
.where(twe.docstatus == 1)
|
||||
.where(twe.withholding_date >= filters.from_date)
|
||||
.where(twe.withholding_date <= filters.to_date)
|
||||
.where(IfNull(twe.withholding_name, "") != "")
|
||||
.where(twe.status != "Duplicate")
|
||||
)
|
||||
|
||||
if filters.get("from_date"):
|
||||
query = query.where(gle.posting_date >= filters.get("from_date"))
|
||||
if filters.get("to_date"):
|
||||
query = query.where(gle.posting_date <= filters.get("to_date"))
|
||||
if filters.get("company"):
|
||||
query = query.where(twe.company == filters.get("company"))
|
||||
|
||||
if filters.get("party_type"):
|
||||
query = query.where(twe.party_type == filters.get("party_type"))
|
||||
|
||||
if filters.get("party"):
|
||||
party = [filters.get("party")]
|
||||
jv_condition = gle.against.isin(party) | (
|
||||
(gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party"))
|
||||
)
|
||||
else:
|
||||
party = frappe.get_all(filters.get("party_type"), pluck="name")
|
||||
jv_condition = gle.against.isin(party) | (
|
||||
(gle.voucher_type == "Journal Entry")
|
||||
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
|
||||
)
|
||||
query = query.where(twe.party == filters.get("party"))
|
||||
|
||||
query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party))
|
||||
if bank_accounts:
|
||||
query = query.where(
|
||||
gle.against.notin(bank_accounts) & (gle.account.isin(tds_accounts) & jv_condition)
|
||||
| gle.party.isin(party)
|
||||
)
|
||||
|
||||
return query
|
||||
return query.run(as_dict=True)
|
||||
|
||||
|
||||
def get_journal_entry_party_map(journal_entries):
|
||||
journal_entry_party_map = {}
|
||||
for d in frappe.db.get_all(
|
||||
"Journal Entry Account",
|
||||
{
|
||||
"parent": ("in", journal_entries),
|
||||
"party_type": ("in", ("Supplier", "Customer")),
|
||||
"party": ("is", "set"),
|
||||
},
|
||||
["parent", "party"],
|
||||
):
|
||||
if d.parent not in journal_entry_party_map:
|
||||
journal_entry_party_map[d.parent] = []
|
||||
journal_entry_party_map[d.parent].append(d.party)
|
||||
|
||||
return journal_entry_party_map
|
||||
|
||||
|
||||
def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
|
||||
common_fields = ["name"]
|
||||
fields_dict = {
|
||||
"Purchase Invoice": [
|
||||
"tax_withholding_category",
|
||||
"base_tax_withholding_net_total",
|
||||
"grand_total",
|
||||
"base_total",
|
||||
"bill_no",
|
||||
"bill_date",
|
||||
],
|
||||
"Sales Invoice": ["base_net_total", "grand_total", "base_total"],
|
||||
"Payment Entry": [
|
||||
"tax_withholding_category",
|
||||
"paid_amount",
|
||||
"paid_amount_after_tax",
|
||||
"base_paid_amount",
|
||||
],
|
||||
"Journal Entry": ["tax_withholding_category", "total_debit"],
|
||||
def get_additional_doc_info(entries):
|
||||
"""Fetch additional document information in batch"""
|
||||
doc_info = {}
|
||||
docs_by_type = {
|
||||
"Purchase Invoice": set(),
|
||||
"Sales Invoice": set(),
|
||||
"Payment Entry": set(),
|
||||
"Journal Entry": set(),
|
||||
}
|
||||
|
||||
entries = frappe.get_all(
|
||||
doctype, filters={"name": ("in", vouchers)}, fields=common_fields + fields_dict[doctype]
|
||||
)
|
||||
# Group documents by type
|
||||
for entry in entries:
|
||||
if entry.taxable_name and entry.taxable_doctype in docs_by_type:
|
||||
docs_by_type[entry.taxable_doctype].add(entry.taxable_name)
|
||||
|
||||
for doctype_name, voucher_set in docs_by_type.items():
|
||||
if voucher_set:
|
||||
_fetch_doc_info(doctype_name, voucher_set, doc_info)
|
||||
|
||||
return doc_info
|
||||
|
||||
|
||||
def _fetch_doc_info(doctype_name, voucher_set, doc_info):
|
||||
doctype = frappe.qb.DocType(doctype_name)
|
||||
fields = [doctype.name]
|
||||
|
||||
# Add doctype-specific fields
|
||||
if doctype_name == "Purchase Invoice":
|
||||
fields.extend([doctype.grand_total, doctype.base_total, doctype.bill_no, doctype.bill_date])
|
||||
elif doctype_name == "Sales Invoice":
|
||||
fields.extend([doctype.grand_total, doctype.base_total])
|
||||
elif doctype_name == "Payment Entry":
|
||||
fields.extend(
|
||||
[doctype.paid_amount_after_tax.as_("grand_total"), doctype.base_paid_amount.as_("base_total")]
|
||||
)
|
||||
elif doctype_name == "Journal Entry":
|
||||
fields.extend([doctype.total_debit.as_("grand_total"), doctype.total_debit.as_("base_total")])
|
||||
else:
|
||||
return
|
||||
|
||||
query = frappe.qb.from_(doctype).select(*fields).where(doctype.name.isin(voucher_set))
|
||||
entries = query.run(as_dict=True)
|
||||
|
||||
for entry in entries:
|
||||
tax_category_map[(doctype, entry.name)] = entry.tax_withholding_category
|
||||
if doctype == "Purchase Invoice":
|
||||
value = [
|
||||
entry.base_tax_withholding_net_total,
|
||||
entry.grand_total,
|
||||
entry.base_total,
|
||||
entry.bill_no,
|
||||
entry.bill_date,
|
||||
]
|
||||
elif doctype == "Sales Invoice":
|
||||
value = [entry.base_net_total, entry.grand_total, entry.base_total]
|
||||
elif doctype == "Payment Entry":
|
||||
value = [entry.paid_amount, entry.paid_amount_after_tax, entry.base_paid_amount]
|
||||
else:
|
||||
value = [entry.total_debit] * 3
|
||||
|
||||
net_total_map[(doctype, entry.name)] = value
|
||||
|
||||
|
||||
def get_tax_rate_map(filters):
|
||||
rate_map = frappe.get_all(
|
||||
"Tax Withholding Rate",
|
||||
filters={"from_date": ("<=", filters.to_date), "to_date": (">=", filters.from_date)},
|
||||
fields=["parent", "tax_withholding_rate", "from_date", "to_date"],
|
||||
)
|
||||
|
||||
rate_list = frappe._dict()
|
||||
|
||||
for rate in rate_map:
|
||||
rate_list.setdefault(rate.parent, []).append(frappe._dict(rate))
|
||||
|
||||
return rate_list
|
||||
|
||||
|
||||
def get_tax_withholding_rates(tax_withholding, posting_date):
|
||||
# returns the row that matches with the fiscal year from posting date
|
||||
for rate in tax_withholding:
|
||||
if getdate(rate.from_date) <= getdate(posting_date) <= getdate(rate.to_date):
|
||||
return rate.tax_withholding_rate
|
||||
|
||||
return 0
|
||||
doc_info[(doctype_name, entry.name)] = entry
|
||||
|
||||
@@ -2,35 +2,18 @@ import frappe
|
||||
from frappe import _
|
||||
|
||||
from erpnext.accounts.report.tax_withholding_details.tax_withholding_details import (
|
||||
get_result,
|
||||
get_tds_docs,
|
||||
get_tax_withholding_data,
|
||||
)
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if filters.get("party_type") == "Customer":
|
||||
party_naming_by = frappe.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)
|
||||
|
||||
data = get_tax_withholding_data(filters)
|
||||
columns = get_columns(filters)
|
||||
(
|
||||
tds_docs,
|
||||
tds_accounts,
|
||||
tax_category_map,
|
||||
journal_entry_party_map,
|
||||
invoice_total_map,
|
||||
) = get_tds_docs(filters)
|
||||
|
||||
res = get_result(
|
||||
filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map, invoice_total_map
|
||||
)
|
||||
final_result = group_by_party_and_category(res, filters)
|
||||
final_result = group_by_party_and_category(data, filters)
|
||||
|
||||
return columns, final_result
|
||||
|
||||
@@ -55,7 +38,6 @@ def group_by_party_and_category(data, filters):
|
||||
party_category_wise_map.setdefault(
|
||||
(row.get("party"), row.get("section_code")),
|
||||
{
|
||||
"pan": row.get("pan"),
|
||||
"tax_id": row.get("tax_id"),
|
||||
"party": row.get("party"),
|
||||
"party_name": row.get("party_name"),
|
||||
@@ -89,9 +71,8 @@ def get_final_result(party_category_wise_map):
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
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": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 90},
|
||||
{
|
||||
"label": _(filters.get("party_type")),
|
||||
"fieldname": "party",
|
||||
@@ -99,47 +80,43 @@ def get_columns(filters):
|
||||
"options": "party_type",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _(f"{filters.get('party_type', 'Party')} Name"),
|
||||
"fieldname": "party_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("Section Code"),
|
||||
"options": "Tax Withholding Category",
|
||||
"fieldname": "section_code",
|
||||
"fieldtype": "Link",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("Entity Type"),
|
||||
"fieldname": "entity_type",
|
||||
"fieldtype": "Data",
|
||||
"width": 180,
|
||||
},
|
||||
{
|
||||
"label": _("Tax Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Total Amount"),
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Tax Amount"),
|
||||
"fieldname": "tax_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
|
||||
if filters.naming_series == "Naming Series":
|
||||
columns.append(
|
||||
{
|
||||
"label": _(filters.party_type + " Name"),
|
||||
"fieldname": "party_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("Section Code"),
|
||||
"options": "Tax Withholding Category",
|
||||
"fieldname": "section_code",
|
||||
"fieldtype": "Link",
|
||||
"width": 180,
|
||||
},
|
||||
{"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180},
|
||||
{
|
||||
"label": _("TDS Rate %") if filters.get("party_type") == "Supplier" else _("TCS Rate %"),
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Percent",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Total Amount"),
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Tax Amount"),
|
||||
"fieldname": "tax_amount",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
return columns
|
||||
|
||||
@@ -90,31 +90,6 @@ frappe.ui.form.on("Purchase Order", {
|
||||
prevent_past_schedule_dates(frm);
|
||||
},
|
||||
|
||||
supplier: function (frm) {
|
||||
// Do not update if inter company reference is there as the details will already be updated
|
||||
if (frm.updating_party_details || frm.doc.inter_company_invoice_reference) return;
|
||||
|
||||
if (frm.doc.__onload && frm.doc.__onload.load_after_mapping) return;
|
||||
|
||||
erpnext.utils.get_party_details(
|
||||
frm,
|
||||
"erpnext.accounts.party.get_party_details",
|
||||
{
|
||||
posting_date: frm.doc.transaction_date,
|
||||
bill_date: frm.doc.bill_date,
|
||||
party: frm.doc.supplier,
|
||||
party_type: "Supplier",
|
||||
account: frm.doc.credit_to,
|
||||
price_list: frm.doc.buying_price_list,
|
||||
fetch_payment_terms_template: cint(!frm.doc.ignore_default_payment_terms_template),
|
||||
},
|
||||
function () {
|
||||
frm.set_df_property("apply_tds", "read_only", frm.supplier_tds ? 0 : 1);
|
||||
frm.set_df_property("tax_withholding_category", "hidden", frm.supplier_tds ? 0 : 1);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
get_materials_from_supplier: function (frm) {
|
||||
let po_details = [];
|
||||
|
||||
@@ -162,15 +137,6 @@ frappe.ui.form.on("Purchase Order", {
|
||||
frm.set_value("transaction_date", frappe.datetime.get_today());
|
||||
}
|
||||
|
||||
if (frm.doc.__onload && frm.doc.supplier) {
|
||||
if (frm.is_new()) {
|
||||
frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
|
||||
}
|
||||
if (!frm.doc.__onload.supplier_tds) {
|
||||
frm.set_df_property("apply_tds", "read_only", 1);
|
||||
}
|
||||
}
|
||||
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function () {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
@@ -181,14 +147,6 @@ frappe.ui.form.on("Purchase Order", {
|
||||
}
|
||||
},
|
||||
|
||||
apply_tds: function (frm) {
|
||||
if (!frm.doc.apply_tds) {
|
||||
frm.set_value("tax_withholding_category", "");
|
||||
} else {
|
||||
frm.set_value("tax_withholding_category", frm.supplier_tds);
|
||||
}
|
||||
},
|
||||
|
||||
get_subcontracting_boms_for_finished_goods: function (fg_item) {
|
||||
return frappe.call({
|
||||
method: "erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom.get_subcontracting_boms_for_finished_goods",
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
"schedule_date",
|
||||
"column_break1",
|
||||
"company",
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"is_subcontracted",
|
||||
"has_unit_price_items",
|
||||
"supplier_warehouse",
|
||||
@@ -57,8 +55,6 @@
|
||||
"column_break_26",
|
||||
"total",
|
||||
"net_total",
|
||||
"tax_withholding_net_total",
|
||||
"base_tax_withholding_net_total",
|
||||
"section_break_48",
|
||||
"pricing_rules",
|
||||
"raw_material_details",
|
||||
@@ -1134,19 +1130,6 @@
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Tax Withholding Amount"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.apply_tds",
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Category",
|
||||
"options": "Tax Withholding Category"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
@@ -1224,28 +1207,6 @@
|
||||
"label": "Additional Info",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "apply_tds",
|
||||
"fieldname": "tax_withholding_net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Tax Withholding Net Total",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "apply_tds",
|
||||
"fieldname": "base_tax_withholding_net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Base Tax Withholding Net Total",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_99",
|
||||
"fieldtype": "Column Break"
|
||||
|
||||
@@ -15,9 +15,6 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
update_linked_doc,
|
||||
validate_inter_company_party,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.party import get_party_account, get_party_account_currency
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
@@ -61,7 +58,6 @@ class PurchaseOrder(BuyingController):
|
||||
advance_payment_status: DF.Literal["Not Initiated", "Initiated", "Partially Paid", "Fully Paid"]
|
||||
amended_from: DF.Link | None
|
||||
apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
|
||||
apply_tds: DF.Check
|
||||
auto_repeat: DF.Link | None
|
||||
base_discount_amount: DF.Currency
|
||||
base_grand_total: DF.Currency
|
||||
@@ -69,7 +65,6 @@ class PurchaseOrder(BuyingController):
|
||||
base_net_total: DF.Currency
|
||||
base_rounded_total: DF.Currency
|
||||
base_rounding_adjustment: DF.Currency
|
||||
base_tax_withholding_net_total: DF.Currency
|
||||
base_taxes_and_charges_added: DF.Currency
|
||||
base_taxes_and_charges_deducted: DF.Currency
|
||||
base_total: DF.Currency
|
||||
@@ -157,8 +152,6 @@ class PurchaseOrder(BuyingController):
|
||||
supplier_name: DF.Data | None
|
||||
supplier_warehouse: DF.Link | None
|
||||
tax_category: DF.Link | None
|
||||
tax_withholding_category: DF.Link | None
|
||||
tax_withholding_net_total: DF.Currency
|
||||
taxes: DF.Table[PurchaseTaxesandCharges]
|
||||
taxes_and_charges: DF.Link | None
|
||||
taxes_and_charges_added: DF.Currency
|
||||
@@ -191,8 +184,6 @@ class PurchaseOrder(BuyingController):
|
||||
]
|
||||
|
||||
def onload(self):
|
||||
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||
self.set_onload("supplier_tds", supplier_tds)
|
||||
self.set_onload("can_update_items", self.can_update_items())
|
||||
|
||||
def before_validate(self):
|
||||
@@ -204,9 +195,6 @@ class PurchaseOrder(BuyingController):
|
||||
|
||||
self.set_status()
|
||||
|
||||
# apply tax withholding only if checked and applicable
|
||||
self.set_tax_withholding()
|
||||
|
||||
self.validate_supplier()
|
||||
self.validate_schedule_date()
|
||||
validate_for_items(self)
|
||||
@@ -284,36 +272,6 @@ class PurchaseOrder(BuyingController):
|
||||
[["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]]
|
||||
)
|
||||
|
||||
def set_tax_withholding(self):
|
||||
if not self.apply_tds:
|
||||
return
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
|
||||
accounts = []
|
||||
for d in self.taxes:
|
||||
if d.account_head == tax_withholding_details.get("account_head"):
|
||||
d.update(tax_withholding_details)
|
||||
accounts.append(d.account_head)
|
||||
|
||||
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||
self.append("taxes", tax_withholding_details)
|
||||
|
||||
to_remove = [
|
||||
d
|
||||
for d in self.taxes
|
||||
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")
|
||||
]
|
||||
|
||||
for d in to_remove:
|
||||
self.remove(d)
|
||||
|
||||
# calculate totals again after applying TDS
|
||||
self.calculate_taxes_and_totals()
|
||||
|
||||
def validate_supplier(self):
|
||||
prevent_po = frappe.db.get_value("Supplier", self.supplier, "prevent_pos")
|
||||
if prevent_po:
|
||||
@@ -695,13 +653,6 @@ class PurchaseOrder(BuyingController):
|
||||
if sco:
|
||||
update_sco_status(sco, "Closed" if self.status == "Closed" else None)
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||
if tds_category and not for_validate:
|
||||
self.set_onload("supplier_tds", tds_category)
|
||||
|
||||
super().set_missing_values(for_validate)
|
||||
|
||||
|
||||
@frappe.request_cache
|
||||
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
|
||||
@@ -839,10 +790,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
target.flags.ignore_permissions = ignore_permissions
|
||||
set_missing_values(source, target)
|
||||
|
||||
# set tax_withholding_category from Purchase Order
|
||||
if source.apply_tds and source.tax_withholding_category and target.apply_tds:
|
||||
target.tax_withholding_category = source.tax_withholding_category
|
||||
|
||||
# Get the advance paid Journal Entries in Purchase Invoice Advance
|
||||
if target.get("allocate_advances_automatically"):
|
||||
target.set_advances()
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"pricing_rules",
|
||||
"stock_uom_rate",
|
||||
"is_free_item",
|
||||
"apply_tds",
|
||||
"section_break_29",
|
||||
"net_rate",
|
||||
"net_amount",
|
||||
@@ -899,12 +898,6 @@
|
||||
"fieldname": "column_break_54",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply TDS"
|
||||
},
|
||||
{
|
||||
"fieldname": "wip_composite_asset",
|
||||
"fieldtype": "Link",
|
||||
|
||||
@@ -18,7 +18,6 @@ class PurchaseOrderItem(Document):
|
||||
actual_qty: DF.Float
|
||||
against_blanket_order: DF.Check
|
||||
amount: DF.Currency
|
||||
apply_tds: DF.Check
|
||||
base_amount: DF.Currency
|
||||
base_net_amount: DF.Currency
|
||||
base_net_rate: DF.Currency
|
||||
|
||||
@@ -37,9 +37,10 @@
|
||||
"dashboard_tab",
|
||||
"tax_tab",
|
||||
"tax_id",
|
||||
"column_break_27",
|
||||
"tax_category",
|
||||
"column_break_27",
|
||||
"tax_withholding_category",
|
||||
"tax_withholding_group",
|
||||
"contact_and_address_tab",
|
||||
"address_contacts",
|
||||
"address_html",
|
||||
@@ -480,6 +481,12 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Customer Numbers",
|
||||
"options": "Customer Number At Supplier"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Group",
|
||||
"options": "Tax Withholding Group"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -493,7 +500,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2025-04-27 12:07:10.859758",
|
||||
"modified": "2025-06-29 05:30:50.398653",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
|
||||
@@ -73,6 +73,7 @@ class Supplier(TransactionBase):
|
||||
tax_category: DF.Link | None
|
||||
tax_id: DF.Data | None
|
||||
tax_withholding_category: DF.Link | None
|
||||
tax_withholding_group: DF.Link | None
|
||||
warn_pos: DF.Check
|
||||
warn_rfqs: DF.Check
|
||||
website: DF.Data | None
|
||||
|
||||
@@ -296,8 +296,6 @@ class AccountsController(TransactionBase):
|
||||
|
||||
if self.doctype == "Purchase Invoice":
|
||||
self.calculate_paid_amount()
|
||||
# apply tax withholding only if checked and applicable
|
||||
self.set_tax_withholding()
|
||||
|
||||
with temporary_flag("company", self.company):
|
||||
validate_regional(self)
|
||||
@@ -1034,6 +1032,12 @@ class AccountsController(TransactionBase):
|
||||
):
|
||||
item.set("is_fixed_asset", ret.get("is_fixed_asset", 0))
|
||||
|
||||
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field(
|
||||
"tax_withholding_category",
|
||||
):
|
||||
if not item.get("tax_withholding_category") and ret.get("tax_withholding_category"):
|
||||
item.set("tax_withholding_category", ret.get("tax_withholding_category"))
|
||||
|
||||
# Double check for cost center
|
||||
# Items add via promotional scheme may not have cost center set
|
||||
if hasattr(item, "cost_center") and not item.get("cost_center"):
|
||||
|
||||
@@ -393,12 +393,14 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
||||
elif doctype == "Purchase Invoice":
|
||||
# look for Print Heading "Debit Note"
|
||||
doc.select_print_heading = frappe.get_cached_value("Print Heading", _("Debit Note"))
|
||||
if source.tax_withholding_category:
|
||||
doc.set_onload("supplier_tds", source.tax_withholding_category)
|
||||
elif doctype == "Delivery Note":
|
||||
# manual additions to the return should hit the return warehous, too
|
||||
doc.set_warehouse = default_warehouse_for_sales_return
|
||||
|
||||
if doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
doc.tax_withholding_group = source.tax_withholding_group
|
||||
doc.ignore_tax_withholding_threshold = source.ignore_tax_withholding_threshold
|
||||
|
||||
for tax in doc.get("taxes") or []:
|
||||
if tax.charge_type == "Actual":
|
||||
tax.tax_amount = -1 * tax.tax_amount
|
||||
@@ -455,6 +457,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = -1 * source_doc.qty
|
||||
target_doc.pricing_rules = None
|
||||
|
||||
if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
|
||||
returned_qty_map = get_returned_qty_map_for_row(
|
||||
source_parent.name, source_parent.supplier, source_doc.name, doctype
|
||||
@@ -525,6 +528,8 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
||||
target_doc.po_detail = source_doc.po_detail
|
||||
target_doc.pr_detail = source_doc.pr_detail
|
||||
target_doc.purchase_invoice_item = source_doc.name
|
||||
target_doc.tax_withholding_category = source_doc.tax_withholding_category
|
||||
target_doc.apply_tds = source_doc.apply_tds
|
||||
|
||||
elif doctype == "Delivery Note":
|
||||
returned_qty_map = get_returned_qty_map_for_row(
|
||||
@@ -556,6 +561,8 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
||||
|
||||
if doctype == "Sales Invoice":
|
||||
target_doc.sales_invoice_item = source_doc.name
|
||||
target_doc.tax_withholding_category = source_doc.tax_withholding_category
|
||||
target_doc.apply_tds = source_doc.apply_tds
|
||||
else:
|
||||
target_doc.pos_invoice_item = source_doc.name
|
||||
|
||||
|
||||
@@ -29,11 +29,6 @@ class calculate_taxes_and_totals:
|
||||
frappe.flags.round_off_applicable_accounts = []
|
||||
frappe.flags.round_row_wise_tax = frappe.get_single_value("Accounts Settings", "round_row_wise_tax")
|
||||
|
||||
if doc.get("round_off_applicable_accounts_for_tax_withholding"):
|
||||
frappe.flags.round_off_applicable_accounts.append(
|
||||
doc.round_off_applicable_accounts_for_tax_withholding
|
||||
)
|
||||
|
||||
self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
|
||||
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
|
||||
self.calculate()
|
||||
@@ -77,24 +72,11 @@ class calculate_taxes_and_totals:
|
||||
self.initialize_taxes()
|
||||
self.determine_exclusive_rate()
|
||||
self.calculate_net_total()
|
||||
self.calculate_tax_withholding_net_total()
|
||||
self.calculate_taxes()
|
||||
self.adjust_grand_total_for_inclusive_tax()
|
||||
self.calculate_totals()
|
||||
self.calculate_total_net_weight()
|
||||
|
||||
def calculate_tax_withholding_net_total(self):
|
||||
if hasattr(self.doc, "tax_withholding_net_total"):
|
||||
sum_net_amount = 0
|
||||
sum_base_net_amount = 0
|
||||
for item in self._items:
|
||||
if hasattr(item, "apply_tds") and item.apply_tds:
|
||||
sum_net_amount += item.net_amount
|
||||
sum_base_net_amount += item.base_net_amount
|
||||
|
||||
self.doc.tax_withholding_net_total = sum_net_amount
|
||||
self.doc.base_tax_withholding_net_total = sum_base_net_amount
|
||||
|
||||
def validate_item_tax_template(self):
|
||||
if self.doc.get("is_return") and self.doc.get("return_against"):
|
||||
return
|
||||
@@ -577,16 +559,7 @@ class calculate_taxes_and_totals:
|
||||
current_net_amount = item.net_amount
|
||||
# distribute the tax amount proportionally to each item row
|
||||
actual = flt(tax.tax_amount, tax.precision("tax_amount"))
|
||||
|
||||
if tax.get("is_tax_withholding_account") and item.meta.get_field("apply_tds"):
|
||||
if not item.get("apply_tds") or not self.doc.tax_withholding_net_total:
|
||||
current_tax_amount = 0.0
|
||||
else:
|
||||
current_tax_amount = item.net_amount * actual / self.doc.tax_withholding_net_total
|
||||
else:
|
||||
current_tax_amount = (
|
||||
item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
|
||||
)
|
||||
current_tax_amount = item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
|
||||
|
||||
elif tax.charge_type == "On Net Total":
|
||||
if tax.account_head in item_tax_map:
|
||||
|
||||
@@ -647,6 +647,10 @@ global_search_doctypes = {
|
||||
],
|
||||
}
|
||||
|
||||
ignore_links_on_delete = [
|
||||
"Tax Withholding Entry",
|
||||
]
|
||||
|
||||
additional_timeline_content = {"*": ["erpnext.telephony.doctype.call_log.call_log.get_linked_call_logs"]}
|
||||
|
||||
|
||||
|
||||
@@ -306,7 +306,6 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
|
||||
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
||||
erpnext.patches.v13_0.drop_unused_sle_index_parts
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
|
||||
erpnext.patches.v14_0.update_partial_tds_fields
|
||||
erpnext.patches.v14_0.create_incoterms_and_migrate_shipment
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
|
||||
erpnext.patches.v14_0.update_entry_type_for_journal_entry
|
||||
@@ -450,3 +449,10 @@ erpnext.patches.v16_0.set_valuation_method_on_companies
|
||||
erpnext.patches.v15_0.migrate_old_item_wise_tax_detail_data_to_table
|
||||
erpnext.patches.v16_0.migrate_budget_records_to_new_structure
|
||||
erpnext.patches.v16_0.populate_budget_distribution_total
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
erpnext.patches.v16_0.set_mr_picked_qty
|
||||
erpnext.patches.v16_0.update_tax_withholding_field_in_payment_entry
|
||||
erpnext.patches.v16_0.migrate_tax_withholding_data
|
||||
|
||||
>>>>>>> c66f78c784 (feat: Introduce tax withholding entry)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import frappe
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.utils import FiscalYearError, get_fiscal_year
|
||||
|
||||
|
||||
def execute():
|
||||
# Only do for current fiscal year, no need to repost for all years
|
||||
for company in frappe.get_all("Company"):
|
||||
try:
|
||||
fiscal_year_details = get_fiscal_year(date=nowdate(), company=company.name, as_dict=True)
|
||||
|
||||
purchase_invoice = frappe.qb.DocType("Purchase Invoice")
|
||||
|
||||
frappe.qb.update(purchase_invoice).set(
|
||||
purchase_invoice.tax_withholding_net_total, purchase_invoice.net_total
|
||||
).set(purchase_invoice.base_tax_withholding_net_total, purchase_invoice.base_net_total).where(
|
||||
purchase_invoice.company == company.name
|
||||
).where(purchase_invoice.apply_tds == 1).where(
|
||||
purchase_invoice.posting_date >= fiscal_year_details.year_start_date
|
||||
).where(purchase_invoice.docstatus == 1).run()
|
||||
|
||||
purchase_order = frappe.qb.DocType("Purchase Order")
|
||||
|
||||
frappe.qb.update(purchase_order).set(
|
||||
purchase_order.tax_withholding_net_total, purchase_order.net_total
|
||||
).set(purchase_order.base_tax_withholding_net_total, purchase_order.base_net_total).where(
|
||||
purchase_order.company == company.name
|
||||
).where(purchase_order.apply_tds == 1).where(
|
||||
purchase_order.transaction_date >= fiscal_year_details.year_start_date
|
||||
).where(purchase_order.docstatus == 1).run()
|
||||
except FiscalYearError:
|
||||
pass
|
||||
1289
erpnext/patches/v16_0/migrate_tax_withholding_data.py
Normal file
1289
erpnext/patches/v16_0/migrate_tax_withholding_data.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
from frappe.query_builder import DocType
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.has_column("Payment Entry", "apply_tax_withholding_amount"):
|
||||
return
|
||||
|
||||
pe = DocType("Payment Entry")
|
||||
(frappe.qb.update(pe).set(pe.apply_tds, pe.apply_tax_withholding_amount)).run()
|
||||
@@ -131,6 +131,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
frm.cscript.calculate_taxes_and_totals();
|
||||
});
|
||||
|
||||
// Tax Withholding Entries - Auto calculate withholding amount when taxable amount or tax rate changes
|
||||
frappe.ui.form.on("Tax Withholding Entry", "taxable_amount", function (frm, cdt, cdn) {
|
||||
me.calculate_withholding_amount(frm, cdt, cdn);
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Tax Withholding Entry", "tax_rate", function (frm, cdt, cdn) {
|
||||
me.calculate_withholding_amount(frm, cdt, cdn);
|
||||
});
|
||||
|
||||
frappe.ui.form.on(this.frm.doctype + " Item", {
|
||||
items_add: function (frm, cdt, cdn) {
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
@@ -583,6 +592,18 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
}
|
||||
|
||||
calculate_withholding_amount(frm, cdt, cdn) {
|
||||
// Calculate withholding amount: taxable_amount * tax_rate / 100
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
let withholding_amount = flt(
|
||||
(row.taxable_amount * row.tax_rate) / 100,
|
||||
precision("withholding_amount", row)
|
||||
);
|
||||
|
||||
// Set the calculated withholding amount
|
||||
frappe.model.set_value(cdt, cdn, "withholding_amount", withholding_amount);
|
||||
}
|
||||
|
||||
send_sms() {
|
||||
var sms_man = new erpnext.SMSManager(this.frm.doc);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,8 @@ erpnext.utils.get_party_details = function (frm, method, args, callback) {
|
||||
args: args,
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
frm.supplier_tds = r.message.supplier_tds;
|
||||
frm.tax_withholding_category = r.message.tax_withholding_category;
|
||||
frm.tax_withholding_group = r.message.tax_withholding_group;
|
||||
frm.updating_party_details = true;
|
||||
frappe.run_serially([
|
||||
() => frm.set_value(r.message),
|
||||
|
||||
@@ -62,9 +62,10 @@
|
||||
"tax_tab",
|
||||
"taxation_section",
|
||||
"tax_id",
|
||||
"column_break_21",
|
||||
"tax_category",
|
||||
"column_break_21",
|
||||
"tax_withholding_category",
|
||||
"tax_withholding_group",
|
||||
"accounting_tab",
|
||||
"credit_limit_section",
|
||||
"payment_terms",
|
||||
@@ -605,6 +606,12 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Supplier Numbers",
|
||||
"options": "Supplier Number At Customer"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Group",
|
||||
"options": "Tax Withholding Group"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
|
||||
@@ -89,6 +89,7 @@ class Customer(TransactionBase):
|
||||
tax_category: DF.Link | None
|
||||
tax_id: DF.Data | None
|
||||
tax_withholding_category: DF.Link | None
|
||||
tax_withholding_group: DF.Link | None
|
||||
territory: DF.Link | None
|
||||
website: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
@@ -105,7 +105,13 @@
|
||||
"customer_details",
|
||||
"customer_items",
|
||||
"item_tax_section_break",
|
||||
"section_break_oilf",
|
||||
"column_break_aytr",
|
||||
"taxes",
|
||||
"section_break_fxqz",
|
||||
"purchase_tax_withholding_category",
|
||||
"column_break_ltlb",
|
||||
"sales_tax_withholding_category",
|
||||
"quality_tab",
|
||||
"inspection_required_before_purchase",
|
||||
"quality_inspection_template",
|
||||
@@ -910,6 +916,37 @@
|
||||
{
|
||||
"fieldname": "column_break_wugd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_oilf",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_aytr",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_fxqz",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Tax Withholding Category",
|
||||
"depends_on": "is_purchase_item",
|
||||
"options": "Tax Withholding Category"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ltlb",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Sales Tax Withholding Category",
|
||||
"depends_on": "is_sales_item",
|
||||
"options": "Tax Withholding Category"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-tag",
|
||||
|
||||
@@ -128,11 +128,13 @@ class Item(Document):
|
||||
over_billing_allowance: DF.Float
|
||||
over_delivery_receipt_allowance: DF.Float
|
||||
production_capacity: DF.Int
|
||||
purchase_tax_withholding_category: DF.Link | None
|
||||
purchase_uom: DF.Link | None
|
||||
quality_inspection_template: DF.Link | None
|
||||
reorder_levels: DF.Table[ItemReorder]
|
||||
retain_sample: DF.Check
|
||||
safety_stock: DF.Float
|
||||
sales_tax_withholding_category: DF.Link | None
|
||||
sales_uom: DF.Link | None
|
||||
sample_quantity: DF.Int
|
||||
serial_no_series: DF.Data | None
|
||||
|
||||
@@ -59,8 +59,6 @@
|
||||
"column_break_27",
|
||||
"total",
|
||||
"net_total",
|
||||
"tax_withholding_net_total",
|
||||
"base_tax_withholding_net_total",
|
||||
"taxes_charges_section",
|
||||
"tax_category",
|
||||
"taxes_and_charges",
|
||||
@@ -1256,24 +1254,6 @@
|
||||
"options": "Subcontracting Receipt",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Tax Withholding Net Total",
|
||||
"no_copy": 1,
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_tax_withholding_net_total",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Base Tax Withholding Net Total",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "dispatch_address",
|
||||
"fieldtype": "Link",
|
||||
|
||||
@@ -55,7 +55,6 @@ class PurchaseReceipt(BuyingController):
|
||||
base_net_total: DF.Currency
|
||||
base_rounded_total: DF.Currency
|
||||
base_rounding_adjustment: DF.Currency
|
||||
base_tax_withholding_net_total: DF.Currency
|
||||
base_taxes_and_charges_added: DF.Currency
|
||||
base_taxes_and_charges_deducted: DF.Currency
|
||||
base_total: DF.Currency
|
||||
@@ -138,7 +137,6 @@ class PurchaseReceipt(BuyingController):
|
||||
supplier_name: DF.Data | None
|
||||
supplier_warehouse: DF.Link | None
|
||||
tax_category: DF.Link | None
|
||||
tax_withholding_net_total: DF.Currency
|
||||
taxes: DF.Table[PurchaseTaxesandCharges]
|
||||
taxes_and_charges: DF.Link | None
|
||||
taxes_and_charges_added: DF.Currency
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
"pricing_rules",
|
||||
"stock_uom_rate",
|
||||
"is_free_item",
|
||||
"apply_tds",
|
||||
"section_break_29",
|
||||
"net_rate",
|
||||
"net_amount",
|
||||
@@ -1107,14 +1106,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Apply TDS",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "return_qty_from_rejected_warehouse",
|
||||
|
||||
@@ -17,7 +17,6 @@ class PurchaseReceiptItem(Document):
|
||||
allow_zero_valuation_rate: DF.Check
|
||||
amount: DF.Currency
|
||||
amount_difference_with_purchase_invoice: DF.Currency
|
||||
apply_tds: DF.Check
|
||||
asset_category: DF.Link | None
|
||||
asset_location: DF.Link | None
|
||||
barcode: DF.Data | None
|
||||
|
||||
@@ -106,6 +106,9 @@ def get_item_details(
|
||||
|
||||
get_party_item_code(ctx, item, out)
|
||||
|
||||
if ctx.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
get_tax_withholding_category(ctx, item, out)
|
||||
|
||||
if ctx.doctype in ["Sales Order", "Quotation"]:
|
||||
set_valuation_rate(out, ctx)
|
||||
|
||||
@@ -1309,6 +1312,32 @@ def get_party_item_code(ctx: ItemDetailsCtx, item_doc, out: ItemDetails):
|
||||
out.supplier_part_no = item_supplier[0].supplier_part_no if item_supplier else None
|
||||
|
||||
|
||||
def get_tax_withholding_category(ctx: ItemDetailsCtx, item_doc, out: ItemDetails):
|
||||
"""
|
||||
Get tax withholding category for the item based on the transaction type and party.
|
||||
"""
|
||||
|
||||
tax_withholding_category = None
|
||||
field = (
|
||||
"sales_tax_withholding_category"
|
||||
if ctx.transaction_type == "selling"
|
||||
else "purchase_tax_withholding_category"
|
||||
)
|
||||
|
||||
if item_doc.get(field):
|
||||
tax_withholding_category = item_doc.get(field)
|
||||
elif ctx.transaction_type == "buying" and ctx.supplier:
|
||||
tax_withholding_category = frappe.get_cached_value(
|
||||
"Supplier", ctx.supplier, "tax_withholding_category"
|
||||
)
|
||||
elif ctx.transaction_type == "selling" and ctx.customer:
|
||||
tax_withholding_category = frappe.get_cached_value(
|
||||
"Customer", ctx.customer, "tax_withholding_category"
|
||||
)
|
||||
|
||||
out.tax_withholding_category = tax_withholding_category
|
||||
|
||||
|
||||
from erpnext.deprecation_dumpster import get_pos_profile_item_details
|
||||
|
||||
|
||||
|
||||
@@ -968,7 +968,6 @@ def make_purchase_receipt(source_name, target_doc=None, save=False, submit=False
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"reset_value": True,
|
||||
"condition": lambda doc: not doc.is_tax_withholding_account,
|
||||
},
|
||||
},
|
||||
postprocess=post_process,
|
||||
|
||||
Reference in New Issue
Block a user