diff --git a/india_compliance/gst_india/doctype/gst_inward_supply/gst_inward_supply.json b/india_compliance/gst_india/doctype/gst_inward_supply/gst_inward_supply.json index 1da9c332f7..96091e8262 100644 --- a/india_compliance/gst_india/doctype/gst_inward_supply/gst_inward_supply.json +++ b/india_compliance/gst_india/doctype/gst_inward_supply/gst_inward_supply.json @@ -23,6 +23,8 @@ "supply_type", "classification", "is_reverse_charge", + "is_downloaded_from_2a", + "is_downloaded_from_2b", "section_break_16", "items", "section_break_ykls", @@ -394,11 +396,27 @@ "fieldname": "taxable_value", "fieldtype": "Float", "label": "Taxable Value" + }, + { + "default": "0", + "fieldname": "is_downloaded_from_2b", + "fieldtype": "Check", + "hidden": 1, + "label": "Downloaded from 2B", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_downloaded_from_2a", + "fieldtype": "Check", + "hidden": 1, + "label": "Downloaded from 2A", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-11-16 16:23:13.402870", + "modified": "2024-12-12 17:28:52.668508", "modified_by": "Administrator", "module": "GST India", "name": "GST Inward Supply", diff --git a/india_compliance/gst_india/utils/gstr_2/gstr.py b/india_compliance/gst_india/utils/gstr_2/gstr.py index 890285f761..7677e0a767 100644 --- a/india_compliance/gst_india/utils/gstr_2/gstr.py +++ b/india_compliance/gst_india/utils/gstr_2/gstr.py @@ -47,8 +47,7 @@ def __init__(self, company, gstin, return_period, data, gen_date_2b): self.setup() def setup(self): - self.existing_transaction = {} - pass + self.existing_transaction = self.get_existing_transaction() def create_transactions(self, category, suppliers): if not suppliers: @@ -75,17 +74,14 @@ def create_transactions(self, category, suppliers): if transaction.get("unique_key") in self.existing_transaction: self.existing_transaction.pop(transaction.get("unique_key")) - self.delete_missing_transactions() - - def delete_missing_transactions(self): - """ - For GSTR2a, transactions are reflected immediately after it's pushed to GSTR-1. - At times, it may later be removed from GSTR-1. + self.handle_missing_transactions() - In such cases, we need to delete such unfilled transactions not present in the latest data. - """ + def handle_missing_transactions(self): return + def get_existing_transaction(self): + return {} + def get_all_transactions(self, category, suppliers): transactions = [] for supplier in suppliers: @@ -110,6 +106,7 @@ def get_transaction(self, category, supplier, invoice): classification=category.value, **self.get_supplier_details(supplier), **self.get_invoice_details(invoice), + **self.get_download_details(), items=self.get_transaction_items(invoice), ) @@ -134,6 +131,9 @@ def get_supplier_details(self, supplier): def get_invoice_details(self, invoice): return {} + def get_download_details(self): + return {} + def get_transaction_items(self, invoice): return [ self.get_transaction_item(frappe._dict(item)) diff --git a/india_compliance/gst_india/utils/gstr_2/gstr_2a.py b/india_compliance/gst_india/utils/gstr_2/gstr_2a.py index 7459f2da16..c96ffc58ff 100644 --- a/india_compliance/gst_india/utils/gstr_2/gstr_2a.py +++ b/india_compliance/gst_india/utils/gstr_2/gstr_2a.py @@ -14,9 +14,9 @@ def map_date_format(date_str, source_format, target_format): class GSTR2a(GSTR): def setup(self): + super().setup() self.all_gstins = set() self.cancelled_gstins = {} - self.existing_transaction = self.get_existing_transaction() def get_existing_transaction(self): category = type(self).__name__[6:] @@ -37,7 +37,14 @@ def get_existing_transaction(self): for transaction in existing_transactions } - def delete_missing_transactions(self): + def handle_missing_transactions(self): + """ + For GSTR2a, transactions are reflected immediately after it's pushed to GSTR-1. + At times, it may later be removed from GSTR-1. + + In such cases, we need to delete such unfilled transactions not present in the latest data. + """ + if self.existing_transaction: for inward_supply_name in self.existing_transaction.values(): frappe.delete_doc("GST Inward Supply", inward_supply_name) @@ -60,6 +67,9 @@ def get_supplier_details(self, supplier): return supplier_details + def get_download_details(self): + return {"is_downloaded_from_2a": 1} + def update_gstins_list(self, supplier_details): self.all_gstins.add(supplier_details.get("supplier_gstin")) diff --git a/india_compliance/gst_india/utils/gstr_2/gstr_2b.py b/india_compliance/gst_india/utils/gstr_2/gstr_2b.py index b062cabc9d..bc8eeff44e 100644 --- a/india_compliance/gst_india/utils/gstr_2/gstr_2b.py +++ b/india_compliance/gst_india/utils/gstr_2/gstr_2b.py @@ -1,10 +1,65 @@ import frappe +from frappe.query_builder.functions import IfNull from india_compliance.gst_india.utils import parse_datetime from india_compliance.gst_india.utils.gstr_2.gstr import GSTR, get_mapped_value class GSTR2b(GSTR): + def get_existing_transaction(self): + category = type(self).__name__[6:] + + gst_is = frappe.qb.DocType("GST Inward Supply") + existing_transactions = ( + frappe.qb.from_(gst_is) + .select(gst_is.name, gst_is.supplier_gstin, gst_is.bill_no) + .where(gst_is.return_period_2b == self.return_period) + .where(gst_is.classification == category) + ).run(as_dict=True) + + return { + f"{transaction.get('supplier_gstin', '')}-{transaction.get('bill_no', '')}": transaction.get( + "name" + ) + for transaction in existing_transactions + } + + def handle_missing_transactions(self): + """ + For GSTR2b, only filed transactions are reported. They may be removed from GSTR-2b later + if marked as pending / rejected from IMS Dashboard. + + In such cases, + 1) we need to clear the return_period_2b as this could change in future. + 2) safer to clear delete them as well if no matching transactions are found (possibly rejected). + """ + if not self.existing_transaction: + return + + missing_transactions = list(self.existing_transaction.values()) + + # clear return_period_2b + inward_supply = frappe.qb.DocType("GST Inward Supply") + ( + frappe.qb.update(inward_supply) + .set(inward_supply.return_period_2b, "") + .set(inward_supply.is_downloaded_from_2b, 0) + .where(inward_supply.name.isin(missing_transactions)) + .run() + ) + + # delete unmatched transactions + unmatched_transactions = ( + frappe.qb.from_(inward_supply) + .select(inward_supply.name) + .where(inward_supply.name.isin(missing_transactions)) + .where(IfNull(inward_supply.link_name, "") == "") + .run(pluck=True) + ) + + for transaction_name in unmatched_transactions: + frappe.delete_doc("GST Inward Supply", transaction_name) + def get_transaction(self, category, supplier, invoice): transaction = super().get_transaction(category, supplier, invoice) transaction.return_period_2b = self.return_period @@ -19,6 +74,9 @@ def get_supplier_details(self, supplier): "sup_return_period": supplier.supprd, } + def get_download_details(self): + return {"is_downloaded_from_2b": 1} + def get_transaction_item(self, item): return { "item_number": item.num, diff --git a/india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py b/india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py index 4126583b3c..09c261d83f 100644 --- a/india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py +++ b/india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py @@ -115,6 +115,7 @@ def test_gstr2a_b2b(self): "gstr_3b_filled": 1, "gstr_1_filing_date": date(2019, 11, 18), "registration_cancel_date": date(2019, 8, 27), + "is_downloaded_from_2a": 1, }, doc, ) @@ -162,6 +163,7 @@ def test_gstr2a_b2ba(self): "gstr_3b_filled": 1, "gstr_1_filing_date": date(2020, 5, 12), "registration_cancel_date": date(2019, 8, 27), + "is_downloaded_from_2a": 1, }, doc, ) @@ -202,6 +204,7 @@ def test_gstr2a_cdn(self): "897ADG56RTY78956HYUG90BNHHIJK453GFTD99845672FDHHHSHGFH4567FG56TR" ), "irn_gen_date": date(2019, 12, 24), + "is_downloaded_from_2a": 1, }, doc, ) @@ -239,6 +242,7 @@ def test_gstr2a_cdna(self): "gstr_3b_filled": 1, "gstr_1_filing_date": date(2019, 11, 18), "registration_cancel_date": date(2019, 8, 27), + "is_downloaded_from_2a": 1, }, doc, ) @@ -261,6 +265,7 @@ def test_gstr2a_isd(self): "cgst": 20, "sgst": 20, "cess": 20, + "is_downloaded_from_2a": 1, }, doc, ) @@ -283,6 +288,7 @@ def test_gstr2a_impg(self): "taxable_value": 123.02, "igst": 123.02, "cess": 0.5, + "is_downloaded_from_2a": 1, }, doc, ) @@ -305,6 +311,7 @@ def test_gstr2a_impgsez(self): "cgst": 0, "sgst": 0, "cess": 0.5, + "is_downloaded_from_2a": 1, }, doc, ) diff --git a/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v3_0.py b/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v3_0.py index 01538c8932..22b537fdcc 100644 --- a/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v3_0.py +++ b/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v3_0.py @@ -67,6 +67,7 @@ def test_gstr2b_b2b(self): "cess": 0, } ], + "is_downloaded_from_2b": 1, }, doc, ) @@ -107,6 +108,7 @@ def test_gstr2b_b2ba(self): "cess": 0, } ], + "is_downloaded_from_2b": 1, }, doc, ) @@ -147,6 +149,7 @@ def test_gstr2b_cdnr(self): "cess": 0, } ], + "is_downloaded_from_2b": 1, }, doc, ) @@ -185,6 +188,7 @@ def test_gstr2b_cdnra(self): "cess": 0, } ], + "is_downloaded_from_2b": 1, }, doc, ) @@ -208,6 +212,7 @@ def test_gstr2b_isd(self): "cgst": 200, "sgst": 200, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -234,6 +239,7 @@ def test_gstr2b_isda(self): "cgst": 200, "sgst": 200, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -253,6 +259,7 @@ def test_gstr2b_impg(self): "taxable_value": 123.02, "igst": 123.02, "cess": 0.5, + "is_downloaded_from_2b": 1, }, doc, ) @@ -274,6 +281,7 @@ def test_gstr2b_impgsez(self): "taxable_value": 123.02, "igst": 123.02, "cess": 0.5, + "is_downloaded_from_2b": 1, }, doc, ) diff --git a/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v4_0.py b/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v4_0.py index fead03e510..a32e7db70f 100644 --- a/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v4_0.py +++ b/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v4_0.py @@ -61,6 +61,7 @@ def test_gstr2b_b2b(self): "cgst": 0, "sgst": 0, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -95,6 +96,7 @@ def test_gstr2b_b2ba(self): "cgst": 0, "sgst": 0, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -129,6 +131,7 @@ def test_gstr2b_cdnr(self): "cgst": 0, "sgst": 0, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -161,6 +164,7 @@ def test_gstr2b_cdnra(self): "cgst": 0, "sgst": 0, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -184,6 +188,7 @@ def test_gstr2b_isd(self): "cgst": 200, "sgst": 200, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -210,6 +215,7 @@ def test_gstr2b_isda(self): "cgst": 200, "sgst": 200, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -229,6 +235,7 @@ def test_gstr2b_impg(self): "taxable_value": 123.02, "igst": 123.02, "cess": 0.5, + "is_downloaded_from_2b": 1, }, doc, ) @@ -250,6 +257,7 @@ def test_gstr2b_impgsez(self): "taxable_value": 123.02, "igst": 123.02, "cess": 0.5, + "is_downloaded_from_2b": 1, }, doc, ) diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index e15618118d..37fa0ca3d1 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -66,3 +66,4 @@ india_compliance.patches.v15.update_action_for_gst_inward_supply india_compliance.patches.v15.set_default_for_new_gst_category_notification india_compliance.patches.v15.make_e_invoice_log_extensible india_compliance.patches.v15.migrate_boe_taxes_to_ic_taxes +india_compliance.patches.v15.set_download_document_for_2a_2b diff --git a/india_compliance/patches/v15/set_download_document_for_2a_2b.py b/india_compliance/patches/v15/set_download_document_for_2a_2b.py new file mode 100644 index 0000000000..b5eba2e0f2 --- /dev/null +++ b/india_compliance/patches/v15/set_download_document_for_2a_2b.py @@ -0,0 +1,18 @@ +import frappe + + +def execute(): + if frappe.db.has_column("GST Inward Supply", "is_downloaded_from_2a"): + # set "is_downloaded_from_2a" to "1" for all GST Inward Supply + frappe.db.set_value( + "GST Inward Supply", {"name": ["is", "set"]}, "is_downloaded_from_2a", 1 + ) + + if frappe.db.has_column("GST Inward Supply", "is_downloaded_from_2b"): + # set "is_downloaded_from_2b" to "1" where 2B return period is set + frappe.db.set_value( + "GST Inward Supply", + {"return_period_2b": ["is", "set"]}, + "is_downloaded_from_2b", + 1, + )