Merge pull request #24478 from nextchamp-saqib/tcs_calculation
feat: tax collected at source using tax withholding category
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 9599d4e..4217711 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -21,6 +21,7 @@
from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
get_loyalty_program_details_with_points, get_loyalty_details, validate_loyalty_points
from erpnext.accounts.deferred_revenue import validate_service_stop_date
+from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
from frappe.model.utils import get_fetch_values
from frappe.contacts.doctype.address.address import get_address_display
@@ -75,6 +76,8 @@
if not self.is_pos:
self.so_dn_required()
+
+ self.set_tax_withholding()
self.validate_proj_cust()
self.validate_pos_return()
@@ -153,6 +156,32 @@
if cost_center_company != self.company:
frappe.throw(_("Row #{0}: Cost Center {1} does not belong to company {2}").format(frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company)))
+ def set_tax_withholding(self):
+ 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):
set_account_for_mode_of_payment(self)
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index 32ad4cb..961bdb1 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -12,37 +12,62 @@
class TaxWithholdingCategory(Document):
pass
-def get_party_tax_withholding_details(ref_doc, tax_withholding_category=None):
+def get_party_details(inv):
+ party_type, party = '', ''
+ if inv.doctype == 'Sales Invoice':
+ party_type = 'Customer'
+ party = inv.customer
+ else:
+ party_type = 'Supplier'
+ party = inv.supplier
+
+ return party_type, party
+
+def get_party_tax_withholding_details(inv, tax_withholding_category=None):
pan_no = ''
- suppliers = []
+ parties = []
+ party_type, party = get_party_details(inv)
if not tax_withholding_category:
- tax_withholding_category, pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, ['tax_withholding_category', 'pan'])
+ tax_withholding_category, pan_no = frappe.db.get_value(party_type, party, ['tax_withholding_category', 'pan'])
if not tax_withholding_category:
return
+ # if tax_withholding_category passed as an argument but not pan_no
if not pan_no:
- pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, 'pan')
+ pan_no = frappe.db.get_value(party_type, party, 'pan')
# Get others suppliers with the same PAN No
if pan_no:
- suppliers = [d.name for d in frappe.get_all('Supplier', fields=['name'], filters={'pan': pan_no})]
+ parties = frappe.get_all(party_type, filters={ 'pan': pan_no }, pluck='name')
- if not suppliers:
- suppliers.append(ref_doc.supplier)
+ if not parties:
+ parties.append(party)
- fy = get_fiscal_year(ref_doc.posting_date, company=ref_doc.company)
- tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company)
+ fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company)
+ tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
+
if not tax_details:
frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
- .format(tax_withholding_category, ref_doc.company))
+ .format(tax_withholding_category, inv.company))
- tds_amount = get_tds_amount(suppliers, ref_doc.net_total, ref_doc.company,
- tax_details, fy, ref_doc.posting_date, pan_no)
+ if party_type == 'Customer' and not tax_details.cumulative_threshold:
+ # TCS is only chargeable on sum of invoiced value
+ frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.')
+ .format(tax_withholding_category, inv.company, party))
- tax_row = get_tax_row(tax_details, tds_amount)
+ tax_amount, tax_deducted = get_tax_amount(
+ party_type, parties,
+ inv, tax_details,
+ fiscal_year, pan_no
+ )
+
+ if party_type == 'Supplier':
+ tax_row = get_tax_row_for_tds(tax_details, tax_amount)
+ else:
+ tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
return tax_row
@@ -69,147 +94,254 @@
frappe.throw(_("No Tax Withholding data found for the current Fiscal Year."))
-def get_tax_row(tax_details, tds_amount):
-
- return {
+def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted):
+ row = {
"category": "Total",
- "add_deduct_tax": "Deduct",
"charge_type": "Actual",
- "account_head": tax_details.account_head,
+ "tax_amount": tax_amount,
"description": tax_details.description,
- "tax_amount": tds_amount
+ "account_head": tax_details.account_head
}
-def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_details, posting_date, pan_no=None):
- fiscal_year, year_start_date, year_end_date = fiscal_year_details
- tds_amount = 0
- tds_deducted = 0
+ if tax_deducted:
+ # TCS already deducted on previous invoices
+ # So, TCS will be calculated by 'Previous Row Total'
- def _get_tds(amount, rate):
- if amount <= 0:
- return 0
-
- return amount * rate / 100
-
- ldc_name = frappe.db.get_value('Lower Deduction Certificate',
- {
- 'pan_no': pan_no,
- 'fiscal_year': fiscal_year
- }, 'name')
- ldc = ''
-
- if ldc_name:
- ldc = frappe.get_doc('Lower Deduction Certificate', ldc_name)
-
- entries = frappe.db.sql("""
- select voucher_no, credit
- from `tabGL Entry`
- where company = %s and
- party in %s and fiscal_year=%s and credit > 0
- and is_opening = 'No'
- """, (company, tuple(suppliers), fiscal_year), as_dict=1)
-
- vouchers = [d.voucher_no for d in entries]
- advance_vouchers = get_advance_vouchers(suppliers, fiscal_year=fiscal_year, company=company)
-
- tds_vouchers = vouchers + advance_vouchers
-
- if tds_vouchers:
- tds_deducted = frappe.db.sql("""
- SELECT sum(credit) FROM `tabGL Entry`
- WHERE
- account=%s and fiscal_year=%s and credit > 0
- and voucher_no in ({0})""". format(','.join(['%s'] * len(tds_vouchers))),
- ((tax_details.account_head, fiscal_year) + tuple(tds_vouchers)))
-
- tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0
-
- if tds_deducted:
- if ldc:
- limit_consumed = frappe.db.get_value('Purchase Invoice',
- {
- 'supplier': ('in', suppliers),
- 'apply_tds': 1,
- 'docstatus': 1
- }, 'sum(net_total)')
-
- if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total,
- ldc.certificate_limit):
-
- tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
+ taxes_excluding_tcs = [d for d in inv.taxes if d.account_head != tax_details.account_head]
+ if taxes_excluding_tcs:
+ # chargeable amount is the total amount after other charges are applied
+ row.update({
+ "charge_type": "On Previous Row Total",
+ "row_id": len(taxes_excluding_tcs),
+ "rate": tax_details.rate
+ })
else:
- tds_amount = _get_tds(net_total, tax_details.rate)
- else:
- supplier_credit_amount = frappe.get_all('Purchase Invoice',
- fields = ['sum(net_total)'],
- filters = {'name': ('in', vouchers), 'docstatus': 1, "apply_tds": 1}, as_list=1)
+ # if only TCS is to be charged, then net total is chargeable amount
+ row.update({
+ "charge_type": "On Net Total",
+ "rate": tax_details.rate
+ })
- supplier_credit_amount = (supplier_credit_amount[0][0]
- if supplier_credit_amount and supplier_credit_amount[0][0] else 0)
+ return row
- jv_supplier_credit_amt = frappe.get_all('Journal Entry Account',
- fields = ['sum(credit_in_account_currency)'],
- filters = {
- 'parent': ('in', vouchers), 'docstatus': 1,
- 'party': ('in', suppliers),
- 'reference_type': ('not in', ['Purchase Invoice'])
- }, as_list=1)
+def get_tax_row_for_tds(tax_details, tax_amount):
+ return {
+ "category": "Total",
+ "charge_type": "Actual",
+ "tax_amount": tax_amount,
+ "add_deduct_tax": "Deduct",
+ "description": tax_details.description,
+ "account_head": tax_details.account_head
+ }
- supplier_credit_amount += (jv_supplier_credit_amt[0][0]
- if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0)
+def get_lower_deduction_certificate(fiscal_year, pan_no):
+ ldc_name = frappe.db.get_value('Lower Deduction Certificate', { 'pan_no': pan_no, 'fiscal_year': fiscal_year }, 'name')
+ if ldc_name:
+ return frappe.get_doc('Lower Deduction Certificate', ldc_name)
- supplier_credit_amount += net_total
+def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
+ fiscal_year = fiscal_year_details[0]
- debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date)
- supplier_credit_amount -= debit_note_amount
+ vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
+ advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
+ taxable_vouchers = vouchers + advance_vouchers
- if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold)
- or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)):
+ tax_deducted = 0
+ if taxable_vouchers:
+ tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
- if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total,
- ldc.certificate_limit):
- tds_amount = get_ltds_amount(supplier_credit_amount, 0, ldc.certificate_limit, ldc.rate,
- tax_details)
+ tax_amount = 0
+ posting_date = inv.posting_date
+ if party_type == 'Supplier':
+ ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
+ if tax_deducted:
+ net_total = inv.net_total
+ if ldc:
+ tax_amount = get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total)
else:
- tds_amount = _get_tds(supplier_credit_amount, tax_details.rate)
+ tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
+ else:
+ tax_amount = get_tds_amount(
+ ldc, parties, inv, tax_details,
+ fiscal_year_details, tax_deducted, vouchers
+ )
+
+ elif party_type == 'Customer':
+ if tax_deducted:
+ # if already TCS is charged, then amount will be calculated based on 'Previous Row Total'
+ tax_amount = 0
+ else:
+ # if no TCS has been charged in FY,
+ # then chargeable value is "prev invoices + advances" value which cross the threshold
+ tax_amount = get_tcs_amount(
+ parties, inv, tax_details,
+ fiscal_year_details, vouchers, advance_vouchers
+ )
+
+ return tax_amount, tax_deducted
+
+def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'):
+ dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
+
+ filters = {
+ dr_or_cr: ['>', 0],
+ 'company': company,
+ 'party_type': party_type,
+ 'party': ['in', parties],
+ 'fiscal_year': fiscal_year,
+ 'is_opening': 'No',
+ 'is_cancelled': 0
+ }
+
+ return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""]
+
+def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None, to_date=None, party_type='Supplier'):
+ # for advance vouchers, debit and credit is reversed
+ dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit'
+
+ filters = {
+ dr_or_cr: ['>', 0],
+ 'is_opening': 'No',
+ 'is_cancelled': 0,
+ 'party_type': party_type,
+ 'party': ['in', parties],
+ 'against_voucher': ['is', 'not set']
+ }
+
+ if fiscal_year:
+ filters['fiscal_year'] = fiscal_year
+ if company:
+ filters['company'] = company
+ if from_date and to_date:
+ filters['posting_date'] = ['between', (from_date, to_date)]
+
+ return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""]
+
+def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details):
+ # check if TDS / TCS account is already charged on taxable vouchers
+ filters = {
+ 'is_cancelled': 0,
+ 'credit': ['>', 0],
+ 'fiscal_year': fiscal_year,
+ 'account': tax_details.account_head,
+ 'voucher_no': ['in', taxable_vouchers],
+ }
+ field = "sum(credit)"
+
+ return frappe.db.get_value('GL Entry', filters, field) or 0.0
+
+def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
+ tds_amount = 0
+
+ supp_credit_amt = frappe.db.get_value('Purchase Invoice', {
+ 'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1
+ }, 'sum(net_total)') or 0.0
+
+ supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
+ 'parent': ('in', vouchers), 'docstatus': 1,
+ 'party': ('in', parties), 'reference_type': ('!=', 'Purchase Invoice')
+ }, 'sum(credit_in_account_currency)') or 0.0
+
+ supp_credit_amt += supp_jv_credit_amt
+ supp_credit_amt += inv.net_total
+
+ debit_note_amount = get_debit_note_amount(parties, fiscal_year_details, inv.company)
+ supp_credit_amt -= debit_note_amount
+
+ threshold = tax_details.get('threshold', 0)
+ cumulative_threshold = tax_details.get('cumulative_threshold', 0)
+
+ if ((threshold and supp_credit_amt >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
+ if ldc and is_valid_certificate(
+ ldc.valid_from, ldc.valid_upto,
+ inv.posting_date, tax_deducted,
+ inv.net_total, ldc.certificate_limit
+ ):
+ tds_amount = get_ltds_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
-def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=None, to_date=None):
- condition = "fiscal_year=%s" % fiscal_year
+def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv_vouchers):
+ tcs_amount = 0
+ fiscal_year, _, _ = fiscal_year_details
+
+ # sum of debit entries made from sales invoices
+ invoiced_amt = frappe.db.get_value('GL Entry', {
+ 'is_cancelled': 0,
+ 'party': ['in', parties],
+ 'company': inv.company,
+ 'voucher_no': ['in', vouchers],
+ }, 'sum(debit)') or 0.0
+
+ # sum of credit entries made from PE / JV with unset 'against voucher'
+ advance_amt = frappe.db.get_value('GL Entry', {
+ 'is_cancelled': 0,
+ 'party': ['in', parties],
+ 'company': inv.company,
+ 'voucher_no': ['in', adv_vouchers],
+ }, 'sum(credit)') or 0.0
+
+ # sum of credit entries made from sales invoice
+ credit_note_amt = frappe.db.get_value('GL Entry', {
+ 'is_cancelled': 0,
+ 'credit': ['>', 0],
+ 'party': ['in', parties],
+ 'fiscal_year': fiscal_year,
+ 'company': inv.company,
+ 'voucher_type': 'Sales Invoice',
+ }, 'sum(credit)') or 0.0
+
+ cumulative_threshold = tax_details.get('cumulative_threshold', 0)
+
+ current_invoice_total = get_invoice_total_without_tcs(inv, tax_details)
+ total_invoiced_amt = current_invoice_total + invoiced_amt + advance_amt - credit_note_amt
+
+ if ((cumulative_threshold and total_invoiced_amt >= cumulative_threshold)):
+ chargeable_amt = total_invoiced_amt - cumulative_threshold
+ tcs_amount = chargeable_amt * tax_details.rate / 100 if chargeable_amt > 0 else 0
+
+ return tcs_amount
+
+def get_invoice_total_without_tcs(inv, tax_details):
+ tcs_tax_row = [d for d in inv.taxes if d.account_head == tax_details.account_head]
+ tcs_tax_row_amount = tcs_tax_row[0].base_tax_amount if tcs_tax_row else 0
+
+ return inv.grand_total - tcs_tax_row_amount
+
+def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total):
+ tds_amount = 0
+ limit_consumed = frappe.db.get_value('Purchase Invoice', {
+ 'supplier': ('in', parties),
+ 'apply_tds': 1,
+ 'docstatus': 1
+ }, 'sum(net_total)')
+
+ if is_valid_certificate(
+ ldc.valid_from, ldc.valid_upto,
+ posting_date, limit_consumed,
+ net_total, ldc.certificate_limit
+ ):
+ tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
+
+ return tds_amount
+
+def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
+ _, year_start_date, year_end_date = fiscal_year_details
+
+ filters = {
+ 'supplier': ['in', suppliers],
+ 'is_return': 1,
+ 'docstatus': 1,
+ 'posting_date': ['between', (year_start_date, year_end_date)]
+ }
+ fields = ['abs(sum(net_total)) as net_total']
if company:
- condition += "and company =%s" % (company)
- if from_date and to_date:
- condition += "and posting_date between %s and %s" % (from_date, to_date)
+ filters['company'] = company
- ## Appending the same supplier again if length of suppliers list is 1
- ## since tuple of single element list contains None, For example ('Test Supplier 1', )
- ## and the below query fails
- if len(suppliers) == 1:
- suppliers.append(suppliers[0])
-
- return frappe.db.sql_list("""
- select distinct voucher_no
- from `tabGL Entry`
- where party in %s and %s and debit > 0
- and is_opening = 'No'
- """, (tuple(suppliers), condition)) or []
-
-def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
- condition = "and 1=1"
- if company:
- condition = " and company=%s " % company
-
- if len(suppliers) == 1:
- suppliers.append(suppliers[0])
-
- return flt(frappe.db.sql("""
- select abs(sum(net_total))
- from `tabPurchase Invoice`
- where supplier in %s and is_return=1 and docstatus=1
- and posting_date between %s and %s %s
- """, (tuple(suppliers), year_start_date, year_end_date, condition)))
+ return frappe.get_all('Purchase Invoice', filters, fields)[0].get('net_total') or 0.0
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
if current_amount < (certificate_limit - deducted_amount):
@@ -227,4 +359,4 @@
certificate_limit > deducted_amount):
valid = True
- return valid
\ No newline at end of file
+ return valid
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index ef77674..9ce8e3f 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -9,7 +9,7 @@
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
-test_dependencies = ["Supplier Group"]
+test_dependencies = ["Supplier Group", "Customer Group"]
class TestTaxWithholdingCategory(unittest.TestCase):
@classmethod
@@ -18,6 +18,9 @@
create_records()
create_tax_with_holding_category()
+ def tearDown(self):
+ cancel_invoices()
+
def test_cumulative_threshold_tds(self):
frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS")
invoices = []
@@ -128,9 +131,59 @@
for d in invoices:
d.cancel()
+ def test_cumulative_threshold_tcs(self):
+ frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
+ invoices = []
+
+ # create invoices for lower than single threshold tax rate
+ for _ in range(2):
+ si = create_sales_invoice(customer = "Test TCS Customer")
+ si.submit()
+ invoices.append(si)
+
+ # create another invoice whose total when added to previously created invoice,
+ # surpasses cumulative threshhold
+ si = create_sales_invoice(customer = "Test TCS Customer", rate=12000)
+ si.submit()
+
+ # assert tax collection on total invoice amount created until now
+ tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC'])
+ self.assertEqual(tcs_charged, 200)
+ self.assertEqual(si.grand_total, 12200)
+ invoices.append(si)
+
+ # TCS is already collected once, so going forward system will collect TCS on every invoice
+ si = create_sales_invoice(customer = "Test TCS Customer", rate=5000)
+ si.submit()
+
+ tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC'])
+ self.assertEqual(tcs_charged, 500)
+ invoices.append(si)
+
+ #delete invoices to avoid clashing
+ for d in invoices:
+ d.cancel()
+
+def cancel_invoices():
+ purchase_invoices = frappe.get_all("Purchase Invoice", {
+ 'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']],
+ 'docstatus': 1
+ }, pluck="name")
+
+ sales_invoices = frappe.get_all("Sales Invoice", {
+ 'customer': 'Test TCS Customer',
+ 'docstatus': 1
+ }, pluck="name")
+
+ for d in purchase_invoices:
+ frappe.get_doc('Purchase Invoice', d).cancel()
+
+ for d in sales_invoices:
+ frappe.get_doc('Sales Invoice', d).cancel()
+
def create_purchase_invoice(**args):
# return sales invoice doc object
- item = frappe.get_doc('Item', {'item_name': 'TDS Item'})
+ item = frappe.db.get_value('Item', {'item_name': 'TDS Item'}, "name")
args = frappe._dict(args)
pi = frappe.get_doc({
@@ -145,7 +198,7 @@
"taxes": [],
"items": [{
'doctype': 'Purchase Invoice Item',
- 'item_code': item.name,
+ 'item_code': item,
'qty': args.qty or 1,
'rate': args.rate or 10000,
'cost_center': 'Main - _TC',
@@ -156,6 +209,33 @@
pi.save()
return pi
+def create_sales_invoice(**args):
+ # return sales invoice doc object
+ item = frappe.db.get_value('Item', {'item_name': 'TCS Item'}, "name")
+
+ args = frappe._dict(args)
+ si = frappe.get_doc({
+ "doctype": "Sales Invoice",
+ "posting_date": today(),
+ "customer": args.customer,
+ "company": '_Test Company',
+ "taxes_and_charges": "",
+ "currency": "INR",
+ "debit_to": "Debtors - _TC",
+ "taxes": [],
+ "items": [{
+ 'doctype': 'Sales Invoice Item',
+ 'item_code': item,
+ 'qty': args.qty or 1,
+ 'rate': args.rate or 10000,
+ 'cost_center': 'Main - _TC',
+ 'expense_account': 'Cost of Goods Sold - _TC'
+ }]
+ })
+
+ si.save()
+ return si
+
def create_records():
# create a new suppliers
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
@@ -168,7 +248,17 @@
"doctype": "Supplier",
}).insert()
- # create an item
+ for name in ['Test TCS Customer']:
+ if frappe.db.exists('Customer', name):
+ continue
+
+ frappe.get_doc({
+ "customer_group": "_Test Customer Group",
+ "customer_name": name,
+ "doctype": "Customer"
+ }).insert()
+
+ # create item
if not frappe.db.exists('Item', "TDS Item"):
frappe.get_doc({
"doctype": "Item",
@@ -178,7 +268,16 @@
"is_stock_item": 0,
}).insert()
- # create an account
+ if not frappe.db.exists('Item', "TCS Item"):
+ frappe.get_doc({
+ "doctype": "Item",
+ "item_code": "TCS Item",
+ "item_name": "TCS Item",
+ "item_group": "All Item Groups",
+ "is_stock_item": 1
+ }).insert()
+
+ # create tds account
if not frappe.db.exists("Account", "TDS - _TC"):
frappe.get_doc({
'doctype': 'Account',
@@ -189,6 +288,17 @@
'root_type': 'Asset'
}).insert()
+ # create tcs account
+ if not frappe.db.exists("Account", "TCS - _TC"):
+ frappe.get_doc({
+ 'doctype': 'Account',
+ 'company': '_Test Company',
+ 'account_name': 'TCS',
+ 'parent_account': 'Duties and Taxes - _TC',
+ 'report_type': 'Balance Sheet',
+ 'root_type': 'Liability'
+ }).insert()
+
def create_tax_with_holding_category():
fiscal_year = get_fiscal_year(today(), company="_Test Company")[0]
@@ -210,6 +320,23 @@
}]
}).insert()
+ if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TCS"):
+ frappe.get_doc({
+ "doctype": "Tax Withholding Category",
+ "name": "Cumulative Threshold TCS",
+ "category_name": "10% TCS",
+ "rates": [{
+ 'fiscal_year': fiscal_year,
+ 'tax_withholding_rate': 10,
+ 'single_threshold': 0,
+ 'cumulative_threshold': 30000.00
+ }],
+ "accounts": [{
+ 'company': '_Test Company',
+ 'account': 'TCS - _TC'
+ }]
+ }).insert()
+
# Single thresold
if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"):
frappe.get_doc({
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 3c0652eb..a048928 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -16,6 +16,8 @@
"customer_name",
"gender",
"customer_type",
+ "pan",
+ "tax_withholding_category",
"default_bank_account",
"lead_name",
"image",
@@ -210,7 +212,8 @@
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Represents Company",
- "options": "Company"
+ "options": "Company",
+ "unique": 1
},
{
"depends_on": "represents_company",
@@ -479,13 +482,25 @@
"fieldname": "dn_required",
"fieldtype": "Check",
"label": "Allow Sales Invoice Creation Without Delivery Note"
+ },
+ {
+ "fieldname": "pan",
+ "fieldtype": "Data",
+ "label": "PAN"
+ },
+ {
+ "fieldname": "tax_withholding_category",
+ "fieldtype": "Link",
+ "label": "Tax Withholding Category",
+ "options": "Tax Withholding Category"
}
],
"icon": "fa fa-user",
"idx": 363,
"image_field": "image",
+ "index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-01-06 19:35:25.418017",
+ "modified": "2021-01-27 12:54:57.258959",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",