Merge pull request #33063 from niralisatapara/tds_purchase_order

feat: item wise tds in purchase order
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 40c732b..23caac0 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
@@ -226,6 +226,42 @@
 		for d in reversed(invoices):
 			d.cancel()
 
+		orders = []
+
+		po = create_purchase_order(supplier="Test TDS Supplier4", rate=20000, do_not_save=True)
+		po.extend(
+			"items",
+			[
+				{
+					"doctype": "Purchase Order Item",
+					"item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"),
+					"qty": 1,
+					"rate": 20000,
+					"cost_center": "Main - _TC",
+					"expense_account": "Stock Received But Not Billed - _TC",
+					"apply_tds": 0,
+				},
+				{
+					"doctype": "Purchase Order Item",
+					"item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"),
+					"qty": 1,
+					"rate": 35000,
+					"cost_center": "Main - _TC",
+					"expense_account": "Stock Received But Not Billed - _TC",
+					"apply_tds": 1,
+				},
+			],
+		)
+		po.save()
+		po.submit()
+		orders.append(po)
+
+		self.assertEqual(po.taxes[0].tax_amount, 5500)
+
+		# cancel orders to avoid clashing
+		for d in reversed(orders):
+			d.cancel()
+
 	def test_multi_category_single_supplier(self):
 		frappe.db.set_value(
 			"Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category"
@@ -348,6 +384,39 @@
 	return pi
 
 
+def create_purchase_order(**args):
+	# return purchase order doc object
+	item = frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name")
+
+	args = frappe._dict(args)
+	po = frappe.get_doc(
+		{
+			"doctype": "Purchase Order",
+			"transaction_date": today(),
+			"schedule_date": today(),
+			"apply_tds": 0 if args.do_not_apply_tds else 1,
+			"supplier": args.supplier,
+			"company": "_Test Company",
+			"taxes_and_charges": "",
+			"currency": "INR",
+			"taxes": [],
+			"items": [
+				{
+					"doctype": "Purchase Order Item",
+					"item_code": item,
+					"qty": args.qty or 1,
+					"rate": args.rate or 10000,
+					"cost_center": "Main - _TC",
+					"expense_account": "Stock Received But Not Billed - _TC",
+				}
+			],
+		}
+	)
+
+	po.save()
+	return po
+
+
 def create_sales_invoice(**args):
 	# return sales invoice doc object
 	item = frappe.db.get_value("Item", {"item_name": "TCS Item"}, "name")
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index ded45b8..e2a70c2 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -54,6 +54,8 @@
   "column_break_26",
   "total",
   "net_total",
+  "tax_withholding_net_total",
+  "base_tax_withholding_net_total",
   "section_break_48",
   "pricing_rules",
   "raw_material_details",
@@ -1221,6 +1223,26 @@
    "oldfieldtype": "Section Break"
   },
   {
+   "default": "0",
+   "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,
+   "options": "currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
    "fieldname": "column_break_99",
    "fieldtype": "Column Break"
   },
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index b8203bd..d471783 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -44,6 +44,7 @@
   "discount_amount",
   "base_rate_with_margin",
   "sec_break2",
+  "apply_tds",
   "rate",
   "amount",
   "item_tax_template",
@@ -889,6 +890,12 @@
   {
    "fieldname": "column_break_54",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "1",
+   "fieldname": "apply_tds",
+   "fieldtype": "Check",
+   "label": "Apply TDS"
   }
  ],
  "idx": 1,
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 2624181..73d5d3e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -317,4 +317,4 @@
 erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
 erpnext.patches.v13_0.update_schedule_type_in_loans
 erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
-erpnext.patches.v14_0.update_tds_fields
+erpnext.patches.v14_0.update_partial_tds_fields
diff --git a/erpnext/patches/v14_0/update_tds_fields.py b/erpnext/patches/v14_0/update_partial_tds_fields.py
similarity index 64%
rename from erpnext/patches/v14_0/update_tds_fields.py
rename to erpnext/patches/v14_0/update_partial_tds_fields.py
index a333c5d..5ccc2dc 100644
--- a/erpnext/patches/v14_0/update_tds_fields.py
+++ b/erpnext/patches/v14_0/update_partial_tds_fields.py
@@ -25,5 +25,21 @@
 			).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