diff --git a/india_compliance/gst_india/constants/e_waybill.py b/india_compliance/gst_india/constants/e_waybill.py index 13a8d5cc1e..b6c723371d 100644 --- a/india_compliance/gst_india/constants/e_waybill.py +++ b/india_compliance/gst_india/constants/e_waybill.py @@ -32,6 +32,8 @@ } PERMITTED_DOCTYPES = list(ADDRESS_FIELDS.keys()) +BUYING_DOCTYPES = {doctype for doctype, address in ADDRESS_FIELDS.items() if address is buying_address} + CANCEL_REASON_CODES = { "Duplicate": "1", "Order Cancelled": "2", diff --git a/india_compliance/gst_india/overrides/test_transaction_data.py b/india_compliance/gst_india/overrides/test_transaction_data.py index 42c4fc0771..89559ca59a 100644 --- a/india_compliance/gst_india/overrides/test_transaction_data.py +++ b/india_compliance/gst_india/overrides/test_transaction_data.py @@ -97,6 +97,7 @@ def test_get_address_details(self): "city": "Test City", "pincode": 380015, "country_code": None, + "gst_category": "Registered Regular", }, ) diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 7d462905e7..2ec4842bde 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -35,6 +35,7 @@ ) from india_compliance.gst_india.constants.e_waybill import ( ADDRESS_FIELDS, + BUYING_DOCTYPES, CANCEL_REASON_CODES, CONSIGNMENT_STATUS, EXTEND_VALIDITY_REASON_CODES, @@ -1657,10 +1658,16 @@ def set_party_address_details(self): self.bill_from = self.get_address_details(address.bill_from) # Defaults - # billing state is changed for SEZ, hence copy() self.ship_to = self.bill_to.copy() self.ship_from = self.bill_from.copy() + # for SEZ, e-Waybill API expects billing state as 96 - Other Countries + # only billing state (fromStateCode/toStateCode), not shipping (actFromStateCode/actToStateCode) + # ERROR CODE: 641, 642 + for side in (self.bill_from, self.bill_to): + if side.gst_category == "SEZ": + side.state_number = 96 + if has_different_to_address and has_different_from_address: transaction_type = 4 self.ship_to = self.get_address_details(address.ship_to) @@ -1679,11 +1686,7 @@ def set_party_address_details(self): to_party = self.transaction_details.party_name from_party = self.transaction_details.company_name - if self.doc.doctype in ( - "Purchase Invoice", - "Purchase Receipt", - "Subcontracting Receipt", - ): + if self.doc.doctype in BUYING_DOCTYPES: to_party, from_party = from_party, to_party if self.doc.get("is_return"): @@ -1692,9 +1695,6 @@ def set_party_address_details(self): self.bill_to.legal_name = to_party or self.bill_to.address_title self.bill_from.legal_name = from_party or self.bill_from.address_title - if self.doc.gst_category == "SEZ": - self.bill_to.state_number = 96 - def get_address_details(self, *args, **kwargs): address_details = super().get_address_details(*args, **kwargs) address_details.state_number = int(address_details.state_number) @@ -1728,6 +1728,7 @@ def get_transaction_data(self): if self.sandbox_mode: REGISTERED_GSTIN = "05AAACG2115R1ZN" OTHER_GSTIN = "05AAACG2140A1ZL" + SEZ_GSTIN = "27AAJCS5738D1Z6" self.transaction_details.update( { @@ -1766,12 +1767,18 @@ def _get_sandbox_gstin(address, key): if address.gstin == "URP": return address.gstin - return sandbox_gstin.get((self.doc.doctype, self.doc.get("is_return") or 0))[key] + gstin = sandbox_gstin.get((self.doc.doctype, self.doc.get("is_return") or 0))[key] + + # SEZ party (non-company side) needs a different GSTIN + if address.gst_category == "SEZ" and gstin == OTHER_GSTIN: + return SEZ_GSTIN + + return gstin self.bill_from.gstin = _get_sandbox_gstin(self.bill_from, 0) self.bill_to.gstin = _get_sandbox_gstin(self.bill_to, 1) - if self.doc.get("is_return"): + if self.doc.get("is_return") or self.bill_to.gst_category == "SEZ": to_state_code = self.bill_to.state_number else: to_state_code = int(self.transaction_details.pos_state_code) diff --git a/india_compliance/gst_india/utils/test_e_waybill.py b/india_compliance/gst_india/utils/test_e_waybill.py index e0156b5654..bcaedb68df 100644 --- a/india_compliance/gst_india/utils/test_e_waybill.py +++ b/india_compliance/gst_india/utils/test_e_waybill.py @@ -1291,6 +1291,117 @@ def test_e_waybill_for_inter_state_sales_return(self): self.assertEqual(e_waybill_data.get("supplyType"), "I") self.assertEqual(e_waybill_data.get("subSupplyType"), 7) + @change_settings("GST Settings", {"enable_overseas_transactions": 1}) + def test_e_waybill_for_sez_outward_invoice(self): + si = create_sales_invoice( + vehicle_no="GJ07DL9009", + company_address="_Test Indian Registered Company-Billing", + customer_address="_Test Registered Customer-Billing-1", + is_out_state=1, + is_export_with_gst=1, + ) + + e_waybill_data = EWaybillData(si).get_data() + + self.assertEqual(e_waybill_data.get("toStateCode"), 96) + self.assertEqual(e_waybill_data.get("fromStateCode"), 24) + + @change_settings("GST Settings", {"enable_overseas_transactions": 1}) + def test_e_waybill_for_sez_sales_return(self): + si = create_sales_invoice( + vehicle_no="GJ07DL9009", + company_address="_Test Indian Registered Company-Billing", + customer_address="_Test Registered Customer-Billing-1", + is_out_state=1, + is_export_with_gst=1, + ) + + credit_note = make_return_doc("Sales Invoice", si.name) + credit_note.vehicle_no = "GJ07DL9009" + credit_note.save() + credit_note.submit() + + e_waybill_data = EWaybillData(credit_note).get_data() + + self.assertEqual(e_waybill_data.get("fromStateCode"), 96) + self.assertEqual(e_waybill_data.get("toStateCode"), 24) + + @change_settings("GST Settings", {"enable_e_waybill_for_sc": 1}) + def test_e_waybill_for_sez_stock_entry(self): + se = create_transaction( + doctype="Stock Entry", + stock_entry_type="Send to Subcontractor", + purpose="Send to Subcontractor", + bill_from_address="_Test Indian Registered Company-Billing", + bill_to_address="_Test Registered Customer-Billing-1", + vehicle_no="GJ07DL9009", + items=[ + { + "item_code": "_Test Trading Goods 1", + "qty": 1, + "gst_hsn_code": "61149090", + "s_warehouse": "Finished Goods - _TIRC", + "t_warehouse": "Goods In Transit - _TIRC", + "amount": 100, + "taxable_value": 100, + } + ], + company="_Test Indian Registered Company", + base_grand_total=100, + ) + + # reload to trigger onload which sets company_gstin, supplier_gstin + se = load_doc("Stock Entry", se.name, "submit") + + e_waybill_data = EWaybillData(se).get_data() + + self.assertEqual(e_waybill_data.get("toStateCode"), 96) + self.assertEqual(e_waybill_data.get("fromStateCode"), 24) + self.assertEqual(e_waybill_data.get("actToStateCode"), 24) + + @change_settings( + "GST Settings", + {"enable_e_waybill_from_pi": 1, "enable_overseas_transactions": 1}, + ) + def test_e_waybill_for_sez_purchase_invoice(self): + pi = create_purchase_invoice( + vehicle_no="GJ07DL9009", + supplier_address="_Test Registered Supplier-Billing-2", + billing_address="_Test Indian Registered Company-Billing", + is_out_state=1, + ) + + e_waybill_data = EWaybillData(pi).get_data() + + # bill_from = supplier (SEZ), bill_to = company + self.assertEqual(e_waybill_data.get("fromStateCode"), 96) + self.assertEqual(e_waybill_data.get("toStateCode"), 24) + self.assertEqual(e_waybill_data.get("actFromStateCode"), 24) + + @change_settings( + "GST Settings", + {"enable_e_waybill_from_pi": 1, "enable_overseas_transactions": 1}, + ) + def test_e_waybill_for_sez_purchase_return(self): + pi = create_purchase_invoice( + vehicle_no="GJ07DL9009", + supplier_address="_Test Registered Supplier-Billing-2", + billing_address="_Test Indian Registered Company-Billing", + is_out_state=1, + ) + + debit_note = make_return_doc("Purchase Invoice", pi.name) + debit_note.vehicle_no = "GJ07DL9009" + debit_note.save() + debit_note.submit() + + e_waybill_data = EWaybillData(debit_note).get_data() + + # return swaps from/to: bill_from = company, bill_to = supplier (SEZ) + self.assertEqual(e_waybill_data.get("fromStateCode"), 24) + self.assertEqual(e_waybill_data.get("toStateCode"), 96) + self.assertEqual(e_waybill_data.get("actToStateCode"), 24) + # helper functions def _generate_e_waybill(self, docname=None, doctype="Sales Invoice", test_data=None, force=False): """ diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index 16302bb5c8..31ccf32766 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -387,6 +387,7 @@ def get_address_details(self, address_name, validate_gstin=False): "country", "gstin", "gst_state_number", + "gst_category", ), as_dict=True, ) @@ -434,6 +435,7 @@ def get_address_details(self, address_name, validate_gstin=False): ), "pincode": int(address.pincode), "country_code": get_validated_country_code(address.country), + "gst_category": address.gst_category, } ) diff --git a/india_compliance/tests/test_records.json b/india_compliance/tests/test_records.json index c79e3ec9e7..4e8afb79a9 100644 --- a/india_compliance/tests/test_records.json +++ b/india_compliance/tests/test_records.json @@ -483,6 +483,23 @@ } ] }, + { + "name": "_Test Registered Supplier-Billing-2", + "address_type": "Billing", + "address_line1": "Test Address - 7", + "city": "Test City", + "state": "Gujarat", + "pincode": "380015", + "country": "India", + "gstin": "24AABCR6898M1ZN", + "gst_category": "SEZ", + "links": [ + { + "link_doctype": "Supplier", + "link_name": "_Test Registered Supplier" + } + ] + }, { "name": "_Test Registered Composition Supplier-Billing", "address_type": "Billing",