Merge pull request #24716 from pateljannat/issue-custom-buttons

fix: custom buttons in issue
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",