fix: Tax breakup based on items, missing GST fields (#27524)

* fix: Tax breakup based on items

* fix: added gst fields,warehouse validation to pos inv,patch

* fix: tax breakup test fix, eway bill hsn fix

Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com>
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index d6e41e6..27d678b 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -40,6 +40,7 @@
 		self.validate_change_amount()
 		self.validate_change_account()
 		self.validate_item_cost_centers()
+		self.validate_warehouse()
 		self.validate_serialised_or_batched_item()
 		self.validate_stock_availablility()
 		self.validate_return_items_qty()
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index da0c315..3720ac3 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1420,15 +1420,22 @@
 		itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
 
 		expected_itemised_tax = {
-			"999800": {
+			"_Test Item": {
 				"Service Tax": {
 					"tax_rate": 10.0,
-					"tax_amount": 1500.0
+					"tax_amount": 1000.0
+				}
+			},
+			"_Test Item 2": {
+				"Service Tax": {
+					"tax_rate": 10.0,
+					"tax_amount": 500.0
 				}
 			}
 		}
 		expected_itemised_taxable_amount = {
-			"999800": 15000.0
+			"_Test Item": 10000.0,
+			"_Test Item 2": 5000.0
 		}
 
 		self.assertEqual(itemised_tax, expected_itemised_tax)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 2148e6e..cee796e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -307,4 +307,5 @@
 erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
 erpnext.patches.v13_0.update_dates_in_tax_withholding_category
 erpnext.patches.v14_0.update_opportunity_currency_fields
+erpnext.patches.v13_0.gst_fields_for_pos_invoice
 erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes
diff --git a/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py
new file mode 100644
index 0000000..5b790d9
--- /dev/null
+++ b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py
@@ -0,0 +1,44 @@
+from __future__ import unicode_literals
+
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name'])
+	if not company:
+		return
+
+	hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
+		fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
+		allow_on_submit=1, print_hide=1, fetch_if_empty=1)
+	nil_rated_exempt = dict(fieldname='is_nil_exempt', label='Is Nil Rated or Exempted',
+		fieldtype='Check', fetch_from='item_code.is_nil_exempt', insert_after='gst_hsn_code',
+		print_hide=1)
+	is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST',
+		fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt',
+		print_hide=1)
+	taxable_value = dict(fieldname='taxable_value', label='Taxable Value',
+		fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
+		print_hide=1)
+	sales_invoice_gst_fields = [
+			dict(fieldname='billing_address_gstin', label='Billing Address GSTIN',
+				fieldtype='Data', insert_after='customer_address', read_only=1,
+				fetch_from='customer_address.gstin', print_hide=1),
+			dict(fieldname='customer_gstin', label='Customer GSTIN',
+				fieldtype='Data', insert_after='shipping_address_name',
+				fetch_from='shipping_address_name.gstin', print_hide=1),
+			dict(fieldname='place_of_supply', label='Place of Supply',
+				fieldtype='Data', insert_after='customer_gstin',
+				print_hide=1, read_only=1),
+			dict(fieldname='company_gstin', label='Company GSTIN',
+				fieldtype='Data', insert_after='company_address',
+				fetch_from='company_address.gstin', print_hide=1, read_only=1),
+		]
+
+	custom_fields = {
+		'POS Invoice': sales_invoice_gst_fields,
+		'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
+	}
+
+	create_custom_fields(custom_fields, update=True)
\ No newline at end of file
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 79dc4b8..ce346bc 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -483,6 +483,7 @@
 		'Purchase Order': purchase_invoice_gst_fields,
 		'Purchase Receipt': purchase_invoice_gst_fields,
 		'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields,
+		'POS Invoice': sales_invoice_gst_fields,
 		'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category,
 		'Payment Entry': payment_entry_fields,
 		'Journal Entry': journal_entry_fields,
@@ -501,6 +502,7 @@
 		'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
 		'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
 		'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
+		'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
 		'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
 		'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
 		'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index bf06d4a..903168d 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -117,7 +117,7 @@
 	else:
 		return [_("Item"), _("Taxable Amount")] + tax_accounts
 
-def get_itemised_tax_breakup_data(doc, account_wise=False):
+def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False):
 	itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise)
 
 	itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
@@ -125,28 +125,29 @@
 	if not frappe.get_meta(doc.doctype + " Item").has_field('gst_hsn_code'):
 		return itemised_tax, itemised_taxable_amount
 
-	item_hsn_map = frappe._dict()
-	for d in doc.items:
-		item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code"))
+	if hsn_wise:
+		item_hsn_map = frappe._dict()
+		for d in doc.items:
+			item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code"))
 
 	hsn_tax = {}
 	for item, taxes in itemised_tax.items():
-		hsn_code = item_hsn_map.get(item)
-		hsn_tax.setdefault(hsn_code, frappe._dict())
+		item_or_hsn = item if not hsn_wise else item_hsn_map.get(item)
+		hsn_tax.setdefault(item_or_hsn, frappe._dict())
 		for tax_desc, tax_detail in taxes.items():
 			key = tax_desc
 			if account_wise:
 				key = tax_detail.get('tax_account')
-			hsn_tax[hsn_code].setdefault(key, {"tax_rate": 0, "tax_amount": 0})
-			hsn_tax[hsn_code][key]["tax_rate"] = tax_detail.get("tax_rate")
-			hsn_tax[hsn_code][key]["tax_amount"] += tax_detail.get("tax_amount")
+			hsn_tax[item_or_hsn].setdefault(key, {"tax_rate": 0, "tax_amount": 0})
+			hsn_tax[item_or_hsn][key]["tax_rate"] = tax_detail.get("tax_rate")
+			hsn_tax[item_or_hsn][key]["tax_amount"] += tax_detail.get("tax_amount")
 
 	# set taxable amount
 	hsn_taxable_amount = frappe._dict()
 	for item in itemised_taxable_amount:
-		hsn_code = item_hsn_map.get(item)
-		hsn_taxable_amount.setdefault(hsn_code, 0)
-		hsn_taxable_amount[hsn_code] += itemised_taxable_amount.get(item)
+		item_or_hsn = item if not hsn_wise else item_hsn_map.get(item)
+		hsn_taxable_amount.setdefault(item_or_hsn, 0)
+		hsn_taxable_amount[item_or_hsn] += itemised_taxable_amount.get(item)
 
 	return hsn_tax, hsn_taxable_amount
 
@@ -440,7 +441,7 @@
 		data.itemList = []
 		data.totalValue = doc.total
 
-		data = get_item_list(data, doc)
+		data = get_item_list(data, doc, hsn_wise=True)
 
 		disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total')
 		data.totInvValue = doc.grand_total if disable_rounded else doc.rounded_total
@@ -551,7 +552,7 @@
 
 	return data
 
-def get_item_list(data, doc):
+def get_item_list(data, doc, hsn_wise=False):
 	for attr in ['cgstValue', 'sgstValue', 'igstValue', 'cessValue', 'OthValue']:
 		data[attr] = 0
 
@@ -563,7 +564,7 @@
 		'cess_account': ['cessRate', 'cessValue']
 	}
 	item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol']
-	hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True)
+	hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise)
 	for hsn_code, taxable_amount in hsn_taxable_amount.items():
 		item_data = frappe._dict()
 		if not hsn_code: