From 072b1d5868d052d206a280c2aae64aa6685a597f Mon Sep 17 00:00:00 2001 From: Vishakh Desai Date: Mon, 5 Aug 2024 11:36:49 +0530 Subject: [PATCH 01/68] fix: GSTR-1 filing apis --- .../gst_india/api_classes/base.py | 8 ++- .../gst_india/api_classes/taxpayer_base.py | 10 +++ .../gst_india/api_classes/taxpayer_returns.py | 70 +++++++++++++++++++ 3 files changed, 85 insertions(+), 3 deletions(-) diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py index 9e2b00301a..11fd6e44a3 100644 --- a/india_compliance/gst_india/api_classes/base.py +++ b/india_compliance/gst_india/api_classes/base.py @@ -85,6 +85,9 @@ def get(self, *args, **kwargs): def post(self, *args, **kwargs): return self._make_request("POST", *args, **kwargs) + def put(self, *args, **kwargs): + return self._make_request("PUT", *args, **kwargs) + def _make_request( self, method, @@ -94,7 +97,7 @@ def _make_request( json=None, ): method = method.upper() - if method not in ("GET", "POST"): + if method not in ("GET", "POST", "PUT"): frappe.throw(_("Invalid method {0}").format(method)) request_args = frappe._dict( @@ -108,7 +111,6 @@ def _make_request( ) log_headers = request_args.headers.copy() - log = frappe._dict( **self.default_log_values, url=request_args.url, @@ -116,7 +118,7 @@ def _make_request( request_headers=log_headers, ) - if method == "POST" and json: + if method in ["POST", "PUT"] and json: request_args.json = json json_data = json.copy() diff --git a/india_compliance/gst_india/api_classes/taxpayer_base.py b/india_compliance/gst_india/api_classes/taxpayer_base.py index acb4311442..369b21060f 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_base.py +++ b/india_compliance/gst_india/api_classes/taxpayer_base.py @@ -150,6 +150,13 @@ def refresh_auth_token(self): endpoint="authenticate", ) + def initiate_otp_for_evc(self, form_type): + return self.get( + action="EVCOTP", + params={"pan": self.company_gstin[2:12], "form_type": form_type}, + endpoint="authenticate", + ) + def decrypt_response(self, response): values = {} @@ -293,6 +300,9 @@ def get(self, *args, **kwargs): def post(self, *args, **kwargs): return self._request("post", *args, **kwargs) + def put(self, *args, **kwargs): + return self._request("put", *args, **kwargs) + def before_request(self, request_args): self.encrypt_request(request_args.get("json")) diff --git a/india_compliance/gst_india/api_classes/taxpayer_returns.py b/india_compliance/gst_india/api_classes/taxpayer_returns.py index 5cf7369959..db2ba0645b 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_returns.py +++ b/india_compliance/gst_india/api_classes/taxpayer_returns.py @@ -24,6 +24,15 @@ def download_files(self, return_period, token, otp=None): return_period, token, action="FILEDET", endpoint="returns", otp=otp ) + def get_return_status(self, return_period, otp=None): + return self.get( + action="RETSTATUS", + return_period=return_period, + params={"ret_period": return_period}, + endpoint="returns", + otp=otp, + ) + class GSTR2bAPI(ReturnsAPI): API_NAME = "GSTR-2B" @@ -75,3 +84,64 @@ def get_einvoice_data(self, section, return_period, otp=None): endpoint="returns/einvoice", otp=otp, ) + + def save_gstr_1_data(self, return_period, data, otp=None): + return self.put( + action="RETSAVE", + return_period=return_period, + json=data, + endpoint="returns/gstr1", + otp=otp, + ) + + def submit_gstr_1_data(self, return_period, otp=None): + return self.post( + action="RETSUBMIT", + return_period=return_period, + json={ + "gstn": self.company_gstin, + "ret_period": return_period, + "generate_summary": "Y", + }, + endpoint="returns/gstr1", + otp=otp, + ) + + def proceed_to_file(self, return_period, otp=None): + return self.post( + action="PROCEEDFILE", + return_period=return_period, + json={"gstn": self.company_gstin, "ret_period": return_period}, + endpoint="returns/gstr1", + otp=otp, + ) + + def get_gstr_1_summary(self, return_period, otp=None): + return self.get( + action="RETSUM", + return_period=return_period, + params={"ret_period": return_period}, + endpoint="returns/gstr1", + otp=otp, + ) + + def file_gstr_1(self, return_period, data, summary_data, otp=None): + # TODO: encrypt data with EVC (using AES 256) + signed_data = None + pan = self.company_gstin[2:12] + # TODO: encrypt summary payload with pan + otp (using HMAC-SHA256) + signed_summary_payload = None + + return self.post( + action="RETFILE", + return_period=return_period, + json={ + "action": "RETFILE", + "data": signed_data, + "sign": signed_summary_payload, + "st": "EVC", + "sid": pan, + }, + endpoint="returns/gstr1", + otp=otp, + ) From 610c8429f16a8faa05332330b6e825b3a1022d6f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sun, 11 Aug 2024 17:17:26 +0530 Subject: [PATCH 02/68] fix: update APIs as per testing --- .../gst_india/api_classes/base.py | 2 +- .../gst_india/api_classes/taxpayer_base.py | 34 ++++++++- .../gst_india/api_classes/taxpayer_returns.py | 69 ++++++------------- 3 files changed, 55 insertions(+), 50 deletions(-) diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py index 11fd6e44a3..05285061e5 100644 --- a/india_compliance/gst_india/api_classes/base.py +++ b/india_compliance/gst_india/api_classes/base.py @@ -260,7 +260,7 @@ def handle_http_code(self, status_code, response_json): raise GatewayTimeoutError def generate_request_id(self, length=12): - return frappe.generate_hash(length=length) + return f"IC{frappe.generate_hash(length=length - 2)}".upper() def mask_sensitive_info(self, log): request_headers = log.request_headers diff --git a/india_compliance/gst_india/api_classes/taxpayer_base.py b/india_compliance/gst_india/api_classes/taxpayer_base.py index 369b21060f..eaed064fa6 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_base.py +++ b/india_compliance/gst_india/api_classes/taxpayer_base.py @@ -150,10 +150,10 @@ def refresh_auth_token(self): endpoint="authenticate", ) - def initiate_otp_for_evc(self, form_type): + def initiate_otp_for_evc(self, pan, form_type): return self.get( action="EVCOTP", - params={"pan": self.company_gstin[2:12], "form_type": form_type}, + params={"pan": pan, "form_type": form_type}, endpoint="authenticate", ) @@ -264,6 +264,7 @@ def _request( self, method, action=None, + return_type=None, return_period=None, params=None, endpoint=None, @@ -278,9 +279,19 @@ def _request( return response headers = {"auth-token": auth_token} + if return_type: + headers["rtn_typ"] = return_type + headers["userrole"] = return_type + if return_period: headers["ret_period"] = return_period + # if return_type: + # __headers = headers.copy() + # __headers.update(self.default_headers) + # __headers.pop("x-api-key", None) + # json["hdr"] = __headers + response = getattr(super(), method)( params={"action": action, **(params or {})}, headers=headers, @@ -332,6 +343,25 @@ def decrypt_response(self, response): return response + def encrypt_request(self, json): + if not json: + return + + super().encrypt_request(json) + + if json.get("data"): + b64_data = b64encode(frappe.as_json(json.get("data")).encode()) + json["data"] = aes_encrypt_data(b64_data.decode(), self.session_key) + + if json.get("st") == "EVC": + # sid may be without pipe character + # eg: with pipe: https://groups.google.com/g/gst-suvidha-provider-gsp-discussion-group/c/1EgkyDvfT7g/m/2O5Rt0tfCgAJ + sid_key = json.get("sid").encode() + json["sign"] = hmac_sha256(b64_data, sid_key) + + else: + json["hmac"] = hmac_sha256(b64_data, self.session_key) + def handle_error_response(self, response): success_value = response.get("status_cd") != 0 diff --git a/india_compliance/gst_india/api_classes/taxpayer_returns.py b/india_compliance/gst_india/api_classes/taxpayer_returns.py index db2ba0645b..b4351bded3 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_returns.py +++ b/india_compliance/gst_india/api_classes/taxpayer_returns.py @@ -24,15 +24,30 @@ def download_files(self, return_period, token, otp=None): return_period, token, action="FILEDET", endpoint="returns", otp=otp ) - def get_return_status(self, return_period, otp=None): + def get_return_status(self, return_period, reference_id, otp=None): return self.get( action="RETSTATUS", return_period=return_period, - params={"ret_period": return_period}, + params={"ret_period": return_period, "ref_id": reference_id}, endpoint="returns", otp=otp, ) + def proceed_to_file(self, return_type, return_period, otp=None): + return self.post( + return_type=return_type, + return_period=return_period, + json={ + "action": "RETNEWPTF", + "data": { + "gstn": self.company_gstin, + "ret_period": return_period, + }, # "isnil": "N" / "Y" + }, + endpoint="returns/gstrptf", + otp=otp, + ) + class GSTR2bAPI(ReturnsAPI): API_NAME = "GSTR-2B" @@ -68,6 +83,7 @@ class GSTR1API(ReturnsAPI): API_NAME = "GSTR-1" def get_gstr_1_data(self, action, return_period, otp=None): + # action: RETSUM for summary return self.get( action=action, return_period=return_period, @@ -87,61 +103,20 @@ def get_einvoice_data(self, section, return_period, otp=None): def save_gstr_1_data(self, return_period, data, otp=None): return self.put( - action="RETSAVE", return_period=return_period, - json=data, + json={"action": "RETSAVE", "data": data}, endpoint="returns/gstr1", otp=otp, ) - def submit_gstr_1_data(self, return_period, otp=None): + def file_gstr_1(self, return_period, summary_data, pan, evc_otp): return self.post( - action="RETSUBMIT", - return_period=return_period, - json={ - "gstn": self.company_gstin, - "ret_period": return_period, - "generate_summary": "Y", - }, - endpoint="returns/gstr1", - otp=otp, - ) - - def proceed_to_file(self, return_period, otp=None): - return self.post( - action="PROCEEDFILE", - return_period=return_period, - json={"gstn": self.company_gstin, "ret_period": return_period}, - endpoint="returns/gstr1", - otp=otp, - ) - - def get_gstr_1_summary(self, return_period, otp=None): - return self.get( - action="RETSUM", - return_period=return_period, - params={"ret_period": return_period}, - endpoint="returns/gstr1", - otp=otp, - ) - - def file_gstr_1(self, return_period, data, summary_data, otp=None): - # TODO: encrypt data with EVC (using AES 256) - signed_data = None - pan = self.company_gstin[2:12] - # TODO: encrypt summary payload with pan + otp (using HMAC-SHA256) - signed_summary_payload = None - - return self.post( - action="RETFILE", return_period=return_period, json={ "action": "RETFILE", - "data": signed_data, - "sign": signed_summary_payload, + "data": summary_data, "st": "EVC", - "sid": pan, + "sid": f"{pan}|{evc_otp}", }, endpoint="returns/gstr1", - otp=otp, ) From 1bf42f493827d93dfac77129aa93c5bab2d4116f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 12 Aug 2024 16:15:30 +0530 Subject: [PATCH 03/68] fix: correct api key for gstin --- india_compliance/gst_india/api_classes/taxpayer_returns.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/gst_india/api_classes/taxpayer_returns.py b/india_compliance/gst_india/api_classes/taxpayer_returns.py index b4351bded3..9301385c3f 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_returns.py +++ b/india_compliance/gst_india/api_classes/taxpayer_returns.py @@ -40,7 +40,7 @@ def proceed_to_file(self, return_type, return_period, otp=None): json={ "action": "RETNEWPTF", "data": { - "gstn": self.company_gstin, + "gstin": self.company_gstin, "ret_period": return_period, }, # "isnil": "N" / "Y" }, From ca9b58a05ae5bc03f779352754379d66285e1a25 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 13 Aug 2024 09:51:54 +0530 Subject: [PATCH 04/68] fix: working APIs for gstr-1 filing --- .../gst_india/api_classes/taxpayer_base.py | 8 -------- .../gst_india/api_classes/taxpayer_returns.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/india_compliance/gst_india/api_classes/taxpayer_base.py b/india_compliance/gst_india/api_classes/taxpayer_base.py index eaed064fa6..f37e89c96c 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_base.py +++ b/india_compliance/gst_india/api_classes/taxpayer_base.py @@ -286,12 +286,6 @@ def _request( if return_period: headers["ret_period"] = return_period - # if return_type: - # __headers = headers.copy() - # __headers.update(self.default_headers) - # __headers.pop("x-api-key", None) - # json["hdr"] = __headers - response = getattr(super(), method)( params={"action": action, **(params or {})}, headers=headers, @@ -354,8 +348,6 @@ def encrypt_request(self, json): json["data"] = aes_encrypt_data(b64_data.decode(), self.session_key) if json.get("st") == "EVC": - # sid may be without pipe character - # eg: with pipe: https://groups.google.com/g/gst-suvidha-provider-gsp-discussion-group/c/1EgkyDvfT7g/m/2O5Rt0tfCgAJ sid_key = json.get("sid").encode() json["sign"] = hmac_sha256(b64_data, sid_key) diff --git a/india_compliance/gst_india/api_classes/taxpayer_returns.py b/india_compliance/gst_india/api_classes/taxpayer_returns.py index 9301385c3f..3fe277a3d0 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_returns.py +++ b/india_compliance/gst_india/api_classes/taxpayer_returns.py @@ -109,6 +109,20 @@ def save_gstr_1_data(self, return_period, data, otp=None): otp=otp, ) + def reset_gstr_1_data(self, return_period, otp=None): + return self.post( + return_period=return_period, + json={ + "action": "RESET", + "data": { + "gstin": self.company_gstin, + "ret_period": return_period, + }, + }, + endpoint="returns/gstr1", + otp=otp, + ) + def file_gstr_1(self, return_period, summary_data, pan, evc_otp): return self.post( return_period=return_period, From 99720d512bba88f69ae7f52a3242ab3395fd7b8f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 23 Aug 2024 17:03:21 +0530 Subject: [PATCH 05/68] chore: create outline for the implementation --- .../doctype/gst_return_log/generate_gstr_1.py | 39 +++++++++++++++++++ .../doctype/gst_return_log/gst_return_log.py | 3 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 48c43a6258..fd2b069f77 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -669,3 +669,42 @@ def normalize_data(data): data[subcategory] = [*subcategory_data.values()] return data + + +class FileGSTR1: + def reset_gstr1(self): + # Make API Request + # Update Fields with reference number + pass + + def process_reset_gstr1(self): + # Unset the Fields with reference number + # Raise error in front-end if not done + # If Success, make unfiled data empty + # Emit success message / error message + pass + + def upload_gstr1(self, json_data): + pass + + def process_upload_gstr1(self): + pass + + def proceed_to_file_gstr1(self): + pass + + def process_proceed_to_file_gstr1(self): + pass + + def file_gstr1(self, pan, otp=None): + # If OTP is none, generate evc OTP + # Use summary from self + # Make API Request + pass + + +def check_return_status(self): + # Cron JOB + # check for logs with refeerence number. + # for each reference, try processing it. + pass diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py index a0307cfd5d..690500301a 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py @@ -10,12 +10,13 @@ from frappe.utils import get_datetime, get_datetime_str, get_last_day, getdate from india_compliance.gst_india.doctype.gst_return_log.generate_gstr_1 import ( + FileGSTR1, GenerateGSTR1, ) from india_compliance.gst_india.utils import is_production_api_enabled -class GSTReturnLog(GenerateGSTR1, Document): +class GSTReturnLog(GenerateGSTR1, FileGSTR1, Document): @property def status(self): return self.generation_status From c27c1e6c774841b47c1adf06294c3d70234f22d6 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 24 Aug 2024 13:28:12 +0530 Subject: [PATCH 06/68] fix: create additional fields errors or summary --- .../gst_return_log/gst_return_log.json | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json index 78857c16f5..b9fff9e579 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json @@ -20,9 +20,11 @@ "section_break_oisv", "unfiled", "filed", + "upload_error", "column_break_hxfu", "unfiled_summary", "filed_summary", + "authenticated_summary", "computed_data_section", "is_latest_data", "section_break_emlz", @@ -32,7 +34,11 @@ "reconciled_data_section", "reconcile", "column_break_ndup", - "reconcile_summary" + "reconcile_summary", + "pending_request_info_section", + "request_type", + "column_break_kohw", + "token" ], "fields": [ { @@ -187,6 +193,38 @@ "label": "Return Type", "read_only": 1, "reqd": 1 + }, + { + "fieldname": "upload_error", + "fieldtype": "Attach", + "label": "Upload Error Data", + "read_only": 1 + }, + { + "fieldname": "authenticated_summary", + "fieldtype": "Attach", + "label": "Authenticated Summary", + "read_only": 1 + }, + { + "fieldname": "pending_request_info_section", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Pending Request Info" + }, + { + "fieldname": "request_type", + "fieldtype": "Data", + "label": "Request Type" + }, + { + "fieldname": "column_break_kohw", + "fieldtype": "Column Break" + }, + { + "fieldname": "token", + "fieldtype": "Data", + "label": "Token" } ], "in_create": 1, @@ -197,7 +235,7 @@ "link_fieldname": "reference_docname" } ], - "modified": "2024-08-14 17:53:03.509293", + "modified": "2024-08-24 13:25:56.482743", "modified_by": "Administrator", "module": "GST India", "name": "GST Return Log", From eb5e20a1bcc1ce2720e73a670d1a039b2db1b7af Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 24 Aug 2024 14:44:36 +0530 Subject: [PATCH 07/68] fix: use APIs for reset / upload gstr1 --- .../doctype/gst_return_log/generate_gstr_1.py | 123 ++++++++++++++++-- .../doctype/gstr_1_beta/gstr_1_beta.py | 22 ++++ 2 files changed, 137 insertions(+), 8 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index fd2b069f77..28a8ec1c93 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -6,6 +6,7 @@ from frappe import unscrub from frappe.utils import flt +from india_compliance.gst_india.api_classes.taxpayer_returns import GSTR1API from india_compliance.gst_india.utils.gstr_1 import GSTR1_SubCategory from india_compliance.gst_india.utils.gstr_1.__init__ import ( CATEGORY_SUB_CATEGORY_MAPPING, @@ -18,6 +19,7 @@ ) from india_compliance.gst_india.utils.gstr_1.gstr_1_json_map import ( GSTR1BooksData, + convert_to_internal_data_format, summarize_retsum_data, ) from india_compliance.gst_india.utils.gstr_utils import request_otp @@ -673,28 +675,133 @@ def normalize_data(data): class FileGSTR1: def reset_gstr1(self): - # Make API Request - # Update Fields with reference number - pass + # TODO: handle otp requested errors for all APIs + self.db_set({"filing_status": "Not Filed"}) + + api = GSTR1API(self) + response = api.reset_gstr1(self.return_period) + + if response.get("reference_id"): + self.db_set( + { + "request_type": "reset", + "token": response.get("reference_id"), + } + ) + # callback def process_reset_gstr1(self): - # Unset the Fields with reference number - # Raise error in front-end if not done - # If Success, make unfiled data empty # Emit success message / error message + + if self.request_type != "reset": + return + + api = GSTR1API(self) + response = api.get_return_status(self.return_period, self.token) + + if response.get("status_cd"): + self.db_set({"request_type": None, "token": None}) + + if response.get("status_cd") == "P": + # callback + + self.update_json_for("unfiled", {}, reset_reconcile=True) + + else: + # callback + pass + pass def upload_gstr1(self, json_data): + if not json_data: + return + + self.db_set({"filing_status": "Not Filed"}) + + # Make API Request + api = GSTR1API(self) + response = api.save_gstr_1_data(self.return_period, json_data) + + if response.get("reference_id"): + self.db_set( + { + "request_type": "upload", + "token": response.get("reference_id"), + } + ) + # callback + pass def process_upload_gstr1(self): - pass + if self.request_type != "upload": + return + + api = GSTR1API(self) + response = api.get_return_status(self.return_period, self.token) + + if response.get("status_cd"): + self.db_set({"request_type": None, "token": None}) + + if response.get("status_cd") == "P": + # callback + pass + + elif response.get("status_cd") == "PE": + # callback + self.update_json_for("upload_error", response) + pass + + else: + # callback + pass def proceed_to_file_gstr1(self): + api = GSTR1API(self) + response = api.proceed_to_file("GSTR1", self.return_period) + + if response.get("reference_id"): + self.db_set( + { + "request_type": "proceed_to_file", + "token": response.get("reference_id"), + } + ) + # callback pass def process_proceed_to_file_gstr1(self): - pass + if self.request_type != "proceed_to_file": + return + + api = GSTR1API(self) + response = api.get_return_status(self.return_period, self.token) + + if response.get("status_cd"): + # callback + self.db_set({"request_type": None, "token": None}) + + if response.get("status_cd") != "P": + # callback + return + + summary = api.get_gstr_1_data("RETSUM", self.return_period) + self.update_json_for("authenticated_summary", summary) + + # check if this summary is same as the one in the summary field + mapped_summary = self.get_json_for("filed").get("summary") # TODO:verify + gov_summary = convert_to_internal_data_format(summary) + + # compare dicts and it's values + # TODO: Compare + if mapped_summary == gov_summary: + # callback + self.db_set({"filing_status": "Ready to File"}) + + else: + # callback + pass def file_gstr1(self, pan, otp=None): # If OTP is none, generate evc OTP diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 88253740fd..167e6f4703 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -162,6 +162,28 @@ def on_generate(self, data, filters=None): doctype=self.doctype, ) + @frappe.whitelist() + def upload_gstr1(self): + from india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_export import ( + download_gstr_1_json, + ) + + data = download_gstr_1_json( + self.company, + self.company_gstin, + self.month_or_quarter, + self.year, + delete_missing=True, + ) + + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(self.month_or_quarter, self.year)}-{self.company_gstin}", + ) + + gstr_1_log.upload_gstr1(data) + pass + ####### DATA ###################################################################################### From b01f7a85956f976eb55781692118987ec4f927d8 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Sat, 24 Aug 2024 14:47:37 +0530 Subject: [PATCH 08/68] fix: add action buttons --- .../doctype/gstr_1_beta/gstr_1_beta.js | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index f192229831..e41c2ed87a 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -194,13 +194,23 @@ frappe.ui.form.on(DOCTYPE, { refresh(frm) { // Primary Action frm.disable_save(); - frm.page.set_primary_action(__("Generate"), () => - frm.call("generate_gstr1") - ); + const gst_data = frm.doc.__gst_data; + + if(gst_data && (!is_gstr1_api_enabled() || gst_data.status == "Filed")) { + frm.page.clear_indicator(); + return; + } + + const button_label = gst_data?.status == "Not Filed" ? "Upload" : "Generate"; + const method = gst_data ? "upload_gstr_1" : "generate_gstr1" + frm.page.set_primary_action(__(button_label), () => frm.call(method)); + + if(!gst_data) return; - // After indicator set in frappe refresh - if (frm.doc.__gst_data) frm.gstr1.render_indicator(); - else frm.page.clear_indicator(); + frm.page.add_button(__("Reset"), () => {}); + frm.page.add_button(__("Proceed to File"), () => {}); + frm.page.clear_menu(); + frm.gstr1.render_indicator(); }, load_gstr1_data(frm) { From a2dad69867582a90b85f6829be6f7d2d5bd99b0b Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Tue, 27 Aug 2024 12:13:54 +0530 Subject: [PATCH 09/68] fix: minor refactor and add refactor function --- .../doctype/gst_return_log/generate_gstr_1.py | 2 +- .../doctype/gstr_1_beta/gstr_1_beta.js | 21 ++++++++++++------- .../doctype/gstr_1_beta/gstr_1_beta.py | 13 +++++++++--- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 28a8ec1c93..c7429d6d42 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -679,7 +679,7 @@ def reset_gstr1(self): self.db_set({"filing_status": "Not Filed"}) api = GSTR1API(self) - response = api.reset_gstr1(self.return_period) + response = api.reset_gstr_1_data(self.return_period) if response.get("reference_id"): self.db_set( diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index e41c2ed87a..a991b322ac 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -196,19 +196,24 @@ frappe.ui.form.on(DOCTYPE, { frm.disable_save(); const gst_data = frm.doc.__gst_data; - if(gst_data && (!is_gstr1_api_enabled() || gst_data.status == "Filed")) { - frm.page.clear_indicator(); + if (gst_data && (!is_gstr1_api_enabled() || gst_data.status == "Filed")) { + frm.gstr1.render_indicator(); return; } const button_label = gst_data?.status == "Not Filed" ? "Upload" : "Generate"; - const method = gst_data ? "upload_gstr_1" : "generate_gstr1" - frm.page.set_primary_action(__(button_label), () => frm.call(method)); + const method = button_label == "Upload" ? "upload_gstr1" : "generate_gstr1"; + frm.page.set_primary_action(__(button_label), () => frm.call(method)); - if(!gst_data) return; + if (!gst_data) { + frm.page.clear_indicator(); + return; + } - frm.page.add_button(__("Reset"), () => {}); - frm.page.add_button(__("Proceed to File"), () => {}); + frm.add_custom_button(__("Reset"), () => { + frm.call("reset_gstr1"); + }); + frm.add_custom_button(__("Proceed to File"), () => {}); frm.page.clear_menu(); frm.gstr1.render_indicator(); }, @@ -999,7 +1004,7 @@ class TabManager { args[2]?.indent == 0 ? `${value}` : isDescriptionCell - ? ` + ? `

${value}

` : value; diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 167e6f4703..047dbac6aa 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -169,10 +169,9 @@ def upload_gstr1(self): ) data = download_gstr_1_json( - self.company, self.company_gstin, - self.month_or_quarter, self.year, + self.month_or_quarter, delete_missing=True, ) @@ -182,7 +181,15 @@ def upload_gstr1(self): ) gstr_1_log.upload_gstr1(data) - pass + + @frappe.whitelist() + def reset_gstr1(self): + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(self.month_or_quarter, self.year)}-{self.company_gstin}", + ) + + gstr_1_log.reset_gstr1() ####### DATA ###################################################################################### From 2d1f291f7028b04b2edb63da388222f211f3583f Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 29 Aug 2024 18:27:19 +0530 Subject: [PATCH 10/68] fix: upload data and reset data to gst portal from gstr-1 beta --- .../doctype/gst_return_log/generate_gstr_1.py | 55 +++++++++------ .../doctype/gstr_1_beta/gstr_1_beta.js | 69 +++++++++++++++++-- .../doctype/gstr_1_beta/gstr_1_beta.py | 17 +++++ 3 files changed, 115 insertions(+), 26 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index c7429d6d42..e56b3d07d3 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -688,7 +688,7 @@ def reset_gstr1(self): "token": response.get("reference_id"), } ) - # callback + publish(self, {"request_type": "reset"}) def process_reset_gstr1(self): # Emit success message / error message @@ -699,19 +699,13 @@ def process_reset_gstr1(self): api = GSTR1API(self) response = api.get_return_status(self.return_period, self.token) - if response.get("status_cd"): - self.db_set({"request_type": None, "token": None}) - if response.get("status_cd") == "P": - # callback - + self.db_set({"request_type": None, "token": None}) self.update_json_for("unfiled", {}, reset_reconcile=True) + return response else: - # callback - pass - - pass + return response def upload_gstr1(self, json_data): if not json_data: @@ -730,9 +724,7 @@ def upload_gstr1(self, json_data): "token": response.get("reference_id"), } ) - # callback - - pass + publish(self, {"request_type": "upload"}) def process_upload_gstr1(self): if self.request_type != "upload": @@ -741,21 +733,18 @@ def process_upload_gstr1(self): api = GSTR1API(self) response = api.get_return_status(self.return_period, self.token) - if response.get("status_cd"): + if response.get("status_cd") in ("P", "PE"): + # callback self.db_set({"request_type": None, "token": None}) - if response.get("status_cd") == "P": - # callback - pass + if response.get("status_cd") == "PE": + self.update_json_for("upload_error", response) - elif response.get("status_cd") == "PE": - # callback - self.update_json_for("upload_error", response) - pass + return response else: # callback - pass + return response def proceed_to_file_gstr1(self): api = GSTR1API(self) @@ -810,6 +799,28 @@ def file_gstr1(self, pan, otp=None): pass +@frappe.whitelist() +def create_notifications(subject, description): + notification = frappe.get_doc( + { + "doctype": "Notification Log", + "for_user": frappe.session.user, + "type": "Alert", + "document_type": "GSTR-1 Beta", + "subject": subject, + "email_content": description, + } + ) + notification.insert() + + +def publish(self, request_type): + frappe.publish_realtime( + "gstr1", + message={**request_type}, + ) + + def check_return_status(self): # Cron JOB # check for logs with refeerence number. diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index a991b322ac..15fcdf57cd 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -39,7 +39,7 @@ const GSTR1_SubCategory = { DOC_ISSUE: "Document Issued", SUPECOM_52: "Liable to collect tax u/s 52(TCS)", - SUPECOM_9_5: "Liable to pay tax u/s 9(5)" + SUPECOM_9_5: "Liable to pay tax u/s 9(5)", }; const INVOICE_TYPE = { @@ -165,10 +165,14 @@ frappe.ui.form.on(DOCTYPE, { return; frappe.after_ajax(() => { - frm.doc.__gst_data = data ; + frm.doc.__gst_data = data; frm.trigger("load_gstr1_data"); }); }); + + frappe.realtime.on("gstr1", async message => { + fetch_with_retry(frm, message.request_type); + }); }, async company(frm) { @@ -210,9 +214,15 @@ frappe.ui.form.on(DOCTYPE, { return; } - frm.add_custom_button(__("Reset"), () => { - frm.call("reset_gstr1"); + frm.add_custom_button(__("Reset"), async () => { + frappe.confirm( + __( + "All the details saved in different tables shall be deleted after reset.
Are you sure, you want to reset the already saved data?" + ), + () => frm.call("reset_gstr1") + ); }); + frm.add_custom_button(__("Proceed to File"), () => {}); frm.page.clear_menu(); frm.gstr1.render_indicator(); @@ -242,6 +252,57 @@ frappe.ui.form.on(DOCTYPE, { }, }); +const retry_intervals = [60000, 120000, 300000, 600000, 720000]; //1 min, 2 min, 5 min, 10 min, 12 min + +async function fetch_with_retry(frm, request_type, retries = 0) { + try { + method = `process_${request_type}_gstr1`; + const response = await frm.call(method); + + frm.doc.__gst_data = frm.gstr1.data; + frm.trigger("load_gstr1_data"); + + if (response.message.status_cd == "IP" && retries < retry_intervals.length) { + await new Promise(resolve => setTimeout(resolve, retry_intervals[retries])); + return fetch_with_retry(frm, request_type, retries + 1); + } + generate_notification(frm, response, request_type); + } catch (error) { + console.error("An error occurred:", error); + } +} + +function generate_notification(frm, response, request_type) { + const status_code_message_map = { + P: `Data ${request_type}ing is complete`, + PE: `Data ${request_type}ing is complete with errors`, + ER: `Data ${request_type}ing encountered errors`, + IP: "Request is in progress", + }; + const alert_message = status_code_message_map[response.message.status_cd] || ""; + const doc = response.docs[0]; + + if ( + window.location.pathname.includes("gstr-1-beta") && + (frm.doc.company == doc.company || + frm.doc.company_gstin == doc.company_gstin || + frm.doc.month_or_quarter == doc.month_or_quarter || + frm.doc.year == doc.year) + ) { + frappe.show_alert(__(alert_message)); + return; + } + + //TODO: on click of notification open GSTR-1 Beta with it's filters + frappe.call({ + method: "india_compliance.gst_india.doctype.gst_return_log.generate_gstr_1.create_notifications", + args: { + subject: `Data ${requestType}ing`, + description: alert_message, + }, + }); +} + class GSTR1 { // Render page / Setup Listeners / Setup Data TABS = [ diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 047dbac6aa..abf4618461 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -182,6 +182,14 @@ def upload_gstr1(self): gstr_1_log.upload_gstr1(data) + @frappe.whitelist() + def process_upload_gstr1(self): + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(self.month_or_quarter, self.year)}-{self.company_gstin}", + ) + return gstr_1_log.process_upload_gstr1() + @frappe.whitelist() def reset_gstr1(self): gstr_1_log = frappe.get_doc( @@ -191,6 +199,15 @@ def reset_gstr1(self): gstr_1_log.reset_gstr1() + @frappe.whitelist() + def process_reset_gstr1(self): + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(self.month_or_quarter, self.year)}-{self.company_gstin}", + ) + + return gstr_1_log.process_reset_gstr1() + ####### DATA ###################################################################################### From 1c24ae39233bdce736dda05dca50a011e069257d Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Fri, 30 Aug 2024 11:59:04 +0530 Subject: [PATCH 11/68] fix: use frappe call instead of frm call --- .../doctype/gst_return_log/generate_gstr_1.py | 1 + .../doctype/gstr_1_beta/gstr_1_beta.js | 59 ++++++++---- .../doctype/gstr_1_beta/gstr_1_beta.py | 91 +++++++++++-------- 3 files changed, 96 insertions(+), 55 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index e56b3d07d3..f9fcd2a7ab 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -818,6 +818,7 @@ def publish(self, request_type): frappe.publish_realtime( "gstr1", message={**request_type}, + user=frappe.session.user, ) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 15fcdf57cd..539fafa8b8 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -206,8 +206,20 @@ frappe.ui.form.on(DOCTYPE, { } const button_label = gst_data?.status == "Not Filed" ? "Upload" : "Generate"; - const method = button_label == "Upload" ? "upload_gstr1" : "generate_gstr1"; - frm.page.set_primary_action(__(button_label), () => frm.call(method)); + frm.page.set_primary_action(__(button_label), () => { + if (button_label == "Generate") { + frm.call("generate_gstr1"); + } else { + frappe.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.upload_gstr1", + args: { + month_or_quarter: frm.doc.month_or_quarter, + year: frm.doc.year, + company_gstin: frm.doc.company_gstin, + }, + }); + } + }); if (!gst_data) { frm.page.clear_indicator(); @@ -219,7 +231,15 @@ frappe.ui.form.on(DOCTYPE, { __( "All the details saved in different tables shall be deleted after reset.
Are you sure, you want to reset the already saved data?" ), - () => frm.call("reset_gstr1") + () => + frappe.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.reset_gstr1", + args: { + month_or_quarter: frm.doc.month_or_quarter, + year: frm.doc.year, + company_gstin: frm.doc.company_gstin, + }, + }) ); }); @@ -256,38 +276,41 @@ const retry_intervals = [60000, 120000, 300000, 600000, 720000]; //1 min, 2 min, async function fetch_with_retry(frm, request_type, retries = 0) { try { - method = `process_${request_type}_gstr1`; - const response = await frm.call(method); + const response = await frappe.call({ + method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.process_${request_type}_gstr1`, + args: { + month_or_quarter: frm.doc.month_or_quarter, + year: frm.doc.year, + company_gstin: frm.doc.company_gstin, + }, + }); - frm.doc.__gst_data = frm.gstr1.data; - frm.trigger("load_gstr1_data"); + if (!response.message) return; - if (response.message.status_cd == "IP" && retries < retry_intervals.length) { + if (response.message.status_cd === "IP" && retries < retry_intervals.length) { await new Promise(resolve => setTimeout(resolve, retry_intervals[retries])); return fetch_with_retry(frm, request_type, retries + 1); } - generate_notification(frm, response, request_type); + generate_notification(frm, response.message, request_type); } catch (error) { console.error("An error occurred:", error); } } -function generate_notification(frm, response, request_type) { - const status_code_message_map = { +function generate_notification(frm, message, request_type) { + const status_message_map = { P: `Data ${request_type}ing is complete`, PE: `Data ${request_type}ing is complete with errors`, ER: `Data ${request_type}ing encountered errors`, IP: "Request is in progress", }; - const alert_message = status_code_message_map[response.message.status_cd] || ""; - const doc = response.docs[0]; + const alert_message = status_message_map[message.status_cd] || ""; if ( window.location.pathname.includes("gstr-1-beta") && - (frm.doc.company == doc.company || - frm.doc.company_gstin == doc.company_gstin || - frm.doc.month_or_quarter == doc.month_or_quarter || - frm.doc.year == doc.year) + frm.doc.company_gstin == message.company_gstin && + frm.doc.month_or_quarter == message.month_or_quarter && + frm.doc.year == message.year ) { frappe.show_alert(__(alert_message)); return; @@ -297,7 +320,7 @@ function generate_notification(frm, response, request_type) { frappe.call({ method: "india_compliance.gst_india.doctype.gst_return_log.generate_gstr_1.create_notifications", args: { - subject: `Data ${requestType}ing`, + subject: `Data ${request_type}ing`, description: alert_message, }, }); diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index abf4618461..ceac9930da 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -162,51 +162,68 @@ def on_generate(self, data, filters=None): doctype=self.doctype, ) - @frappe.whitelist() - def upload_gstr1(self): - from india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_export import ( - download_gstr_1_json, - ) - data = download_gstr_1_json( - self.company_gstin, - self.year, - self.month_or_quarter, - delete_missing=True, - ) +@frappe.whitelist() +def upload_gstr1(month_or_quarter, year, company_gstin): + from india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_export import ( + download_gstr_1_json, + ) - gstr_1_log = frappe.get_doc( - "GST Return Log", - f"GSTR1-{get_period(self.month_or_quarter, self.year)}-{self.company_gstin}", - ) + data = download_gstr_1_json( + company_gstin, + year, + month_or_quarter, + delete_missing=True, + ) - gstr_1_log.upload_gstr1(data) + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", + ) + gstr_1_log.upload_gstr1(data) - @frappe.whitelist() - def process_upload_gstr1(self): - gstr_1_log = frappe.get_doc( - "GST Return Log", - f"GSTR1-{get_period(self.month_or_quarter, self.year)}-{self.company_gstin}", - ) - return gstr_1_log.process_upload_gstr1() - @frappe.whitelist() - def reset_gstr1(self): - gstr_1_log = frappe.get_doc( - "GST Return Log", - f"GSTR1-{get_period(self.month_or_quarter, self.year)}-{self.company_gstin}", - ) +@frappe.whitelist() +def process_upload_gstr1(month_or_quarter, year, company_gstin): + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", + ) + data = gstr_1_log.process_upload_gstr1() + data.update( + { + "month_or_quarter": month_or_quarter, + "year": year, + "company_gstin": company_gstin, + } + ) + return data - gstr_1_log.reset_gstr1() - @frappe.whitelist() - def process_reset_gstr1(self): - gstr_1_log = frappe.get_doc( - "GST Return Log", - f"GSTR1-{get_period(self.month_or_quarter, self.year)}-{self.company_gstin}", - ) +@frappe.whitelist() +def reset_gstr1(month_or_quarter, year, company_gstin): + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", + ) + gstr_1_log.reset_gstr1() - return gstr_1_log.process_reset_gstr1() + +@frappe.whitelist() +def process_reset_gstr1(month_or_quarter, year, company_gstin): + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", + ) + data = gstr_1_log.process_reset_gstr1() + data.update( + { + "month_or_quarter": month_or_quarter, + "year": year, + "company_gstin": company_gstin, + } + ) + return data ####### DATA ###################################################################################### From 56a494b3e8a04ea4711ed28d6d178df292b62d2f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 30 Aug 2024 13:13:11 +0530 Subject: [PATCH 12/68] fix: changes as per review --- .../doctype/gstr_1_beta/gstr_1_beta.js | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 539fafa8b8..8a7627a698 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -274,9 +274,9 @@ frappe.ui.form.on(DOCTYPE, { const retry_intervals = [60000, 120000, 300000, 600000, 720000]; //1 min, 2 min, 5 min, 10 min, 12 min -async function fetch_with_retry(frm, request_type, retries = 0) { - try { - const response = await frappe.call({ +function fetch_with_retry(frm, request_type, retries = 0, now = false) { + setTimeout(async () => { + const { message } = await frappe.call({ method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.process_${request_type}_gstr1`, args: { month_or_quarter: frm.doc.month_or_quarter, @@ -285,16 +285,15 @@ async function fetch_with_retry(frm, request_type, retries = 0) { }, }); - if (!response.message) return; + if (!message) return; - if (response.message.status_cd === "IP" && retries < retry_intervals.length) { - await new Promise(resolve => setTimeout(resolve, retry_intervals[retries])); + if (message.status_cd === "IP" && retries < retry_intervals.length) return fetch_with_retry(frm, request_type, retries + 1); - } - generate_notification(frm, response.message, request_type); - } catch (error) { - console.error("An error occurred:", error); - } + + generate_notification(frm, message, request_type); + + }, now ? 0 : retry_intervals[retries]); + } function generate_notification(frm, message, request_type) { From 1a58714be56ed70ff48425984bcfd143addb6a87 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Fri, 30 Aug 2024 15:17:46 +0530 Subject: [PATCH 13/68] fix: set token to none only when status is not IP --- .../doctype/gst_return_log/generate_gstr_1.py | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index f9fcd2a7ab..cb569cb63d 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -699,13 +699,13 @@ def process_reset_gstr1(self): api = GSTR1API(self) response = api.get_return_status(self.return_period, self.token) - if response.get("status_cd") == "P": + if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) + + if response.get("status_cd") == "P": self.update_json_for("unfiled", {}, reset_reconcile=True) - return response - else: - return response + return response def upload_gstr1(self, json_data): if not json_data: @@ -733,18 +733,13 @@ def process_upload_gstr1(self): api = GSTR1API(self) response = api.get_return_status(self.return_period, self.token) - if response.get("status_cd") in ("P", "PE"): - # callback + if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) - if response.get("status_cd") == "PE": - self.update_json_for("upload_error", response) - - return response + if response.get("status_cd") == "PE": + self.update_json_for("upload_error", response) - else: - # callback - return response + return response def proceed_to_file_gstr1(self): api = GSTR1API(self) From 1dea459196f0c68206f899ccf0cec9d68b9b5729 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Fri, 30 Aug 2024 19:01:23 +0530 Subject: [PATCH 14/68] fix: on reset regenerate and minor refactor --- .../doctype/gst_return_log/generate_gstr_1.py | 28 ++-- .../doctype/gstr_1_beta/gstr_1_beta.js | 136 +++++++++--------- .../doctype/gstr_1_beta/gstr_1_beta.py | 17 ++- 3 files changed, 88 insertions(+), 93 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index cb569cb63d..182bd4c077 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -688,7 +688,6 @@ def reset_gstr1(self): "token": response.get("reference_id"), } ) - publish(self, {"request_type": "reset"}) def process_reset_gstr1(self): # Emit success message / error message @@ -701,6 +700,7 @@ def process_reset_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) + create_notifications(self.return_period, "reset", response.get("status_cd")) if response.get("status_cd") == "P": self.update_json_for("unfiled", {}, reset_reconcile=True) @@ -724,7 +724,6 @@ def upload_gstr1(self, json_data): "token": response.get("reference_id"), } ) - publish(self, {"request_type": "upload"}) def process_upload_gstr1(self): if self.request_type != "upload": @@ -735,6 +734,9 @@ def process_upload_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) + create_notifications( + self.return_period, "upload", response.get("status_cd") + ) if response.get("status_cd") == "PE": self.update_json_for("upload_error", response) @@ -794,29 +796,27 @@ def file_gstr1(self, pan, otp=None): pass -@frappe.whitelist() -def create_notifications(subject, description): +def create_notifications(return_period, request_type, status_cd): + status_message_map = { + "P": f"Data {request_type}ing for return period {return_period} has been successfully completed.", + "PE": f"Data {request_type}ing for return period {return_period} is complete with errors", + "ER": f"Data {request_type}ing for return period {return_period} has encountered errors", + "IP": f"The request for {request_type} is currently in progress for the return period {return_period}.", + } + notification = frappe.get_doc( { "doctype": "Notification Log", "for_user": frappe.session.user, "type": "Alert", "document_type": "GSTR-1 Beta", - "subject": subject, - "email_content": description, + "subject": f"Data {request_type}ing", + "email_content": status_message_map.get(status_cd), } ) notification.insert() -def publish(self, request_type): - frappe.publish_realtime( - "gstr1", - message={**request_type}, - user=frappe.session.user, - ) - - def check_return_status(self): # Cron JOB # check for logs with refeerence number. diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 8a7627a698..9d43d6c20d 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -169,10 +169,6 @@ frappe.ui.form.on(DOCTYPE, { frm.trigger("load_gstr1_data"); }); }); - - frappe.realtime.on("gstr1", async message => { - fetch_with_retry(frm, message.request_type); - }); }, async company(frm) { @@ -206,43 +202,16 @@ frappe.ui.form.on(DOCTYPE, { } const button_label = gst_data?.status == "Not Filed" ? "Upload" : "Generate"; - frm.page.set_primary_action(__(button_label), () => { - if (button_label == "Generate") { - frm.call("generate_gstr1"); - } else { - frappe.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.upload_gstr1", - args: { - month_or_quarter: frm.doc.month_or_quarter, - year: frm.doc.year, - company_gstin: frm.doc.company_gstin, - }, - }); - } - }); + frm.page.set_primary_action(__(button_label), () => + primary_button_action(frm, button_label) + ); if (!gst_data) { frm.page.clear_indicator(); return; } - frm.add_custom_button(__("Reset"), async () => { - frappe.confirm( - __( - "All the details saved in different tables shall be deleted after reset.
Are you sure, you want to reset the already saved data?" - ), - () => - frappe.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.reset_gstr1", - args: { - month_or_quarter: frm.doc.month_or_quarter, - year: frm.doc.year, - company_gstin: frm.doc.company_gstin, - }, - }) - ); - }); - + frm.add_custom_button(__("Reset"), () => reset_gstr1_data(frm)); frm.add_custom_button(__("Proceed to File"), () => {}); frm.page.clear_menu(); frm.gstr1.render_indicator(); @@ -272,57 +241,84 @@ frappe.ui.form.on(DOCTYPE, { }, }); -const retry_intervals = [60000, 120000, 300000, 600000, 720000]; //1 min, 2 min, 5 min, 10 min, 12 min +function primary_button_action(frm, button_label) { + if (button_label == "Generate") { + frm.call("generate_gstr1"); + return; + } + frappe.call({ + method: 'india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.upload_gstr1', + args: { + month_or_quarter: frm.doc.month_or_quarter, + year: frm.doc.year, + company_gstin: frm.doc.company_gstin, + }, + callback: () => { + fetch_with_retry(frm, "upload"); + }, + }); +} -function fetch_with_retry(frm, request_type, retries = 0, now = false) { - setTimeout(async () => { - const { message } = await frappe.call({ - method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.process_${request_type}_gstr1`, - args: { - month_or_quarter: frm.doc.month_or_quarter, - year: frm.doc.year, - company_gstin: frm.doc.company_gstin, - }, - }); +function reset_gstr1_data(frm) { + frappe.confirm( + __( + "All the details saved in different tables shall be deleted after reset.
Are you sure, you want to reset the already saved data?" + ), + () => { + frappe.show_alert(__("Please wait while we reset and regenerate the data.")) + frm.call("reset_gstr1").then( + fetch_with_retry(frm, "reset") + ) + } + ); +} + +const retry_intervals = [5000, 15000, 30000]; // 5 second, 15 second, 30 second - if (!message) return; +function fetch_with_retry(frm, request_type, retries = 0, now = false) { + setTimeout( + async () => { + const { message } = await frappe.call({ + method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.process_${request_type}_gstr1`, + args: { + month_or_quarter: frm.doc.month_or_quarter, + year: frm.doc.year, + company_gstin: frm.doc.company_gstin, + }, + }); - if (message.status_cd === "IP" && retries < retry_intervals.length) - return fetch_with_retry(frm, request_type, retries + 1); + if (!message) return; - generate_notification(frm, message, request_type); + if (message.status_cd === "IP" && retries < retry_intervals.length) + return fetch_with_retry(frm, request_type, retries + 1); - }, now ? 0 : retry_intervals[retries]); + if(request_type == "reset") frm.call("generate_gstr1") + mark_notification(frm, message, request_type); + }, + now ? 0 : retry_intervals[retries] + ); } -function generate_notification(frm, message, request_type) { +function mark_notification(frm, message, request_type) { + //TODO: on click of notification open GSTR-1 Beta with it's filters + const status_message_map = { - P: `Data ${request_type}ing is complete`, - PE: `Data ${request_type}ing is complete with errors`, - ER: `Data ${request_type}ing encountered errors`, - IP: "Request is in progress", - }; - const alert_message = status_message_map[message.status_cd] || ""; + "P": `Data ${request_type}ing has been successfully completed.`, + "PE": `Data ${request_type}ing is complete with errors`, + "ER": `Data ${request_type}ing has encountered errors`, + "IP": `The request for ${request_type} is currently in progress`, + } + const alert_message = status_message_map[message.status_cd] || "" if ( window.location.pathname.includes("gstr-1-beta") && frm.doc.company_gstin == message.company_gstin && frm.doc.month_or_quarter == message.month_or_quarter && frm.doc.year == message.year - ) { + ) frappe.show_alert(__(alert_message)); - return; - } - - //TODO: on click of notification open GSTR-1 Beta with it's filters - frappe.call({ - method: "india_compliance.gst_india.doctype.gst_return_log.generate_gstr_1.create_notifications", - args: { - subject: `Data ${request_type}ing`, - description: alert_message, - }, - }); + //TODO: mark the notification as marked } class GSTR1 { diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index ceac9930da..e5d15181b6 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -162,6 +162,14 @@ def on_generate(self, data, filters=None): doctype=self.doctype, ) + @frappe.whitelist() + def reset_gstr1(self): + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(self.month_or_quarter, self.year)}-{self.company_gstin}", + ) + gstr_1_log.reset_gstr1() + @frappe.whitelist() def upload_gstr1(month_or_quarter, year, company_gstin): @@ -200,15 +208,6 @@ def process_upload_gstr1(month_or_quarter, year, company_gstin): return data -@frappe.whitelist() -def reset_gstr1(month_or_quarter, year, company_gstin): - gstr_1_log = frappe.get_doc( - "GST Return Log", - f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", - ) - gstr_1_log.reset_gstr1() - - @frappe.whitelist() def process_reset_gstr1(month_or_quarter, year, company_gstin): gstr_1_log = frappe.get_doc( From 9312fa6a5005108ea2f27154c7218ee0d166733d Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Mon, 2 Sep 2024 13:19:58 +0530 Subject: [PATCH 15/68] fix: functionify and notification refactor --- .../doctype/gst_return_log/generate_gstr_1.py | 12 ++- .../doctype/gstr_1_beta/gstr_1_beta.js | 83 +++++++++++-------- .../doctype/gstr_1_beta/gstr_1_beta.py | 3 + 3 files changed, 62 insertions(+), 36 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 182bd4c077..407d934c62 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -700,7 +700,10 @@ def process_reset_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) - create_notifications(self.return_period, "reset", response.get("status_cd")) + doc_name = create_notifications( + self.return_period, "reset", response.get("status_cd") + ) + response["notification_name"] = doc_name if response.get("status_cd") == "P": self.update_json_for("unfiled", {}, reset_reconcile=True) @@ -734,9 +737,10 @@ def process_upload_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) - create_notifications( + doc_name = create_notifications( self.return_period, "upload", response.get("status_cd") ) + response["notification_name"] = doc_name if response.get("status_cd") == "PE": self.update_json_for("upload_error", response) @@ -810,11 +814,13 @@ def create_notifications(return_period, request_type, status_cd): "for_user": frappe.session.user, "type": "Alert", "document_type": "GSTR-1 Beta", - "subject": f"Data {request_type}ing", + "document_name": "GSTR-1 Beta", + "subject": f"Data {request_type} for return period {return_period}", "email_content": status_message_map.get(status_cd), } ) notification.insert() + return notification.name def check_return_status(self): diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 9d43d6c20d..a59f2857d1 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -202,9 +202,9 @@ frappe.ui.form.on(DOCTYPE, { } const button_label = gst_data?.status == "Not Filed" ? "Upload" : "Generate"; - frm.page.set_primary_action(__(button_label), () => - primary_button_action(frm, button_label) - ); + const method = + button_label === "Generate" ? handle_generate_button : handle_upload_button; + frm.page.set_primary_action(__(button_label), () => method(frm)); if (!gst_data) { frm.page.clear_indicator(); @@ -241,40 +241,51 @@ frappe.ui.form.on(DOCTYPE, { }, }); -function primary_button_action(frm, button_label) { - if (button_label == "Generate") { - frm.call("generate_gstr1"); - return; - } +function handle_upload_button(frm) { frappe.call({ - method: 'india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.upload_gstr1', + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.upload_gstr1", args: { month_or_quarter: frm.doc.month_or_quarter, year: frm.doc.year, company_gstin: frm.doc.company_gstin, }, - callback: () => { - fetch_with_retry(frm, "upload"); - }, + callback: () => fetch_with_retry(frm, "upload"), }); } +async function handle_generate_button(frm) { + const { message: return_period } = await frappe.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.get_period", + args: { month_or_quarter: frm.doc.month_or_quarter, year: frm.doc.year }, + }); + + const log_name = `GSTR1-${return_period}-${frm.doc.company_gstin}`; + const { message } = await frappe.db.get_value("GST Return Log", log_name, [ + "token", + "request_type", + ]); + + await frm.call("generate_gstr1"); + if (message.token) { + fetch_with_retry(frm, message.request_type, (now = true)); + } +} + function reset_gstr1_data(frm) { frappe.confirm( __( "All the details saved in different tables shall be deleted after reset.
Are you sure, you want to reset the already saved data?" ), () => { - frappe.show_alert(__("Please wait while we reset and regenerate the data.")) - frm.call("reset_gstr1").then( - fetch_with_retry(frm, "reset") - ) + frappe.show_alert( + __("Please wait while we reset and regenerate the data.") + ); + frm.call("reset_gstr1").then(fetch_with_retry(frm, "reset")); } ); } const retry_intervals = [5000, 15000, 30000]; // 5 second, 15 second, 30 second - function fetch_with_retry(frm, request_type, retries = 0, now = false) { setTimeout( async () => { @@ -292,33 +303,39 @@ function fetch_with_retry(frm, request_type, retries = 0, now = false) { if (message.status_cd === "IP" && retries < retry_intervals.length) return fetch_with_retry(frm, request_type, retries + 1); - if(request_type == "reset") frm.call("generate_gstr1") - - mark_notification(frm, message, request_type); + if (request_type == "reset") frm.call("generate_gstr1"); + handle_notification(frm, message, request_type); }, now ? 0 : retry_intervals[retries] ); } -function mark_notification(frm, message, request_type) { - //TODO: on click of notification open GSTR-1 Beta with it's filters +async function handle_notification(frm, message, request_type) { + if (!message.status_cd) return; const status_message_map = { - "P": `Data ${request_type}ing has been successfully completed.`, - "PE": `Data ${request_type}ing is complete with errors`, - "ER": `Data ${request_type}ing has encountered errors`, - "IP": `The request for ${request_type} is currently in progress`, - } - const alert_message = status_message_map[message.status_cd] || "" + P: `Data ${request_type}ing has been successfully completed.`, + PE: `Data ${request_type}ing is complete with errors`, + ER: `Data ${request_type}ing has encountered errors`, + IP: `The request for ${request_type} is currently in progress`, + }; + const alert_message = status_message_map[message.status_cd]; - if ( + const on_current_document = window.location.pathname.includes("gstr-1-beta") && frm.doc.company_gstin == message.company_gstin && frm.doc.month_or_quarter == message.month_or_quarter && - frm.doc.year == message.year - ) - frappe.show_alert(__(alert_message)); - //TODO: mark the notification as marked + frm.doc.year == message.year; + + if (!on_current_document) return; + + frappe.show_alert(__(alert_message)); + + //mark the notifications as seen + $(".notifications-seen").css("display", "inline"); + $(".notifications-unseen").css("display", "none"); + await frappe.db.set_value("Notification Log", message.notification_name, "read", 1, update_modified=false) + $(`[data-name="${message.notification_name}"]`).removeClass("unread"); } class GSTR1 { diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index e5d15181b6..93de2ca4dd 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -215,6 +215,8 @@ def process_reset_gstr1(month_or_quarter, year, company_gstin): f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", ) data = gstr_1_log.process_reset_gstr1() + if not data: + data = {} data.update( { "month_or_quarter": month_or_quarter, @@ -274,6 +276,7 @@ def get_net_gst_liability(company, company_gstin, month_or_quarter, year): ####### UTILS ###################################################################################### +@frappe.whitelist() def get_period(month_or_quarter: str, year: str) -> str: """ Returns the period in the format MMYYYY From 5a8d4fcc94e0798c44b58df63c9e3bf5ca3eaa88 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 4 Sep 2024 00:24:59 +0530 Subject: [PATCH 16/68] fix: proceed to file gstr1 --- .../doctype/gst_return_log/generate_gstr_1.py | 52 ++--- .../doctype/gstr_1_beta/gstr_1_beta.js | 198 +++++++++++++++--- .../doctype/gstr_1_beta/gstr_1_beta.py | 27 ++- 3 files changed, 208 insertions(+), 69 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 407d934c62..3060257686 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -700,10 +700,9 @@ def process_reset_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) - doc_name = create_notifications( + response["notification_name"] = create_notifications( self.return_period, "reset", response.get("status_cd") ) - response["notification_name"] = doc_name if response.get("status_cd") == "P": self.update_json_for("unfiled", {}, reset_reconcile=True) @@ -737,10 +736,9 @@ def process_upload_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) - doc_name = create_notifications( + response["notification_name"] = create_notifications( self.return_period, "upload", response.get("status_cd") ) - response["notification_name"] = doc_name if response.get("status_cd") == "PE": self.update_json_for("upload_error", response) @@ -758,8 +756,6 @@ def proceed_to_file_gstr1(self): "token": response.get("reference_id"), } ) - # callback - pass def process_proceed_to_file_gstr1(self): if self.request_type != "proceed_to_file": @@ -768,36 +764,44 @@ def process_proceed_to_file_gstr1(self): api = GSTR1API(self) response = api.get_return_status(self.return_period, self.token) - if response.get("status_cd"): - # callback - self.db_set({"request_type": None, "token": None}) + if response.get("status_cd") == "IP": + return response - if response.get("status_cd") != "P": - # callback - return + if response.get("status_cd") != "IP": + self.db_set({"request_type": None, "token": None}) summary = api.get_gstr_1_data("RETSUM", self.return_period) self.update_json_for("authenticated_summary", summary) - # check if this summary is same as the one in the summary field - mapped_summary = self.get_json_for("filed").get("summary") # TODO:verify + # compare the data + mapped_summary = self.get_json_for("books_summary") + # amended_summary = self.get_json_for("unfiled_summary") + gov_summary = convert_to_internal_data_format(summary) - # compare dicts and it's values - # TODO: Compare + # # compare dicts and it's values + # # TODO: Compare if mapped_summary == gov_summary: - # callback self.db_set({"filing_status": "Ready to File"}) - + response["filing_status"] = "Ready to File" else: - # callback - pass + response["filing_status"] = "Summary Not Matched" + + response["notification_name"] = create_notifications( + self.return_period, "upload", response.get("status_cd") + ) + return response def file_gstr1(self, pan, otp=None): - # If OTP is none, generate evc OTP - # Use summary from self - # Make API Request - pass + if not otp: + # If OTP is none, generate evc OTP + pass + + summary = self.get_json_for("authenticated_summary") + api = GSTR1API(self) + response = api.file_gstr_1(self.return_period, pan, otp, summary) + + return response def create_notifications(return_period, request_type, status_cd): diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index a59f2857d1..8129d06317 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -201,10 +201,21 @@ frappe.ui.form.on(DOCTYPE, { return; } - const button_label = gst_data?.status == "Not Filed" ? "Upload" : "Generate"; - const method = - button_label === "Generate" ? handle_generate_button : handle_upload_button; - frm.page.set_primary_action(__(button_label), () => method(frm)); + const actions = { + Generate: generate_gstr1_data, + Upload: upload_gstr1_data, + "Proceed to File": proceed_to_file, + File: file_gstr1_data, + }; + + const primary_button_label = ["Not Filed", "Ready to File"].includes( + gst_data?.status + ) + ? "Upload" + : "Generate"; + frm.page.set_primary_action(__(primary_button_label), () => + actions[primary_button_label](frm) + ); if (!gst_data) { frm.page.clear_indicator(); @@ -212,7 +223,13 @@ frappe.ui.form.on(DOCTYPE, { } frm.add_custom_button(__("Reset"), () => reset_gstr1_data(frm)); - frm.add_custom_button(__("Proceed to File"), () => {}); + + const secondary_button_label = + gst_data?.status === "Ready to File" ? "File" : "Proceed to File"; + frm.add_custom_button(__(secondary_button_label), () => + actions[secondary_button_label](frm) + ); + frm.page.clear_menu(); frm.gstr1.render_indicator(); }, @@ -241,19 +258,7 @@ frappe.ui.form.on(DOCTYPE, { }, }); -function handle_upload_button(frm) { - frappe.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.upload_gstr1", - args: { - month_or_quarter: frm.doc.month_or_quarter, - year: frm.doc.year, - company_gstin: frm.doc.company_gstin, - }, - callback: () => fetch_with_retry(frm, "upload"), - }); -} - -async function handle_generate_button(frm) { +async function generate_gstr1_data(frm) { const { message: return_period } = await frappe.call({ method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.get_period", args: { month_or_quarter: frm.doc.month_or_quarter, year: frm.doc.year }, @@ -267,58 +272,173 @@ async function handle_generate_button(frm) { await frm.call("generate_gstr1"); if (message.token) { - fetch_with_retry(frm, message.request_type, (now = true)); + fetch_status_with_retry(frm, message.request_type, (now = true)); } } +function upload_gstr1_data(frm) { + frappe.show_alert(__("Please wait while we upload the data.It may take some time.")); + call_gstr1_method(frm, "upload"); +} + function reset_gstr1_data(frm) { frappe.confirm( __( "All the details saved in different tables shall be deleted after reset.
Are you sure, you want to reset the already saved data?" ), - () => { + async () => { frappe.show_alert( __("Please wait while we reset and regenerate the data.") ); - frm.call("reset_gstr1").then(fetch_with_retry(frm, "reset")); + await frm.call("reset_gstr1"); + fetch_status_with_retry(frm, "reset"); } ); } +function proceed_to_file(frm) { + frappe.show_alert(__("Please wait while we proceed to file the data.")); + call_gstr1_method(frm, "proceed_to_file"); +} + +function call_gstr1_method(frm, action, additional_args = {}) { + const base_args = { + month_or_quarter: frm.doc.month_or_quarter, + year: frm.doc.year, + company_gstin: frm.doc.company_gstin, + }; + + const args = { ...base_args, ...additional_args }; + + frappe.call({ + method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.${action}_gstr1`, + args: args, + callback: () => fetch_status_with_retry(frm, action), + }); +} + const retry_intervals = [5000, 15000, 30000]; // 5 second, 15 second, 30 second -function fetch_with_retry(frm, request_type, retries = 0, now = false) { +function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { setTimeout( async () => { const { message } = await frappe.call({ - method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.process_${request_type}_gstr1`, + method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.process_gstr1_request`, args: { month_or_quarter: frm.doc.month_or_quarter, year: frm.doc.year, company_gstin: frm.doc.company_gstin, + request_type: request_type, }, }); - if (!message) return; - if (message.status_cd === "IP" && retries < retry_intervals.length) - return fetch_with_retry(frm, request_type, retries + 1); + return fetch_status_with_retry(frm, request_type, retries + 1); + + //is it possible that still status_cd is IP => what to do then + + if (request_type == "reset") { + frm.page.set_indicator("Not Filed", "orange"); + frm.call("generate_gstr1"); + } + + if (request_type == "proceed_to_file") + handle_proceed_to_file_response(frm, message.filing_status); - if (request_type == "reset") frm.call("generate_gstr1"); handle_notification(frm, message, request_type); }, now ? 0 : retry_intervals[retries] ); } +function handle_proceed_to_file_response(frm, filing_status) { + if (!filing_status) return; + + if (filing_status == "Ready to File") { + frm.remove_custom_button("Proceed to File"); + frm.add_custom_button(__("Ready to File"), () => file_gstr1_data(frm)); + frm.page.clear_menu(); + frm.page.set_indicator("Ready to File", "orange"); + return; + } + + frappe.msgprint({ + message: __("Summary has not matched. Please sync with GSTIN."), + indicator: "red", + title: __("GSTIN Sync Required"), + primary_action: { + label: __("Sync with GSTIN"), + action() { + render_empty_state(frm); + //here for what it will sync for + frm.call("sync_with_gstn", { sync_for: "unfiled" }).then(() => { + frappe.msgprint(__("Synced successfully with GSTIN.")); + }); + }, + }, + }); +} + +function file_gstr1_data(frm) { + const dialog = new frappe.ui.Dialog({ + title: "Filing GSTR-1", + fields: [ + { + label: "PAN", + fieldname: "pan", + fieldtype: "Data", + default: frm.doc.company_gstin.substr(2, 10), + reqd: 1, + }, + { + label: "OTP", + fieldname: "otp", + fieldtype: "Data", + hidden: 1, + }, + ], + primary_action_label: "Verify PAN", + primary_action() { + if (dialog.get_value("pan").length != 10) { + frappe.throw(__("PAN should be 10 characters long")); + } + validate_details_and_file_gstr1(frm, dialog); + }, + }); + dialog.show(); +} + +function validate_details_and_file_gstr1(frm, dialog) { + const PAN_REGEX = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/; + const pan = dialog.get_value("pan")?.trim().toUpperCase(); + + if (!PAN_REGEX.test(pan)) { + frappe.throw(__("Invalid PAN format")); + } + + dialog.get_field("otp").toggle(true); + dialog.set_primary_action("Authenticate OTP", () => { + //authenticate otp + //set pan to gstin doc + call_gstr1_method(frm, "file", { pan: pan, otp: dialog.get_value("otp") }); + dialog.hide(); + }); +} + async function handle_notification(frm, message, request_type) { if (!message.status_cd) return; + const request_status = + request_type === "proceed_to_file" + ? "Proceed to file" + : `Data ${request_type}ing`; + const status_message_map = { - P: `Data ${request_type}ing has been successfully completed.`, - PE: `Data ${request_type}ing is complete with errors`, - ER: `Data ${request_type}ing has encountered errors`, - IP: `The request for ${request_type} is currently in progress`, + P: `${request_status} has been successfully completed.`, + PE: `${request_status} is completed with errors`, + ER: `${request_status} has encountered errors`, + IP: `The request for ${request_status} is currently in progress`, }; + const alert_message = status_message_map[message.status_cd]; const on_current_document = @@ -330,12 +450,22 @@ async function handle_notification(frm, message, request_type) { if (!on_current_document) return; frappe.show_alert(__(alert_message)); + mark_notification(message.notification_name); +} - //mark the notifications as seen +async function mark_notification(notification_name) { $(".notifications-seen").css("display", "inline"); $(".notifications-unseen").css("display", "none"); - await frappe.db.set_value("Notification Log", message.notification_name, "read", 1, update_modified=false) - $(`[data-name="${message.notification_name}"]`).removeClass("unread"); + + frappe.db.set_value( + "Notification Log", + notification_name, + "read", + 1, + (update_modified = false) + ).then(()=> { + $(`[data-name="${notification_name}"]`).removeClass("unread"); + }) } class GSTR1 { diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 93de2ca4dd..6388f796f2 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -192,29 +192,34 @@ def upload_gstr1(month_or_quarter, year, company_gstin): @frappe.whitelist() -def process_upload_gstr1(month_or_quarter, year, company_gstin): +def proceed_to_file_gstr1(month_or_quarter, year, company_gstin): gstr_1_log = frappe.get_doc( "GST Return Log", f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", ) - data = gstr_1_log.process_upload_gstr1() - data.update( - { - "month_or_quarter": month_or_quarter, - "year": year, - "company_gstin": company_gstin, - } + gstr_1_log.proceed_to_file_gstr1() + + +@frappe.whitelist() +def file_gstr1(month_or_quarter, year, company_gstin, pan, otp): + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", ) - return data + gstr_1_log.file_gstr1(pan, otp) @frappe.whitelist() -def process_reset_gstr1(month_or_quarter, year, company_gstin): +def process_gstr1_request(month_or_quarter, year, company_gstin, request_type): gstr_1_log = frappe.get_doc( "GST Return Log", f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", ) - data = gstr_1_log.process_reset_gstr1() + + method_name = f"process_{request_type}_gstr1" + method = getattr(gstr_1_log, method_name) + data = method() + if not data: data = {} data.update( From 72990cdc8c1dc7dc86336db5ce11339156949833 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 4 Sep 2024 15:30:52 +0530 Subject: [PATCH 17/68] fix: compare book and gov summary --- .../doctype/gst_return_log/generate_gstr_1.py | 35 +++++++++++++------ .../doctype/gstr_1_beta/gstr_1_beta.js | 6 ++-- .../doctype/gstr_1_beta/gstr_1_beta.py | 2 +- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 3060257686..93e1b2a6bd 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -773,19 +773,14 @@ def process_proceed_to_file_gstr1(self): summary = api.get_gstr_1_data("RETSUM", self.return_period) self.update_json_for("authenticated_summary", summary) - # compare the data mapped_summary = self.get_json_for("books_summary") - # amended_summary = self.get_json_for("unfiled_summary") - gov_summary = convert_to_internal_data_format(summary) - # # compare dicts and it's values - # # TODO: Compare - if mapped_summary == gov_summary: - self.db_set({"filing_status": "Ready to File"}) - response["filing_status"] = "Ready to File" - else: - response["filing_status"] = "Summary Not Matched" + response["filing_status"] = ( + "Ready to File" + if are_summaries_same(mapped_summary, gov_summary) + else "Summary Not Matched" + ) response["notification_name"] = create_notifications( self.return_period, "upload", response.get("status_cd") @@ -804,6 +799,26 @@ def file_gstr1(self, pan, otp=None): return response +def are_summaries_same(mapped_summary, gov_summary): + KEYS_TO_COMPARE = { + "no_of_records", + "total_cess_amount", + "total_cgst_amount", + "total_igst_amount", + "total_sgst_amount", + "total_taxable_value", + } + gov_summary = gov_summary.get("summary") + + for dict in mapped_summary: + gov_dict = gov_summary.get(dict["description"]) + + for key in KEYS_TO_COMPARE: + if gov_dict.get(key) != dict.get(key): + return False + return True + + def create_notifications(return_period, request_type, status_cd): status_message_map = { "P": f"Data {request_type}ing for return period {return_period} has been successfully completed.", diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 8129d06317..3eec8dc5a3 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -355,7 +355,7 @@ function handle_proceed_to_file_response(frm, filing_status) { if (filing_status == "Ready to File") { frm.remove_custom_button("Proceed to File"); - frm.add_custom_button(__("Ready to File"), () => file_gstr1_data(frm)); + frm.add_custom_button(__("File"), () => file_gstr1_data(frm)); frm.page.clear_menu(); frm.page.set_indicator("Ready to File", "orange"); return; @@ -424,7 +424,7 @@ function validate_details_and_file_gstr1(frm, dialog) { }); } -async function handle_notification(frm, message, request_type) { +function handle_notification(frm, message, request_type) { if (!message.status_cd) return; const request_status = @@ -453,7 +453,7 @@ async function handle_notification(frm, message, request_type) { mark_notification(message.notification_name); } -async function mark_notification(notification_name) { +function mark_notification(notification_name) { $(".notifications-seen").css("display", "inline"); $(".notifications-unseen").css("display", "none"); diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 6388f796f2..d1f2e95480 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -188,7 +188,7 @@ def upload_gstr1(month_or_quarter, year, company_gstin): "GST Return Log", f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", ) - gstr_1_log.upload_gstr1(data) + gstr_1_log.upload_gstr1(data.get("data")) @frappe.whitelist() From f5c497af67ff3d9c50ab274324dc45120a4bd1a3 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 4 Sep 2024 23:34:20 +0530 Subject: [PATCH 18/68] fix: new field in GSTIN and Error Tab skeleton --- .../doctype/gst_return_log/generate_gstr_1.py | 1 + .../gst_india/doctype/gstin/gstin.json | 9 +- .../doctype/gstr_1_beta/gstr_1_beta.js | 131 ++++++++++++++++-- 3 files changed, 127 insertions(+), 14 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 93e1b2a6bd..38bd4978f9 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -770,6 +770,7 @@ def process_proceed_to_file_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) + # what to do when you have PE or ER summary = api.get_gstr_1_data("RETSUM", self.return_period) self.update_json_for("authenticated_summary", summary) diff --git a/india_compliance/gst_india/doctype/gstin/gstin.json b/india_compliance/gst_india/doctype/gstin/gstin.json index a5acf0c6fd..d0bcb0e52a 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.json +++ b/india_compliance/gst_india/doctype/gstin/gstin.json @@ -14,6 +14,7 @@ "last_updated_on", "cancelled_date", "is_blocked", + "last_pan_used_for_gstr", "section_break_ttzc", "gstr_1_filed_upto" ], @@ -77,12 +78,18 @@ "fieldtype": "Date", "hidden": 1, "label": "GSTR-1 Filed Upto" + }, + { + "fieldname": "last_pan_used_for_gstr", + "fieldtype": "Data", + "hidden": 1, + "label": "Last PAN Used for GSTR" } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-06-14 13:59:53.998262", + "modified": "2024-09-04 17:12:30.951878", "modified_by": "Administrator", "module": "GST India", "name": "GSTIN", diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 3eec8dc5a3..020b465988 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -277,7 +277,9 @@ async function generate_gstr1_data(frm) { } function upload_gstr1_data(frm) { - frappe.show_alert(__("Please wait while we upload the data.It may take some time.")); + frappe.show_alert( + __("Please wait while we upload the data.It may take some time.") + ); call_gstr1_method(frm, "upload"); } @@ -334,8 +336,12 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { if (message.status_cd === "IP" && retries < retry_intervals.length) return fetch_status_with_retry(frm, request_type, retries + 1); + if (message.status_cd === "PE" || message.status_cd === "ER") + handle_errors(frm,message); + //is it possible that still status_cd is IP => what to do then + //will this code executed when you have PE or ER if (request_type == "reset") { frm.page.set_indicator("Not Filed", "orange"); frm.call("generate_gstr1"); @@ -350,6 +356,19 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { ); } +function handle_errors(frm, message) { + frm.gstr1.tabs.error_tab.show() + const data = [{ + "category" : "B2B", + "error_code" : "12345", + "description" : "I am testing", + "party_gstin" : "123456789", + "place_of_supply" : "27", + "invoice_number" : "12345678", + }] + frm.gstr1.tabs['error_tab'].tabmanager.refresh_data(data) +} + function handle_proceed_to_file_response(frm, filing_status) { if (!filing_status) return; @@ -378,7 +397,13 @@ function handle_proceed_to_file_response(frm, filing_status) { }); } -function file_gstr1_data(frm) { +async function file_gstr1_data(frm) { + const { message } = await frappe.db.get_value("GSTIN", frm.doc.company_gstin, [ + "last_pan_used_for_gstr", + ]); + const pan_no = + message.last_pan_used_for_gstr || frm.doc.company_gstin.substr(2, 10); + const dialog = new frappe.ui.Dialog({ title: "Filing GSTR-1", fields: [ @@ -386,7 +411,7 @@ function file_gstr1_data(frm) { label: "PAN", fieldname: "pan", fieldtype: "Data", - default: frm.doc.company_gstin.substr(2, 10), + default: pan_no, reqd: 1, }, { @@ -418,7 +443,13 @@ function validate_details_and_file_gstr1(frm, dialog) { dialog.get_field("otp").toggle(true); dialog.set_primary_action("Authenticate OTP", () => { //authenticate otp - //set pan to gstin doc + frappe.db.set_value( + "GSTIN", + frm.doc.company_gstin, + "last_pan_used_for_gstr", + pan + ); + call_gstr1_method(frm, "file", { pan: pan, otp: dialog.get_value("otp") }); dialog.hide(); }); @@ -457,15 +488,17 @@ function mark_notification(notification_name) { $(".notifications-seen").css("display", "inline"); $(".notifications-unseen").css("display", "none"); - frappe.db.set_value( - "Notification Log", - notification_name, - "read", - 1, - (update_modified = false) - ).then(()=> { - $(`[data-name="${notification_name}"]`).removeClass("unread"); - }) + frappe.db + .set_value( + "Notification Log", + notification_name, + "read", + 1, + (update_modified = false) + ) + .then(() => { + $(`[data-name="${notification_name}"]`).removeClass("unread"); + }); } class GSTR1 { @@ -492,6 +525,11 @@ class GSTR1 { name: "filed", _TabManager: FiledTab, }, + { + label: __("Error"), + name: "error", + _TabManager: ErrorTab, + }, ]; constructor(frm) { @@ -2255,6 +2293,73 @@ class ReconcileTab extends FiledTab { } } +class ErrorTab extends TabManager { + set_default_title() { + this.DEFAULT_TITLE = "Error Summary"; + TabManager.prototype.set_default_title.call(this); + } + + get_summary_columns() { + return [ + { + name: "Category", + fieldname: "category", + width: 100, + }, + { + name: "Error Code", + fieldname: "error_code", + width: 130, + }, + { + name: "Error Description", + fieldname: "description", + width: 200, + }, + { + name: "Party GSTIN", + fieldname: "party_gstin", + width: 130, + }, + { + name: "Place Of Supply", + fieldname: "place_of_supply", + width: 130, + }, + { + name: "Invoice Number", + fieldname: "invoice_number", + width: 130, + }, + ]; + } + + setup_actions(){} + set_creation_time_string(){} + + refresh_data(data) + { + this.set_default_title(); + super.refresh_data(data, data, "Error Summary"); + $('.dt-footer').remove(); + } + setup_wrapper() { + this.wrapper.append(` +
+
+
+
 
+
+
+
+ +
+
+ `); + } + +} + class DetailViewDialog { CURRENCY_FIELD_MAP = { [GSTR1_DataField.TAXABLE_VALUE]: "Taxable Value", From 1008e18c88f4bba56e6f87611984772bac863f29 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 5 Sep 2024 15:30:45 +0530 Subject: [PATCH 19/68] fix: show error properly in error tab --- .../doctype/gst_return_log/generate_gstr_1.py | 14 ++-- .../doctype/gstr_1_beta/gstr_1_beta.js | 66 +++++++++++-------- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 38bd4978f9..69dcaa53b6 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -690,8 +690,6 @@ def reset_gstr1(self): ) def process_reset_gstr1(self): - # Emit success message / error message - if self.request_type != "reset": return @@ -766,8 +764,7 @@ def process_proceed_to_file_gstr1(self): if response.get("status_cd") == "IP": return response - - if response.get("status_cd") != "IP": + else: self.db_set({"request_type": None, "token": None}) # what to do when you have PE or ER @@ -809,13 +806,13 @@ def are_summaries_same(mapped_summary, gov_summary): "total_sgst_amount", "total_taxable_value", } - gov_summary = gov_summary.get("summary") + gov_summary_data = gov_summary.get("summary") - for dict in mapped_summary: - gov_dict = gov_summary.get(dict["description"]) + for mapped_entry in mapped_summary: + gov_entry = gov_summary_data.get(mapped_entry["description"]) for key in KEYS_TO_COMPARE: - if gov_dict.get(key) != dict.get(key): + if gov_entry.get(key) != mapped_entry.get(key): return False return True @@ -825,7 +822,6 @@ def create_notifications(return_period, request_type, status_cd): "P": f"Data {request_type}ing for return period {return_period} has been successfully completed.", "PE": f"Data {request_type}ing for return period {return_period} is complete with errors", "ER": f"Data {request_type}ing for return period {return_period} has encountered errors", - "IP": f"The request for {request_type} is currently in progress for the return period {return_period}.", } notification = frappe.get_doc( diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 020b465988..d61cd9d4aa 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -337,11 +337,8 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { return fetch_status_with_retry(frm, request_type, retries + 1); if (message.status_cd === "PE" || message.status_cd === "ER") - handle_errors(frm,message); + handle_errors(frm, message); - //is it possible that still status_cd is IP => what to do then - - //will this code executed when you have PE or ER if (request_type == "reset") { frm.page.set_indicator("Not Filed", "orange"); frm.call("generate_gstr1"); @@ -357,16 +354,30 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { } function handle_errors(frm, message) { - frm.gstr1.tabs.error_tab.show() - const data = [{ - "category" : "B2B", - "error_code" : "12345", - "description" : "I am testing", - "party_gstin" : "123456789", - "place_of_supply" : "27", - "invoice_number" : "12345678", - }] - frm.gstr1.tabs['error_tab'].tabmanager.refresh_data(data) + const { status_cd, error_report } = message; + let data = []; + + if (status_cd == "ER") { + data.push({ + error_code: error_report.error_cd, + description: error_report.error_msg, + }); + } else { + for (let category in error_report) { + for (let object of error_report[category]) { + data.push({ + category: category.toUpperCase(), + error_code: object.error_cd, + description: object.error_msg, + party_gstin: object.ctin, + place_of_supply: object.inv[0].pos, + invoice_number: object.inv[0].inum, + }); + } + } + } + frm.gstr1.tabs.error_tab.show(); + frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(data); } function handle_proceed_to_file_response(frm, filing_status) { @@ -423,9 +434,6 @@ async function file_gstr1_data(frm) { ], primary_action_label: "Verify PAN", primary_action() { - if (dialog.get_value("pan").length != 10) { - frappe.throw(__("PAN should be 10 characters long")); - } validate_details_and_file_gstr1(frm, dialog); }, }); @@ -436,6 +444,10 @@ function validate_details_and_file_gstr1(frm, dialog) { const PAN_REGEX = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/; const pan = dialog.get_value("pan")?.trim().toUpperCase(); + if (pan.length != 10) { + frappe.throw(__("PAN should be 10 characters long")); + } + if (!PAN_REGEX.test(pan)) { frappe.throw(__("Invalid PAN format")); } @@ -2304,22 +2316,22 @@ class ErrorTab extends TabManager { { name: "Category", fieldname: "category", - width: 100, + width: 80, }, { name: "Error Code", fieldname: "error_code", - width: 130, + width: 100, }, { name: "Error Description", fieldname: "description", - width: 200, + width: 250, }, { name: "Party GSTIN", fieldname: "party_gstin", - width: 130, + width: 170, }, { name: "Place Of Supply", @@ -2329,19 +2341,20 @@ class ErrorTab extends TabManager { { name: "Invoice Number", fieldname: "invoice_number", + fieldtype: "Link", + options: "Sales Invoice", width: 130, }, ]; } - setup_actions(){} - set_creation_time_string(){} + setup_actions() {} + set_creation_time_string() {} - refresh_data(data) - { + refresh_data(data) { this.set_default_title(); super.refresh_data(data, data, "Error Summary"); - $('.dt-footer').remove(); + $(".dt-footer").remove(); } setup_wrapper() { this.wrapper.append(` @@ -2357,7 +2370,6 @@ class ErrorTab extends TabManager {
`); } - } class DetailViewDialog { From 969c5b52cbbc19493e57f366281d32819327202a Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 5 Sep 2024 18:22:13 +0530 Subject: [PATCH 20/68] fix: changes as per review --- .../doctype/gst_return_log/generate_gstr_1.py | 77 ++++++++++---- .../doctype/gstr_1_beta/gstr_1_beta.js | 100 +++++++----------- 2 files changed, 95 insertions(+), 82 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 69dcaa53b6..e01dc168f1 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -674,8 +674,9 @@ def normalize_data(data): class FileGSTR1: + # TODO: handle otp requested errors for all APIs def reset_gstr1(self): - # TODO: handle otp requested errors for all APIs + # reset called after proceed to file self.db_set({"filing_status": "Not Filed"}) api = GSTR1API(self) @@ -698,7 +699,7 @@ def process_reset_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) - response["notification_name"] = create_notifications( + create_success_notifications( self.return_period, "reset", response.get("status_cd") ) @@ -711,6 +712,7 @@ def upload_gstr1(self, json_data): if not json_data: return + # upload data after proceed to file self.db_set({"filing_status": "Not Filed"}) # Make API Request @@ -734,7 +736,7 @@ def process_upload_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) - response["notification_name"] = create_notifications( + create_success_notifications( self.return_period, "upload", response.get("status_cd") ) @@ -747,6 +749,8 @@ def proceed_to_file_gstr1(self): api = GSTR1API(self) response = api.proceed_to_file("GSTR1", self.return_period) + # TODO: handle case where it's already proceeded to file + if response.get("reference_id"): self.db_set( { @@ -767,25 +771,39 @@ def process_proceed_to_file_gstr1(self): else: self.db_set({"request_type": None, "token": None}) - # what to do when you have PE or ER + if response.get("status_cd") != "P": + # TODO: Failure Notification + return + summary = api.get_gstr_1_data("RETSUM", self.return_period) self.update_json_for("authenticated_summary", summary) mapped_summary = self.get_json_for("books_summary") gov_summary = convert_to_internal_data_format(summary) + gov_summary = summarize_retsum_data(gov_summary) - response["filing_status"] = ( - "Ready to File" - if are_summaries_same(mapped_summary, gov_summary) - else "Summary Not Matched" - ) + differing_categories = get_differing_categories(mapped_summary, gov_summary) + + if not differing_categories: + self.db_set({"filing_status": "Ready to File"}) + response["filing_status"] = "Ready to File" + create_success_notifications( + self.return_period, "upload", response.get("status_cd") + ) + + else: + response.update( + { + "filing_status": "Summary Not Matched", + "differing_categories": differing_categories, + } + ) + # TODO: Failure Notification - response["notification_name"] = create_notifications( - self.return_period, "upload", response.get("status_cd") - ) return response def file_gstr1(self, pan, otp=None): + # TODO: Try to file before proceeding to file if not otp: # If OTP is none, generate evc OTP pass @@ -797,27 +815,43 @@ def file_gstr1(self, pan, otp=None): return response -def are_summaries_same(mapped_summary, gov_summary): +def get_differing_categories(mapped_summary, gov_summary): KEYS_TO_COMPARE = { - "no_of_records", "total_cess_amount", "total_cgst_amount", "total_igst_amount", "total_sgst_amount", "total_taxable_value", } - gov_summary_data = gov_summary.get("summary") - for mapped_entry in mapped_summary: - gov_entry = gov_summary_data.get(mapped_entry["description"]) + gov_summary = {row["description"]: row for row in gov_summary if row["indent"] == 0} + compared_categories = set() + differing_categories = set() + + # This will intentionally skip the row in govt_summary with amended data + for row in mapped_summary: + if row["indent"] != 0: + continue + + category = row["description"] + compared_categories.add(category) + gov_entry = gov_summary.get(category, {}) for key in KEYS_TO_COMPARE: - if gov_entry.get(key) != mapped_entry.get(key): - return False - return True + if gov_entry.get(key, 0) != row.get(key): + differing_categories.add(category) + break + + for row in gov_summary.values(): + # Amendments are with indent 1. Hence auto-skipped + if row["description"] not in compared_categories: + differing_categories.add(row["description"]) + + return differing_categories -def create_notifications(return_period, request_type, status_cd): +def create_success_notifications(return_period, request_type, status_cd): + # TODO: GSTIN in message. It's not clear if this is for GSTR-1 status_message_map = { "P": f"Data {request_type}ing for return period {return_period} has been successfully completed.", "PE": f"Data {request_type}ing for return period {return_period} is complete with errors", @@ -829,6 +863,7 @@ def create_notifications(return_period, request_type, status_cd): "doctype": "Notification Log", "for_user": frappe.session.user, "type": "Alert", + # TODO: check possibility to link to Integration Request Log. Create different notification if error in API. "document_type": "GSTR-1 Beta", "document_name": "GSTR-1 Beta", "subject": f"Data {request_type} for return period {return_period}", diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index d61cd9d4aa..6257d48dbe 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -230,7 +230,6 @@ frappe.ui.form.on(DOCTYPE, { actions[secondary_button_label](frm) ); - frm.page.clear_menu(); frm.gstr1.render_indicator(); }, @@ -277,10 +276,8 @@ async function generate_gstr1_data(frm) { } function upload_gstr1_data(frm) { - frappe.show_alert( - __("Please wait while we upload the data.It may take some time.") - ); - call_gstr1_method(frm, "upload"); + frappe.show_alert(__("Uploading data to GSTN")); + perform_gstr1_action(frm, "upload"); } function reset_gstr1_data(frm) { @@ -289,9 +286,7 @@ function reset_gstr1_data(frm) { "All the details saved in different tables shall be deleted after reset.
Are you sure, you want to reset the already saved data?" ), async () => { - frappe.show_alert( - __("Please wait while we reset and regenerate the data.") - ); + frappe.show_alert(__("Resetting GSTR-1 data")); await frm.call("reset_gstr1"); fetch_status_with_retry(frm, "reset"); } @@ -300,10 +295,42 @@ function reset_gstr1_data(frm) { function proceed_to_file(frm) { frappe.show_alert(__("Please wait while we proceed to file the data.")); - call_gstr1_method(frm, "proceed_to_file"); + perform_gstr1_action(frm, "proceed_to_file"); +} + +async function file_gstr1_data(frm) { + const { message } = await frappe.db.get_value("GSTIN", frm.doc.company_gstin, [ + "last_pan_used_for_gstr", + ]); + const pan_no = + message.last_pan_used_for_gstr || frm.doc.company_gstin.substr(2, 10); + + const dialog = new frappe.ui.Dialog({ + title: "Filing GSTR-1", + fields: [ + { + label: "PAN", + fieldname: "pan", + fieldtype: "Data", + default: pan_no, + reqd: 1, + }, + { + label: "OTP", + fieldname: "otp", + fieldtype: "Data", + hidden: 1, + }, + ], + primary_action_label: "Verify PAN", + primary_action() { + validate_details_and_file_gstr1(frm, dialog); + }, + }); + dialog.show(); } -function call_gstr1_method(frm, action, additional_args = {}) { +function perform_gstr1_action(frm, action, additional_args = {}) { const base_args = { month_or_quarter: frm.doc.month_or_quarter, year: frm.doc.year, @@ -391,6 +418,7 @@ function handle_proceed_to_file_response(frm, filing_status) { return; } + // TODO: Show differeing categories frappe.msgprint({ message: __("Summary has not matched. Please sync with GSTIN."), indicator: "red", @@ -408,38 +436,6 @@ function handle_proceed_to_file_response(frm, filing_status) { }); } -async function file_gstr1_data(frm) { - const { message } = await frappe.db.get_value("GSTIN", frm.doc.company_gstin, [ - "last_pan_used_for_gstr", - ]); - const pan_no = - message.last_pan_used_for_gstr || frm.doc.company_gstin.substr(2, 10); - - const dialog = new frappe.ui.Dialog({ - title: "Filing GSTR-1", - fields: [ - { - label: "PAN", - fieldname: "pan", - fieldtype: "Data", - default: pan_no, - reqd: 1, - }, - { - label: "OTP", - fieldname: "otp", - fieldtype: "Data", - hidden: 1, - }, - ], - primary_action_label: "Verify PAN", - primary_action() { - validate_details_and_file_gstr1(frm, dialog); - }, - }); - dialog.show(); -} - function validate_details_and_file_gstr1(frm, dialog) { const PAN_REGEX = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/; const pan = dialog.get_value("pan")?.trim().toUpperCase(); @@ -462,7 +458,7 @@ function validate_details_and_file_gstr1(frm, dialog) { pan ); - call_gstr1_method(frm, "file", { pan: pan, otp: dialog.get_value("otp") }); + perform_gstr1_action(frm, "file", { pan: pan, otp: dialog.get_value("otp") }); dialog.hide(); }); } @@ -493,24 +489,6 @@ function handle_notification(frm, message, request_type) { if (!on_current_document) return; frappe.show_alert(__(alert_message)); - mark_notification(message.notification_name); -} - -function mark_notification(notification_name) { - $(".notifications-seen").css("display", "inline"); - $(".notifications-unseen").css("display", "none"); - - frappe.db - .set_value( - "Notification Log", - notification_name, - "read", - 1, - (update_modified = false) - ) - .then(() => { - $(`[data-name="${notification_name}"]`).removeClass("unread"); - }); } class GSTR1 { From 43d6fb88d96a2aada710bc2fabf7730df7ebbc72 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Fri, 6 Sep 2024 15:51:36 +0530 Subject: [PATCH 21/68] fix: implementing todos --- .../gst_india/api_classes/base.py | 1 + .../gst_india/api_classes/taxpayer_base.py | 2 + .../doctype/gst_return_log/generate_gstr_1.py | 97 ++++++++++++++----- .../doctype/gstr_1_beta/gstr_1_beta.js | 49 ++++++++-- .../doctype/gstr_1_beta/gstr_1_beta.py | 19 ++-- 5 files changed, 127 insertions(+), 41 deletions(-) diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py index 05285061e5..83511299d7 100644 --- a/india_compliance/gst_india/api_classes/base.py +++ b/india_compliance/gst_india/api_classes/base.py @@ -137,6 +137,7 @@ def _make_request( response = requests.request(method, **request_args) if api_request_id := response.headers.get("x-amzn-RequestId"): + self.request_id = api_request_id log.request_id = api_request_id try: diff --git a/india_compliance/gst_india/api_classes/taxpayer_base.py b/india_compliance/gst_india/api_classes/taxpayer_base.py index f37e89c96c..8ace1d8b18 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_base.py +++ b/india_compliance/gst_india/api_classes/taxpayer_base.py @@ -89,6 +89,8 @@ class TaxpayerAuthenticate(BaseAPI): # "AUTH4034": "invalid_otp", # Invalid OTP "AUTH4038": "authorization_failed", # Session Expired "TEC4002": "invalid_public_key", + "RET00003": "Return Form already ready to be filed", + "RET09001": "Latest Summary is not available. Please generate summary and try again.", } def request_otp(self): diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index e01dc168f1..b0103c1392 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -699,8 +699,11 @@ def process_reset_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) - create_success_notifications( - self.return_period, "reset", response.get("status_cd") + enqueue_notification( + self.return_period, + "reset", + response.get("status_cd"), + self.gstin, ) if response.get("status_cd") == "P": @@ -736,8 +739,11 @@ def process_upload_gstr1(self): if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) - create_success_notifications( - self.return_period, "upload", response.get("status_cd") + enqueue_notification( + self.return_period, + "upload", + response.get("status_cd"), + self.gstin, ) if response.get("status_cd") == "PE": @@ -749,7 +755,8 @@ def proceed_to_file_gstr1(self): api = GSTR1API(self) response = api.proceed_to_file("GSTR1", self.return_period) - # TODO: handle case where it's already proceeded to file + if response.error and response.error.error_cd == "RET00003": + return self.fetch_and_compare_summary(api) if response.get("reference_id"): self.db_set( @@ -772,23 +779,38 @@ def process_proceed_to_file_gstr1(self): self.db_set({"request_type": None, "token": None}) if response.get("status_cd") != "P": - # TODO: Failure Notification + enqueue_notification( + self.return_period, + "proceed_to_file", + response.get("status_cd"), + self.gstin, + api.request_id, + ) return + return self.fetch_and_compare_summary(api, response) + + def fetch_and_compare_summary(self, api, response=None): + if response is None: + response = {} + summary = api.get_gstr_1_data("RETSUM", self.return_period) self.update_json_for("authenticated_summary", summary) mapped_summary = self.get_json_for("books_summary") - gov_summary = convert_to_internal_data_format(summary) - gov_summary = summarize_retsum_data(gov_summary) + gov_summary = convert_to_internal_data_format(summary).get("summary") + gov_summary = summarize_retsum_data(gov_summary.values()) differing_categories = get_differing_categories(mapped_summary, gov_summary) if not differing_categories: self.db_set({"filing_status": "Ready to File"}) response["filing_status"] = "Ready to File" - create_success_notifications( - self.return_period, "upload", response.get("status_cd") + enqueue_notification( + self.return_period, + "proceed_to_file", + response.get("status_cd"), + self.gstin, ) else: @@ -798,12 +820,17 @@ def process_proceed_to_file_gstr1(self): "differing_categories": differing_categories, } ) - # TODO: Failure Notification + enqueue_notification( + self.return_period, + "proceed_to_file", + response.get("status_cd"), + self.gstin, + api.request_id, + ) return response def file_gstr1(self, pan, otp=None): - # TODO: Try to file before proceeding to file if not otp: # If OTP is none, generate evc OTP pass @@ -812,6 +839,10 @@ def file_gstr1(self, pan, otp=None): api = GSTR1API(self) response = api.file_gstr_1(self.return_period, pan, otp, summary) + if response.error and response.error.error_cd == "RET09001": + self.db_set({"filing_status": "Not Filed"}) + self.update_json_for("authenticated_summary", None) + return response @@ -850,28 +881,50 @@ def get_differing_categories(mapped_summary, gov_summary): return differing_categories -def create_success_notifications(return_period, request_type, status_cd): - # TODO: GSTIN in message. It's not clear if this is for GSTR-1 +def enqueue_notification( + return_period, request_type, status_cd, gstin, request_id=None +): + frappe.enqueue( + "india_compliance.gst_india.doctype.gst_return_log.generate_gstr_1.create_notification", + queue="long", + return_period=return_period, + request_type=request_type, + status_cd=status_cd, + gstin=gstin, + request_id=request_id, + ) + + +def create_notification(return_period, request_type, status_cd, gstin, request_id=None): + # request_id shows failure response status_message_map = { - "P": f"Data {request_type}ing for return period {return_period} has been successfully completed.", - "PE": f"Data {request_type}ing for return period {return_period} is complete with errors", - "ER": f"Data {request_type}ing for return period {return_period} has encountered errors", + "P": f"Data {request_type} for GSTIN {gstin} and return period {return_period} has been successfully completed.", + "PE": f"Data {request_type} for GSTIN {gstin} and return period {return_period} is completed with errors", + "ER": f"Data {request_type} for GSTIN {gstin} and return period {return_period} has encountered errors", } + if request_id and ( + doc_name := frappe.db.get_value( + "Integration Request", {"request_id": request_id} + ) + ): + document_type = "Integration Request" + document_name = doc_name + else: + document_type = document_name = "GSTR-1 Beta" + notification = frappe.get_doc( { "doctype": "Notification Log", "for_user": frappe.session.user, "type": "Alert", - # TODO: check possibility to link to Integration Request Log. Create different notification if error in API. - "document_type": "GSTR-1 Beta", - "document_name": "GSTR-1 Beta", - "subject": f"Data {request_type} for return period {return_period}", + "document_type": document_type, + "document_name": document_name, + "subject": f"Data {request_type} for GSTIN {gstin} and return period {return_period}", "email_content": status_message_map.get(status_cd), } ) notification.insert() - return notification.name def check_return_status(self): diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 6257d48dbe..f154d39708 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -285,10 +285,9 @@ function reset_gstr1_data(frm) { __( "All the details saved in different tables shall be deleted after reset.
Are you sure, you want to reset the already saved data?" ), - async () => { + () => { frappe.show_alert(__("Resetting GSTR-1 data")); - await frm.call("reset_gstr1"); - fetch_status_with_retry(frm, "reset"); + perform_gstr1_action(frm, "reset"); } ); } @@ -342,11 +341,32 @@ function perform_gstr1_action(frm, action, additional_args = {}) { frappe.call({ method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.${action}_gstr1`, args: args, - callback: () => fetch_status_with_retry(frm, action), + callback: response => { + const message = response?.message; + + if (action == "file" && message?.error?.error_cd === "RET09001") { + frm.remove_custom_button("File"); + frm.add_custom_button(__("Proceed to File"), () => + proceed_to_file(frm) + ); + frm.page.set_indicator("Not Filed", "orange"); + frappe.msgprint( + __( + "Latest Summary is not available. Please generate summary and try again." + ) + ) + } else { + if (Object.keys(response).length == 0) { + fetch_status_with_retry(frm, action); + } else { + handle_proceed_to_file_response(frm, response.message); + } + } + }, }); } -const retry_intervals = [5000, 15000, 30000]; // 5 second, 15 second, 30 second +const retry_intervals = [5000, 15000, 30000, 60000, 120000, 300000, 600000, 720000]; // 5 second, 15 second, 30 second, 1 min, 2 min, 5 min, 10 min, 12 min function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { setTimeout( async () => { @@ -372,7 +392,7 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { } if (request_type == "proceed_to_file") - handle_proceed_to_file_response(frm, message.filing_status); + handle_proceed_to_file_response(frm, message); handle_notification(frm, message, request_type); }, @@ -407,7 +427,8 @@ function handle_errors(frm, message) { frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(data); } -function handle_proceed_to_file_response(frm, filing_status) { +function handle_proceed_to_file_response(frm, response) { + const filing_status = response.filing_status; if (!filing_status) return; if (filing_status == "Ready to File") { @@ -418,16 +439,24 @@ function handle_proceed_to_file_response(frm, filing_status) { return; } - // TODO: Show differeing categories + const differing_categories = response.differing_categories + .map(item => `
  • ${item}
  • `) + .join(""); + const message = ` +

    ${__( + "Summary for the following categories has not matched. Please sync with GSTIN." + )}

    +
      ${differing_categories}
    + `; + frappe.msgprint({ - message: __("Summary has not matched. Please sync with GSTIN."), + message: message, indicator: "red", title: __("GSTIN Sync Required"), primary_action: { label: __("Sync with GSTIN"), action() { render_empty_state(frm); - //here for what it will sync for frm.call("sync_with_gstn", { sync_for: "unfiled" }).then(() => { frappe.msgprint(__("Synced successfully with GSTIN.")); }); diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index d1f2e95480..4977cfdbbb 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -162,13 +162,14 @@ def on_generate(self, data, filters=None): doctype=self.doctype, ) - @frappe.whitelist() - def reset_gstr1(self): - gstr_1_log = frappe.get_doc( - "GST Return Log", - f"GSTR1-{get_period(self.month_or_quarter, self.year)}-{self.company_gstin}", - ) - gstr_1_log.reset_gstr1() + +@frappe.whitelist() +def reset_gstr1(month_or_quarter, year, company_gstin): + gstr_1_log = frappe.get_doc( + "GST Return Log", + f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", + ) + gstr_1_log.reset_gstr1() @frappe.whitelist() @@ -197,7 +198,7 @@ def proceed_to_file_gstr1(month_or_quarter, year, company_gstin): "GST Return Log", f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", ) - gstr_1_log.proceed_to_file_gstr1() + return gstr_1_log.proceed_to_file_gstr1() @frappe.whitelist() @@ -206,7 +207,7 @@ def file_gstr1(month_or_quarter, year, company_gstin, pan, otp): "GST Return Log", f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", ) - gstr_1_log.file_gstr1(pan, otp) + return gstr_1_log.file_gstr1(pan, otp) @frappe.whitelist() From 587500affb9f39f650b660561df5ee6881c0a570 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Mon, 9 Sep 2024 12:13:41 +0530 Subject: [PATCH 22/68] fix: upload data in parts and minor fixes --- .../gst_india/api_classes/taxpayer_base.py | 4 +- .../doctype/gst_return_log/generate_gstr_1.py | 67 ++++++++++++++++--- .../doctype/gstr_1_beta/gstr_1_beta.js | 53 ++++++--------- .../doctype/gstr_1_beta/gstr_1_beta.py | 1 - 4 files changed, 82 insertions(+), 43 deletions(-) diff --git a/india_compliance/gst_india/api_classes/taxpayer_base.py b/india_compliance/gst_india/api_classes/taxpayer_base.py index 8ace1d8b18..788030a9f8 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_base.py +++ b/india_compliance/gst_india/api_classes/taxpayer_base.py @@ -89,8 +89,8 @@ class TaxpayerAuthenticate(BaseAPI): # "AUTH4034": "invalid_otp", # Invalid OTP "AUTH4038": "authorization_failed", # Session Expired "TEC4002": "invalid_public_key", - "RET00003": "Return Form already ready to be filed", - "RET09001": "Latest Summary is not available. Please generate summary and try again.", + "RET00003": "Return Form already ready to be filed", # Actions performed on portal directly + "RET09001": "Latest Summary is not available. Please generate summary and try again.", # Actions performed on portal directly } def request_otp(self): diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index b0103c1392..9b65b6c8b7 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -1,6 +1,7 @@ # Copyright (c) 2024, Resilient Tech and contributors # For license information, please see license.txt import itertools +import json import frappe from frappe import unscrub @@ -720,22 +721,34 @@ def upload_gstr1(self, json_data): # Make API Request api = GSTR1API(self) - response = api.save_gstr_1_data(self.return_period, json_data) + reference_ids = [] - if response.get("reference_id"): - self.db_set( - { - "request_type": "upload", - "token": response.get("reference_id"), - } - ) + json_data_parts = get_partitioned_data(json_data) + + for json_data in json_data_parts: + response = api.save_gstr_1_data(self.return_period, json_data) + if response.get("reference_id"): + reference_ids.append(response.get("reference_id")) + + self.db_set( + { + "request_type": "upload", + "token": json.dumps(reference_ids), + } + ) def process_upload_gstr1(self): if self.request_type != "upload": return api = GSTR1API(self) - response = api.get_return_status(self.return_period, self.token) + + tokens = json.loads(self.token) + for token in tokens: + response = api.get_return_status(self.return_period, token) + + if response.get("status_cd") != "P": + break if response.get("status_cd") != "IP": self.db_set({"request_type": None, "token": None}) @@ -846,6 +859,42 @@ def file_gstr1(self, pan, otp=None): return response +def get_partitioned_data(json_data): + result = [] + base_data = {"gstin": json_data["gstin"], "fp": json_data["fp"]} + + partial_data = base_data.copy() + gst_categories = [ + category for category in json_data.keys() if category not in ["gstin", "fp"] + ] + + for category in gst_categories: + partial_data[category] = [] + index = 0 + + for obj in json_data.get(category, []): + partial_data[category].append({"ctin": obj["ctin"], "inv": []}) + + for invoice in obj.get("inv", []): + partial_data[category][index]["inv"].append(invoice) + + if len(json.dumps(partial_data)) > 5200000: + invoice = partial_data[category][index]["inv"].pop() + result.append(partial_data) + + partial_data = base_data.copy() + partial_data[category] = [{"ctin": obj["ctin"], "inv": []}] + + partial_data[category][0]["inv"].append(invoice) + + index += 1 + + if partial_data[category][index - 1]["inv"]: + result.append(partial_data) + + return result + + def get_differing_categories(mapped_summary, gov_summary): KEYS_TO_COMPARE = { "total_cess_amount", diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index f154d39708..895982fd2e 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -257,22 +257,10 @@ frappe.ui.form.on(DOCTYPE, { }, }); -async function generate_gstr1_data(frm) { - const { message: return_period } = await frappe.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.get_period", - args: { month_or_quarter: frm.doc.month_or_quarter, year: frm.doc.year }, - }); - - const log_name = `GSTR1-${return_period}-${frm.doc.company_gstin}`; - const { message } = await frappe.db.get_value("GST Return Log", log_name, [ - "token", - "request_type", - ]); - - await frm.call("generate_gstr1"); - if (message.token) { - fetch_status_with_retry(frm, message.request_type, (now = true)); - } +function generate_gstr1_data(frm) { + frm.call("generate_gstr1"); + const request_types = ['upload', 'reset', 'proceed_to_file'] + request_types.map( request_type => fetch_status_with_retry(frm, request_type, (now = true))) } function upload_gstr1_data(frm) { @@ -342,19 +330,8 @@ function perform_gstr1_action(frm, action, additional_args = {}) { method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.${action}_gstr1`, args: args, callback: response => { - const message = response?.message; - - if (action == "file" && message?.error?.error_cd === "RET09001") { - frm.remove_custom_button("File"); - frm.add_custom_button(__("Proceed to File"), () => - proceed_to_file(frm) - ); - frm.page.set_indicator("Not Filed", "orange"); - frappe.msgprint( - __( - "Latest Summary is not available. Please generate summary and try again." - ) - ) + if (action == "file") { + handle_file_response(frm, response) } else { if (Object.keys(response).length == 0) { fetch_status_with_retry(frm, action); @@ -379,6 +356,7 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { request_type: request_type, }, }); + if(!message.status_cd) return; if (message.status_cd === "IP" && retries < retry_intervals.length) return fetch_status_with_retry(frm, request_type, retries + 1); @@ -427,6 +405,21 @@ function handle_errors(frm, message) { frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(data); } +function handle_file_response(frm, response){ + if(response.message?.error?.error_cd === "RET09001"){ + frm.remove_custom_button("File"); + frm.add_custom_button(__("Proceed to File"), () => + proceed_to_file(frm) + ); + frm.page.set_indicator("Not Filed", "orange"); + frappe.msgprint( + __( + "Latest Summary is not available. Please generate summary and try again." + ) + ) + } +} + function handle_proceed_to_file_response(frm, response) { const filing_status = response.filing_status; if (!filing_status) return; @@ -493,8 +486,6 @@ function validate_details_and_file_gstr1(frm, dialog) { } function handle_notification(frm, message, request_type) { - if (!message.status_cd) return; - const request_status = request_type === "proceed_to_file" ? "Proceed to file" diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 4977cfdbbb..1510f420ca 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -282,7 +282,6 @@ def get_net_gst_liability(company, company_gstin, month_or_quarter, year): ####### UTILS ###################################################################################### -@frappe.whitelist() def get_period(month_or_quarter: str, year: str) -> str: """ Returns the period in the format MMYYYY From 72d926e6759e9e62214a39750356eb71568c0b06 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Mon, 9 Sep 2024 22:18:47 +0530 Subject: [PATCH 23/68] fix: extend token to gstr log and minor refactor --- .../doctype/gst_return_log/generate_gstr_1.py | 147 +++++++++++------- .../gst_return_log/gst_return_log.json | 23 +-- .../doctype/gstr_1_beta/gstr_1_beta.js | 31 ++-- .../gst_india/doctype/gstr_action/__init__.py | 0 .../doctype/gstr_action/gstr_action.json | 51 ++++++ .../doctype/gstr_action/gstr_action.py | 9 ++ 6 files changed, 176 insertions(+), 85 deletions(-) create mode 100644 india_compliance/gst_india/doctype/gstr_action/__init__.py create mode 100644 india_compliance/gst_india/doctype/gstr_action/gstr_action.json create mode 100644 india_compliance/gst_india/doctype/gstr_action/gstr_action.py diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 9b65b6c8b7..eefe5fc3c4 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -25,6 +25,13 @@ ) from india_compliance.gst_india.utils.gstr_utils import request_otp +code_to_status_map = { + "P": "Processed", + "PE": "Processed with Errors", + "ER": "Error", + "IP": "In Progress", +} + class SummarizeGSTR1: AMOUNT_FIELDS = { @@ -684,31 +691,42 @@ def reset_gstr1(self): response = api.reset_gstr_1_data(self.return_period) if response.get("reference_id"): - self.db_set( + self.append( + "actions", { "request_type": "reset", "token": response.get("reference_id"), - } + "creation_time": frappe.utils.now_datetime(), + }, ) + self.save() def process_reset_gstr1(self): - if self.request_type != "reset": + if not self.actions: return api = GSTR1API(self) - response = api.get_return_status(self.return_period, self.token) + response = None - if response.get("status_cd") != "IP": - self.db_set({"request_type": None, "token": None}) - enqueue_notification( - self.return_period, - "reset", - response.get("status_cd"), - self.gstin, - ) + for doc in self.actions: + if doc.request_type != "reset" or doc.status: + continue + + response = api.get_return_status(self.return_period, doc.token) + + if response.get("status_cd") != "IP": + doc.db_set( + {"status": code_to_status_map.get(response.get("status_cd"))} + ) + enqueue_notification( + self.return_period, + "reset", + response.get("status_cd"), + self.gstin, + ) - if response.get("status_cd") == "P": - self.update_json_for("unfiled", {}, reset_reconcile=True) + if response.get("status_cd") == "P": + self.update_json_for("unfiled", {}, reset_reconcile=True) return response @@ -721,46 +739,49 @@ def upload_gstr1(self, json_data): # Make API Request api = GSTR1API(self) - reference_ids = [] json_data_parts = get_partitioned_data(json_data) for json_data in json_data_parts: response = api.save_gstr_1_data(self.return_period, json_data) - if response.get("reference_id"): - reference_ids.append(response.get("reference_id")) - self.db_set( - { - "request_type": "upload", - "token": json.dumps(reference_ids), - } - ) + if response.get("reference_id"): + self.append( + "actions", + { + "request_type": "upload", + "token": response.get("reference_id"), + "creation_time": frappe.utils.now_datetime(), + }, + ) + self.save() def process_upload_gstr1(self): - if self.request_type != "upload": + if not self.actions: return api = GSTR1API(self) + response = None - tokens = json.loads(self.token) - for token in tokens: - response = api.get_return_status(self.return_period, token) + for doc in self.actions: + if doc.request_type != "upload" or doc.status: + continue - if response.get("status_cd") != "P": - break + response = api.get_return_status(self.return_period, doc.token) - if response.get("status_cd") != "IP": - self.db_set({"request_type": None, "token": None}) - enqueue_notification( - self.return_period, - "upload", - response.get("status_cd"), - self.gstin, - ) + if response.get("status_cd") != "IP": + doc.db_set( + {"status": code_to_status_map.get(response.get("status_cd"))} + ) + enqueue_notification( + self.return_period, + "upload", + response.get("status_cd"), + self.gstin, + ) - if response.get("status_cd") == "PE": - self.update_json_for("upload_error", response) + if response.get("status_cd") == "PE": + self.update_json_for("upload_error", response) return response @@ -772,42 +793,53 @@ def proceed_to_file_gstr1(self): return self.fetch_and_compare_summary(api) if response.get("reference_id"): - self.db_set( + self.append( + "actions", { "request_type": "proceed_to_file", "token": response.get("reference_id"), - } + "creation_time": frappe.utils.now_datetime(), + }, ) + self.save() def process_proceed_to_file_gstr1(self): - if self.request_type != "proceed_to_file": + if not self.actions: return api = GSTR1API(self) - response = api.get_return_status(self.return_period, self.token) + response = None - if response.get("status_cd") == "IP": - return response - else: - self.db_set({"request_type": None, "token": None}) + for doc in self.actions: + if doc.request_type != "proceed_to_file" or doc.status: + continue - if response.get("status_cd") != "P": - enqueue_notification( - self.return_period, - "proceed_to_file", - response.get("status_cd"), - self.gstin, - api.request_id, - ) - return + response = api.get_return_status(self.return_period, doc.token) - return self.fetch_and_compare_summary(api, response) + if response.get("status_cd") == "IP": + return response + + doc.db_set({"status": code_to_status_map.get(response.get("status_cd"))}) + if response.get("status_cd") != "P": + enqueue_notification( + self.return_period, + "proceed_to_file", + response.get("status_cd"), + self.gstin, + api.request_id, + ) + return + + return self.fetch_and_compare_summary(api, response) def fetch_and_compare_summary(self, api, response=None): if response is None: response = {} summary = api.get_gstr_1_data("RETSUM", self.return_period) + if summary.error: + return + self.update_json_for("authenticated_summary", summary) mapped_summary = self.get_json_for("books_summary") @@ -859,6 +891,7 @@ def file_gstr1(self, pan, otp=None): return response +# TODO : optimse this def get_partitioned_data(json_data): result = [] base_data = {"gstin": json_data["gstin"], "fp": json_data["fp"]} diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json index b9fff9e579..3094a3638b 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json @@ -36,9 +36,7 @@ "column_break_ndup", "reconcile_summary", "pending_request_info_section", - "request_type", - "column_break_kohw", - "token" + "actions" ], "fields": [ { @@ -209,22 +207,13 @@ { "fieldname": "pending_request_info_section", "fieldtype": "Section Break", - "hidden": 1, "label": "Pending Request Info" }, { - "fieldname": "request_type", - "fieldtype": "Data", - "label": "Request Type" - }, - { - "fieldname": "column_break_kohw", - "fieldtype": "Column Break" - }, - { - "fieldname": "token", - "fieldtype": "Data", - "label": "Token" + "fieldname": "actions", + "fieldtype": "Table", + "label": "Actions", + "options": "GSTR Action" } ], "in_create": 1, @@ -235,7 +224,7 @@ "link_fieldname": "reference_docname" } ], - "modified": "2024-08-24 13:25:56.482743", + "modified": "2024-09-09 17:58:04.543487", "modified_by": "Administrator", "module": "GST India", "name": "GST Return Log", diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 895982fd2e..c475da1ff1 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -259,8 +259,10 @@ frappe.ui.form.on(DOCTYPE, { function generate_gstr1_data(frm) { frm.call("generate_gstr1"); - const request_types = ['upload', 'reset', 'proceed_to_file'] - request_types.map( request_type => fetch_status_with_retry(frm, request_type, (now = true))) + const request_types = ["upload", "reset", "proceed_to_file"]; + request_types.map(request_type => + fetch_status_with_retry(frm, request_type, (now = true)) + ); } function upload_gstr1_data(frm) { @@ -331,7 +333,7 @@ function perform_gstr1_action(frm, action, additional_args = {}) { args: args, callback: response => { if (action == "file") { - handle_file_response(frm, response) + handle_file_response(frm, response); } else { if (Object.keys(response).length == 0) { fetch_status_with_retry(frm, action); @@ -356,7 +358,7 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { request_type: request_type, }, }); - if(!message.status_cd) return; + if (!message.status_cd) return; if (message.status_cd === "IP" && retries < retry_intervals.length) return fetch_status_with_retry(frm, request_type, retries + 1); @@ -369,6 +371,15 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { frm.call("generate_gstr1"); } + if (request_type == "upload" && frm.gstr1.status === "Ready to File") { + frm.remove_custom_button("File"); + frm.add_custom_button(__("Proceed to File"), () => + proceed_to_file(frm) + ); + frm.page.set_indicator("Not Filed", "orange"); + frm.gstr1.status = "Not Filed"; + } + if (request_type == "proceed_to_file") handle_proceed_to_file_response(frm, message); @@ -405,18 +416,16 @@ function handle_errors(frm, message) { frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(data); } -function handle_file_response(frm, response){ - if(response.message?.error?.error_cd === "RET09001"){ +function handle_file_response(frm, response) { + if (response.message?.error?.error_cd === "RET09001") { frm.remove_custom_button("File"); - frm.add_custom_button(__("Proceed to File"), () => - proceed_to_file(frm) - ); + frm.add_custom_button(__("Proceed to File"), () => proceed_to_file(frm)); frm.page.set_indicator("Not Filed", "orange"); frappe.msgprint( __( "Latest Summary is not available. Please generate summary and try again." ) - ) + ); } } @@ -427,8 +436,8 @@ function handle_proceed_to_file_response(frm, response) { if (filing_status == "Ready to File") { frm.remove_custom_button("Proceed to File"); frm.add_custom_button(__("File"), () => file_gstr1_data(frm)); - frm.page.clear_menu(); frm.page.set_indicator("Ready to File", "orange"); + frm.gstr1.status = "Ready to File"; return; } diff --git a/india_compliance/gst_india/doctype/gstr_action/__init__.py b/india_compliance/gst_india/doctype/gstr_action/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/india_compliance/gst_india/doctype/gstr_action/gstr_action.json b/india_compliance/gst_india/doctype/gstr_action/gstr_action.json new file mode 100644 index 0000000000..72611c1161 --- /dev/null +++ b/india_compliance/gst_india/doctype/gstr_action/gstr_action.json @@ -0,0 +1,51 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-09-09 17:43:22.979394", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "request_type", + "token", + "status", + "creation_time" + ], + "fields": [ + { + "fieldname": "request_type", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Request Type" + }, + { + "fieldname": "token", + "fieldtype": "Data", + "label": "Token" + }, + { + "fieldname": "status", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Status" + }, + { + "fieldname": "creation_time", + "fieldtype": "Datetime", + "in_list_view": 1, + "label": "Creation Time" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-09-09 21:08:51.624884", + "modified_by": "Administrator", + "module": "GST India", + "name": "GSTR Action", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/india_compliance/gst_india/doctype/gstr_action/gstr_action.py b/india_compliance/gst_india/doctype/gstr_action/gstr_action.py new file mode 100644 index 0000000000..f267f39ec4 --- /dev/null +++ b/india_compliance/gst_india/doctype/gstr_action/gstr_action.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Resilient Tech and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class GSTRAction(Document): + pass From 9e642dd365787b0e139e42ffd3b21b6b9ab27b22 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 11 Sep 2024 14:24:34 +0530 Subject: [PATCH 24/68] fix: optimise upload gstr1 --- .../doctype/gst_return_log/generate_gstr_1.py | 116 +++++++++++++----- .../doctype/gstr_1_beta/gstr_1_beta.js | 1 + 2 files changed, 85 insertions(+), 32 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index eefe5fc3c4..7c7b050f33 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -5,7 +5,7 @@ import frappe from frappe import unscrub -from frappe.utils import flt +from frappe.utils import floor, flt from india_compliance.gst_india.api_classes.taxpayer_returns import GSTR1API from india_compliance.gst_india.utils.gstr_1 import GSTR1_SubCategory @@ -25,12 +25,13 @@ ) from india_compliance.gst_india.utils.gstr_utils import request_otp -code_to_status_map = { +status_code_map = { "P": "Processed", "PE": "Processed with Errors", "ER": "Error", "IP": "In Progress", } +MAXIMUM_UPLOAD_SIZE = 5200000 class SummarizeGSTR1: @@ -715,9 +716,7 @@ def process_reset_gstr1(self): response = api.get_return_status(self.return_period, doc.token) if response.get("status_cd") != "IP": - doc.db_set( - {"status": code_to_status_map.get(response.get("status_cd"))} - ) + doc.db_set({"status": status_code_map.get(response.get("status_cd"))}) enqueue_notification( self.return_period, "reset", @@ -770,9 +769,7 @@ def process_upload_gstr1(self): response = api.get_return_status(self.return_period, doc.token) if response.get("status_cd") != "IP": - doc.db_set( - {"status": code_to_status_map.get(response.get("status_cd"))} - ) + doc.db_set({"status": status_code_map.get(response.get("status_cd"))}) enqueue_notification( self.return_period, "upload", @@ -819,7 +816,7 @@ def process_proceed_to_file_gstr1(self): if response.get("status_cd") == "IP": return response - doc.db_set({"status": code_to_status_map.get(response.get("status_cd"))}) + doc.db_set({"status": status_code_map.get(response.get("status_cd"))}) if response.get("status_cd") != "P": enqueue_notification( self.return_period, @@ -891,41 +888,96 @@ def file_gstr1(self, pan, otp=None): return response -# TODO : optimse this +def is_within_limit(result): + return len(json.dumps(result, indent=4)) < MAXIMUM_UPLOAD_SIZE + + def get_partitioned_data(json_data): - result = [] + if is_within_limit(json_data): + yield json_data + return + base_data = {"gstin": json_data["gstin"], "fp": json_data["fp"]} + result = {} + has_yielded = False + + for category, category_data in json_data.items(): + result[category] = category_data + + if is_within_limit(result): + continue + + # 2 case : 1st - large categories, 2nd - combined category data is large + del result[category] + if result.keys() in [ + "b2b", + "cdnr", + ]: # Ensure at least one category is present before yielding the result + yield result + + result = base_data.copy() + result[category] = category_data - partial_data = base_data.copy() - gst_categories = [ - category for category in json_data.keys() if category not in ["gstin", "fp"] - ] + if is_within_limit(result): + continue + + has_yielded = True + # Handle the case where individual objects within the category need to be partitioned + yield from partition_by_objects(result, category, category_data, base_data) + + if not has_yielded: + yield result + + +def partition_by_objects(result, category, category_data, base_data): + result[category] = [] + has_yielded = False + for object in category_data: + result[category].append(object) - for category in gst_categories: - partial_data[category] = [] - index = 0 + if is_within_limit(result): + continue + + # 2 case: 1st object is so big, combine 2 object is big + object_data = result[category].pop() + if result[ + category + ]: # Ensure at least one object is present before yielding the result + yield result + + result = base_data.copy() + result[category] = [object_data] + + if is_within_limit(result): + continue - for obj in json_data.get(category, []): - partial_data[category].append({"ctin": obj["ctin"], "inv": []}) + has_yielded = True + # Handle the case where invoices within the object need to be partitioned + yield from partition_by_invoices(result, category, object) - for invoice in obj.get("inv", []): - partial_data[category][index]["inv"].append(invoice) + if not has_yielded: + yield result - if len(json.dumps(partial_data)) > 5200000: - invoice = partial_data[category][index]["inv"].pop() - result.append(partial_data) - partial_data = base_data.copy() - partial_data[category] = [{"ctin": obj["ctin"], "inv": []}] +def partition_by_invoices(result, category, object): + result[category] = [{"ctin": object.get("ctin"), "inv": []}] - partial_data[category][0]["inv"].append(invoice) + result_size = len(json.dumps(result, indent=4)) + invoice_size = 1200 + invoice_to_add = floor((MAXIMUM_UPLOAD_SIZE - result_size) / invoice_size) + count = 0 - index += 1 + for invoice in object.get("inv"): + count += 1 + result[category][-1]["inv"].append(invoice) - if partial_data[category][index - 1]["inv"]: - result.append(partial_data) + if count == invoice_to_add: + count = 0 + yield result + result[category] = [{"ctin": object.get("ctin"), "inv": []}] - return result + if result[category][-1]["inv"]: + yield result def get_differing_categories(mapped_summary, gov_summary): diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index c475da1ff1..0c4b3cbdd1 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -320,6 +320,7 @@ async function file_gstr1_data(frm) { } function perform_gstr1_action(frm, action, additional_args = {}) { + frm.gstr1.tabs.error_tab.hide(); const base_args = { month_or_quarter: frm.doc.month_or_quarter, year: frm.doc.year, From b4ec8bacf9f6a78d8a192f6c5eeac1beab08cddb Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 11 Sep 2024 20:05:51 +0530 Subject: [PATCH 25/68] fix: changes as per review, added todos after live tests --- .../gst_india/api_classes/taxpayer_base.py | 6 +++ .../doctype/gst_return_log/generate_gstr_1.py | 43 ++++++++++++------- .../doctype/gstr_1_beta/gstr_1_beta.js | 28 +++++++++++- .../doctype/gstr_1_beta/gstr_1_beta.py | 17 +++++++- .../gst_india/utils/gstr_1/gstr_1_download.py | 1 + 5 files changed, 75 insertions(+), 20 deletions(-) diff --git a/india_compliance/gst_india/api_classes/taxpayer_base.py b/india_compliance/gst_india/api_classes/taxpayer_base.py index 7af2e06d79..1b32dbb766 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_base.py +++ b/india_compliance/gst_india/api_classes/taxpayer_base.py @@ -491,6 +491,12 @@ def validate_auth_token(self): # Dummy request self.get_filing_preference() + frappe.cache.set_value( + f"authenticated_gstin:{self.company_gstin}", + True, + expires_in_sec=60 * 15, + ) + return def get_filing_preference(self): diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 62fabf9e96..2595c9c62a 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -8,7 +8,7 @@ from frappe.utils import floor, flt from india_compliance.gst_india.api_classes.taxpayer_returns import GSTR1API -from india_compliance.gst_india.utils.gstr_1 import GSTR1_SubCategory +from india_compliance.gst_india.utils.gstr_1 import GovJsonKey, GSTR1_SubCategory from india_compliance.gst_india.utils.gstr_1.__init__ import ( CATEGORY_SUB_CATEGORY_MAPPING, SUBCATEGORIES_NOT_CONSIDERED_IN_TOTAL_TAX, @@ -673,9 +673,10 @@ def normalize_data(data): class FileGSTR1: - # TODO: handle otp requested errors for all APIs def reset_gstr1(self): # reset called after proceed to file + # TODO: Throw if already in progress + self.db_set({"filing_status": "Not Filed"}) api = GSTR1API(self) @@ -715,6 +716,8 @@ def process_reset_gstr1(self): ) if response.get("status_cd") == "P": + # TODO: Better way to handle this. Exclude such records from books data. + self.remove_json_for("books") self.update_json_for("unfiled", {}, reset_reconcile=True) return response @@ -723,26 +726,30 @@ def upload_gstr1(self, json_data): if not json_data: return + keys = {category.value for category in GovJsonKey} + if any(key not in json_data for key in keys): + frappe.throw("Nothing to upload") + # upload data after proceed to file self.db_set({"filing_status": "Not Filed"}) + # remove error file if it exists + self.remove_json_for("upload_error") + # Make API Request api = GSTR1API(self) + response = api.save_gstr_1_data(self.return_period, json_data) - json_data_parts = get_partitioned_data(json_data) - - for json_data in json_data_parts: - response = api.save_gstr_1_data(self.return_period, json_data) + if response.get("reference_id"): + self.append( + "actions", + { + "request_type": "upload", + "token": response.get("reference_id"), + "creation_time": frappe.utils.now_datetime(), + }, + ) - if response.get("reference_id"): - self.append( - "actions", - { - "request_type": "upload", - "token": response.get("reference_id"), - "creation_time": frappe.utils.now_datetime(), - }, - ) self.save() def process_upload_gstr1(self): @@ -862,7 +869,7 @@ def fetch_and_compare_summary(self, api, response=None): return response - def file_gstr1(self, pan, otp=None): + def file_gstr1(self, pan, otp): if not otp: # If OTP is none, generate evc OTP pass @@ -875,6 +882,10 @@ def file_gstr1(self, pan, otp=None): self.db_set({"filing_status": "Not Filed"}) self.update_json_for("authenticated_summary", None) + # TODO: On success. Update status, update ack no and date + # TODO: Update data for generate gstr-1 + # TODO: 2nd phase Accounting Entry. + return response diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index c2d2bced14..b12c8ac38b 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -131,6 +131,7 @@ frappe.ui.form.on(DOCTYPE, { ) return; + // TODO: show generate button if (frm.$wrapper.find(".form-message.orange").length) return; frm.set_intro( __( @@ -226,6 +227,10 @@ frappe.ui.form.on(DOCTYPE, { const secondary_button_label = gst_data?.status === "Ready to File" ? "File" : "Proceed to File"; + + // TODO: proceed to file is not very relevant as long as we have not uploaded. + // after upload is successfully processed, we can update unfiled data = books data + // TODO: If no differences, we can skip the upload button. frm.add_custom_button(__(secondary_button_label), () => actions[secondary_button_label](frm) ); @@ -246,7 +251,13 @@ frappe.ui.form.on(DOCTYPE, { }); function generate_gstr1_data(frm) { - frm.taxpayer_api_call("generate_gstr1"); + frm.taxpayer_api_call("generate_gstr1").then((r) => { + // TODO: Do this for all API calls + // TODO: Check size before sending it via redis. Instead it could raise alert. + if (!r.message) return; + frm.doc.__gst_data = r.message; + frm.trigger("load_gstr1_data"); + }); const request_types = ["upload", "reset", "proceed_to_file"]; request_types.map(request_type => fetch_status_with_retry(frm, request_type, (now = true)) @@ -276,6 +287,12 @@ function proceed_to_file(frm) { } async function file_gstr1_data(frm) { + // TODO: If amendments, show table for total liability breakup. + // compute other than amendments. + // This table to be shown only if there are amendments. + + // TODO: EVC Generation, Resend, and Filing + const { message } = await frappe.db.get_value("GSTIN", frm.doc.company_gstin, [ "last_pan_used_for_gstr", ]); @@ -296,7 +313,7 @@ async function file_gstr1_data(frm) { label: "OTP", fieldname: "otp", fieldtype: "Data", - hidden: 1, + hidden: 1, // TODO: 2nd priority instead disable input }, ], primary_action_label: "Verify PAN", @@ -353,6 +370,13 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { return fetch_status_with_retry(frm, request_type, retries + 1); if (message.status_cd === "PE" || message.status_cd === "ER") + // TODO: Show errors on generate if any + // after filing or mark as filed, clear error files + // Highlight error tab + + // TODO: ER => Throw error. PE => Show error (only for upload) + // Link to Integration Request if ER. + handle_errors(frm, message); if (request_type == "reset") { diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 0fca956be7..bf75f8e335 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -101,8 +101,7 @@ def generate_gstr1(self, sync_for=None, recompute_books=False): data = data data["status"] = gstr1_log.filing_status or "Not Filed" gstr1_log.update_status("Generated") - self.on_generate(data) - return + return data # validate auth token if gstr1_log.is_sek_needed(settings): @@ -163,6 +162,7 @@ def on_generate(self, data, filters=None): @frappe.whitelist() +@otp_handler def reset_gstr1(month_or_quarter, year, company_gstin): gstr_1_log = frappe.get_doc( "GST Return Log", @@ -172,6 +172,7 @@ def reset_gstr1(month_or_quarter, year, company_gstin): @frappe.whitelist() +@otp_handler def upload_gstr1(month_or_quarter, year, company_gstin): from india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_export import ( download_gstr_1_json, @@ -192,6 +193,7 @@ def upload_gstr1(month_or_quarter, year, company_gstin): @frappe.whitelist() +@otp_handler def proceed_to_file_gstr1(month_or_quarter, year, company_gstin): gstr_1_log = frappe.get_doc( "GST Return Log", @@ -201,6 +203,15 @@ def proceed_to_file_gstr1(month_or_quarter, year, company_gstin): @frappe.whitelist() +def generate_evc_otp(company_gstin, pan): + # TODO: permissions check for all whitelisted functions + frappe.has_permission("GSTR-1 Beta", "write", throw=True) + + return TaxpayerBaseAPI(company_gstin).initiate_otp_for_evc(pan, "R1") + + +@frappe.whitelist() +@otp_handler def file_gstr1(month_or_quarter, year, company_gstin, pan, otp): gstr_1_log = frappe.get_doc( "GST Return Log", @@ -210,6 +221,7 @@ def file_gstr1(month_or_quarter, year, company_gstin, pan, otp): @frappe.whitelist() +@otp_handler def process_gstr1_request(month_or_quarter, year, company_gstin, request_type): gstr_1_log = frappe.get_doc( "GST Return Log", @@ -222,6 +234,7 @@ def process_gstr1_request(month_or_quarter, year, company_gstin, request_type): if not data: data = {} + data.update( { "month_or_quarter": month_or_quarter, diff --git a/india_compliance/gst_india/utils/gstr_1/gstr_1_download.py b/india_compliance/gst_india/utils/gstr_1/gstr_1_download.py index f623e208a0..d6366e9648 100644 --- a/india_compliance/gst_india/utils/gstr_1/gstr_1_download.py +++ b/india_compliance/gst_india/utils/gstr_1/gstr_1_download.py @@ -110,6 +110,7 @@ def save_gstr_1(gstin, return_period, json_data, return_type): gstr1_log = frappe.get_doc("GST Return Log", f"GSTR1-{return_period}-{gstin}") gstr1_log.update_json_for(data_field, mapped_data, overwrite=False) + gstr1_log.update_status("Generated") def save_gstr_1_filed_data(gstin, return_period, json_data): From 8ff820e2ea39f6b92396c5ddac422e6a39887d73 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 12 Sep 2024 13:00:00 +0530 Subject: [PATCH 26/68] fix: add integration request in actions and wrapper in gstr1 beta --- .../doctype/gst_return_log/generate_gstr_1.py | 71 ++++++++++--------- .../gst_return_log/gst_return_log.json | 5 +- .../doctype/gstr_1_beta/gstr_1_beta.py | 34 +++++---- .../doctype/gstr_action/gstr_action.json | 14 +++- 4 files changed, 69 insertions(+), 55 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 2595c9c62a..72d87b1b1b 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -682,16 +682,7 @@ def reset_gstr1(self): api = GSTR1API(self) response = api.reset_gstr_1_data(self.return_period) - if response.get("reference_id"): - self.append( - "actions", - { - "request_type": "reset", - "token": response.get("reference_id"), - "creation_time": frappe.utils.now_datetime(), - }, - ) - self.save() + set_gstr1_actions(self, "reset", response.get("reference_id"), api.request_id) def process_reset_gstr1(self): if not self.actions: @@ -727,7 +718,7 @@ def upload_gstr1(self, json_data): return keys = {category.value for category in GovJsonKey} - if any(key not in json_data for key in keys): + if all(key not in json_data for key in keys): frappe.throw("Nothing to upload") # upload data after proceed to file @@ -740,17 +731,7 @@ def upload_gstr1(self, json_data): api = GSTR1API(self) response = api.save_gstr_1_data(self.return_period, json_data) - if response.get("reference_id"): - self.append( - "actions", - { - "request_type": "upload", - "token": response.get("reference_id"), - "creation_time": frappe.utils.now_datetime(), - }, - ) - - self.save() + set_gstr1_actions(self, "upload", response.get("reference_id"), api.request_id) def process_upload_gstr1(self): if not self.actions: @@ -786,16 +767,9 @@ def proceed_to_file_gstr1(self): if response.error and response.error.error_cd == "RET00003": return self.fetch_and_compare_summary(api) - if response.get("reference_id"): - self.append( - "actions", - { - "request_type": "proceed_to_file", - "token": response.get("reference_id"), - "creation_time": frappe.utils.now_datetime(), - }, - ) - self.save() + set_gstr1_actions( + self, "proceed_to_file", response.get("reference_id"), api.request_id + ) def process_proceed_to_file_gstr1(self): if not self.actions: @@ -874,6 +848,8 @@ def file_gstr1(self, pan, otp): # If OTP is none, generate evc OTP pass + # TODO : add actions for file gstr1 + summary = self.get_json_for("authenticated_summary") api = GSTR1API(self) response = api.file_gstr_1(self.return_period, pan, otp, summary) @@ -1016,6 +992,37 @@ def get_differing_categories(mapped_summary, gov_summary): return differing_categories +def set_gstr1_actions(doc, request_type, token, request_id): + if token: + doc.append( + "actions", + { + "request_type": request_type, + "token": token, + "creation_time": frappe.utils.now_datetime(), + }, + ) + doc.save() + enqueue_actions(token, request_id) + + +def enqueue_actions(token, request_id): + frappe.enqueue( + "india_compliance.gst_india.doctype.gst_return_log.generate_gstr_1.add_integration_request", + queue="long", + token=token, + request_id=request_id, + ) + + +def add_integration_request(token, request_id): + doc_name = frappe.db.get_value("Integration Request", {"request_id": request_id}) + if doc_name: + frappe.db.set_value( + "GSTR Action", {"token": token}, {"integration_request": doc_name} + ) + + def enqueue_notification( return_period, request_type, status_cd, gstin, request_id=None ): diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json index 3094a3638b..f09562d13a 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json @@ -206,8 +206,7 @@ }, { "fieldname": "pending_request_info_section", - "fieldtype": "Section Break", - "label": "Pending Request Info" + "fieldtype": "Section Break" }, { "fieldname": "actions", @@ -224,7 +223,7 @@ "link_fieldname": "reference_docname" } ], - "modified": "2024-09-09 17:58:04.543487", + "modified": "2024-09-12 11:48:46.154973", "modified_by": "Administrator", "module": "GST India", "name": "GST Return Log", diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index bf75f8e335..1560694c58 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -161,14 +161,20 @@ def on_generate(self, data, filters=None): ) -@frappe.whitelist() -@otp_handler -def reset_gstr1(month_or_quarter, year, company_gstin): +def handle_gstr1_action(action, month_or_quarter, year, company_gstin, *args): + frappe.has_permission("GSTR-1 Beta", "write", throw=True) + gstr_1_log = frappe.get_doc( "GST Return Log", f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", ) - gstr_1_log.reset_gstr1() + return getattr(gstr_1_log, action)(*args) + + +@frappe.whitelist() +@otp_handler +def reset_gstr1(month_or_quarter, year, company_gstin): + handle_gstr1_action("reset_gstr1", month_or_quarter, year, company_gstin) @frappe.whitelist() @@ -185,39 +191,31 @@ def upload_gstr1(month_or_quarter, year, company_gstin): delete_missing=True, ) - gstr_1_log = frappe.get_doc( - "GST Return Log", - f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", + handle_gstr1_action( + "upload_gstr1", month_or_quarter, year, company_gstin, data.get("data") ) - gstr_1_log.upload_gstr1(data.get("data")) @frappe.whitelist() @otp_handler def proceed_to_file_gstr1(month_or_quarter, year, company_gstin): - gstr_1_log = frappe.get_doc( - "GST Return Log", - f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", + return handle_gstr1_action( + "proceed_to_file_gstr1", month_or_quarter, year, company_gstin ) - return gstr_1_log.proceed_to_file_gstr1() @frappe.whitelist() def generate_evc_otp(company_gstin, pan): - # TODO: permissions check for all whitelisted functions frappe.has_permission("GSTR-1 Beta", "write", throw=True) - return TaxpayerBaseAPI(company_gstin).initiate_otp_for_evc(pan, "R1") @frappe.whitelist() @otp_handler def file_gstr1(month_or_quarter, year, company_gstin, pan, otp): - gstr_1_log = frappe.get_doc( - "GST Return Log", - f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", + return handle_gstr1_action( + "file_gstr1", month_or_quarter, year, company_gstin, pan, otp ) - return gstr_1_log.file_gstr1(pan, otp) @frappe.whitelist() diff --git a/india_compliance/gst_india/doctype/gstr_action/gstr_action.json b/india_compliance/gst_india/doctype/gstr_action/gstr_action.json index 72611c1161..39dc83d302 100644 --- a/india_compliance/gst_india/doctype/gstr_action/gstr_action.json +++ b/india_compliance/gst_india/doctype/gstr_action/gstr_action.json @@ -9,7 +9,8 @@ "request_type", "token", "status", - "creation_time" + "creation_time", + "integration_request" ], "fields": [ { @@ -21,6 +22,7 @@ { "fieldname": "token", "fieldtype": "Data", + "hidden": 1, "label": "Token" }, { @@ -34,12 +36,20 @@ "fieldtype": "Datetime", "in_list_view": 1, "label": "Creation Time" + }, + { + "fieldname": "integration_request", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Integration Request", + "options": "Integration Request", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-09-09 21:08:51.624884", + "modified": "2024-09-12 12:36:05.679413", "modified_by": "Administrator", "module": "GST India", "name": "GSTR Action", From 99cb1a3563d3ba374d579adf56c3e3a4691ad047 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 12 Sep 2024 14:41:50 +0530 Subject: [PATCH 27/68] fix: show error tab on generate and for upload --- .../doctype/gst_return_log/generate_gstr_1.py | 11 ++-- .../doctype/gstr_1_beta/gstr_1_beta.js | 58 +++++++++---------- .../doctype/gstr_1_beta/gstr_1_beta.py | 2 + 3 files changed, 36 insertions(+), 35 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 72d87b1b1b..ca4d4ec3f7 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -745,17 +745,19 @@ def process_upload_gstr1(self): continue response = api.get_return_status(self.return_period, doc.token) + status_cd = response.get("status_cd") - if response.get("status_cd") != "IP": - doc.db_set({"status": status_code_map.get(response.get("status_cd"))}) + if status_cd != "IP": + doc.db_set({"status": status_code_map.get(status_cd)}) enqueue_notification( self.return_period, "upload", - response.get("status_cd"), + status_cd, self.gstin, + api.request_id if status_cd == "ER" else None, ) - if response.get("status_cd") == "PE": + if status_cd == "PE": self.update_json_for("upload_error", response) return response @@ -861,6 +863,7 @@ def file_gstr1(self, pan, otp): # TODO: On success. Update status, update ack no and date # TODO: Update data for generate gstr-1 # TODO: 2nd phase Accounting Entry. + # after filing or mark as filed, clear error files return response diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index b12c8ac38b..be8fff239f 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -251,7 +251,7 @@ frappe.ui.form.on(DOCTYPE, { }); function generate_gstr1_data(frm) { - frm.taxpayer_api_call("generate_gstr1").then((r) => { + frm.taxpayer_api_call("generate_gstr1").then(r => { // TODO: Do this for all API calls // TODO: Check size before sending it via redis. Instead it could raise alert. if (!r.message) return; @@ -369,16 +369,14 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { if (message.status_cd === "IP" && retries < retry_intervals.length) return fetch_status_with_retry(frm, request_type, retries + 1); - if (message.status_cd === "PE" || message.status_cd === "ER") - // TODO: Show errors on generate if any - // after filing or mark as filed, clear error files - // Highlight error tab - - // TODO: ER => Throw error. PE => Show error (only for upload) - // Link to Integration Request if ER. + if (message.status_cd == "ER") + frappe.throw(__(message.error_report.error_msg)); + if (message.status_cd == "PE" && request_type == "upload") handle_errors(frm, message); + // Highlight error tab + if (request_type == "reset") { frm.page.set_indicator("Not Filed", "orange"); frm.call("generate_gstr1"); @@ -403,30 +401,8 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { } function handle_errors(frm, message) { - const { status_cd, error_report } = message; - let data = []; - - if (status_cd == "ER") { - data.push({ - error_code: error_report.error_cd, - description: error_report.error_msg, - }); - } else { - for (let category in error_report) { - for (let object of error_report[category]) { - data.push({ - category: category.toUpperCase(), - error_code: object.error_cd, - description: object.error_msg, - party_gstin: object.ctin, - place_of_supply: object.inv[0].pos, - invoice_number: object.inv[0].inum, - }); - } - } - } frm.gstr1.tabs.error_tab.show(); - frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(data); + frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(message); } function handle_file_response(frm, response) { @@ -2373,6 +2349,7 @@ class ErrorTab extends TabManager { refresh_data(data) { this.set_default_title(); + data = this.get_error_data(data); super.refresh_data(data, data, "Error Summary"); $(".dt-footer").remove(); } @@ -2390,6 +2367,25 @@ class ErrorTab extends TabManager {
    `); } + + get_error_data(message) { + const { error_report } = message; + let data = []; + + for (let category in error_report) { + for (let object of error_report[category]) { + data.push({ + category: category.toUpperCase(), + error_code: object.error_cd, + description: object.error_msg, + party_gstin: object.ctin, + place_of_supply: object.inv[0].pos, + invoice_number: object.inv[0].inum, + }); + } + } + return data; + } } class DetailViewDialog { diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 1560694c58..d191229624 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -100,6 +100,8 @@ def generate_gstr1(self, sync_for=None, recompute_books=False): if data: data = data data["status"] = gstr1_log.filing_status or "Not Filed" + if error_data := gstr1_log.get_json_for("upload_error"): + data["error"] = error_data gstr1_log.update_status("Generated") return data From 3ab9103f39cf6a68baf50d67666b3ab30fed3888 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 12 Sep 2024 17:01:02 +0530 Subject: [PATCH 28/68] fix: set primary buttons --- .../doctype/gst_return_log/generate_gstr_1.py | 6 ++ .../doctype/gstr_1_beta/gstr_1_beta.js | 102 ++++++++---------- 2 files changed, 52 insertions(+), 56 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index ca4d4ec3f7..d3f44a660f 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -760,6 +760,12 @@ def process_upload_gstr1(self): if status_cd == "PE": self.update_json_for("upload_error", response) + if status_cd == "P": + self.update_json_for( + "unfiled_summary", self.get_json_for("books_summary") + ) + self.update_json_for("unfiled", self.get_json_for("books")) + return response def proceed_to_file_gstr1(self): diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index be8fff239f..fafd9e3519 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -131,7 +131,6 @@ frappe.ui.form.on(DOCTYPE, { ) return; - // TODO: show generate button if (frm.$wrapper.find(".form-message.orange").length) return; frm.set_intro( __( @@ -139,6 +138,7 @@ frappe.ui.form.on(DOCTYPE, { ), "orange" ); + set_primary_secondary_buttons(frm); }); frappe.realtime.on("show_message", message => { @@ -195,47 +195,7 @@ frappe.ui.form.on(DOCTYPE, { refresh(frm) { // Primary Action frm.disable_save(); - const gst_data = frm.doc.__gst_data; - - if (gst_data && (!is_gstr1_api_enabled() || gst_data.status == "Filed")) { - frm.gstr1.render_indicator(); - return; - } - - const actions = { - Generate: generate_gstr1_data, - Upload: upload_gstr1_data, - "Proceed to File": proceed_to_file, - File: file_gstr1_data, - }; - - const primary_button_label = ["Not Filed", "Ready to File"].includes( - gst_data?.status - ) - ? "Upload" - : "Generate"; - frm.page.set_primary_action(__(primary_button_label), () => - actions[primary_button_label](frm) - ); - - if (!gst_data) { - frm.page.clear_indicator(); - return; - } - - frm.add_custom_button(__("Reset"), () => reset_gstr1_data(frm)); - - const secondary_button_label = - gst_data?.status === "Ready to File" ? "File" : "Proceed to File"; - - // TODO: proceed to file is not very relevant as long as we have not uploaded. - // after upload is successfully processed, we can update unfiled data = books data - // TODO: If no differences, we can skip the upload button. - frm.add_custom_button(__(secondary_button_label), () => - actions[secondary_button_label](frm) - ); - - frm.gstr1.render_indicator(); + set_primary_secondary_buttons(frm); }, load_gstr1_data(frm) { @@ -250,6 +210,46 @@ frappe.ui.form.on(DOCTYPE, { }, }); +function set_primary_secondary_buttons(frm) { + const gst_data = frm.doc.__gst_data; + + if (gst_data && (!is_gstr1_api_enabled() || gst_data.status == "Filed")) { + frm.gstr1.render_indicator(); + return; + } + + const actions = { + Generate: generate_gstr1_data, + Upload: upload_gstr1_data, + "Proceed to File": proceed_to_file, + File: file_gstr1_data, + }; + + if (!gst_data || frm.$wrapper.find(".form-message.orange").length) + primary_button_label = "Generate"; + + else if (gst_data.status == "Ready to File") primary_button_label = "File"; + else if (gst_data.status == "Not Filed") { + //TODO: after upload do i neeed to set primary action to proceed to file + primary_button_label = gst_data.reconcile_summary + ? "Upload" + : "Proceed to File"; + } + + frm.page.set_primary_action(__(primary_button_label), () => + actions[primary_button_label](frm) + ); + + if (!gst_data) { + frm.page.clear_indicator(); + return; + } + + frm.add_custom_button(__("Reset"), () => reset_gstr1_data(frm)); + + frm.gstr1.render_indicator(); +} + function generate_gstr1_data(frm) { frm.taxpayer_api_call("generate_gstr1").then(r => { // TODO: Do this for all API calls @@ -378,19 +378,10 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { // Highlight error tab if (request_type == "reset") { - frm.page.set_indicator("Not Filed", "orange"); + frm.page.set_primary_action("Upload", () => upload_gstr1_data(frm)); frm.call("generate_gstr1"); } - if (request_type == "upload" && frm.gstr1.status === "Ready to File") { - frm.remove_custom_button("File"); - frm.add_custom_button(__("Proceed to File"), () => - proceed_to_file(frm) - ); - frm.page.set_indicator("Not Filed", "orange"); - frm.gstr1.status = "Not Filed"; - } - if (request_type == "proceed_to_file") handle_proceed_to_file_response(frm, message); @@ -407,9 +398,9 @@ function handle_errors(frm, message) { function handle_file_response(frm, response) { if (response.message?.error?.error_cd === "RET09001") { - frm.remove_custom_button("File"); - frm.add_custom_button(__("Proceed to File"), () => proceed_to_file(frm)); + frm.page.set_primary_action("Proceed to File", () => proceed_to_file(frm)); frm.page.set_indicator("Not Filed", "orange"); + frm.gstr1.status = "Not Filed"; frappe.msgprint( __( "Latest Summary is not available. Please generate summary and try again." @@ -423,8 +414,7 @@ function handle_proceed_to_file_response(frm, response) { if (!filing_status) return; if (filing_status == "Ready to File") { - frm.remove_custom_button("Proceed to File"); - frm.add_custom_button(__("File"), () => file_gstr1_data(frm)); + frm.page.set_primary_action("File", () => file_gstr1_data(frm)); frm.page.set_indicator("Ready to File", "orange"); frm.gstr1.status = "Ready to File"; return; From c713d81b3a622b043e2e49a367c2e9e03f9e1feb Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 12 Sep 2024 17:34:53 +0530 Subject: [PATCH 29/68] fix: throw if already in progress --- .../doctype/gst_return_log/generate_gstr_1.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index d3f44a660f..9449b102d1 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -675,7 +675,7 @@ def normalize_data(data): class FileGSTR1: def reset_gstr1(self): # reset called after proceed to file - # TODO: Throw if already in progress + verify_request_in_progress(self, "reset") self.db_set({"filing_status": "Not Filed"}) @@ -717,6 +717,8 @@ def upload_gstr1(self, json_data): if not json_data: return + verify_request_in_progress(self, "upload") + keys = {category.value for category in GovJsonKey} if all(key not in json_data for key in keys): frappe.throw("Nothing to upload") @@ -769,6 +771,8 @@ def process_upload_gstr1(self): return response def proceed_to_file_gstr1(self): + verify_request_in_progress(self, "proceed_to_file") + api = GSTR1API(self) response = api.proceed_to_file("GSTR1", self.return_period) @@ -852,10 +856,7 @@ def fetch_and_compare_summary(self, api, response=None): return response def file_gstr1(self, pan, otp): - if not otp: - # If OTP is none, generate evc OTP - pass - + verify_request_in_progress(self, "file") # TODO : add actions for file gstr1 summary = self.get_json_for("authenticated_summary") @@ -874,6 +875,14 @@ def file_gstr1(self, pan, otp): return response +def verify_request_in_progress(self, request_type): + for doc in self.actions: + if doc.request_type == request_type and not doc.status: + frappe.throw( + f"{request_type.capitalize()} is in progress.Please wait for the process to complete." + ) + + def is_within_limit(result): return len(json.dumps(result, indent=4)) < MAXIMUM_UPLOAD_SIZE From e465e2ee4e370651ad0b1bc71ffc067aeb4e001d Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 12 Sep 2024 18:33:46 +0530 Subject: [PATCH 30/68] fix: update gstr_1_log on filing --- .../doctype/gst_return_log/generate_gstr_1.py | 13 +++++++++---- .../gst_india/doctype/gstr_1_beta/gstr_1_beta.js | 3 +++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 9449b102d1..c917fd2701 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -857,7 +857,6 @@ def fetch_and_compare_summary(self, api, response=None): def file_gstr1(self, pan, otp): verify_request_in_progress(self, "file") - # TODO : add actions for file gstr1 summary = self.get_json_for("authenticated_summary") api = GSTR1API(self) @@ -867,10 +866,16 @@ def file_gstr1(self, pan, otp): self.db_set({"filing_status": "Not Filed"}) self.update_json_for("authenticated_summary", None) - # TODO: On success. Update status, update ack no and date - # TODO: Update data for generate gstr-1 + if response.get("ack_num"): + self.db_set({"filing_status": "Filed"}) + self.db_set({"filing_date": frappe.utils.nowdate()}) + self.db_set({"acknowledgement_number": response.get("ack_num")}) + + set_gstr1_actions(self, "file", response.get("ack_num"), api.request_id) + + self.remove_json_for("upload_error") + # TODO: 2nd phase Accounting Entry. - # after filing or mark as filed, clear error files return response diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index fafd9e3519..77fa6f8b3e 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -407,6 +407,9 @@ function handle_file_response(frm, response) { ) ); } + if(response.message.ack_num){ + frm.call("generate_gstr1") + } } function handle_proceed_to_file_response(frm, response) { From c710a70a1ef690d1b89ebdf01e81e4ee080599bb Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Fri, 13 Sep 2024 14:28:59 +0530 Subject: [PATCH 31/68] fix: don't use socket-io to pass large data --- .../doctype/gst_return_log/generate_gstr_1.py | 4 +-- .../doctype/gstr_1_beta/gstr_1_beta.js | 27 +++++++++++-------- .../doctype/gstr_1_beta/gstr_1_beta.py | 4 +-- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index c917fd2701..0fdd73e818 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -542,7 +542,7 @@ def generate_gstr1_data(self, filters, callback=None): data["books"] = self.normalize_data(books_data) self.summarize_data(data) - return callback and callback(data, filters) + return callback and callback(filters) def generate_only_books_data(self, data, filters, callback=None): status = "Not Filed" @@ -553,7 +553,7 @@ def generate_only_books_data(self, data, filters, callback=None): data["status"] = status self.summarize_data(data) - return callback and callback(data, filters) + return callback and callback(filters) # GET DATA def get_gov_gstr1_data(self): diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 77fa6f8b3e..688dd7bbd3 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -156,7 +156,7 @@ frappe.ui.form.on(DOCTYPE, { }); frappe.realtime.on("gstr1_data_prepared", message => { - const { data, filters } = message; + const { filters } = message; if ( frm.doc.company_gstin !== filters.company_gstin || @@ -165,8 +165,8 @@ frappe.ui.form.on(DOCTYPE, { ) return; - frappe.after_ajax(() => { - frm.doc.__gst_data = data; + frm.call("generate_gstr1").then(r => { + frm.doc.__gst_data = r.message; frm.trigger("load_gstr1_data"); }); }); @@ -252,16 +252,15 @@ function set_primary_secondary_buttons(frm) { function generate_gstr1_data(frm) { frm.taxpayer_api_call("generate_gstr1").then(r => { - // TODO: Do this for all API calls - // TODO: Check size before sending it via redis. Instead it could raise alert. if (!r.message) return; frm.doc.__gst_data = r.message; frm.trigger("load_gstr1_data"); + + const request_types = ["upload", "reset", "proceed_to_file"]; + request_types.map(request_type => + fetch_status_with_retry(frm, request_type, (now = true)) + ); }); - const request_types = ["upload", "reset", "proceed_to_file"]; - request_types.map(request_type => - fetch_status_with_retry(frm, request_type, (now = true)) - ); } function upload_gstr1_data(frm) { @@ -379,7 +378,10 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { if (request_type == "reset") { frm.page.set_primary_action("Upload", () => upload_gstr1_data(frm)); - frm.call("generate_gstr1"); + frm.call("generate_gstr1").then(r => { + frm.doc.__gst_data = r.message; + frm.trigger("load_gstr1_data"); + }); } if (request_type == "proceed_to_file") @@ -408,7 +410,10 @@ function handle_file_response(frm, response) { ); } if(response.message.ack_num){ - frm.call("generate_gstr1") + frm.call("generate_gstr1").then(r => { + frm.doc.__gst_data = r.message; + frm.trigger("load_gstr1_data"); + }); } } diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index d191229624..58ae7757dd 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -143,7 +143,7 @@ def _generate_gstr1(self): raise e - def on_generate(self, data, filters=None): + def on_generate(self, filters=None): """ Once data is generated, update the status and publish the data """ @@ -157,7 +157,7 @@ def on_generate(self, data, filters=None): frappe.publish_realtime( "gstr1_data_prepared", - message={"data": data, "filters": filters}, + message={"filters": filters}, user=frappe.session.user, doctype=self.doctype, ) From 4612ce7f2d53b4dddccfeee6269f2f751d0a8424 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Fri, 13 Sep 2024 14:35:03 +0530 Subject: [PATCH 32/68] fix: remove data partition code --- .../doctype/gst_return_log/generate_gstr_1.py | 95 +------------------ 1 file changed, 1 insertion(+), 94 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 0fdd73e818..d7e366f2c2 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -1,11 +1,10 @@ # Copyright (c) 2024, Resilient Tech and contributors # For license information, please see license.txt import itertools -import json import frappe from frappe import unscrub -from frappe.utils import floor, flt +from frappe.utils import flt from india_compliance.gst_india.api_classes.taxpayer_returns import GSTR1API from india_compliance.gst_india.utils.gstr_1 import GovJsonKey, GSTR1_SubCategory @@ -888,98 +887,6 @@ def verify_request_in_progress(self, request_type): ) -def is_within_limit(result): - return len(json.dumps(result, indent=4)) < MAXIMUM_UPLOAD_SIZE - - -def get_partitioned_data(json_data): - if is_within_limit(json_data): - yield json_data - return - - base_data = {"gstin": json_data["gstin"], "fp": json_data["fp"]} - result = {} - has_yielded = False - - for category, category_data in json_data.items(): - result[category] = category_data - - if is_within_limit(result): - continue - - # 2 case : 1st - large categories, 2nd - combined category data is large - del result[category] - if result.keys() in [ - "b2b", - "cdnr", - ]: # Ensure at least one category is present before yielding the result - yield result - - result = base_data.copy() - result[category] = category_data - - if is_within_limit(result): - continue - - has_yielded = True - # Handle the case where individual objects within the category need to be partitioned - yield from partition_by_objects(result, category, category_data, base_data) - - if not has_yielded: - yield result - - -def partition_by_objects(result, category, category_data, base_data): - result[category] = [] - has_yielded = False - for object in category_data: - result[category].append(object) - - if is_within_limit(result): - continue - - # 2 case: 1st object is so big, combine 2 object is big - object_data = result[category].pop() - if result[ - category - ]: # Ensure at least one object is present before yielding the result - yield result - - result = base_data.copy() - result[category] = [object_data] - - if is_within_limit(result): - continue - - has_yielded = True - # Handle the case where invoices within the object need to be partitioned - yield from partition_by_invoices(result, category, object) - - if not has_yielded: - yield result - - -def partition_by_invoices(result, category, object): - result[category] = [{"ctin": object.get("ctin"), "inv": []}] - - result_size = len(json.dumps(result, indent=4)) - invoice_size = 1200 - invoice_to_add = floor((MAXIMUM_UPLOAD_SIZE - result_size) / invoice_size) - count = 0 - - for invoice in object.get("inv"): - count += 1 - result[category][-1]["inv"].append(invoice) - - if count == invoice_to_add: - count = 0 - yield result - result[category] = [{"ctin": object.get("ctin"), "inv": []}] - - if result[category][-1]["inv"]: - yield result - - def get_differing_categories(mapped_summary, gov_summary): KEYS_TO_COMPARE = { "total_cess_amount", From 05f0dbe1fbe8417b059177ec27e26750f225cf1b Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Sat, 14 Sep 2024 08:19:02 +0530 Subject: [PATCH 33/68] fix: common method and directly set primary button --- .../doctype/gstr_1_beta/gstr_1_beta.js | 9 ++- .../doctype/gstr_1_beta/gstr_1_beta.py | 56 ++++++------------- 2 files changed, 22 insertions(+), 43 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 688dd7bbd3..d6239d3bca 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -138,7 +138,9 @@ frappe.ui.form.on(DOCTYPE, { ), "orange" ); - set_primary_secondary_buttons(frm); + frm.page.set_primary_action(__("Generate"), () => + generate_gstr1_data(frm) + ); }); frappe.realtime.on("show_message", message => { @@ -225,7 +227,7 @@ function set_primary_secondary_buttons(frm) { File: file_gstr1_data, }; - if (!gst_data || frm.$wrapper.find(".form-message.orange").length) + if (!gst_data) primary_button_label = "Generate"; else if (gst_data.status == "Ready to File") primary_button_label = "File"; @@ -326,6 +328,7 @@ async function file_gstr1_data(frm) { function perform_gstr1_action(frm, action, additional_args = {}) { frm.gstr1.tabs.error_tab.hide(); const base_args = { + action: `${action}_gstr1`, month_or_quarter: frm.doc.month_or_quarter, year: frm.doc.year, company_gstin: frm.doc.company_gstin, @@ -334,7 +337,7 @@ function perform_gstr1_action(frm, action, additional_args = {}) { const args = { ...base_args, ...additional_args }; frappe.call({ - method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.${action}_gstr1`, + method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action`, args: args, callback: response => { if (action == "file") { diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 58ae7757dd..63a2309b6d 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -163,47 +163,31 @@ def on_generate(self, filters=None): ) -def handle_gstr1_action(action, month_or_quarter, year, company_gstin, *args): +@frappe.whitelist() +@otp_handler +def handle_gstr1_action(action, month_or_quarter, year, company_gstin, **kwargs): frappe.has_permission("GSTR-1 Beta", "write", throw=True) gstr_1_log = frappe.get_doc( "GST Return Log", f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", ) - return getattr(gstr_1_log, action)(*args) - - -@frappe.whitelist() -@otp_handler -def reset_gstr1(month_or_quarter, year, company_gstin): - handle_gstr1_action("reset_gstr1", month_or_quarter, year, company_gstin) - - -@frappe.whitelist() -@otp_handler -def upload_gstr1(month_or_quarter, year, company_gstin): - from india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_export import ( - download_gstr_1_json, - ) - - data = download_gstr_1_json( - company_gstin, - year, - month_or_quarter, - delete_missing=True, - ) + del kwargs["cmd"] - handle_gstr1_action( - "upload_gstr1", month_or_quarter, year, company_gstin, data.get("data") - ) + if action == "upload_gstr1": + from india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_export import ( + download_gstr_1_json, + ) + data = download_gstr_1_json( + company_gstin, + year, + month_or_quarter, + delete_missing=True, + ) + kwargs["json_data"] = data.get("data") -@frappe.whitelist() -@otp_handler -def proceed_to_file_gstr1(month_or_quarter, year, company_gstin): - return handle_gstr1_action( - "proceed_to_file_gstr1", month_or_quarter, year, company_gstin - ) + return getattr(gstr_1_log, action)(**kwargs) @frappe.whitelist() @@ -212,14 +196,6 @@ def generate_evc_otp(company_gstin, pan): return TaxpayerBaseAPI(company_gstin).initiate_otp_for_evc(pan, "R1") -@frappe.whitelist() -@otp_handler -def file_gstr1(month_or_quarter, year, company_gstin, pan, otp): - return handle_gstr1_action( - "file_gstr1", month_or_quarter, year, company_gstin, pan, otp - ) - - @frappe.whitelist() @otp_handler def process_gstr1_request(month_or_quarter, year, company_gstin, request_type): From 3d2c4dd95fc45106bf2c17938b6e5d60c6b08837 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Sat, 14 Sep 2024 08:35:23 +0530 Subject: [PATCH 34/68] fix: after upload set button to proceed to file --- .../doctype/gstr_1_beta/gstr_1_beta.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index d6239d3bca..277a2da11e 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -138,9 +138,7 @@ frappe.ui.form.on(DOCTYPE, { ), "orange" ); - frm.page.set_primary_action(__("Generate"), () => - generate_gstr1_data(frm) - ); + frm.page.set_primary_action(__("Generate"), () => generate_gstr1_data(frm)); }); frappe.realtime.on("show_message", message => { @@ -227,12 +225,9 @@ function set_primary_secondary_buttons(frm) { File: file_gstr1_data, }; - if (!gst_data) - primary_button_label = "Generate"; - + if (!gst_data) primary_button_label = "Generate"; else if (gst_data.status == "Ready to File") primary_button_label = "File"; else if (gst_data.status == "Not Filed") { - //TODO: after upload do i neeed to set primary action to proceed to file primary_button_label = gst_data.reconcile_summary ? "Upload" : "Proceed to File"; @@ -377,7 +372,12 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { if (message.status_cd == "PE" && request_type == "upload") handle_errors(frm, message); - // Highlight error tab + // TODO: Highlight error tab + + if (request_type == "upload") { + //TODO: data is not refreshed immediately + frm.call("sync_with_gstn", { sync_for: "unfiled" }); + } if (request_type == "reset") { frm.page.set_primary_action("Upload", () => upload_gstr1_data(frm)); @@ -412,7 +412,7 @@ function handle_file_response(frm, response) { ) ); } - if(response.message.ack_num){ + if (response.message.ack_num) { frm.call("generate_gstr1").then(r => { frm.doc.__gst_data = r.message; frm.trigger("load_gstr1_data"); From 4e2fa710868237c152d520361d95f2cee4e2c40a Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Sat, 14 Sep 2024 12:17:50 +0530 Subject: [PATCH 35/68] fix: show total amendment liability --- .../doctype/gst_return_log/generate_gstr_1.py | 43 ++++++++++- .../doctype/gstr_1_beta/gstr_1_beta.js | 71 +++++++++++++++++-- 2 files changed, 107 insertions(+), 7 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index d7e366f2c2..ad38a7e8c6 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -866,9 +866,13 @@ def file_gstr1(self, pan, otp): self.update_json_for("authenticated_summary", None) if response.get("ack_num"): - self.db_set({"filing_status": "Filed"}) - self.db_set({"filing_date": frappe.utils.nowdate()}) - self.db_set({"acknowledgement_number": response.get("ack_num")}) + self.db_set( + { + "filing_status": "Filed", + "filing_date": frappe.utils.nowdate(), + "acknowledgement_number": response.get("ack_num"), + } + ) set_gstr1_actions(self, "file", response.get("ack_num"), api.request_id) @@ -878,6 +882,39 @@ def file_gstr1(self, pan, otp): return response + def get_amendment_data(self): + authenticated_summary = convert_to_internal_data_format( + self.get_json_for("authenticated_summary") + ).get("summary") + authenticated_summary = summarize_retsum_data(authenticated_summary.values()) + + non_amended_entries = { + "no_of_records": 0, + "total_igst_amount": 0, + "total_cgst_amount": 0, + "total_sgst_amount": 0, + "total_cess_amount": 0, + "total_taxable_value": 0, + } + amended_liability = {} + + for data in authenticated_summary: + if "Net Liability from Amendments" == data["description"]: + amended_liability = data + elif data.get("consider_in_total_taxable_value") or data.get( + "consider_in_total_tax" + ): + for key, value in data.items(): + if key not in non_amended_entries: + continue + + non_amended_entries[key] += value + + return { + "non_amended_liability": non_amended_entries, + "amended_liability": amended_liability, + } + def verify_request_in_progress(self, request_type): for doc in self.actions: diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 277a2da11e..3f065b7221 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -283,9 +283,21 @@ function proceed_to_file(frm) { } async function file_gstr1_data(frm) { - // TODO: If amendments, show table for total liability breakup. - // compute other than amendments. - // This table to be shown only if there are amendments. + const total_liability = await frappe.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", + args: { + action: "get_amendment_data", + month_or_quarter: frm.doc.month_or_quarter, + year: frm.doc.year, + company_gstin: frm.doc.company_gstin, + }, + }); + + const { amended_liability, non_amended_liability } = total_liability.message; + const amendment_table_html = generate_liability_table( + amended_liability, + non_amended_liability + ); // TODO: EVC Generation, Resend, and Filing @@ -298,6 +310,13 @@ async function file_gstr1_data(frm) { const dialog = new frappe.ui.Dialog({ title: "Filing GSTR-1", fields: [ + { + label: "Amendment Liability", + fieldname: "amendment_html", + fieldtype: "HTML", + options: amendment_table_html, + hidden: !amendment_table_html, + }, { label: "PAN", fieldname: "pan", @@ -320,6 +339,50 @@ async function file_gstr1_data(frm) { dialog.show(); } +function generate_liability_table(amended_liability, non_amended_liability) { + if (Object.keys(amended_liability).length === 0) return ""; + + let table_html = ` + + + + + + + + + + + + + + `; + + table_html += generate_table_row("Amended Liability", amended_liability); + table_html += generate_table_row("Non-Amended Liability", non_amended_liability); + + table_html += ` + +
    DescriptionNo. of RecordsTotal IGSTTotal CGSTTotal SGSTTotal CessTotal Taxable Value
    + `; + + return table_html; +} + +function generate_table_row(description, liability) { + return ` + + ${description} + ${liability.no_of_records || 0} + ${liability.total_igst_amount || 0} + ${liability.total_cgst_amount || 0} + ${liability.total_sgst_amount || 0} + ${liability.total_cess_amount || 0} + ${liability.total_taxable_value || 0} + + `; +} + function perform_gstr1_action(frm, action, additional_args = {}) { frm.gstr1.tabs.error_tab.hide(); const base_args = { @@ -332,7 +395,7 @@ function perform_gstr1_action(frm, action, additional_args = {}) { const args = { ...base_args, ...additional_args }; frappe.call({ - method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action`, + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", args: args, callback: response => { if (action == "file") { From 3b7e49af55af42d3689c46d42611329fa7bda5e5 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 14 Sep 2024 13:49:54 +0530 Subject: [PATCH 36/68] fix: Sanitize books data in ReconcileGSTR1 class --- .../doctype/gst_return_log/generate_gstr_1.py | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index ad38a7e8c6..c152904dfc 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -235,15 +235,15 @@ def get_reconcile_gstr1_data(self, gov_data, books_data): if not books_subdata and not gov_subdata: continue - is_list = False # Object Type for the subdata_value + # Object Type for the subdata_value + is_list = self.is_list(books_subdata, gov_subdata) + + self.sanitize_books_data(books_subdata, is_list) reconcile_subdata = {} # Books vs Gov for key, books_value in books_subdata.items(): - if not reconcile_subdata: - is_list = isinstance(books_value, list) - gov_value = gov_subdata.get(key) reconcile_row = self.get_reconciled_row(books_value, gov_value) @@ -258,9 +258,6 @@ def get_reconcile_gstr1_data(self, gov_data, books_data): # Update each row in Books Data for row in books_values: - if row.get("upload_status") == "Missing in Books": - continue - if not gov_value: row["upload_status"] = "Not Uploaded" continue @@ -275,9 +272,6 @@ def get_reconcile_gstr1_data(self, gov_data, books_data): if key in books_subdata: continue - if not reconcile_subdata: - is_list = isinstance(gov_value, list) - reconcile_subdata[key] = self.get_reconciled_row(None, gov_value) if not update_books_match: @@ -296,13 +290,21 @@ def get_reconcile_gstr1_data(self, gov_data, books_data): if reconcile_subdata: reconciled_data[subcategory] = reconcile_subdata - if update_books_match: - self.update_json_for("books", books_data) - + self.update_json_for("books", books_data) self.update_json_for("reconcile", reconciled_data) return reconciled_data + def sanitize_books_data(self, books_subdata, is_list): + for key, value in books_subdata.copy().items(): + values = value if is_list else [value] + if values[0].get("upload_status") == "Missing in Books": + del books_subdata[key] + continue + + for row in values: + row.pop("upload_status", None) + @staticmethod def get_reconciled_row(books_row, gov_row): """ @@ -411,6 +413,13 @@ def get_empty_row(row: dict, unrequired_keys=None): return empty_row + @staticmethod + def is_list(books_subdata: dict, gov_subdata: dict): + book_row = next(iter(books_subdata.values()), None) + gov_row = next(iter(gov_subdata.values()), None) + + return isinstance(book_row or gov_row, list) + class AggregateInvoices: IGNORED_FIELDS = {GSTR1_DataField.TAX_RATE.value, GSTR1_DataField.DOC_VALUE.value} @@ -706,8 +715,6 @@ def process_reset_gstr1(self): ) if response.get("status_cd") == "P": - # TODO: Better way to handle this. Exclude such records from books data. - self.remove_json_for("books") self.update_json_for("unfiled", {}, reset_reconcile=True) return response From b74f32efdb818a7fab5079eecee935f176b09b5b Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 14 Sep 2024 15:36:26 +0530 Subject: [PATCH 37/68] fix: better dialog for filing of gst returns --- .../doctype/gst_return_log/gst_return_log.py | 1 + .../doctype/gstr_1_beta/gstr_1_beta.js | 195 +++++++++++++----- 2 files changed, 140 insertions(+), 56 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py index 690500301a..9ca51bf8fa 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py @@ -265,6 +265,7 @@ def get_file_doc(doctype, docname, attached_to_field): ) except frappe.DoesNotExistError: + frappe.clear_last_message() return None diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 3f065b7221..2437064dca 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -165,7 +165,7 @@ frappe.ui.form.on(DOCTYPE, { ) return; - frm.call("generate_gstr1").then(r => { + frm.taxpayer_api_call("generate_gstr1").then(r => { frm.doc.__gst_data = r.message; frm.trigger("load_gstr1_data"); }); @@ -282,85 +282,125 @@ function proceed_to_file(frm) { perform_gstr1_action(frm, "proceed_to_file"); } -async function file_gstr1_data(frm) { - const total_liability = await frappe.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", - args: { - action: "get_amendment_data", - month_or_quarter: frm.doc.month_or_quarter, - year: frm.doc.year, - company_gstin: frm.doc.company_gstin, - }, - }); - - const { amended_liability, non_amended_liability } = total_liability.message; - const amendment_table_html = generate_liability_table( - amended_liability, - non_amended_liability - ); - +function file_gstr1_data(frm) { // TODO: EVC Generation, Resend, and Filing - - const { message } = await frappe.db.get_value("GSTIN", frm.doc.company_gstin, [ - "last_pan_used_for_gstr", - ]); - const pan_no = - message.last_pan_used_for_gstr || frm.doc.company_gstin.substr(2, 10); - const dialog = new frappe.ui.Dialog({ - title: "Filing GSTR-1", + title: "File GSTR-1", fields: [ { - label: "Amendment Liability", - fieldname: "amendment_html", + label: "Company GSTIN", + fieldname: "company_gstin", + fieldtype: "Data", + read_only: 1, + default: frm.doc.company_gstin, + }, + { + label: "Period", + fieldname: "period", + fieldtype: "Data", + read_only: 1, + default: `${frm.doc.month_or_quarter} - ${frm.doc.year}`, + }, + { + label: "Total Liability", + fieldtype: "Section Break", + fieldname: "total_liability_section", + }, + { + fieldname: "liability_breakup_html", fieldtype: "HTML", - options: amendment_table_html, - hidden: !amendment_table_html, + hidden: 1, + }, + { + label: "Sign using EVC", + fieldtype: "Section Break", }, { - label: "PAN", + label: "Authorised PAN", fieldname: "pan", fieldtype: "Data", - default: pan_no, reqd: 1, }, { - label: "OTP", + label: "EVC OTP", fieldname: "otp", fieldtype: "Data", - hidden: 1, // TODO: 2nd priority instead disable input + read_only: 1, + }, + { + label: "Once you file your return you will not be able to undo the action", + fieldname: "acknowledged", + fieldtype: "Check", + default: 0, + read_only: 1, }, ], - primary_action_label: "Verify PAN", + primary_action_label: "Get OTP", primary_action() { - validate_details_and_file_gstr1(frm, dialog); + validate_pan(frm, dialog); + show_otp_field(frm, dialog, pan); + + }, + }); + + frappe.db.get_value("GSTIN", frm.doc.company_gstin, [ + "last_pan_used_for_gstr", + ]).then(({ message }) => { + const pan_no = + message.last_pan_used_for_gstr || frm.doc.company_gstin.substr(2, 10); + + dialog.set_value("pan", pan_no); + }); + + taxpayer_api.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", + args: { + action: "get_amendment_data", + month_or_quarter: frm.doc.month_or_quarter, + year: frm.doc.year, + company_gstin: frm.doc.company_gstin, }, + callback: r => { + if (!r.message) return; + const { amended_liability, non_amended_liability } = r.message; + const amendment_table_html = generate_liability_table( + amended_liability, + non_amended_liability + ); + const field = dialog.get_field("liability_breakup_html"); + + if (!amendment_table_html) return; + field.toggle(true); + + field.df.options = amendment_table_html; + dialog.refresh(); + } }); + dialog.show(); + } function generate_liability_table(amended_liability, non_amended_liability) { - if (Object.keys(amended_liability).length === 0) return ""; + // if (Object.keys(amended_liability).length === 0) return ""; let table_html = ` - - `; - table_html += generate_table_row("Amended Liability", amended_liability); - table_html += generate_table_row("Non-Amended Liability", non_amended_liability); - + table_html += generate_table_row("For current period", non_amended_liability); + table_html += generate_table_row("From amendments", amended_liability); + // TODO: Add total row table_html += `
    DescriptionNo. of Records Total IGST Total CGST Total SGST Total CessTotal Taxable Value
    @@ -370,15 +410,14 @@ function generate_liability_table(amended_liability, non_amended_liability) { } function generate_table_row(description, liability) { + // TODO: amount format as currency return ` ${description} - ${liability.no_of_records || 0} ${liability.total_igst_amount || 0} ${liability.total_cgst_amount || 0} ${liability.total_sgst_amount || 0} ${liability.total_cess_amount || 0} - ${liability.total_taxable_value || 0} `; } @@ -394,7 +433,7 @@ function perform_gstr1_action(frm, action, additional_args = {}) { const args = { ...base_args, ...additional_args }; - frappe.call({ + taxpayer_api.call({ method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", args: args, callback: response => { @@ -415,7 +454,7 @@ const retry_intervals = [5000, 15000, 30000, 60000, 120000, 300000, 600000, 7200 function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { setTimeout( async () => { - const { message } = await frappe.call({ + const { message } = await taxpayer_api.call({ method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.process_gstr1_request`, args: { month_or_quarter: frm.doc.month_or_quarter, @@ -439,12 +478,12 @@ function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { if (request_type == "upload") { //TODO: data is not refreshed immediately - frm.call("sync_with_gstn", { sync_for: "unfiled" }); + frm.taxpayer_api_call("sync_with_gstn", { sync_for: "unfiled" }); } if (request_type == "reset") { frm.page.set_primary_action("Upload", () => upload_gstr1_data(frm)); - frm.call("generate_gstr1").then(r => { + frm.taxpayer_api_call("generate_gstr1").then(r => { frm.doc.__gst_data = r.message; frm.trigger("load_gstr1_data"); }); @@ -476,7 +515,7 @@ function handle_file_response(frm, response) { ); } if (response.message.ack_num) { - frm.call("generate_gstr1").then(r => { + frm.taxpayer_api_call("generate_gstr1").then(r => { frm.doc.__gst_data = r.message; frm.trigger("load_gstr1_data"); }); @@ -512,7 +551,7 @@ function handle_proceed_to_file_response(frm, response) { label: __("Sync with GSTIN"), action() { render_empty_state(frm); - frm.call("sync_with_gstn", { sync_for: "unfiled" }).then(() => { + frm.taxpayer_api_call("sync_with_gstn", { sync_for: "unfiled" }).then(() => { frappe.msgprint(__("Synced successfully with GSTIN.")); }); }, @@ -520,21 +559,33 @@ function handle_proceed_to_file_response(frm, response) { }); } -function validate_details_and_file_gstr1(frm, dialog) { +function validate_pan(frm, dialog) { const PAN_REGEX = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/; const pan = dialog.get_value("pan")?.trim().toUpperCase(); if (pan.length != 10) { - frappe.throw(__("PAN should be 10 characters long")); + frappe.throw(("PAN should be 10 characters long")); } if (!PAN_REGEX.test(pan)) { - frappe.throw(__("Invalid PAN format")); + frappe.throw(("Invalid PAN format")); } - dialog.get_field("otp").toggle(true); - dialog.set_primary_action("Authenticate OTP", () => { - //authenticate otp +} + +async function show_otp_field(frm, dialog, pan) { + dialog.set_df_property("otp", "read_only", 0); + dialog.set_df_property("acknowledged", "read_only", 0); + + dialog.set_primary_action("File", () => { + if (!dialog.get_value("acknowledged")) { + frappe.msgprint( + __("Please acknowledge that you can not undo this action.") + ); + return; + } + +// TODO: do this in backend frappe.db.set_value( "GSTIN", frm.doc.company_gstin, @@ -545,6 +596,38 @@ function validate_details_and_file_gstr1(frm, dialog) { perform_gstr1_action(frm, "file", { pan: pan, otp: dialog.get_value("otp") }); dialog.hide(); }); + + dialog.set_secondary_action_label("Cancel"); + dialog.set_secondary_action(() => { + dialog.hide(); + }); + + dialog.get_field("otp").set_description(` +
    + + Didn't receive OTP? + + +
    + `); + + dialog.$wrapper.find(".otp-section").css({ + "text-align": "right", + "margin-top": "10px", + }); + + dialog.$wrapper.find(".resend-otp-btn").on("click", () => { + otp_data = generate_evc_otp(frm, pan); + }); +} + +function generate_evc_otp(frm, pan) { + return frappe.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.generate_evc_otp", + args: { company_gstin: frm.doc.company_gstin, pan: pan }, + }); } function handle_notification(frm, message, request_type) { From 3cd4a1bdf93c2e8768c7d6785a0ffa2cc35e752c Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 14 Sep 2024 16:13:16 +0530 Subject: [PATCH 38/68] fix: validate pan and minor refactor --- .../gst_india/client_scripts/party.js | 10 +-- .../doctype/gstr_1_beta/gstr_1_beta.js | 69 ++++++------------- india_compliance/public/js/regex_constants.js | 1 + india_compliance/public/js/utils.js | 17 +++++ 4 files changed, 40 insertions(+), 57 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/party.js b/india_compliance/gst_india/client_scripts/party.js index fdea52d273..f59db1a65f 100644 --- a/india_compliance/gst_india/client_scripts/party.js +++ b/india_compliance/gst_india/client_scripts/party.js @@ -85,15 +85,7 @@ function validate_pan(doctype) { let { pan } = frm.doc; if (!pan || pan.length < 10) return; - if (pan.length > 10) { - frappe.throw(__("PAN should be 10 characters long")); - } - - pan = pan.trim().toUpperCase(); - - if (!PAN_REGEX.test(pan)) { - frappe.throw(__("Invalid PAN format")); - } + pan = india_compliance.validate_pan(pan); frm.doc.pan = pan; frm.refresh_field("pan"); diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 2437064dca..a3f8530585 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -336,9 +336,18 @@ function file_gstr1_data(frm) { }, ], primary_action_label: "Get OTP", - primary_action() { - validate_pan(frm, dialog); - show_otp_field(frm, dialog, pan); + async primary_action() { + const pan = dialog.get_value("pan"); + india_compliance.validate_pan(pan); + + // generate otp + await generate_evc_otp(frm.doc.company_gstin, pan) + + // show otp field + dialog.set_df_property("otp", "read_only", 0); + dialog.set_df_property("acknowledged", "read_only", 0); + + update_actions(frm, dialog, pan); }, }); @@ -559,24 +568,7 @@ function handle_proceed_to_file_response(frm, response) { }); } -function validate_pan(frm, dialog) { - const PAN_REGEX = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/; - const pan = dialog.get_value("pan")?.trim().toUpperCase(); - - if (pan.length != 10) { - frappe.throw(("PAN should be 10 characters long")); - } - - if (!PAN_REGEX.test(pan)) { - frappe.throw(("Invalid PAN format")); - } - -} - -async function show_otp_field(frm, dialog, pan) { - dialog.set_df_property("otp", "read_only", 0); - dialog.set_df_property("acknowledged", "read_only", 0); - +async function update_actions(frm, dialog, pan) { dialog.set_primary_action("File", () => { if (!dialog.get_value("acknowledged")) { frappe.msgprint( @@ -585,7 +577,7 @@ async function show_otp_field(frm, dialog, pan) { return; } -// TODO: do this in backend + // TODO: do this in backend frappe.db.set_value( "GSTIN", frm.doc.company_gstin, @@ -593,40 +585,21 @@ async function show_otp_field(frm, dialog, pan) { pan ); - perform_gstr1_action(frm, "file", { pan: pan, otp: dialog.get_value("otp") }); - dialog.hide(); + perform_gstr1_action(frm, "file", { pan: pan, otp: dialog.get_value("otp") }) + dialog.hide() }); - dialog.set_secondary_action_label("Cancel"); + dialog.set_secondary_action_label("Resend OTP"); dialog.set_secondary_action(() => { - dialog.hide(); - }); - - dialog.get_field("otp").set_description(` -
    - - Didn't receive OTP? - - -
    - `); - - dialog.$wrapper.find(".otp-section").css({ - "text-align": "right", - "margin-top": "10px", - }); - - dialog.$wrapper.find(".resend-otp-btn").on("click", () => { - otp_data = generate_evc_otp(frm, pan); + generate_evc_otp(frm, pan); }); } -function generate_evc_otp(frm, pan) { +function generate_evc_otp(company_gstin, pan) { + // TODO: common function for all returns return frappe.call({ method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.generate_evc_otp", - args: { company_gstin: frm.doc.company_gstin, pan: pan }, + args: { company_gstin: company_gstin, pan: pan }, }); } diff --git a/india_compliance/public/js/regex_constants.js b/india_compliance/public/js/regex_constants.js index a442b826aa..5b8d1f19d8 100644 --- a/india_compliance/public/js/regex_constants.js +++ b/india_compliance/public/js/regex_constants.js @@ -19,3 +19,4 @@ export const GSTIN_REGEX = new RegExp( ); export const GST_INVOICE_NUMBER_FORMAT = new RegExp("^[^\\W_][A-Za-z\\d\\-/]{0,15}$"); +export const PAN_REGEX = new RegExp("^[A-Z]{5}[0-9]{4}[A-Z]{1}$"); \ No newline at end of file diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index a889da558e..9eadc47337 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -6,6 +6,7 @@ import { TDS_REGEX, TCS_REGEX, GST_INVOICE_NUMBER_FORMAT, + PAN_REGEX, } from "./regex_constants"; frappe.provide("india_compliance"); @@ -225,6 +226,22 @@ Object.assign(india_compliance, { return india_compliance.is_api_enabled() && gst_settings.enable_e_invoice; }, + validate_pan(pan) { + if (!pan) return; + + pan = pan.trim().toUpperCase(); + + if (pan.length != 10) { + frappe.throw(("PAN should be 10 characters long")); + } + + if (!PAN_REGEX.test(pan)) { + frappe.throw(("Invalid PAN format")); + } + + return pan; + }, + validate_gstin(gstin) { if (!gstin || gstin.length !== 15) { frappe.msgprint(__("GSTIN must be 15 characters long")); From d589de89f8d3f2c9fdb6704d3b7a3bee452a0663 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 14 Sep 2024 17:36:49 +0530 Subject: [PATCH 39/68] refactor: gstr-1 actions --- .../doctype/gstr_1_beta/gstr_1_beta.js | 510 +++++++++--------- .../doctype/gstr_1_beta/gstr_1_beta.py | 4 +- 2 files changed, 262 insertions(+), 252 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index a3f8530585..8341928fa5 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -193,9 +193,16 @@ frappe.ui.form.on(DOCTYPE, { }, refresh(frm) { - // Primary Action frm.disable_save(); - set_primary_secondary_buttons(frm); + + frm.gstr1?.render_form_actions(); + + if (!frm.doc.__gst_data) { + frm.page.clear_indicator(); + return; + } + + frm.gstr1.render_indicator(); }, load_gstr1_data(frm) { @@ -210,78 +217,6 @@ frappe.ui.form.on(DOCTYPE, { }, }); -function set_primary_secondary_buttons(frm) { - const gst_data = frm.doc.__gst_data; - - if (gst_data && (!is_gstr1_api_enabled() || gst_data.status == "Filed")) { - frm.gstr1.render_indicator(); - return; - } - - const actions = { - Generate: generate_gstr1_data, - Upload: upload_gstr1_data, - "Proceed to File": proceed_to_file, - File: file_gstr1_data, - }; - - if (!gst_data) primary_button_label = "Generate"; - else if (gst_data.status == "Ready to File") primary_button_label = "File"; - else if (gst_data.status == "Not Filed") { - primary_button_label = gst_data.reconcile_summary - ? "Upload" - : "Proceed to File"; - } - - frm.page.set_primary_action(__(primary_button_label), () => - actions[primary_button_label](frm) - ); - - if (!gst_data) { - frm.page.clear_indicator(); - return; - } - - frm.add_custom_button(__("Reset"), () => reset_gstr1_data(frm)); - - frm.gstr1.render_indicator(); -} - -function generate_gstr1_data(frm) { - frm.taxpayer_api_call("generate_gstr1").then(r => { - if (!r.message) return; - frm.doc.__gst_data = r.message; - frm.trigger("load_gstr1_data"); - - const request_types = ["upload", "reset", "proceed_to_file"]; - request_types.map(request_type => - fetch_status_with_retry(frm, request_type, (now = true)) - ); - }); -} - -function upload_gstr1_data(frm) { - frappe.show_alert(__("Uploading data to GSTN")); - perform_gstr1_action(frm, "upload"); -} - -function reset_gstr1_data(frm) { - frappe.confirm( - __( - "All the details saved in different tables shall be deleted after reset.
    Are you sure, you want to reset the already saved data?" - ), - () => { - frappe.show_alert(__("Resetting GSTR-1 data")); - perform_gstr1_action(frm, "reset"); - } - ); -} - -function proceed_to_file(frm) { - frappe.show_alert(__("Please wait while we proceed to file the data.")); - perform_gstr1_action(frm, "proceed_to_file"); -} - function file_gstr1_data(frm) { // TODO: EVC Generation, Resend, and Filing const dialog = new frappe.ui.Dialog({ @@ -341,25 +276,24 @@ function file_gstr1_data(frm) { india_compliance.validate_pan(pan); // generate otp - await generate_evc_otp(frm.doc.company_gstin, pan) + await generate_evc_otp(frm.doc.company_gstin, pan); // show otp field dialog.set_df_property("otp", "read_only", 0); dialog.set_df_property("acknowledged", "read_only", 0); update_actions(frm, dialog, pan); - }, }); - frappe.db.get_value("GSTIN", frm.doc.company_gstin, [ - "last_pan_used_for_gstr", - ]).then(({ message }) => { - const pan_no = - message.last_pan_used_for_gstr || frm.doc.company_gstin.substr(2, 10); + frappe.db + .get_value("GSTIN", frm.doc.company_gstin, ["last_pan_used_for_gstr"]) + .then(({ message }) => { + const pan_no = + message.last_pan_used_for_gstr || frm.doc.company_gstin.substr(2, 10); - dialog.set_value("pan", pan_no); - }); + dialog.set_value("pan", pan_no); + }); taxpayer_api.call({ method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", @@ -383,11 +317,10 @@ function file_gstr1_data(frm) { field.df.options = amendment_table_html; dialog.refresh(); - } + }, }); dialog.show(); - } function generate_liability_table(amended_liability, non_amended_liability) { @@ -431,143 +364,6 @@ function generate_table_row(description, liability) { `; } -function perform_gstr1_action(frm, action, additional_args = {}) { - frm.gstr1.tabs.error_tab.hide(); - const base_args = { - action: `${action}_gstr1`, - month_or_quarter: frm.doc.month_or_quarter, - year: frm.doc.year, - company_gstin: frm.doc.company_gstin, - }; - - const args = { ...base_args, ...additional_args }; - - taxpayer_api.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", - args: args, - callback: response => { - if (action == "file") { - handle_file_response(frm, response); - } else { - if (Object.keys(response).length == 0) { - fetch_status_with_retry(frm, action); - } else { - handle_proceed_to_file_response(frm, response.message); - } - } - }, - }); -} - -const retry_intervals = [5000, 15000, 30000, 60000, 120000, 300000, 600000, 720000]; // 5 second, 15 second, 30 second, 1 min, 2 min, 5 min, 10 min, 12 min -function fetch_status_with_retry(frm, request_type, retries = 0, now = false) { - setTimeout( - async () => { - const { message } = await taxpayer_api.call({ - method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.process_gstr1_request`, - args: { - month_or_quarter: frm.doc.month_or_quarter, - year: frm.doc.year, - company_gstin: frm.doc.company_gstin, - request_type: request_type, - }, - }); - if (!message.status_cd) return; - - if (message.status_cd === "IP" && retries < retry_intervals.length) - return fetch_status_with_retry(frm, request_type, retries + 1); - - if (message.status_cd == "ER") - frappe.throw(__(message.error_report.error_msg)); - - if (message.status_cd == "PE" && request_type == "upload") - handle_errors(frm, message); - - // TODO: Highlight error tab - - if (request_type == "upload") { - //TODO: data is not refreshed immediately - frm.taxpayer_api_call("sync_with_gstn", { sync_for: "unfiled" }); - } - - if (request_type == "reset") { - frm.page.set_primary_action("Upload", () => upload_gstr1_data(frm)); - frm.taxpayer_api_call("generate_gstr1").then(r => { - frm.doc.__gst_data = r.message; - frm.trigger("load_gstr1_data"); - }); - } - - if (request_type == "proceed_to_file") - handle_proceed_to_file_response(frm, message); - - handle_notification(frm, message, request_type); - }, - now ? 0 : retry_intervals[retries] - ); -} - -function handle_errors(frm, message) { - frm.gstr1.tabs.error_tab.show(); - frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(message); -} - -function handle_file_response(frm, response) { - if (response.message?.error?.error_cd === "RET09001") { - frm.page.set_primary_action("Proceed to File", () => proceed_to_file(frm)); - frm.page.set_indicator("Not Filed", "orange"); - frm.gstr1.status = "Not Filed"; - frappe.msgprint( - __( - "Latest Summary is not available. Please generate summary and try again." - ) - ); - } - if (response.message.ack_num) { - frm.taxpayer_api_call("generate_gstr1").then(r => { - frm.doc.__gst_data = r.message; - frm.trigger("load_gstr1_data"); - }); - } -} - -function handle_proceed_to_file_response(frm, response) { - const filing_status = response.filing_status; - if (!filing_status) return; - - if (filing_status == "Ready to File") { - frm.page.set_primary_action("File", () => file_gstr1_data(frm)); - frm.page.set_indicator("Ready to File", "orange"); - frm.gstr1.status = "Ready to File"; - return; - } - - const differing_categories = response.differing_categories - .map(item => `
  • ${item}
  • `) - .join(""); - const message = ` -

    ${__( - "Summary for the following categories has not matched. Please sync with GSTIN." - )}

    -
      ${differing_categories}
    - `; - - frappe.msgprint({ - message: message, - indicator: "red", - title: __("GSTIN Sync Required"), - primary_action: { - label: __("Sync with GSTIN"), - action() { - render_empty_state(frm); - frm.taxpayer_api_call("sync_with_gstn", { sync_for: "unfiled" }).then(() => { - frappe.msgprint(__("Synced successfully with GSTIN.")); - }); - }, - }, - }); -} - async function update_actions(frm, dialog, pan) { dialog.set_primary_action("File", () => { if (!dialog.get_value("acknowledged")) { @@ -585,8 +381,8 @@ async function update_actions(frm, dialog, pan) { pan ); - perform_gstr1_action(frm, "file", { pan: pan, otp: dialog.get_value("otp") }) - dialog.hide() + perform_gstr1_action(frm, "file", { pan: pan, otp: dialog.get_value("otp") }); + dialog.hide(); }); dialog.set_secondary_action_label("Resend OTP"); @@ -603,32 +399,6 @@ function generate_evc_otp(company_gstin, pan) { }); } -function handle_notification(frm, message, request_type) { - const request_status = - request_type === "proceed_to_file" - ? "Proceed to file" - : `Data ${request_type}ing`; - - const status_message_map = { - P: `${request_status} has been successfully completed.`, - PE: `${request_status} is completed with errors`, - ER: `${request_status} has encountered errors`, - IP: `The request for ${request_status} is currently in progress`, - }; - - const alert_message = status_message_map[message.status_cd]; - - const on_current_document = - window.location.pathname.includes("gstr-1-beta") && - frm.doc.company_gstin == message.company_gstin && - frm.doc.month_or_quarter == message.month_or_quarter && - frm.doc.year == message.year; - - if (!on_current_document) return; - - frappe.show_alert(__(alert_message)); -} - class GSTR1 { // Render page / Setup Listeners / Setup Data TABS = [ @@ -843,6 +613,34 @@ class GSTR1 { this.frm.page.set_indicator(this.status, color); } + render_form_actions() { + if (this.data && (!is_gstr1_api_enabled() || this.status == "Filed")) return; + + let gstr1_action = new GSTR1Action(this.frm); + + // Custom Buttons + if (this.data) + this.frm.add_custom_button(__("Reset"), () => gstr1_action.reset_gstr1_data()); + + // Primary Button + const actions = { + Generate: gstr1_action.generate_gstr1_data, + Upload: gstr1_action.upload_gstr1_data, + "Proceed to File": gstr1_action.proceed_to_file, + File: gstr1_action.file_gstr1_data, + }; + + let primary_button_label = "Generate"; + + if (this.status === "Not Filed") primary_action_label = "Upload"; + else if (this.status === "Uploaded") primary_button_label = "Proceed to File"; + else if (this.status == "Ready to File") primary_button_label = "File"; + + this.frm.page.set_primary_action(__(primary_button_label), () => + actions[primary_button_label].call(gstr1_action) + ); + } + // SETUP setup_filter_button() { @@ -2582,6 +2380,218 @@ class DetailViewDialog { } } +class FileGSTR1Dialog { } + +class GSTR1Action extends FileGSTR1Dialog { + RETRY_INTERVALS = [5000, 15000, 30000, 60000, 120000, 300000, 600000, 720000]; // 5 second, 15 second, 30 second, 1 min, 2 min, 5 min, 10 min, 12 min + + constructor(frm) { + super(); + this.frm = frm; + this.defaults = { + month_or_quarter: frm.doc.month_or_quarter, + year: frm.doc.year, + company_gstin: frm.doc.company_gstin, + }; + } + + generate_gstr1_data() { + this.frm.taxpayer_api_call("generate_gstr1").then(r => { + if (!r.message) return; + this.frm.doc.__gst_data = r.message; + this.frm.trigger("load_gstr1_data"); + + const request_types = ["upload", "reset", "proceed_to_file"]; + request_types.map(request_type => + this.fetch_status_with_retry(request_type, (now = true)) + ); + }); + } + + upload_gstr1_data() { + frappe.show_alert(__("Uploading data to GSTN")); + this.perform_gstr1_action("upload"); + } + + reset_gstr1_data() { + frappe.confirm( + __( + "All the details saved in different tables shall be deleted after reset.
    Are you sure, you want to reset the already saved data?" + ), + () => { + frappe.show_alert(__("Resetting GSTR-1 data")); + this.perform_gstr1_action("reset"); + } + ); + } + + proceed_to_file() { + frappe.show_alert(__("Please wait while we proceed to file the data.")); + this.perform_gstr1_action("proceed_to_file"); + } + + perform_gstr1_action(action, additional_args = {}) { + this.frm.gstr1.tabs.error_tab.hide(); + const args = { + ...this.defaults, + ...additional_args, + action: `${action}_gstr1`, + }; + + taxpayer_api.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", + args: args, + callback: response => { + if (action == "file") { + handle_file_response(this.frm, response); + } else { + if (Object.keys(response).length == 0) { + fetch_status_with_retry(this.frm, action); + } else { + handle_proceed_to_file_response(this.frm, response.message); + } + } + }, + }); + } + + fetch_status_with_retry(action, retries = 0, now = false) { + setTimeout( + async () => { + const { message } = await taxpayer_api.call({ + method: `india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.process_gstr1_request`, + args: { ...this.defaults, action }, + }); + if (!message.status_cd) return; + + if (message.status_cd === "IP" && retries < retry_intervals.length) + return this.fetch_status_with_retry(action, retries + 1); + + if (message.status_cd == "ER") + frappe.throw(__(message.error_report.error_msg)); + + if (message.status_cd == "PE" && action == "upload") + handle_errors(this.frm, message); + + // TODO: Highlight error tab + + if (action == "upload") { + //TODO: data is not refreshed immediately + this.frm.taxpayer_api_call("sync_with_gstn", { + sync_for: "unfiled", + }); + } + + if (action == "reset") { + this.frm.page.set_primary_action("Upload", () => + upload_gstr1_data(this.frm) + ); + this.frm.taxpayer_api_call("generate_gstr1").then(r => { + this.frm.doc.__gst_data = r.message; + this.frm.trigger("load_gstr1_data"); + }); + } + + if (action == "proceed_to_file") + handle_proceed_to_file_response(this.frm, message); + + this.handle_notification(message, action); + }, + now ? 0 : this.RETRY_INTERVALS[retries] + ); + } + + handle_errors(frm, message) { + frm.gstr1.tabs.error_tab.show(); + frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(message); + } + + handle_file_response(frm, response) { + if (response.message?.error?.error_cd === "RET09001") { + frm.page.set_primary_action("Proceed to File", () => proceed_to_file(frm)); + frm.page.set_indicator("Not Filed", "orange"); + frm.gstr1.status = "Not Filed"; + frappe.msgprint( + __( + "Latest Summary is not available. Please generate summary and try again." + ) + ); + } + if (response.message.ack_num) { + frm.taxpayer_api_call("generate_gstr1").then(r => { + frm.doc.__gst_data = r.message; + frm.trigger("load_gstr1_data"); + }); + } + } + + handle_proceed_to_file_response(response) { + const filing_status = response.filing_status; + if (!filing_status) return; + + if (filing_status == "Ready to File") { + this.frm.page.set_primary_action("File", () => file_gstr1_data(this.frm)); + this.frm.page.set_indicator("Ready to File", "orange"); + this.frm.gstr1.status = "Ready to File"; + return; + } + + const differing_categories = response.differing_categories + .map(item => `
  • ${item}
  • `) + .join(""); + const message = ` +

    ${__( + "Summary for the following categories has not matched. Please sync with GSTIN." + )}

    +
      ${differing_categories}
    + `; + + frappe.msgprint({ + message: message, + indicator: "red", + title: __("GSTIN Sync Required"), + primary_action: { + label: __("Sync with GSTIN"), + action() { + render_empty_state(this.frm); + this.frm + .taxpayer_api_call("sync_with_gstn", { + sync_for: "unfiled", + }) + .then(() => { + frappe.msgprint(__("Synced successfully with GSTIN.")); + }); + }, + }, + }); + } + + handle_notification(response, action) { + const request_status = + action === "proceed_to_file" ? "Proceed to file" : `Data ${action}ing`; + + const status_message_map = { + P: `${request_status} has been successfully completed.`, + PE: `${request_status} is completed with errors`, + ER: `${request_status} has encountered errors`, + IP: `The request for ${request_status} is currently in progress`, + }; + + const alert_message = status_message_map[response.status_cd]; + + const doc = this.frm.doc; + const on_current_document = + window.location.pathname.includes("gstr-1-beta") && + doc.company_gstin == response.company_gstin && + doc.month_or_quarter == response.month_or_quarter && + doc.year == response.year; + + if (!on_current_document) return; + + frappe.show_alert(__(alert_message)); + } +} + // UTILITY FUNCTIONS function is_gstr1_api_enabled() { return ( diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 63a2309b6d..0320982363 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -198,13 +198,13 @@ def generate_evc_otp(company_gstin, pan): @frappe.whitelist() @otp_handler -def process_gstr1_request(month_or_quarter, year, company_gstin, request_type): +def process_gstr1_request(month_or_quarter, year, company_gstin, action): gstr_1_log = frappe.get_doc( "GST Return Log", f"GSTR1-{get_period(month_or_quarter, year)}-{company_gstin}", ) - method_name = f"process_{request_type}_gstr1" + method_name = f"process_{action}_gstr1" method = getattr(gstr_1_log, method_name) data = method() From b3a87cd9661b022c63582166a64e4b3542140d37 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Mon, 16 Sep 2024 17:35:22 +0530 Subject: [PATCH 40/68] refactor: move functions to classes --- .../doctype/gst_return_log/generate_gstr_1.py | 10 +- .../doctype/gstr_1_beta/gstr_1_beta.js | 447 +++++++++--------- .../doctype/gstr_1_beta/gstr_1_beta.py | 6 - .../gst_india/utils/gstr/generate_evc.py | 9 + india_compliance/public/js/utils.js | 17 +- 5 files changed, 263 insertions(+), 226 deletions(-) create mode 100644 india_compliance/gst_india/utils/gstr/generate_evc.py diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index c152904dfc..f9d1fb2d81 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -769,6 +769,7 @@ def process_upload_gstr1(self): self.update_json_for("upload_error", response) if status_cd == "P": + self.db_set({"filing_status": "Uploaded"}) self.update_json_for( "unfiled_summary", self.get_json_for("books_summary") ) @@ -845,9 +846,10 @@ def fetch_and_compare_summary(self, api, response=None): ) else: + self.db_set({"filing_status": "Not Filed"}) response.update( { - "filing_status": "Summary Not Matched", + "filing_status": "Not Filed", "differing_categories": differing_categories, } ) @@ -873,6 +875,7 @@ def file_gstr1(self, pan, otp): self.update_json_for("authenticated_summary", None) if response.get("ack_num"): + frappe.db.set_value("GSTIN", self.gstin, "last_pan_used_for_gstr", pan) self.db_set( { "filing_status": "Filed", @@ -896,12 +899,10 @@ def get_amendment_data(self): authenticated_summary = summarize_retsum_data(authenticated_summary.values()) non_amended_entries = { - "no_of_records": 0, "total_igst_amount": 0, "total_cgst_amount": 0, "total_sgst_amount": 0, "total_cess_amount": 0, - "total_taxable_value": 0, } amended_liability = {} @@ -926,8 +927,9 @@ def get_amendment_data(self): def verify_request_in_progress(self, request_type): for doc in self.actions: if doc.request_type == request_type and not doc.status: + formatted_request_type = request_type.replace("_", " ").title() frappe.throw( - f"{request_type.capitalize()} is in progress.Please wait for the process to complete." + f"{formatted_request_type} is in progress.Please wait for the process to complete." ) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 8341928fa5..c460eeb02d 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -138,7 +138,9 @@ frappe.ui.form.on(DOCTYPE, { ), "orange" ); - frm.page.set_primary_action(__("Generate"), () => generate_gstr1_data(frm)); + frm.page.set_primary_action(__("Generate"), () => + frm.gstr1.gstr1_action.generate_gstr1_data(frm) + ); }); frappe.realtime.on("show_message", message => { @@ -217,188 +219,6 @@ frappe.ui.form.on(DOCTYPE, { }, }); -function file_gstr1_data(frm) { - // TODO: EVC Generation, Resend, and Filing - const dialog = new frappe.ui.Dialog({ - title: "File GSTR-1", - fields: [ - { - label: "Company GSTIN", - fieldname: "company_gstin", - fieldtype: "Data", - read_only: 1, - default: frm.doc.company_gstin, - }, - { - label: "Period", - fieldname: "period", - fieldtype: "Data", - read_only: 1, - default: `${frm.doc.month_or_quarter} - ${frm.doc.year}`, - }, - { - label: "Total Liability", - fieldtype: "Section Break", - fieldname: "total_liability_section", - }, - { - fieldname: "liability_breakup_html", - fieldtype: "HTML", - hidden: 1, - }, - { - label: "Sign using EVC", - fieldtype: "Section Break", - }, - { - label: "Authorised PAN", - fieldname: "pan", - fieldtype: "Data", - reqd: 1, - }, - { - label: "EVC OTP", - fieldname: "otp", - fieldtype: "Data", - read_only: 1, - }, - { - label: "Once you file your return you will not be able to undo the action", - fieldname: "acknowledged", - fieldtype: "Check", - default: 0, - read_only: 1, - }, - ], - primary_action_label: "Get OTP", - async primary_action() { - const pan = dialog.get_value("pan"); - india_compliance.validate_pan(pan); - - // generate otp - await generate_evc_otp(frm.doc.company_gstin, pan); - - // show otp field - dialog.set_df_property("otp", "read_only", 0); - dialog.set_df_property("acknowledged", "read_only", 0); - - update_actions(frm, dialog, pan); - }, - }); - - frappe.db - .get_value("GSTIN", frm.doc.company_gstin, ["last_pan_used_for_gstr"]) - .then(({ message }) => { - const pan_no = - message.last_pan_used_for_gstr || frm.doc.company_gstin.substr(2, 10); - - dialog.set_value("pan", pan_no); - }); - - taxpayer_api.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", - args: { - action: "get_amendment_data", - month_or_quarter: frm.doc.month_or_quarter, - year: frm.doc.year, - company_gstin: frm.doc.company_gstin, - }, - callback: r => { - if (!r.message) return; - const { amended_liability, non_amended_liability } = r.message; - const amendment_table_html = generate_liability_table( - amended_liability, - non_amended_liability - ); - const field = dialog.get_field("liability_breakup_html"); - - if (!amendment_table_html) return; - field.toggle(true); - - field.df.options = amendment_table_html; - dialog.refresh(); - }, - }); - - dialog.show(); -} - -function generate_liability_table(amended_liability, non_amended_liability) { - // if (Object.keys(amended_liability).length === 0) return ""; - - let table_html = ` - - - - - - - - - - - - `; - - table_html += generate_table_row("For current period", non_amended_liability); - table_html += generate_table_row("From amendments", amended_liability); - // TODO: Add total row - table_html += ` - -
    DescriptionTotal IGSTTotal CGSTTotal SGSTTotal Cess
    - `; - - return table_html; -} - -function generate_table_row(description, liability) { - // TODO: amount format as currency - return ` - - ${description} - ${liability.total_igst_amount || 0} - ${liability.total_cgst_amount || 0} - ${liability.total_sgst_amount || 0} - ${liability.total_cess_amount || 0} - - `; -} - -async function update_actions(frm, dialog, pan) { - dialog.set_primary_action("File", () => { - if (!dialog.get_value("acknowledged")) { - frappe.msgprint( - __("Please acknowledge that you can not undo this action.") - ); - return; - } - - // TODO: do this in backend - frappe.db.set_value( - "GSTIN", - frm.doc.company_gstin, - "last_pan_used_for_gstr", - pan - ); - - perform_gstr1_action(frm, "file", { pan: pan, otp: dialog.get_value("otp") }); - dialog.hide(); - }); - - dialog.set_secondary_action_label("Resend OTP"); - dialog.set_secondary_action(() => { - generate_evc_otp(frm, pan); - }); -} - -function generate_evc_otp(company_gstin, pan) { - // TODO: common function for all returns - return frappe.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.generate_evc_otp", - args: { company_gstin: company_gstin, pan: pan }, - }); -} - class GSTR1 { // Render page / Setup Listeners / Setup Data TABS = [ @@ -616,28 +436,30 @@ class GSTR1 { render_form_actions() { if (this.data && (!is_gstr1_api_enabled() || this.status == "Filed")) return; - let gstr1_action = new GSTR1Action(this.frm); + this.gstr1_action = new GSTR1Action(this.frm); // Custom Buttons if (this.data) - this.frm.add_custom_button(__("Reset"), () => gstr1_action.reset_gstr1_data()); + this.frm.add_custom_button(__("Reset"), () => + this.gstr1_action.reset_gstr1_data() + ); // Primary Button const actions = { - Generate: gstr1_action.generate_gstr1_data, - Upload: gstr1_action.upload_gstr1_data, - "Proceed to File": gstr1_action.proceed_to_file, - File: gstr1_action.file_gstr1_data, + Generate: this.gstr1_action.generate_gstr1_data, + Upload: this.gstr1_action.upload_gstr1_data, + "Proceed to File": this.gstr1_action.proceed_to_file, + File: this.gstr1_action.file_gstr1_data, }; let primary_button_label = "Generate"; - if (this.status === "Not Filed") primary_action_label = "Upload"; + if (this.status === "Not Filed") primary_button_label = "Upload"; else if (this.status === "Uploaded") primary_button_label = "Proceed to File"; else if (this.status == "Ready to File") primary_button_label = "File"; this.frm.page.set_primary_action(__(primary_button_label), () => - actions[primary_button_label].call(gstr1_action) + actions[primary_button_label].call(this.gstr1_action) ); } @@ -2380,13 +2202,195 @@ class DetailViewDialog { } } -class FileGSTR1Dialog { } +class FileGSTR1Dialog { + constructor(frm) { + this.frm = frm; + this.dialog = null; + } + + file_gstr1_data() { + // TODO: EVC Generation, Resend, and Filing + const file_gstr1 = this; + + this.dialog = new frappe.ui.Dialog({ + title: "File GSTR-1", + fields: [ + { + label: "Company GSTIN", + fieldname: "company_gstin", + fieldtype: "Data", + read_only: 1, + default: this.frm.doc.company_gstin, + }, + { + label: "Period", + fieldname: "period", + fieldtype: "Data", + read_only: 1, + default: `${this.frm.doc.month_or_quarter} - ${this.frm.doc.year}`, + }, + { + label: "Total Liability", + fieldtype: "Section Break", + fieldname: "total_liability_section", + }, + { + fieldname: "liability_breakup_html", + fieldtype: "HTML", + hidden: 1, + }, + { + label: "Sign using EVC", + fieldtype: "Section Break", + }, + { + label: "Authorised PAN", + fieldname: "pan", + fieldtype: "Data", + reqd: 1, + }, + { + label: "EVC OTP", + fieldname: "otp", + fieldtype: "Data", + read_only: 1, + }, + { + label: "Once you file your return you will not be able to undo the action", + fieldname: "acknowledged", + fieldtype: "Check", + default: 0, + read_only: 1, + }, + ], + primary_action_label: "Get OTP", + async primary_action() { + const pan = file_gstr1.dialog.get_value("pan"); + india_compliance.validate_pan(pan); + + // generate otp + await india_compliance.generate_evc_otp( + file_gstr1.frm.doc.company_gstin, + pan, + "R1" + ); + + // show otp field + file_gstr1.dialog.set_df_property("otp", "read_only", 0); + file_gstr1.dialog.set_df_property("acknowledged", "read_only", 0); + + file_gstr1.update_actions(pan); + }, + }); + + // get last used pan + frappe.db + .get_value("GSTIN", this.frm.doc.company_gstin, ["last_pan_used_for_gstr"]) + .then(({ message }) => { + const pan_no = + message.last_pan_used_for_gstr || + this.frm.doc.company_gstin.substr(2, 10); + + this.dialog.set_value("pan", pan_no); + }); + + // update total amendes + taxpayer_api.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", + args: { + action: "get_amendment_data", + month_or_quarter: this.frm.doc.month_or_quarter, + year: this.frm.doc.year, + company_gstin: this.frm.doc.company_gstin, + }, + callback: r => { + if (!r.message) return; + const { amended_liability, non_amended_liability } = r.message; + const amendment_table_html = this.generate_liability_table( + amended_liability, + non_amended_liability + ); + const field = this.dialog.get_field("liability_breakup_html"); + + if (!amendment_table_html) return; + field.toggle(true); + + field.df.options = amendment_table_html; + this.dialog.refresh(); + }, + }); + + this.dialog.show(); + } + + generate_liability_table(amended_liability, non_amended_liability) { + let table_html = ` + + + + + + + + + + + + `; + + table_html += this.generate_table_row( + "For current period", + non_amended_liability + ); + table_html += this.generate_table_row("From amendments", amended_liability); + // TODO: Add total row + table_html += ` + +
    DescriptionTotal IGSTTotal CGSTTotal SGSTTotal Cess
    + `; + + return table_html; + } + + generate_table_row(description, liability) { + return ` + + ${description} + ${format_currency(liability.total_igst_amount)} + ${format_currency(liability.total_cgst_amount)} + ${format_currency(liability.total_sgst_amount)} + ${format_currency(liability.total_cess_amount)} + + `; + } + + update_actions(pan) { + this.dialog.set_primary_action("File", () => { + if (!this.dialog.get_value("acknowledged")) { + frappe.msgprint( + __("Please acknowledge that you can not undo this action.") + ); + return; + } + + this.perform_gstr1_action("file", { + pan: pan, + otp: this.dialog.get_value("otp"), + }); + }); + + this.dialog.set_secondary_action_label("Resend OTP"); + this.dialog.set_secondary_action(() => { + india_compliance.generate_evc_otp(this.frm.doc.company_gstin, pan, "R1"); + }); + } +} class GSTR1Action extends FileGSTR1Dialog { RETRY_INTERVALS = [5000, 15000, 30000, 60000, 120000, 300000, 600000, 720000]; // 5 second, 15 second, 30 second, 1 min, 2 min, 5 min, 10 min, 12 min constructor(frm) { - super(); + super(frm); this.frm = frm; this.defaults = { month_or_quarter: frm.doc.month_or_quarter, @@ -2403,7 +2407,7 @@ class GSTR1Action extends FileGSTR1Dialog { const request_types = ["upload", "reset", "proceed_to_file"]; request_types.map(request_type => - this.fetch_status_with_retry(request_type, (now = true)) + this.fetch_status_with_retry(request_type, true) ); }); } @@ -2443,12 +2447,12 @@ class GSTR1Action extends FileGSTR1Dialog { args: args, callback: response => { if (action == "file") { - handle_file_response(this.frm, response); + this.handle_file_response(response); } else { if (Object.keys(response).length == 0) { - fetch_status_with_retry(this.frm, action); + this.fetch_status_with_retry(action); } else { - handle_proceed_to_file_response(this.frm, response.message); + this.handle_proceed_to_file_response(response.message); } } }, @@ -2471,7 +2475,7 @@ class GSTR1Action extends FileGSTR1Dialog { frappe.throw(__(message.error_report.error_msg)); if (message.status_cd == "PE" && action == "upload") - handle_errors(this.frm, message); + this.handle_errors(message); // TODO: Highlight error tab @@ -2484,7 +2488,7 @@ class GSTR1Action extends FileGSTR1Dialog { if (action == "reset") { this.frm.page.set_primary_action("Upload", () => - upload_gstr1_data(this.frm) + this.upload_gstr1_data() ); this.frm.taxpayer_api_call("generate_gstr1").then(r => { this.frm.doc.__gst_data = r.message; @@ -2493,7 +2497,7 @@ class GSTR1Action extends FileGSTR1Dialog { } if (action == "proceed_to_file") - handle_proceed_to_file_response(this.frm, message); + this.handle_proceed_to_file_response(message); this.handle_notification(message, action); }, @@ -2501,16 +2505,27 @@ class GSTR1Action extends FileGSTR1Dialog { ); } - handle_errors(frm, message) { - frm.gstr1.tabs.error_tab.show(); - frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(message); + handle_errors(message) { + this.frm.gstr1.tabs.error_tab.show(); + this.frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(message); } - handle_file_response(frm, response) { - if (response.message?.error?.error_cd === "RET09001") { - frm.page.set_primary_action("Proceed to File", () => proceed_to_file(frm)); - frm.page.set_indicator("Not Filed", "orange"); - frm.gstr1.status = "Not Filed"; + handle_file_response(response) { + if (response.message.error?.error_cd === "RET13509") { + this.dialog + .get_field("otp") + .set_description(`

    Invalid OTP

    `); + return; + } + + this.dialog.hide(); + + if (response.message.error?.error_cd === "RET09001") { + this.frm.page.set_primary_action("Proceed to File", () => + this.proceed_to_file() + ); + this.frm.page.set_indicator("Not Filed", "orange"); + this.frm.gstr1.status = "Not Filed"; frappe.msgprint( __( "Latest Summary is not available. Please generate summary and try again." @@ -2518,9 +2533,9 @@ class GSTR1Action extends FileGSTR1Dialog { ); } if (response.message.ack_num) { - frm.taxpayer_api_call("generate_gstr1").then(r => { - frm.doc.__gst_data = r.message; - frm.trigger("load_gstr1_data"); + this.frm.taxpayer_api_call("generate_gstr1").then(r => { + this.frm.doc.__gst_data = r.message; + this.frm.trigger("load_gstr1_data"); }); } } @@ -2530,12 +2545,16 @@ class GSTR1Action extends FileGSTR1Dialog { if (!filing_status) return; if (filing_status == "Ready to File") { - this.frm.page.set_primary_action("File", () => file_gstr1_data(this.frm)); + this.frm.page.set_primary_action("File", () => + this.frm.gstr1.gstr1_action.file_gstr1_data() + ); this.frm.page.set_indicator("Ready to File", "orange"); this.frm.gstr1.status = "Ready to File"; return; } + this.frm.page.set_primary_action("Upload", () => this.upload_gstr1_data()); + const differing_categories = response.differing_categories .map(item => `
  • ${item}
  • `) .join(""); @@ -2685,6 +2704,10 @@ function render_empty_state(frm) { if ($(".gst-ledger-difference").length) { $(".gst-ledger-difference").remove(); } + if (frm.doc.__gst_data) { + frm.gstr1.data = null; + frm.gstr1.status = null; + } frm.doc.__gst_data = null; frm.refresh(); } diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 0320982363..98b0fb92ca 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -190,12 +190,6 @@ def handle_gstr1_action(action, month_or_quarter, year, company_gstin, **kwargs) return getattr(gstr_1_log, action)(**kwargs) -@frappe.whitelist() -def generate_evc_otp(company_gstin, pan): - frappe.has_permission("GSTR-1 Beta", "write", throw=True) - return TaxpayerBaseAPI(company_gstin).initiate_otp_for_evc(pan, "R1") - - @frappe.whitelist() @otp_handler def process_gstr1_request(month_or_quarter, year, company_gstin, action): diff --git a/india_compliance/gst_india/utils/gstr/generate_evc.py b/india_compliance/gst_india/utils/gstr/generate_evc.py new file mode 100644 index 0000000000..308bdcf0a7 --- /dev/null +++ b/india_compliance/gst_india/utils/gstr/generate_evc.py @@ -0,0 +1,9 @@ +import frappe + +from india_compliance.gst_india.api_classes.taxpayer_base import TaxpayerBaseAPI + + +@frappe.whitelist() +def generate_evc_otp(company_gstin, pan, request_type): + frappe.has_permission("GSTR-1 Beta", "write", throw=True) + return TaxpayerBaseAPI(company_gstin).initiate_otp_for_evc(pan, request_type) diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 9eadc47337..49e6c15e1f 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -232,11 +232,11 @@ Object.assign(india_compliance, { pan = pan.trim().toUpperCase(); if (pan.length != 10) { - frappe.throw(("PAN should be 10 characters long")); + frappe.throw(__("PAN should be 10 characters long")); } if (!PAN_REGEX.test(pan)) { - frappe.throw(("Invalid PAN format")); + frappe.throw(__("Invalid PAN format")); } return pan; @@ -257,6 +257,17 @@ Object.assign(india_compliance, { } }, + generate_evc_otp(company_gstin, pan, request_type) { + return frappe.call({ + method: "india_compliance.gst_india.utils.gstr.generate_evc.generate_evc_otp", + args: { + company_gstin: company_gstin, + pan: pan, + request_type: request_type, + }, + }); + }, + guess_gst_category(gstin, country) { if (!gstin) { if (country && country !== "India") return "Overseas"; @@ -371,12 +382,10 @@ Object.assign(india_compliance, { return position === "start" ? `${current_year - 1}-03-01` : `${current_year - 1}-09-30`; - } else if (current_month <= 9) { return position === "start" ? `${current_year - 1}-10-01` : `${current_year}-03-31`; - } else { return position === "start" ? `${current_year}-04-01` From a7940619b33ef3502a3855d59b726129d50aee0c Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 1 Oct 2024 12:36:22 +0530 Subject: [PATCH 41/68] fix: better comparision algorithm for summary --- .../doctype/gst_return_log/generate_gstr_1.py | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index f9d1fb2d81..b467df34d7 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -12,6 +12,7 @@ CATEGORY_SUB_CATEGORY_MAPPING, SUBCATEGORIES_NOT_CONSIDERED_IN_TOTAL_TAX, SUBCATEGORIES_NOT_CONSIDERED_IN_TOTAL_TAXABLE_VALUE, + GSTR1_Category, GSTR1_DataField, ) from india_compliance.gst_india.utils.gstr_1.gstr_1_download import ( @@ -942,6 +943,18 @@ def get_differing_categories(mapped_summary, gov_summary): "total_taxable_value", } + # TODO: Check this for all categories + CATEGORY_KEYS = { + (GSTR1_Category.NIL_EXEMPT.value): { + "total_exempted_amount", + "total_nil_rated_amount", + "total_non_gst_amount", + }, + (GSTR1_Category.DOC_ISSUE.value): { + "no_of_records", + }, + } + gov_summary = {row["description"]: row for row in gov_summary if row["indent"] == 0} compared_categories = set() differing_categories = set() @@ -955,7 +968,9 @@ def get_differing_categories(mapped_summary, gov_summary): compared_categories.add(category) gov_entry = gov_summary.get(category, {}) - for key in KEYS_TO_COMPARE: + keys_to_compare = CATEGORY_KEYS.get(category, KEYS_TO_COMPARE) + + for key in keys_to_compare: if gov_entry.get(key, 0) != row.get(key): differing_categories.add(category) break @@ -963,7 +978,12 @@ def get_differing_categories(mapped_summary, gov_summary): for row in gov_summary.values(): # Amendments are with indent 1. Hence auto-skipped if row["description"] not in compared_categories: - differing_categories.add(row["description"]) + keys_to_compare = CATEGORY_KEYS.get(row["description"], KEYS_TO_COMPARE) + + for key in keys_to_compare: + if row.get(key, 0) != 0: + differing_categories.add(row["description"]) + break return differing_categories From 0e85a04523113ffe5ca356e874cde9231214d9a5 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 1 Oct 2024 12:37:18 +0530 Subject: [PATCH 42/68] fix: skip proceed to file action button after upload --- .../doctype/gstr_1_beta/gstr_1_beta.js | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index c460eeb02d..9932543766 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -302,6 +302,12 @@ class GSTR1 { return; } + if (this.data.status == "Ready to File" && ["books", "unfiled", "reconcile"].includes(tab_name)) { + tab.hide(); + _tab.shown = false; + return; + } + tab.show(); _tab.shown = true; tab.tabmanager.refresh_data( @@ -1016,10 +1022,10 @@ class TabManager { args[2]?.indent == 0 ? `${value}` : isDescriptionCell - ? ` + ? `

    ${value}

    ` - : value; + : value; return value; } @@ -1769,9 +1775,9 @@ class FiledTab extends GSTR1_TabManager { const { include_uploaded, delete_missing } = dialog ? dialog.get_values() : { - include_uploaded: true, - delete_missing: false, - }; + include_uploaded: true, + delete_missing: false, + }; const doc = me.instance.frm.doc; @@ -2011,7 +2017,7 @@ class ReconcileTab extends FiledTab { }); } - get_creation_time_string() {} // pass + get_creation_time_string() { } // pass get_detail_view_column() { return [ @@ -2084,8 +2090,8 @@ class ErrorTab extends TabManager { ]; } - setup_actions() {} - set_creation_time_string() {} + setup_actions() { } + set_creation_time_string() { } refresh_data(data) { this.set_default_title(); @@ -2480,10 +2486,8 @@ class GSTR1Action extends FileGSTR1Dialog { // TODO: Highlight error tab if (action == "upload") { - //TODO: data is not refreshed immediately - this.frm.taxpayer_api_call("sync_with_gstn", { - sync_for: "unfiled", - }); + this.perform_gstr1_action("proceed_to_file"); + return; } if (action == "reset") { @@ -2496,8 +2500,12 @@ class GSTR1Action extends FileGSTR1Dialog { }); } - if (action == "proceed_to_file") + if (action == "proceed_to_file") { + ["books", "unfiled", "reconcile"].map(tab => + this.frm.gstr1.tabs[`${tab}_tab`].hide() + ); this.handle_proceed_to_file_response(message); + } this.handle_notification(message, action); }, @@ -2621,7 +2629,7 @@ function is_gstr1_api_enabled() { } function patch_set_indicator(frm) { - frm.toolbar.set_indicator = function () {}; + frm.toolbar.set_indicator = function () { }; } async function set_default_company_gstin(frm) { From 589bd0f0c023a3b4987c05cd3329c84b048a39ee Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 1 Oct 2024 13:50:11 +0530 Subject: [PATCH 43/68] fix: verify upload request for proceed to file as well --- .../doctype/gst_return_log/generate_gstr_1.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index b467df34d7..1f95312ef9 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -724,7 +724,7 @@ def upload_gstr1(self, json_data): if not json_data: return - verify_request_in_progress(self, "upload") + verify_request_in_progress(self, ["upload", "proceed_to_file"]) keys = {category.value for category in GovJsonKey} if all(key not in json_data for key in keys): @@ -925,12 +925,15 @@ def get_amendment_data(self): } -def verify_request_in_progress(self, request_type): +def verify_request_in_progress(self, request_types): + if isinstance(request_types, str): + request_types = [request_types] + for doc in self.actions: - if doc.request_type == request_type and not doc.status: - formatted_request_type = request_type.replace("_", " ").title() + if doc.request_type in request_types and not doc.status: + formatted_request_type = request_types[0].replace("_", " ").title() frappe.throw( - f"{formatted_request_type} is in progress.Please wait for the process to complete." + f"{formatted_request_type} is in progress. Please wait for the process to complete." ) From 21d74d76b5c387608f896cf100148bc1e4d808c4 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 1 Oct 2024 15:07:56 +0530 Subject: [PATCH 44/68] fix: only allow one request in progress --- .../doctype/gst_return_log/generate_gstr_1.py | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 1f95312ef9..ee32fdf739 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -3,7 +3,7 @@ import itertools import frappe -from frappe import unscrub +from frappe import _, unscrub from frappe.utils import flt from india_compliance.gst_india.api_classes.taxpayer_returns import GSTR1API @@ -684,7 +684,7 @@ def normalize_data(data): class FileGSTR1: def reset_gstr1(self): # reset called after proceed to file - verify_request_in_progress(self, "reset") + verify_request_in_progress(self) self.db_set({"filing_status": "Not Filed"}) @@ -724,7 +724,7 @@ def upload_gstr1(self, json_data): if not json_data: return - verify_request_in_progress(self, ["upload", "proceed_to_file"]) + verify_request_in_progress(self) keys = {category.value for category in GovJsonKey} if all(key not in json_data for key in keys): @@ -779,7 +779,7 @@ def process_upload_gstr1(self): return response def proceed_to_file_gstr1(self): - verify_request_in_progress(self, "proceed_to_file") + verify_request_in_progress(self) api = GSTR1API(self) response = api.proceed_to_file("GSTR1", self.return_period) @@ -865,7 +865,7 @@ def fetch_and_compare_summary(self, api, response=None): return response def file_gstr1(self, pan, otp): - verify_request_in_progress(self, "file") + verify_request_in_progress(self) summary = self.get_json_for("authenticated_summary") api = GSTR1API(self) @@ -925,15 +925,13 @@ def get_amendment_data(self): } -def verify_request_in_progress(self, request_types): - if isinstance(request_types, str): - request_types = [request_types] - - for doc in self.actions: - if doc.request_type in request_types and not doc.status: - formatted_request_type = request_types[0].replace("_", " ").title() +def verify_request_in_progress(return_log): + for row in return_log.actions: + if not row.status: frappe.throw( - f"{formatted_request_type} is in progress. Please wait for the process to complete." + _( + "There is a {0} request in progress. Please wait for the process to complete." + ).format(row.request_type) ) From 33fbb5c6354da84b302ac1e7fffbf85206078f85 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 1 Oct 2024 16:00:52 +0530 Subject: [PATCH 45/68] fix: one action at a time in frontend --- .../doctype/gstr_1_beta/gstr_1_beta.js | 70 ++++++++++++++----- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 9932543766..96919f1400 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -302,7 +302,10 @@ class GSTR1 { return; } - if (this.data.status == "Ready to File" && ["books", "unfiled", "reconcile"].includes(tab_name)) { + if ( + this.data.status == "Ready to File" && + ["books", "unfiled", "reconcile"].includes(tab_name) + ) { tab.hide(); _tab.shown = false; return; @@ -311,8 +314,8 @@ class GSTR1 { tab.show(); _tab.shown = true; tab.tabmanager.refresh_data( - this.data[tab_name], - this.data[`${tab_name}_summary`], + this.data[tab_name] || {}, + this.data[`${tab_name}_summary`] || [], this.status ); }); @@ -2215,6 +2218,8 @@ class FileGSTR1Dialog { } file_gstr1_data() { + if (this.is_request_in_progress()) return; + // TODO: EVC Generation, Resend, and Filing const file_gstr1 = this; @@ -2419,11 +2424,15 @@ class GSTR1Action extends FileGSTR1Dialog { } upload_gstr1_data() { + if (this.is_request_in_progress()) return; + frappe.show_alert(__("Uploading data to GSTN")); this.perform_gstr1_action("upload"); } reset_gstr1_data() { + if (this.is_request_in_progress()) return; + frappe.confirm( __( "All the details saved in different tables shall be deleted after reset.
    Are you sure, you want to reset the already saved data?" @@ -2441,6 +2450,9 @@ class GSTR1Action extends FileGSTR1Dialog { } perform_gstr1_action(action, additional_args = {}) { + this.toggle_actions(false, action); + + // TODO: Why error tabs are hidden here. Only on successful upload this.frm.gstr1.tabs.error_tab.hide(); const args = { ...this.defaults, @@ -2477,23 +2489,21 @@ class GSTR1Action extends FileGSTR1Dialog { if (message.status_cd === "IP" && retries < retry_intervals.length) return this.fetch_status_with_retry(action, retries + 1); - if (message.status_cd == "ER") - frappe.throw(__(message.error_report.error_msg)); - - if (message.status_cd == "PE" && action == "upload") - this.handle_errors(message); - - // TODO: Highlight error tab - if (action == "upload") { - this.perform_gstr1_action("proceed_to_file"); - return; + if (message.status_cd == "P") { + this.perform_gstr1_action("proceed_to_file"); + return; + } else if (message.status_cd == "PE") this.handle_errors(message); + // TODO: Highlight error tab } + this.toggle_actions(true); + + if (message.status_cd == "ER") + frappe.throw(__(message.error_report.error_msg)); + if (action == "reset") { - this.frm.page.set_primary_action("Upload", () => - this.upload_gstr1_data() - ); + render_empty_state(this.frm); this.frm.taxpayer_api_call("generate_gstr1").then(r => { this.frm.doc.__gst_data = r.message; this.frm.trigger("load_gstr1_data"); @@ -2504,7 +2514,10 @@ class GSTR1Action extends FileGSTR1Dialog { ["books", "unfiled", "reconcile"].map(tab => this.frm.gstr1.tabs[`${tab}_tab`].hide() ); + this.frm.gstr1.tabs.filed_tab.set_active(); + this.handle_proceed_to_file_response(message); + action = "upload"; // for notification } this.handle_notification(message, action); @@ -2617,6 +2630,31 @@ class GSTR1Action extends FileGSTR1Dialog { frappe.show_alert(__(alert_message)); } + + is_request_in_progress() { + const in_progress = this.frm.__action_performed; + if (!in_progress) return false; + + frappe.show_alert({ + message: __(`Already ${in_progress}ing`), + indicator: "red", + }); + + return true; + } + + toggle_actions(show, action) { + const actions = ["Upload", "Reset", "File"]; + const btns = $(actions.map(action => `[data-label="${action}"]`).join(",")); + + if (show) { + this.frm.__action_performed = null; + btns && btns.removeClass("disabled"); + } else { + this.frm.__action_performed = action; + btns && btns.addClass("disabled"); + } + } } // UTILITY FUNCTIONS From d104a3881238105aa0711aa76fc137c84a5e1928 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 1 Oct 2024 17:06:05 +0530 Subject: [PATCH 46/68] refactor: get all actions together --- .../doctype/gstr_1_beta/gstr_1_beta.js | 179 +++++++++--------- 1 file changed, 92 insertions(+), 87 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 96919f1400..9690fcaec2 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -2214,16 +2214,14 @@ class DetailViewDialog { class FileGSTR1Dialog { constructor(frm) { this.frm = frm; - this.dialog = null; + this.filing_dialog = null; } file_gstr1_data() { if (this.is_request_in_progress()) return; // TODO: EVC Generation, Resend, and Filing - const file_gstr1 = this; - - this.dialog = new frappe.ui.Dialog({ + this.filing_dialog = new frappe.ui.Dialog({ title: "File GSTR-1", fields: [ { @@ -2275,22 +2273,22 @@ class FileGSTR1Dialog { }, ], primary_action_label: "Get OTP", - async primary_action() { - const pan = file_gstr1.dialog.get_value("pan"); + primary_action: async () => { + const pan = this.filing_dialog.get_value("pan"); india_compliance.validate_pan(pan); // generate otp await india_compliance.generate_evc_otp( - file_gstr1.frm.doc.company_gstin, + this.frm.doc.company_gstin, pan, "R1" ); // show otp field - file_gstr1.dialog.set_df_property("otp", "read_only", 0); - file_gstr1.dialog.set_df_property("acknowledged", "read_only", 0); + this.filing_dialog.set_df_property("otp", "read_only", 0); + this.filing_dialog.set_df_property("acknowledged", "read_only", 0); - file_gstr1.update_actions(pan); + this.update_actions(pan); }, }); @@ -2302,7 +2300,7 @@ class FileGSTR1Dialog { message.last_pan_used_for_gstr || this.frm.doc.company_gstin.substr(2, 10); - this.dialog.set_value("pan", pan_no); + this.filing_dialog.set_value("pan", pan_no); }); // update total amendes @@ -2321,17 +2319,17 @@ class FileGSTR1Dialog { amended_liability, non_amended_liability ); - const field = this.dialog.get_field("liability_breakup_html"); + const field = this.filing_dialog.get_field("liability_breakup_html"); if (!amendment_table_html) return; field.toggle(true); field.df.options = amendment_table_html; - this.dialog.refresh(); + this.filing_dialog.refresh(); }, }); - this.dialog.show(); + this.filing_dialog.show(); } generate_liability_table(amended_liability, non_amended_liability) { @@ -2376,29 +2374,63 @@ class FileGSTR1Dialog { } update_actions(pan) { - this.dialog.set_primary_action("File", () => { - if (!this.dialog.get_value("acknowledged")) { + this.filing_dialog.set_primary_action("File", () => { + if (!this.filing_dialog.get_value("acknowledged")) { frappe.msgprint( __("Please acknowledge that you can not undo this action.") ); return; } - this.perform_gstr1_action("file", { - pan: pan, - otp: this.dialog.get_value("otp"), - }); + this.perform_gstr1_action( + "file", + r => this.handle_filing_response(r.message), + { + pan: pan, + otp: this.filing_dialog.get_value("otp"), + } + ); }); - this.dialog.set_secondary_action_label("Resend OTP"); - this.dialog.set_secondary_action(() => { + this.filing_dialog.set_secondary_action_label("Resend OTP"); + this.filing_dialog.set_secondary_action(() => { india_compliance.generate_evc_otp(this.frm.doc.company_gstin, pan, "R1"); }); } + + handle_filing_response(response) { + if (response.error?.error_cd === "RET13509") { + this.filing_dialog + .get_field("otp") + .set_description(`

    Invalid OTP

    `); + return; + } + + this.filing_dialog.hide(); + + if (response.error?.error_cd === "RET09001") { + this.frm.page.set_primary_action("Proceed to File", () => + this.proceed_to_file() + ); + this.frm.page.set_indicator("Not Filed", "orange"); + this.frm.gstr1.status = "Not Filed"; + frappe.msgprint( + __( + "Latest Summary is not available. Please generate summary and try again." + ) + ); + } + if (response.ack_num) { + this.frm.taxpayer_api_call("generate_gstr1").then(r => { + this.frm.doc.__gst_data = r.message; + this.frm.trigger("load_gstr1_data"); + }); + } + } } class GSTR1Action extends FileGSTR1Dialog { - RETRY_INTERVALS = [5000, 15000, 30000, 60000, 120000, 300000, 600000, 720000]; // 5 second, 15 second, 30 second, 1 min, 2 min, 5 min, 10 min, 12 min + RETRY_INTERVALS = [2000, 3000, 15000, 30000, 60000, 120000, 300000, 600000, 720000]; // 5 second, 15 second, 30 second, 1 min, 2 min, 5 min, 10 min, 12 min constructor(frm) { super(frm); @@ -2426,30 +2458,44 @@ class GSTR1Action extends FileGSTR1Dialog { upload_gstr1_data() { if (this.is_request_in_progress()) return; + const action = "upload"; + frappe.show_alert(__("Uploading data to GSTN")); - this.perform_gstr1_action("upload"); + this.perform_gstr1_action(action, () => this.fetch_status_with_retry(action)); } reset_gstr1_data() { if (this.is_request_in_progress()) return; + const action = "reset"; + frappe.confirm( __( "All the details saved in different tables shall be deleted after reset.
    Are you sure, you want to reset the already saved data?" ), () => { frappe.show_alert(__("Resetting GSTR-1 data")); - this.perform_gstr1_action("reset"); + this.perform_gstr1_action(action, () => + this.fetch_status_with_retry(action) + ); } ); } proceed_to_file() { + const action = "proceed_to_file"; + frappe.show_alert(__("Please wait while we proceed to file the data.")); - this.perform_gstr1_action("proceed_to_file"); + this.perform_gstr1_action(action, r => { + if (r.message) + this.handle_proceed_to_file_response( + r.message + ); // already proceed to file + else this.fetch_status_with_retry(action); + }); } - perform_gstr1_action(action, additional_args = {}) { + perform_gstr1_action(action, callback, additional_args = {}) { this.toggle_actions(false, action); // TODO: Why error tabs are hidden here. Only on successful upload @@ -2463,17 +2509,7 @@ class GSTR1Action extends FileGSTR1Dialog { taxpayer_api.call({ method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.handle_gstr1_action", args: args, - callback: response => { - if (action == "file") { - this.handle_file_response(response); - } else { - if (Object.keys(response).length == 0) { - this.fetch_status_with_retry(action); - } else { - this.handle_proceed_to_file_response(response.message); - } - } - }, + callback: response => callback && callback(response), }); } @@ -2490,10 +2526,8 @@ class GSTR1Action extends FileGSTR1Dialog { return this.fetch_status_with_retry(action, retries + 1); if (action == "upload") { - if (message.status_cd == "P") { - this.perform_gstr1_action("proceed_to_file"); - return; - } else if (message.status_cd == "PE") this.handle_errors(message); + if (message.status_cd == "P") return this.proceed_to_file(); + else if (message.status_cd == "PE") this.show_errors(message); // TODO: Highlight error tab } @@ -2511,11 +2545,6 @@ class GSTR1Action extends FileGSTR1Dialog { } if (action == "proceed_to_file") { - ["books", "unfiled", "reconcile"].map(tab => - this.frm.gstr1.tabs[`${tab}_tab`].hide() - ); - this.frm.gstr1.tabs.filed_tab.set_active(); - this.handle_proceed_to_file_response(message); action = "upload"; // for notification } @@ -2526,45 +2555,22 @@ class GSTR1Action extends FileGSTR1Dialog { ); } - handle_errors(message) { + show_errors(message) { this.frm.gstr1.tabs.error_tab.show(); this.frm.gstr1.tabs["error_tab"].tabmanager.refresh_data(message); } - handle_file_response(response) { - if (response.message.error?.error_cd === "RET13509") { - this.dialog - .get_field("otp") - .set_description(`

    Invalid OTP

    `); - return; - } - - this.dialog.hide(); - - if (response.message.error?.error_cd === "RET09001") { - this.frm.page.set_primary_action("Proceed to File", () => - this.proceed_to_file() - ); - this.frm.page.set_indicator("Not Filed", "orange"); - this.frm.gstr1.status = "Not Filed"; - frappe.msgprint( - __( - "Latest Summary is not available. Please generate summary and try again." - ) - ); - } - if (response.message.ack_num) { - this.frm.taxpayer_api_call("generate_gstr1").then(r => { - this.frm.doc.__gst_data = r.message; - this.frm.trigger("load_gstr1_data"); - }); - } - } - handle_proceed_to_file_response(response) { + // only show filed tab + ["books", "unfiled", "reconcile"].map(tab => + this.frm.gstr1.tabs[`${tab}_tab`].hide() + ); + this.frm.gstr1.tabs.filed_tab.set_active(); + const filing_status = response.filing_status; if (!filing_status) return; + // summary matched if (filing_status == "Ready to File") { this.frm.page.set_primary_action("File", () => this.frm.gstr1.gstr1_action.file_gstr1_data() @@ -2574,11 +2580,13 @@ class GSTR1Action extends FileGSTR1Dialog { return; } + // summary not matched this.frm.page.set_primary_action("Upload", () => this.upload_gstr1_data()); const differing_categories = response.differing_categories .map(item => `
  • ${item}
  • `) .join(""); + const message = `

    ${__( "Summary for the following categories has not matched. Please sync with GSTIN." @@ -2592,15 +2600,11 @@ class GSTR1Action extends FileGSTR1Dialog { title: __("GSTIN Sync Required"), primary_action: { label: __("Sync with GSTIN"), - action() { + action: () => { render_empty_state(this.frm); - this.frm - .taxpayer_api_call("sync_with_gstn", { - sync_for: "unfiled", - }) - .then(() => { - frappe.msgprint(__("Synced successfully with GSTIN.")); - }); + this.frm.taxpayer_api_call("sync_with_gstn", { + sync_for: "unfiled", + }); }, }, }); @@ -2632,8 +2636,9 @@ class GSTR1Action extends FileGSTR1Dialog { } is_request_in_progress() { - const in_progress = this.frm.__action_performed; + let in_progress = this.frm.__action_performed; if (!in_progress) return false; + else if (in_progress == "proceed_to_file") in_progress = "upload"; frappe.show_alert({ message: __(`Already ${in_progress}ing`), From b9828bb393ecbc45820cf3d99e512d715118e25a Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 1 Oct 2024 17:07:14 +0530 Subject: [PATCH 47/68] refactor: formatting --- .../gst_india/doctype/gstr_1_beta/gstr_1_beta.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 9690fcaec2..e750ad89eb 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -2487,10 +2487,8 @@ class GSTR1Action extends FileGSTR1Dialog { frappe.show_alert(__("Please wait while we proceed to file the data.")); this.perform_gstr1_action(action, r => { - if (r.message) - this.handle_proceed_to_file_response( - r.message - ); // already proceed to file + // already proceed to file + if (r.message) this.handle_proceed_to_file_response(r.message); else this.fetch_status_with_retry(action); }); } From 8f903ffdc8babd97979068377186199002efd75e Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 1 Oct 2024 18:24:35 +0530 Subject: [PATCH 48/68] fix: don't process all requests --- .../doctype/gst_return_log/generate_gstr_1.py | 110 +++++++++--------- .../doctype/gst_return_log/gst_return_log.py | 5 + 2 files changed, 61 insertions(+), 54 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index ee32fdf739..67fb63ac0b 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -700,23 +700,24 @@ def process_reset_gstr1(self): api = GSTR1API(self) response = None - for doc in self.actions: - if doc.request_type != "reset" or doc.status: - continue + doc = self.get_unprocessed_action("reset") - response = api.get_return_status(self.return_period, doc.token) + if not doc: + return - if response.get("status_cd") != "IP": - doc.db_set({"status": status_code_map.get(response.get("status_cd"))}) - enqueue_notification( - self.return_period, - "reset", - response.get("status_cd"), - self.gstin, - ) + response = api.get_return_status(self.return_period, doc.token) - if response.get("status_cd") == "P": - self.update_json_for("unfiled", {}, reset_reconcile=True) + if response.get("status_cd") != "IP": + doc.db_set({"status": status_code_map.get(response.get("status_cd"))}) + enqueue_notification( + self.return_period, + "reset", + response.get("status_cd"), + self.gstin, + ) + + if response.get("status_cd") == "P": + self.update_json_for("unfiled", {}, reset_reconcile=True) return response @@ -749,32 +750,32 @@ def process_upload_gstr1(self): api = GSTR1API(self) response = None - for doc in self.actions: - if doc.request_type != "upload" or doc.status: - continue + doc = self.get_unprocessed_action("upload") - response = api.get_return_status(self.return_period, doc.token) - status_cd = response.get("status_cd") - - if status_cd != "IP": - doc.db_set({"status": status_code_map.get(status_cd)}) - enqueue_notification( - self.return_period, - "upload", - status_cd, - self.gstin, - api.request_id if status_cd == "ER" else None, - ) + if not doc: + return - if status_cd == "PE": - self.update_json_for("upload_error", response) + response = api.get_return_status(self.return_period, doc.token) + status_cd = response.get("status_cd") - if status_cd == "P": - self.db_set({"filing_status": "Uploaded"}) - self.update_json_for( - "unfiled_summary", self.get_json_for("books_summary") - ) - self.update_json_for("unfiled", self.get_json_for("books")) + if status_cd != "IP": + doc.db_set({"status": status_code_map.get(status_cd)}) + enqueue_notification( + self.return_period, + "upload", + status_cd, + self.gstin, + api.request_id if status_cd == "ER" else None, + ) + + if status_cd == "PE": + self.update_json_for("upload_error", response.get("error_report")) + return response.get("error_report") + + if status_cd == "P": + self.db_set({"filing_status": "Uploaded"}) + self.update_json_for("unfiled_summary", self.get_json_for("books_summary")) + self.update_json_for("unfiled", self.get_json_for("books")) return response @@ -798,27 +799,28 @@ def process_proceed_to_file_gstr1(self): api = GSTR1API(self) response = None - for doc in self.actions: - if doc.request_type != "proceed_to_file" or doc.status: - continue + doc = self.get_unprocessed_action("proceed_to_file") - response = api.get_return_status(self.return_period, doc.token) + if not doc: + return - if response.get("status_cd") == "IP": - return response + response = api.get_return_status(self.return_period, doc.token) - doc.db_set({"status": status_code_map.get(response.get("status_cd"))}) - if response.get("status_cd") != "P": - enqueue_notification( - self.return_period, - "proceed_to_file", - response.get("status_cd"), - self.gstin, - api.request_id, - ) - return + if response.get("status_cd") == "IP": + return response + + doc.db_set({"status": status_code_map.get(response.get("status_cd"))}) + if response.get("status_cd") != "P": + enqueue_notification( + self.return_period, + "proceed_to_file", + response.get("status_cd"), + self.gstin, + api.request_id, + ) + return - return self.fetch_and_compare_summary(api, response) + return self.fetch_and_compare_summary(api, response) def fetch_and_compare_summary(self, api, response=None): if response is None: diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py index 9ca51bf8fa..8b0730aa65 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py @@ -188,6 +188,11 @@ def get_applicable_file_fields(self, settings=None): return fields + def get_unprocessed_action(self, action): + for row in self.get("actions") or []: + if row.request_type == action and not row.status: + return row + def process_gstr_1_returns_info(company, gstin, response): return_info = {} From b0b57686db80f6ad661c33ac9ac47d0e30d8df20 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 1 Oct 2024 19:40:28 +0530 Subject: [PATCH 49/68] fix: show errors tab --- .../doctype/gst_return_log/generate_gstr_1.py | 6 ++- .../doctype/gstr_1_beta/gstr_1_beta.css | 12 ++++- .../doctype/gstr_1_beta/gstr_1_beta.js | 51 +++++++------------ .../gst_india/utils/gstr_1/__init__.py | 6 +++ .../gst_india/utils/gstr_1/gstr_1_json_map.py | 25 ++++++++- 5 files changed, 60 insertions(+), 40 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 67fb63ac0b..027b4cb35b 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -769,8 +769,10 @@ def process_upload_gstr1(self): ) if status_cd == "PE": - self.update_json_for("upload_error", response.get("error_report")) - return response.get("error_report") + response["error_report"] = convert_to_internal_data_format( + response.get("error_report"), True + ) + self.update_json_for("upload_error", response) if status_cd == "P": self.db_set({"filing_status": "Uploaded"}) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.css b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.css index 63ba3901e5..c7092e98c6 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.css +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.css @@ -6,6 +6,10 @@ div[data-page-route="GSTR-1 Beta"] [data-fieldname="tabs_html"] .section-body { margin-top: 0; } +div[data-page-route="GSTR-1 Beta"] .section-body { + max-width: 100% !important; +} + div[data-page-route="GSTR-1 Beta"] [data-fieldname="tabs_html"] .form-tabs-list { margin-top: var(--margin-sm); display: flex; @@ -100,13 +104,17 @@ div[data-page-route="GSTR-1 Beta"] .custom-tabs > .nav-item > .nav-link.active { color: var(--primary); } -div[data-page-route="GSTR-1 Beta"] .custom-tabs > .nav-item:first-child:not(:has(.disabled)):hover { +div[data-page-route="GSTR-1 Beta"] + .custom-tabs + > .nav-item:first-child:not(:has(.disabled)):hover { background-color: var(--btn-default-hover-bg); border-radius: 0.4rem 0 0 0.4rem; color: var(--text-color); } -div[data-page-route="GSTR-1 Beta"] .custom-tabs > .nav-item:last-child:not(:has(.disabled)):hover { +div[data-page-route="GSTR-1 Beta"] + .custom-tabs + > .nav-item:last-child:not(:has(.disabled)):hover { background-color: var(--btn-default-hover-bg); border-radius: 0 0.4rem 0.4rem 0; color: var(--text-color); diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index e750ad89eb..0e9bfee5d3 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -2051,6 +2051,7 @@ class ReconcileTab extends FiledTab { } class ErrorTab extends TabManager { + DEFAULT_SUBTITLE = "Fix these errors and upload again"; set_default_title() { this.DEFAULT_TITLE = "Error Summary"; TabManager.prototype.set_default_title.call(this); @@ -2061,7 +2062,7 @@ class ErrorTab extends TabManager { { name: "Category", fieldname: "category", - width: 80, + width: 120, }, { name: "Error Code", @@ -2069,25 +2070,25 @@ class ErrorTab extends TabManager { width: 100, }, { - name: "Error Description", - fieldname: "description", + name: "Error Message", + fieldname: "error_message", width: 250, }, { - name: "Party GSTIN", - fieldname: "party_gstin", - width: 170, + name: "Invoice Number", + fieldname: GSTR1_DataField.DOC_NUMBER, + fieldtype: "Link", + options: "Sales Invoice", + width: 150, }, { - name: "Place Of Supply", - fieldname: "place_of_supply", - width: 130, + name: "Party GSTIN", + fieldname: GSTR1_DataField.CUST_GSTIN, + width: 160, }, { - name: "Invoice Number", - fieldname: "invoice_number", - fieldtype: "Link", - options: "Sales Invoice", + name: "Place Of Supply", + fieldname: GSTR1_DataField.POS, width: 130, }, ]; @@ -2097,11 +2098,12 @@ class ErrorTab extends TabManager { set_creation_time_string() { } refresh_data(data) { - this.set_default_title(); - data = this.get_error_data(data); + console.log(data); + data = data.error_report super.refresh_data(data, data, "Error Summary"); $(".dt-footer").remove(); } + setup_wrapper() { this.wrapper.append(`

    @@ -2116,25 +2118,6 @@ class ErrorTab extends TabManager {
    `); } - - get_error_data(message) { - const { error_report } = message; - let data = []; - - for (let category in error_report) { - for (let object of error_report[category]) { - data.push({ - category: category.toUpperCase(), - error_code: object.error_cd, - description: object.error_msg, - party_gstin: object.ctin, - place_of_supply: object.inv[0].pos, - invoice_number: object.inv[0].inum, - }); - } - } - return data; - } } class DetailViewDialog { diff --git a/india_compliance/gst_india/utils/gstr_1/__init__.py b/india_compliance/gst_india/utils/gstr_1/__init__.py index 66718a0a89..c4fd2e4924 100644 --- a/india_compliance/gst_india/utils/gstr_1/__init__.py +++ b/india_compliance/gst_india/utils/gstr_1/__init__.py @@ -124,6 +124,9 @@ class GSTR1_DataField(Enum): NET_ISSUE = "net_issue" UPLOAD_STATUS = "upload_status" + ERROR_CD = "error_code" + ERROR_MSG = "error_message" + class GSTR1_ItemField(Enum): INDEX = "idx" @@ -194,6 +197,9 @@ class GovDataField(Enum): SUPECOM_52 = "clttx" SUPECOM_9_5 = "paytx" + ERROR_CD = "error_cd" + ERROR_MSG = "error_msg" + FLAG = "flag" diff --git a/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py b/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py index 7d1b0f1477..90eb7ff4fa 100644 --- a/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py +++ b/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py @@ -315,6 +315,12 @@ def convert_to_internal_data_format(self, input_data): GSTR1_DataField.CUST_NAME.value: self.guess_customer_name( customer_gstin ), + GSTR1_DataField.ERROR_CD.value: customer_data.get( + GovDataField.ERROR_CD.value + ), + GSTR1_DataField.ERROR_MSG.value: customer_data.get( + GovDataField.ERROR_MSG.value + ), } for invoice in customer_data.get(GovDataField.INVOICES.value): @@ -968,6 +974,12 @@ def convert_to_internal_data_format(self, input_data): GSTR1_DataField.CUST_NAME.value: self.guess_customer_name( customer_gstin ), + GSTR1_DataField.ERROR_CD.value: customer_data.get( + GovDataField.ERROR_CD.value + ), + GSTR1_DataField.ERROR_MSG.value: customer_data.get( + GovDataField.ERROR_MSG.value + ), }, ) self.update_totals( @@ -1859,7 +1871,7 @@ def map_document_types(self, doc_type, *args): } -def convert_to_internal_data_format(gov_data): +def convert_to_internal_data_format(gov_data, for_errors=False): """ Converts Gov data format to internal data format for all categories """ @@ -1873,7 +1885,16 @@ def convert_to_internal_data_format(gov_data): mapper_class().convert_to_internal_data_format(gov_data.get(category)) ) - return output + if not for_errors: + return output + + errors = [] + for category, data in output.items(): + for row in data.values(): + row["category"] = category + errors.append(row) + + return errors def get_category_wise_data( From 4d3556bfbe80810c0c5b87ae0dff649fdf154fcc Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 1 Oct 2024 20:22:41 +0530 Subject: [PATCH 50/68] fix: clear notifications for filing of gstr-1 --- .../doctype/gst_return_log/generate_gstr_1.py | 17 +---------------- .../doctype/gstr_1_beta/gstr_1_beta.js | 6 +----- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 027b4cb35b..3401eedd55 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -812,15 +812,6 @@ def process_proceed_to_file_gstr1(self): return response doc.db_set({"status": status_code_map.get(response.get("status_cd"))}) - if response.get("status_cd") != "P": - enqueue_notification( - self.return_period, - "proceed_to_file", - response.get("status_cd"), - self.gstin, - api.request_id, - ) - return return self.fetch_and_compare_summary(api, response) @@ -843,12 +834,6 @@ def fetch_and_compare_summary(self, api, response=None): if not differing_categories: self.db_set({"filing_status": "Ready to File"}) response["filing_status"] = "Ready to File" - enqueue_notification( - self.return_period, - "proceed_to_file", - response.get("status_cd"), - self.gstin, - ) else: self.db_set({"filing_status": "Not Filed"}) @@ -873,7 +858,7 @@ def file_gstr1(self, pan, otp): summary = self.get_json_for("authenticated_summary") api = GSTR1API(self) - response = api.file_gstr_1(self.return_period, pan, otp, summary) + response = api.file_gstr_1(self.return_period, summary, pan, otp) if response.error and response.error.error_cd == "RET09001": self.db_set({"filing_status": "Not Filed"}) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 0e9bfee5d3..11df9f5d65 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -439,6 +439,7 @@ class GSTR1 { const color = this.status === "Filed" ? "green" : "orange"; this.$wrapper.find(`[data-fieldname="filed_tab"]`).html(tab_name); + this.$wrapper.find(`[data-fieldname="error_tab"]`).html("â›” Error"); this.frm.page.set_indicator(this.status, color); } @@ -2467,8 +2468,6 @@ class GSTR1Action extends FileGSTR1Dialog { proceed_to_file() { const action = "proceed_to_file"; - - frappe.show_alert(__("Please wait while we proceed to file the data.")); this.perform_gstr1_action(action, r => { // already proceed to file if (r.message) this.handle_proceed_to_file_response(r.message); @@ -2478,9 +2477,6 @@ class GSTR1Action extends FileGSTR1Dialog { perform_gstr1_action(action, callback, additional_args = {}) { this.toggle_actions(false, action); - - // TODO: Why error tabs are hidden here. Only on successful upload - this.frm.gstr1.tabs.error_tab.hide(); const args = { ...this.defaults, ...additional_args, From 20bba96edf1d65b4ebd03efb6c04a3b68d6cff51 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 2 Oct 2024 12:15:21 +0530 Subject: [PATCH 51/68] fix: on click of gz download json file --- .../doctype/gst_return_log/gst_return_log.js | 18 ++++++++++++++++++ .../doctype/gst_return_log/gst_return_log.py | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.js b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.js index 0c7e31968a..72da829bca 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.js +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.js @@ -2,6 +2,24 @@ // For license information, please see license.txt frappe.ui.form.on("GST Return Log", { + onload(frm) { + const attachFields = ['unfiled', 'unfiled_summary', 'filed', 'filed_summary', 'upload_error', 'authenticated_summary', 'books', 'books_summary', 'reconcile', 'reconcile_summary']; + + attachFields.forEach(field => { + $(frm.fields_dict[field].wrapper).on('click', '.control-value a', function (e) { + e.preventDefault(); + + frm.call("get_data_for_gz", { file_field: field }).then(res => { + const args = { + cmd: "india_compliance.gst_india.doctype.gst_return_log.gst_return_log.download_file", + data: res.message, + file_name: `${field}.json` + }; + open_url_post(frappe.request.url, args); + }); + }); + }); + }, refresh(frm) { const [month_or_quarter, year] = india_compliance.get_month_year_from_period( frm.doc.return_period diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py index 8b0730aa65..61b7066b77 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py @@ -193,6 +193,21 @@ def get_unprocessed_action(self, action): if row.request_type == action and not row.status: return row + @frappe.whitelist() + def get_data_for_gz(self, file_field): + return self.get_json_for(file_field) + + +@frappe.whitelist() +def download_file(): + frappe.has_permission("GST Return Log", "read") + + data = frappe._dict(frappe.local.form_dict) + frappe.response["filename"] = data["file_name"] + frappe.response["filecontent"] = data["data"] + frappe.response["content_type"] = "application/json" + frappe.response["type"] = "download" + def process_gstr_1_returns_info(company, gstin, response): return_info = {} From 856f4277e463e46734b76b8ffdae178819c57b1b Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 2 Oct 2024 12:20:15 +0530 Subject: [PATCH 52/68] fix: read only gstr actions --- .../gst_india/doctype/gst_return_log/gst_return_log.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json index f09562d13a..e43a0084a2 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.json @@ -212,7 +212,8 @@ "fieldname": "actions", "fieldtype": "Table", "label": "Actions", - "options": "GSTR Action" + "options": "GSTR Action", + "read_only": 1 } ], "in_create": 1, @@ -223,7 +224,7 @@ "link_fieldname": "reference_docname" } ], - "modified": "2024-09-12 11:48:46.154973", + "modified": "2024-10-02 12:16:04.271962", "modified_by": "Administrator", "module": "GST India", "name": "GST Return Log", From 68a23efb76ceab558ae621e3a4f40061a0bec1c7 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 2 Oct 2024 13:35:43 +0530 Subject: [PATCH 53/68] fix: proper message for invalid otp and enable file button --- india_compliance/gst_india/api_classes/taxpayer_base.py | 1 + .../gst_india/doctype/gstr_1_beta/gstr_1_beta.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/api_classes/taxpayer_base.py b/india_compliance/gst_india/api_classes/taxpayer_base.py index 1b32dbb766..00756054fa 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_base.py +++ b/india_compliance/gst_india/api_classes/taxpayer_base.py @@ -115,6 +115,7 @@ class TaxpayerAuthenticate(BaseAPI): # "AUTH4034": "invalid_otp", # Invalid OTP "AUTH4038": "authorization_failed", # Session Expired "TEC4002": "invalid_public_key", + "RET13506": "OTP is either expired or incorrect", "RET00003": "Return Form already ready to be filed", # Actions performed on portal directly "RET09001": "Latest Summary is not available. Please generate summary and try again.", # Actions performed on portal directly } diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 11df9f5d65..601c766db7 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -2383,10 +2383,12 @@ class FileGSTR1Dialog { } handle_filing_response(response) { - if (response.error?.error_cd === "RET13509") { + if (response.error?.error_cd === "RET13506") { this.filing_dialog .get_field("otp") - .set_description(`

    Invalid OTP

    `); + .set_description(`

    OTP is either expired or incorrect.

    `); + + this.toggle_actions(true); return; } From e1abecaef1ecf34384cfd841f94e3b94079c445c Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 2 Oct 2024 15:20:11 +0530 Subject: [PATCH 54/68] fix: resolving linters --- .../gst_india/doctype/gst_return_log/generate_gstr_1.py | 2 +- india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 3401eedd55..93eaa8fe74 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -729,7 +729,7 @@ def upload_gstr1(self, json_data): keys = {category.value for category in GovJsonKey} if all(key not in json_data for key in keys): - frappe.throw("Nothing to upload") + frappe.throw(_("Nothing to upload")) # upload data after proceed to file self.db_set({"filing_status": "Not Filed"}) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 601c766db7..1bbd3f8d33 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -2620,7 +2620,7 @@ class GSTR1Action extends FileGSTR1Dialog { else if (in_progress == "proceed_to_file") in_progress = "upload"; frappe.show_alert({ - message: __(`Already ${in_progress}ing`), + message: __('Already ' + in_progress + 'ing'), indicator: "red", }); From 64da75f959281ab66da31656fec4aafa409bf43b Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 2 Oct 2024 15:31:25 +0530 Subject: [PATCH 55/68] fix: toggle upload action when no data to upload --- .../gst_india/doctype/gst_return_log/generate_gstr_1.py | 3 ++- .../gst_india/doctype/gstr_1_beta/gstr_1_beta.js | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 93eaa8fe74..27210de3af 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -729,7 +729,8 @@ def upload_gstr1(self, json_data): keys = {category.value for category in GovJsonKey} if all(key not in json_data for key in keys): - frappe.throw(_("Nothing to upload")) + frappe.msgprint(_("No data to upload"), indicator="red") + return # upload data after proceed to file self.db_set({"filing_status": "Not Filed"}) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 1bbd3f8d33..64d80a978f 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -2447,7 +2447,13 @@ class GSTR1Action extends FileGSTR1Dialog { const action = "upload"; frappe.show_alert(__("Uploading data to GSTN")); - this.perform_gstr1_action(action, () => this.fetch_status_with_retry(action)); + this.perform_gstr1_action(action, (response) => { + if(response._server_messages) { + this.toggle_actions(true); + return + } + this.fetch_status_with_retry(action) + }); } reset_gstr1_data() { From 0a72f6761dd988412aebb843fbbe8d247e7d47dc Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Mon, 7 Oct 2024 15:57:48 +0530 Subject: [PATCH 56/68] fix: go back to previous action button --- .../doctype/gstr_1_beta/gstr_1_beta.js | 39 ++++++++++++++++--- .../doctype/gstr_1_beta/gstr_1_beta.py | 11 ++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 64d80a978f..ddaadecea6 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -462,11 +462,17 @@ class GSTR1 { File: this.gstr1_action.file_gstr1_data, }; - let primary_button_label = "Generate"; - - if (this.status === "Not Filed") primary_button_label = "Upload"; - else if (this.status === "Uploaded") primary_button_label = "Proceed to File"; - else if (this.status == "Ready to File") primary_button_label = "File"; + let primary_button_label = { + "Not Filed": "Upload", + "Uploaded": "Proceed to File", + "Ready to File": "File", + }[this.status] || "Generate"; + + if (this.status === "Ready to File") { + this.frm.add_custom_button(__("Previous Action"), () => { + this.gstr1_action.previous_action_handler(); + }); + } this.frm.page.set_primary_action(__(primary_button_label), () => actions[primary_button_label].call(this.gstr1_action) @@ -2483,6 +2489,24 @@ class GSTR1Action extends FileGSTR1Dialog { }); } + previous_action_handler() { + if(this.is_request_in_progress()) return; + + const { company, company_gstin, month_or_quarter, year } = this.frm.doc; + const filters = { + "company": company, "company_gstin": company_gstin, "month_or_quarter": month_or_quarter, "year": year + }; + + frappe.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.update_filing_status", + args: { filters }, + callback: () => { + this.frm.doc.__gst_data.status = "Not Filed"; + this.frm.trigger("load_gstr1_data"); + } + }) + } + perform_gstr1_action(action, callback, additional_args = {}) { this.toggle_actions(false, action); const args = { @@ -2560,6 +2584,9 @@ class GSTR1Action extends FileGSTR1Dialog { this.frm.page.set_primary_action("File", () => this.frm.gstr1.gstr1_action.file_gstr1_data() ); + this.frm.add_custom_button(__("Previous Action"), () => { + this.frm.gstr1.gstr1_action.previous_action_handler(); + }); this.frm.page.set_indicator("Ready to File", "orange"); this.frm.gstr1.status = "Ready to File"; return; @@ -2634,7 +2661,7 @@ class GSTR1Action extends FileGSTR1Dialog { } toggle_actions(show, action) { - const actions = ["Upload", "Reset", "File"]; + const actions = ["Upload", "Reset", "File", "Previous%20Action"]; const btns = $(actions.map(action => `[data-label="${action}"]`).join(",")); if (show) { diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 98b0fb92ca..5dd8c74a1c 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -1,6 +1,7 @@ # Copyright (c) 2024, Resilient Tech and contributors # For license information, please see license.txt +import json from datetime import datetime import frappe @@ -215,6 +216,16 @@ def process_gstr1_request(month_or_quarter, year, company_gstin, action): return data +@frappe.whitelist() +def update_filing_status(filters): + frappe.has_permission("GST Return Log", "write", throw=True) + + filters = frappe._dict(json.loads(filters)) + log_name = f"GSTR1-{get_period(filters.month_or_quarter, filters.year)}-{filters.company_gstin}" + + frappe.db.set_value("GST Return Log", log_name, "filing_status", "Not Filed") + + ####### DATA ###################################################################################### From b4e80a3d1d6f83c393319ed9277b3172ac825d61 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Tue, 8 Oct 2024 13:54:24 +0530 Subject: [PATCH 57/68] fix: post filing Journal Entry for Sales RCM --- .../doctype/gstr_1_beta/gstr_1_beta.js | 55 ++++++++++++- .../doctype/gstr_1_beta/gstr_1_beta.py | 78 +++++++++++++++++++ 2 files changed, 129 insertions(+), 4 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index ddaadecea6..3327c686df 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -444,15 +444,19 @@ class GSTR1 { } render_form_actions() { - if (this.data && (!is_gstr1_api_enabled() || this.status == "Filed")) return; + if (this.data && !is_gstr1_api_enabled()) return; this.gstr1_action = new GSTR1Action(this.frm); // Custom Buttons - if (this.data) + if (this.data){ + if (this.status == "Filed") + return this.get_journal_entries() + this.frm.add_custom_button(__("Reset"), () => this.gstr1_action.reset_gstr1_data() ); + } // Primary Button const actions = { @@ -712,6 +716,22 @@ class GSTR1 { let element = $('[data-fieldname="data_section"]'); element.prepend(gst_liability_html); } + + get_journal_entries(){ + const { company, month_or_quarter, year } = this.frm.doc; + + frappe.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.get_general_entries", + args: {month_or_quarter, year, company}, + callback: (r) => { + if(r.message) return; + + this.frm.add_custom_button(__("Create Journal Entry"), () => + this.gstr1_action.make_journal_entry() + ); + } + }) + } } class TabManager { @@ -2454,7 +2474,7 @@ class GSTR1Action extends FileGSTR1Dialog { frappe.show_alert(__("Uploading data to GSTN")); this.perform_gstr1_action(action, (response) => { - if(response._server_messages) { + if (response._server_messages) { this.toggle_actions(true); return } @@ -2490,7 +2510,7 @@ class GSTR1Action extends FileGSTR1Dialog { } previous_action_handler() { - if(this.is_request_in_progress()) return; + if (this.is_request_in_progress()) return; const { company, company_gstin, month_or_quarter, year } = this.frm.doc; const filters = { @@ -2507,6 +2527,33 @@ class GSTR1Action extends FileGSTR1Dialog { }) } + make_journal_entry() { + const d = new frappe.ui.Dialog({ + title : "Create Journal Entry", + fields: [ + { + fieldname: "auto_submit", + fieldtype: "Check", + label: "Submit After Creation", + }, + ], + primary_action_label: "Create", + primary_action: async (values) => { + const { company, company_gstin, month_or_quarter, year } = this.frm.doc; + + frappe.call({ + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.make_journal_entry", + args: { company, company_gstin, month_or_quarter, year, auto_submit: values.auto_submit }, + callback: (r) => { + frappe.set_route("journal-entry", r.message); + d.hide(); + } + }) + } + }) + d.show(); + } + perform_gstr1_action(action, callback, additional_args = {}) { this.toggle_actions(false, action); const args = { diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 5dd8c74a1c..55f1e02857 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -226,6 +226,84 @@ def update_filing_status(filters): frappe.db.set_value("GST Return Log", log_name, "filing_status", "Not Filed") +@frappe.whitelist() +def get_general_entries(month_or_quarter, year, company): + from_date, to_date = get_gstr_1_from_and_to_date(month_or_quarter, year) + + journal_entry = frappe.qb.DocType("Journal Entry") + journal_entry_account = frappe.qb.DocType("Journal Entry Account") + + gst_accounts = get_gst_accounts_by_type(company, "Sales Reverse Charge") + if not gst_accounts: + return "No GST Accounts found" + + return ( + frappe.qb.from_(journal_entry) + .join(journal_entry_account) + .on(journal_entry.name == journal_entry_account.parent) + .select( + journal_entry_account.name, + ) + .where(journal_entry.posting_date.between(getdate(from_date), getdate(to_date))) + .where(journal_entry_account.account.in_(gst_accounts)) + .run() + ) + + +@frappe.whitelist() +def make_journal_entry(company, company_gstin, month_or_quarter, year, auto_submit): + frappe.has_permission("Journal Entry", "write", throw=True) + + from_date, to_date = get_gstr_1_from_and_to_date(month_or_quarter, year) + sales_invoice = frappe.qb.DocType("Sales Invoice") + sales_invoice_taxes = frappe.qb.DocType("Sales Taxes and Charges") + + data = ( + frappe.qb.from_(sales_invoice) + .join(sales_invoice_taxes) + .on(sales_invoice.name == sales_invoice_taxes.parent) + .select( + sales_invoice_taxes.account_head.as_("account"), + Sum(sales_invoice_taxes.tax_amount).as_("tax_amount"), + ) + .where(sales_invoice.is_reverse_charge == 1) + .where( + Date(sales_invoice.posting_date).between( + getdate(from_date), getdate(to_date) + ) + ) + .groupby(sales_invoice_taxes.account_head) + .run(as_dict=True) + ) + + journal_entry = frappe.get_doc( + { + "doctype": "Journal Entry", + "company": company, + "company_gstin": company_gstin, + "posting_date": get_last_day(to_date), + } + ) + + for tax in data: + journal_entry.append( + "accounts", + { + "account": tax.account, + "reference_type": "Sales Invoice", + "debit_in_account_currency": max(tax.tax_amount, 0), + "credit_in_account_currency": abs(min(tax.tax_amount, 0)), + }, + ) + + journal_entry.save() + + if auto_submit == "1": + journal_entry.submit() + + return journal_entry.name + + ####### DATA ###################################################################################### From db3d402e6fbb9e7dccdd102b3de9c5d7a24dda19 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 9 Oct 2024 10:12:53 +0530 Subject: [PATCH 58/68] fix: full data for doc_issue to be uploaded --- india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_export.py | 1 + 1 file changed, 1 insertion(+) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_export.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_export.py index dfb245fbe8..4e166cef60 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_export.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_export.py @@ -2062,6 +2062,7 @@ def download_gstr_1_json( if subcategory in { GSTR1_SubCategory.NIL_EXEMPT.value, GSTR1_SubCategory.HSN.value, + GSTR1_SubCategory.DOC_ISSUE.value, }: continue From 6efe43b98722669bec51d977c88614ffef1ffff0 Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Wed, 9 Oct 2024 17:03:17 +0530 Subject: [PATCH 59/68] fix: on click of attachment in gst return log download gz file --- .../doctype/gst_return_log/gst_return_log.js | 16 ++++++++-------- .../doctype/gst_return_log/gst_return_log.py | 10 ++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.js b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.js index 72da829bca..746edde706 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.js +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.js @@ -9,14 +9,14 @@ frappe.ui.form.on("GST Return Log", { $(frm.fields_dict[field].wrapper).on('click', '.control-value a', function (e) { e.preventDefault(); - frm.call("get_data_for_gz", { file_field: field }).then(res => { - const args = { - cmd: "india_compliance.gst_india.doctype.gst_return_log.gst_return_log.download_file", - data: res.message, - file_name: `${field}.json` - }; - open_url_post(frappe.request.url, args); - }); + const args = { + cmd: "india_compliance.gst_india.doctype.gst_return_log.gst_return_log.download_file", + file_field: field, + name: frm.doc.name, + doctype: frm.doc.doctype, + file_name: `${field}.gz` + }; + open_url_post(frappe.request.url, args); }); }); }, diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py index 61b7066b77..fa4c9a5f43 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py @@ -193,10 +193,6 @@ def get_unprocessed_action(self, action): if row.request_type == action and not row.status: return row - @frappe.whitelist() - def get_data_for_gz(self, file_field): - return self.get_json_for(file_field) - @frappe.whitelist() def download_file(): @@ -204,8 +200,10 @@ def download_file(): data = frappe._dict(frappe.local.form_dict) frappe.response["filename"] = data["file_name"] - frappe.response["filecontent"] = data["data"] - frappe.response["content_type"] = "application/json" + + file = get_file_doc(data["doctype"], data["name"], data["file_field"]) + frappe.response["filecontent"] = file.get_content(encodings=[]) + frappe.response["type"] = "download" From 140d4043164639f9b1d1570145caf9e12489056f Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Thu, 10 Oct 2024 13:38:10 +0530 Subject: [PATCH 60/68] fix: changes as per review for sales rcm --- .../doctype/gstr_1_beta/gstr_1_beta.js | 20 ++++---- .../doctype/gstr_1_beta/gstr_1_beta.py | 49 +++++++++++-------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 3327c686df..c98da4ac2d 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -444,14 +444,12 @@ class GSTR1 { } render_form_actions() { - if (this.data && !is_gstr1_api_enabled()) return; - this.gstr1_action = new GSTR1Action(this.frm); // Custom Buttons - if (this.data){ - if (this.status == "Filed") - return this.get_journal_entries() + if (this.data) { + if (this.status === "Filed") return this.check_for_journal_entry(); + if (!is_gstr1_api_enabled()) return; this.frm.add_custom_button(__("Reset"), () => this.gstr1_action.reset_gstr1_data() @@ -717,14 +715,14 @@ class GSTR1 { element.prepend(gst_liability_html); } - get_journal_entries(){ + check_for_journal_entry() { const { company, month_or_quarter, year } = this.frm.doc; frappe.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.get_general_entries", - args: {month_or_quarter, year, company}, + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.get_journal_entries", + args: { month_or_quarter, year, company }, callback: (r) => { - if(r.message) return; + if (r.message) return; this.frm.add_custom_button(__("Create Journal Entry"), () => this.gstr1_action.make_journal_entry() @@ -2125,7 +2123,6 @@ class ErrorTab extends TabManager { set_creation_time_string() { } refresh_data(data) { - console.log(data); data = data.error_report super.refresh_data(data, data, "Error Summary"); $(".dt-footer").remove(); @@ -2529,7 +2526,7 @@ class GSTR1Action extends FileGSTR1Dialog { make_journal_entry() { const d = new frappe.ui.Dialog({ - title : "Create Journal Entry", + title: "Create Journal Entry", fields: [ { fieldname: "auto_submit", @@ -2545,6 +2542,7 @@ class GSTR1Action extends FileGSTR1Dialog { method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.make_journal_entry", args: { company, company_gstin, month_or_quarter, year, auto_submit: values.auto_submit }, callback: (r) => { + frappe.open_in_new_tab = true; frappe.set_route("journal-entry", r.message); d.hide(); } diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 55f1e02857..eb747ca0f1 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -7,7 +7,9 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.query_builder.functions import Date, Sum +from frappe.query_builder import Case +from frappe.query_builder.custom import ConstantColumn +from frappe.query_builder.functions import Date, IfNull, Sum from frappe.utils import get_last_day, getdate from india_compliance.gst_india.api_classes.taxpayer_base import ( @@ -227,25 +229,31 @@ def update_filing_status(filters): @frappe.whitelist() -def get_general_entries(month_or_quarter, year, company): +def get_journal_entries(month_or_quarter, year, company): + frappe.has_permission("Journal Entry", "read", throw=True) + from_date, to_date = get_gstr_1_from_and_to_date(month_or_quarter, year) journal_entry = frappe.qb.DocType("Journal Entry") journal_entry_account = frappe.qb.DocType("Journal Entry Account") - gst_accounts = get_gst_accounts_by_type(company, "Sales Reverse Charge") + gst_accounts = list( + get_gst_accounts_by_type(company, "Sales Reverse Charge", throw=False).values() + ) + if not gst_accounts: - return "No GST Accounts found" + return True - return ( + return bool( frappe.qb.from_(journal_entry) .join(journal_entry_account) .on(journal_entry.name == journal_entry_account.parent) .select( - journal_entry_account.name, + journal_entry.name, ) .where(journal_entry.posting_date.between(getdate(from_date), getdate(to_date))) - .where(journal_entry_account.account.in_(gst_accounts)) + .where(journal_entry_account.account.isin(gst_accounts)) + .where(journal_entry.docstatus == 1) .run() ) @@ -264,7 +272,18 @@ def make_journal_entry(company, company_gstin, month_or_quarter, year, auto_subm .on(sales_invoice.name == sales_invoice_taxes.parent) .select( sales_invoice_taxes.account_head.as_("account"), - Sum(sales_invoice_taxes.tax_amount).as_("tax_amount"), + ConstantColumn("Sales Invoice").as_("reference_type"), + Case() + .when( + sales_invoice_taxes.tax_amount > 0, Sum(sales_invoice_taxes.tax_amount) + ) + .as_("debit_in_account_currency"), + Case() + .when( + sales_invoice_taxes.tax_amount < 0, + Sum(sales_invoice_taxes.tax_amount * (-1)), + ) + .as_("credit_in_account_currency"), ) .where(sales_invoice.is_reverse_charge == 1) .where( @@ -272,6 +291,7 @@ def make_journal_entry(company, company_gstin, month_or_quarter, year, auto_subm getdate(from_date), getdate(to_date) ) ) + .where(IfNull(sales_invoice_taxes.gst_tax_type, "") != "") .groupby(sales_invoice_taxes.account_head) .run(as_dict=True) ) @@ -284,18 +304,7 @@ def make_journal_entry(company, company_gstin, month_or_quarter, year, auto_subm "posting_date": get_last_day(to_date), } ) - - for tax in data: - journal_entry.append( - "accounts", - { - "account": tax.account, - "reference_type": "Sales Invoice", - "debit_in_account_currency": max(tax.tax_amount, 0), - "credit_in_account_currency": abs(min(tax.tax_amount, 0)), - }, - ) - + journal_entry.extend("accounts", data) journal_entry.save() if auto_submit == "1": From 9bc6e4b6d14a1706790a6fe83e79da4728aa478d Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 14 Oct 2024 09:43:28 +0530 Subject: [PATCH 61/68] fix: minor fixes as per review --- .../doctype/gst_return_log/generate_gstr_1.py | 23 ++++++++++++++----- .../doctype/gstr_1_beta/gstr_1_beta.js | 17 +++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index 27210de3af..c71eeda6e7 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -946,6 +946,8 @@ def get_differing_categories(mapped_summary, gov_summary): }, } + IGNORED_CATEGORIES = {"Net Liability from Amendments"} + gov_summary = {row["description"]: row for row in gov_summary if row["indent"] == 0} compared_categories = set() differing_categories = set() @@ -956,6 +958,9 @@ def get_differing_categories(mapped_summary, gov_summary): continue category = row["description"] + if category in IGNORED_CATEGORIES: + continue + compared_categories.add(category) gov_entry = gov_summary.get(category, {}) @@ -968,13 +973,19 @@ def get_differing_categories(mapped_summary, gov_summary): for row in gov_summary.values(): # Amendments are with indent 1. Hence auto-skipped - if row["description"] not in compared_categories: - keys_to_compare = CATEGORY_KEYS.get(row["description"], KEYS_TO_COMPARE) + category = row["description"] + if category in IGNORED_CATEGORIES: + continue - for key in keys_to_compare: - if row.get(key, 0) != 0: - differing_categories.add(row["description"]) - break + if category in compared_categories: + continue + + keys_to_compare = CATEGORY_KEYS.get(row["description"], KEYS_TO_COMPARE) + + for key in keys_to_compare: + if row.get(key, 0) != 0: + differing_categories.add(row["description"]) + break return differing_categories diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index c98da4ac2d..06a3c83532 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -2295,7 +2295,7 @@ class FileGSTR1Dialog { this.filing_dialog.set_df_property("otp", "read_only", 0); this.filing_dialog.set_df_property("acknowledged", "read_only", 0); - this.update_actions(pan); + this.update_actions_for_filing(pan); }, }); @@ -2380,7 +2380,7 @@ class FileGSTR1Dialog { `; } - update_actions(pan) { + update_actions_for_filing(pan) { this.filing_dialog.set_primary_action("File", () => { if (!this.filing_dialog.get_value("acknowledged")) { frappe.msgprint( @@ -2429,6 +2429,7 @@ class FileGSTR1Dialog { ) ); } + if (response.ack_num) { this.frm.taxpayer_api_call("generate_gstr1").then(r => { this.frm.doc.__gst_data = r.message; @@ -2615,17 +2616,17 @@ class GSTR1Action extends FileGSTR1Dialog { } handle_proceed_to_file_response(response) { - // only show filed tab - ["books", "unfiled", "reconcile"].map(tab => - this.frm.gstr1.tabs[`${tab}_tab`].hide() - ); - this.frm.gstr1.tabs.filed_tab.set_active(); - const filing_status = response.filing_status; if (!filing_status) return; // summary matched if (filing_status == "Ready to File") { + // only show filed tab + ["books", "unfiled", "reconcile"].map(tab => + this.frm.gstr1.tabs[`${tab}_tab`].hide() + ); + this.frm.gstr1.tabs.filed_tab.set_active(); + this.frm.page.set_primary_action("File", () => this.frm.gstr1.gstr1_action.file_gstr1_data() ); From 03491f788ce0ab2770911936a7587c3eed28cf2a Mon Sep 17 00:00:00 2001 From: Sanket322 Date: Mon, 14 Oct 2024 12:43:43 +0530 Subject: [PATCH 62/68] fix: show alert only when required and change in button --- .../gst_india/doctype/gstr_1_beta/gstr_1_beta.js | 6 +++--- .../gst_india/doctype/gstr_1_beta/gstr_1_beta.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 06a3c83532..dd58873b7e 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -2418,8 +2418,8 @@ class FileGSTR1Dialog { this.filing_dialog.hide(); if (response.error?.error_cd === "RET09001") { - this.frm.page.set_primary_action("Proceed to File", () => - this.proceed_to_file() + this.frm.page.set_primary_action("Upload", () => + this.upload_gstr1_data() ); this.frm.page.set_indicator("Not Filed", "orange"); this.frm.gstr1.status = "Not Filed"; @@ -2431,7 +2431,7 @@ class FileGSTR1Dialog { } if (response.ack_num) { - this.frm.taxpayer_api_call("generate_gstr1").then(r => { + this.frm.taxpayer_api_call("generate_gstr1", { display_alert: false }).then(r => { this.frm.doc.__gst_data = r.message; this.frm.trigger("load_gstr1_data"); }); diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index eb747ca0f1..37151d84e5 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -54,7 +54,7 @@ def mark_as_filed(self): @frappe.whitelist() @otp_handler - def generate_gstr1(self, sync_for=None, recompute_books=False): + def generate_gstr1(self, sync_for=None, recompute_books=False, display_alert=True): period = get_period(self.month_or_quarter, self.year) # get gstr1 log @@ -117,7 +117,8 @@ def generate_gstr1(self, sync_for=None, recompute_books=False): # generate gstr1 gstr1_log.update_status("In Progress") frappe.enqueue(self._generate_gstr1, queue="short") - frappe.msgprint(_("GSTR-1 is being prepared"), alert=True) + if display_alert: + frappe.msgprint(_("GSTR-1 is being prepared"), alert=True) def _generate_gstr1(self): """ From cad847505257d14b7d12c40f01d892728580c7bb Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 4 Nov 2024 12:47:29 +0530 Subject: [PATCH 63/68] refactor: move evc otp function to utils --- india_compliance/gst_india/utils/gstr/generate_evc.py | 9 --------- india_compliance/gst_india/utils/gstr_utils.py | 6 ++++++ india_compliance/public/js/utils.js | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 india_compliance/gst_india/utils/gstr/generate_evc.py diff --git a/india_compliance/gst_india/utils/gstr/generate_evc.py b/india_compliance/gst_india/utils/gstr/generate_evc.py deleted file mode 100644 index 308bdcf0a7..0000000000 --- a/india_compliance/gst_india/utils/gstr/generate_evc.py +++ /dev/null @@ -1,9 +0,0 @@ -import frappe - -from india_compliance.gst_india.api_classes.taxpayer_base import TaxpayerBaseAPI - - -@frappe.whitelist() -def generate_evc_otp(company_gstin, pan, request_type): - frappe.has_permission("GSTR-1 Beta", "write", throw=True) - return TaxpayerBaseAPI(company_gstin).initiate_otp_for_evc(pan, request_type) diff --git a/india_compliance/gst_india/utils/gstr_utils.py b/india_compliance/gst_india/utils/gstr_utils.py index f0d68345f3..67ae50e802 100644 --- a/india_compliance/gst_india/utils/gstr_utils.py +++ b/india_compliance/gst_india/utils/gstr_utils.py @@ -42,6 +42,12 @@ def authenticate_otp(company_gstin, otp): return api.process_response(response) +@frappe.whitelist() +def generate_evc_otp(company_gstin, pan, request_type): + frappe.has_permission("GSTR-1 Beta", "write", throw=True) + return TaxpayerBaseAPI(company_gstin).initiate_otp_for_evc(pan, request_type) + + def download_queued_request(): queued_requests = frappe.get_all( "GSTR Import Log", diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 49e6c15e1f..1f7a75619b 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -259,7 +259,7 @@ Object.assign(india_compliance, { generate_evc_otp(company_gstin, pan, request_type) { return frappe.call({ - method: "india_compliance.gst_india.utils.gstr.generate_evc.generate_evc_otp", + method: "india_compliance.gst_india.utils.gstr_utils.generate_evc_otp", args: { company_gstin: company_gstin, pan: pan, From 900c09b044f7d72caeee453901b4c036cc4f5ba8 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 4 Nov 2024 12:50:08 +0530 Subject: [PATCH 64/68] refactor: move function to gst utils --- india_compliance/public/js/gst_api_handler.js | 11 +++++++++++ india_compliance/public/js/utils.js | 11 ----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/india_compliance/public/js/gst_api_handler.js b/india_compliance/public/js/gst_api_handler.js index 207fd061e6..47a7e2eb9a 100644 --- a/india_compliance/public/js/gst_api_handler.js +++ b/india_compliance/public/js/gst_api_handler.js @@ -77,6 +77,17 @@ Object.assign(india_compliance, { return true; } }, + + generate_evc_otp(company_gstin, pan, request_type) { + return frappe.call({ + method: "india_compliance.gst_india.utils.gstr_utils.generate_evc_otp", + args: { + company_gstin: company_gstin, + pan: pan, + request_type: request_type, + }, + }); + }, }); class IndiaComplianceForm extends frappe.ui.form.Form { diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 1f7a75619b..171ca02e2a 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -257,17 +257,6 @@ Object.assign(india_compliance, { } }, - generate_evc_otp(company_gstin, pan, request_type) { - return frappe.call({ - method: "india_compliance.gst_india.utils.gstr_utils.generate_evc_otp", - args: { - company_gstin: company_gstin, - pan: pan, - request_type: request_type, - }, - }); - }, - guess_gst_category(gstin, country) { if (!gstin) { if (country && country !== "India") return "Overseas"; From 44bc26da2b23471414f70f5011bf1c61a5c43c7c Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 4 Nov 2024 14:40:05 +0530 Subject: [PATCH 65/68] fix: error codes and messages for other categories --- .../gst_india/utils/gstr_1/gstr_1_json_map.py | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py b/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py index 90eb7ff4fa..4dd97aa3a7 100644 --- a/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py +++ b/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py @@ -472,6 +472,12 @@ def convert_to_internal_data_format(self, input_data): default_invoice_data = { GSTR1_DataField.POS.value: pos, GSTR1_DataField.DOC_TYPE.value: self.DOCUMENT_CATEGORY, + GSTR1_DataField.ERROR_CD.value: pos_data.get( + GovDataField.ERROR_CD.value + ), + GSTR1_DataField.ERROR_MSG.value: pos_data.get( + GovDataField.ERROR_MSG.value + ), } for invoice in pos_data.get(GovDataField.INVOICES.value): @@ -606,6 +612,12 @@ def convert_to_internal_data_format(self, input_data): default_invoice_data = { GSTR1_DataField.DOC_TYPE.value: document_type, + GSTR1_DataField.ERROR_CD.value: export_category.get( + GovDataField.ERROR_CD.value + ), + GSTR1_DataField.ERROR_MSG.value: export_category.get( + GovDataField.ERROR_MSG.value + ), } for invoice in export_category.get(GovDataField.INVOICES.value): @@ -700,6 +712,8 @@ class B2CS(GovDataMapper): GovDataField.CGST.value: GSTR1_DataField.CGST.value, GovDataField.SGST.value: GSTR1_DataField.SGST.value, GovDataField.CESS.value: GSTR1_DataField.CESS.value, + GovDataField.ERROR_CD.value: GSTR1_DataField.ERROR_CD.value, + GovDataField.ERROR_MSG.value: GSTR1_DataField.ERROR_MSG.value, } def __init__(self): @@ -810,8 +824,15 @@ def __init__(self): def convert_to_internal_data_format(self, input_data): output = {} + default_data = { + GSTR1_DataField.ERROR_CD.value: input_data.get(GovDataField.ERROR_CD.value), + GSTR1_DataField.ERROR_MSG.value: input_data.get( + GovDataField.ERROR_MSG.value + ), + } + for invoice in input_data[GovDataField.INVOICES.value]: - invoice_data = self.format_data(invoice) + invoice_data = self.format_data(invoice, default_data) if not invoice_data: continue @@ -1109,6 +1130,8 @@ class CDNUR(GovDataMapper): GovDataField.TAXABLE_VALUE.value: GSTR1_ItemField.TAXABLE_VALUE.value, GovDataField.IGST.value: GSTR1_ItemField.IGST.value, GovDataField.CESS.value: GSTR1_ItemField.CESS.value, + GovDataField.ERROR_CD.value: GSTR1_DataField.ERROR_CD.value, + GovDataField.ERROR_MSG.value: GSTR1_DataField.ERROR_MSG.value, } DOCUMENT_TYPES = { "C": "Credit Note", @@ -1251,6 +1274,13 @@ def __init__(self): def convert_to_internal_data_format(self, input_data): output = {} + default_data = { + GSTR1_DataField.ERROR_CD.value: input_data.get(GovDataField.ERROR_CD.value), + GSTR1_DataField.ERROR_MSG.value: input_data.get( + GovDataField.ERROR_MSG.value + ), + } + for invoice in input_data[GovDataField.HSN_DATA.value]: output[ " - ".join( @@ -1260,7 +1290,7 @@ def convert_to_internal_data_format(self, input_data): str(flt(invoice.get(GovDataField.TAX_RATE.value))), ) ) - ] = self.format_data(invoice) + ] = self.format_data(invoice, default_data) return {self.SUBCATEGORY: output} @@ -1357,6 +1387,8 @@ class AT(GovDataMapper): GovDataField.CGST.value: GSTR1_DataField.CGST.value, GovDataField.SGST.value: GSTR1_DataField.SGST.value, GovDataField.CESS.value: GSTR1_DataField.CESS.value, + GovDataField.ERROR_CD.value: GSTR1_DataField.ERROR_CD.value, + GovDataField.ERROR_MSG.value: GSTR1_DataField.ERROR_MSG.value, } DEFAULT_ITEM_AMOUNTS = { GSTR1_DataField.IGST.value: 0, From a1384b1b475b370f2a5d155bb54dbf2022940041 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 4 Nov 2024 14:40:32 +0530 Subject: [PATCH 66/68] fix: minor fixes at the time of generating gstr-1 --- .../doctype/gst_return_log/generate_gstr_1.py | 21 +++++++++---------- .../doctype/gstr_1_beta/gstr_1_beta.js | 18 ++++++++-------- .../doctype/gstr_1_beta/gstr_1_beta.py | 18 +++++++++------- .../doctype/gstr_1_beta/gstr_1_export.py | 2 +- 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py index c71eeda6e7..65140f63d2 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py +++ b/india_compliance/gst_india/doctype/gst_return_log/generate_gstr_1.py @@ -185,9 +185,11 @@ def default_subcategory_summary(self, subcategory): @staticmethod def count_doc_issue_summary(summary_row, data_row): - summary_row["no_of_records"] += data_row.get( - GSTR1_DataField.TOTAL_COUNT.value, 0 - ) - data_row.get(GSTR1_DataField.CANCELLED_COUNT.value, 0) + summary_row["no_of_records"] += ( + data_row.get(GSTR1_DataField.TOTAL_COUNT.value, 0) + - data_row.get(GSTR1_DataField.CANCELLED_COUNT.value, 0) + - data_row.get(GSTR1_DataField.DRAFT_COUNT.value, 0) + ) @staticmethod def count_hsn_summary(summary_row): @@ -777,8 +779,12 @@ def process_upload_gstr1(self): if status_cd == "P": self.db_set({"filing_status": "Uploaded"}) - self.update_json_for("unfiled_summary", self.get_json_for("books_summary")) + self.update_json_for("unfiled", self.get_json_for("books")) + self.update_json_for("unfiled_summary", self.get_json_for("books_summary")) + + self.update_json_for("reconcile", {}) + self.update_json_for("reconcile_summary", {}) return response @@ -1065,10 +1071,3 @@ def create_notification(return_period, request_type, status_cd, gstin, request_i } ) notification.insert() - - -def check_return_status(self): - # Cron JOB - # check for logs with refeerence number. - # for each reference, try processing it. - pass diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index dd58873b7e..faf4de298a 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -471,8 +471,8 @@ class GSTR1 { }[this.status] || "Generate"; if (this.status === "Ready to File") { - this.frm.add_custom_button(__("Previous Action"), () => { - this.gstr1_action.previous_action_handler(); + this.frm.add_custom_button(__("Mark as Unfiled"), () => { + this.gstr1_action.mark_as_unfiled(); }); } @@ -1810,7 +1810,7 @@ class FiledTab extends GSTR1_TabManager { const doc = me.instance.frm.doc; frappe.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_export.download_gstr_1_json", + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_export.get_gstr_1_json", args: { company_gstin: doc.company_gstin, year: doc.year, @@ -2431,7 +2431,7 @@ class FileGSTR1Dialog { } if (response.ack_num) { - this.frm.taxpayer_api_call("generate_gstr1", { display_alert: false }).then(r => { + this.frm.taxpayer_api_call("generate_gstr1", { message: "Verifying filed GSTR-1" }).then(r => { this.frm.doc.__gst_data = r.message; this.frm.trigger("load_gstr1_data"); }); @@ -2507,7 +2507,7 @@ class GSTR1Action extends FileGSTR1Dialog { }); } - previous_action_handler() { + mark_as_unfiled() { if (this.is_request_in_progress()) return; const { company, company_gstin, month_or_quarter, year } = this.frm.doc; @@ -2516,7 +2516,7 @@ class GSTR1Action extends FileGSTR1Dialog { }; frappe.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.update_filing_status", + method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.mark_as_unfiled", args: { filters }, callback: () => { this.frm.doc.__gst_data.status = "Not Filed"; @@ -2630,8 +2630,8 @@ class GSTR1Action extends FileGSTR1Dialog { this.frm.page.set_primary_action("File", () => this.frm.gstr1.gstr1_action.file_gstr1_data() ); - this.frm.add_custom_button(__("Previous Action"), () => { - this.frm.gstr1.gstr1_action.previous_action_handler(); + this.frm.add_custom_button(__("Mark as Unfiled"), () => { + this.frm.gstr1.gstr1_action.mark_as_unfiled(); }); this.frm.page.set_indicator("Ready to File", "orange"); this.frm.gstr1.status = "Ready to File"; @@ -2707,7 +2707,7 @@ class GSTR1Action extends FileGSTR1Dialog { } toggle_actions(show, action) { - const actions = ["Upload", "Reset", "File", "Previous%20Action"]; + const actions = ["Upload", "Reset", "File", "Mark%20as%20Unfiled"]; const btns = $(actions.map(action => `[data-label="${action}"]`).join(",")); if (show) { diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py index 37151d84e5..fe4cf447a1 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.py @@ -54,7 +54,7 @@ def mark_as_filed(self): @frappe.whitelist() @otp_handler - def generate_gstr1(self, sync_for=None, recompute_books=False, display_alert=True): + def generate_gstr1(self, sync_for=None, recompute_books=False, message=None): period = get_period(self.month_or_quarter, self.year) # get gstr1 log @@ -117,8 +117,11 @@ def generate_gstr1(self, sync_for=None, recompute_books=False, display_alert=Tru # generate gstr1 gstr1_log.update_status("In Progress") frappe.enqueue(self._generate_gstr1, queue="short") - if display_alert: - frappe.msgprint(_("GSTR-1 is being prepared"), alert=True) + + if not message: + message = "GSTR-1 is being prepared" + + frappe.msgprint(_(message), alert=True) def _generate_gstr1(self): """ @@ -180,10 +183,10 @@ def handle_gstr1_action(action, month_or_quarter, year, company_gstin, **kwargs) if action == "upload_gstr1": from india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_export import ( - download_gstr_1_json, + get_gstr_1_json, ) - data = download_gstr_1_json( + data = get_gstr_1_json( company_gstin, year, month_or_quarter, @@ -203,8 +206,7 @@ def process_gstr1_request(month_or_quarter, year, company_gstin, action): ) method_name = f"process_{action}_gstr1" - method = getattr(gstr_1_log, method_name) - data = method() + data = getattr(gstr_1_log, method_name)() if not data: data = {} @@ -220,7 +222,7 @@ def process_gstr1_request(month_or_quarter, year, company_gstin, action): @frappe.whitelist() -def update_filing_status(filters): +def mark_as_unfiled(filters): frappe.has_permission("GST Return Log", "write", throw=True) filters = frappe._dict(json.loads(filters)) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_export.py b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_export.py index 4e166cef60..17ce7af2d9 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_export.py +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_export.py @@ -2037,7 +2037,7 @@ def download_reconcile_as_excel(company_gstin, month_or_quarter, year): @frappe.whitelist() -def download_gstr_1_json( +def get_gstr_1_json( company_gstin, year, month_or_quarter, From 6303a4a6746168bcabd2d4131b04d3b1761c5c68 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 4 Nov 2024 15:07:19 +0530 Subject: [PATCH 67/68] fix: update default data only if it exists --- india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py b/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py index 4dd97aa3a7..e552a0d6c9 100644 --- a/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py +++ b/india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py @@ -98,7 +98,11 @@ def format_data( output = {} if default_data: - output.update(default_data) + for key, value in default_data.items(): + if not (value or value == 0): + continue + + output[key] = value key_mapping = self.KEY_MAPPING.copy() From 29f5f5314eb305c694b130035876beca1f8cb387 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 4 Nov 2024 16:28:50 +0530 Subject: [PATCH 68/68] fix: changes as per review --- .../doctype/gstr_1_beta/gstr_1_beta.js | 72 ++++++++----------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index faf4de298a..e288419a8a 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -257,7 +257,7 @@ class GSTR1 { init(frm) { this.frm = frm; - this.data = frm.doc._data; + this.data = null; this.filters = []; this.$wrapper = frm.fields_dict.tabs_html.$wrapper; } @@ -448,7 +448,7 @@ class GSTR1 { // Custom Buttons if (this.data) { - if (this.status === "Filed") return this.check_for_journal_entry(); + if (this.status === "Filed") return; if (!is_gstr1_api_enabled()) return; this.frm.add_custom_button(__("Reset"), () => @@ -715,20 +715,10 @@ class GSTR1 { element.prepend(gst_liability_html); } - check_for_journal_entry() { - const { company, month_or_quarter, year } = this.frm.doc; - - frappe.call({ - method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.get_journal_entries", - args: { month_or_quarter, year, company }, - callback: (r) => { - if (r.message) return; - - this.frm.add_custom_button(__("Create Journal Entry"), () => - this.gstr1_action.make_journal_entry() - ); - } - }) + show_suggested_jv_dialog() { + // Get JV table + // show in dialog as a confirm dialog + // on Create JV btn => create JV } } @@ -1871,7 +1861,7 @@ class FiledTab extends GSTR1_TabManager { render_empty_state(this.instance.frm); this.instance.frm .taxpayer_api_call("mark_as_filed") - .then(() => this.instance.frm.trigger("load_gstr1_data")); + .then(() => this.instance.frm.trigger("load_gstr1_data") && this.instance.show_suggested_jv_dialog()); } // COLUMNS @@ -2272,7 +2262,7 @@ class FileGSTR1Dialog { read_only: 1, }, { - label: "Once you file your return you will not be able to undo the action", + label: "I confirm that this GSTR-1 filing cannot be undone and that all details are correct to the best of my knowledge.", fieldname: "acknowledged", fieldtype: "Check", default: 0, @@ -2293,7 +2283,10 @@ class FileGSTR1Dialog { // show otp field this.filing_dialog.set_df_property("otp", "read_only", 0); + this.filing_dialog.set_df_property("otp", "reqd", 1); + this.filing_dialog.set_df_property("acknowledged", "read_only", 0); + this.filing_dialog.set_df_property("acknowledged", "reqd", 1); this.update_actions_for_filing(pan); }, @@ -2322,16 +2315,16 @@ class FileGSTR1Dialog { callback: r => { if (!r.message) return; const { amended_liability, non_amended_liability } = r.message; - const amendment_table_html = this.generate_liability_table( + const liability_html = this.generate_liability_table( amended_liability, non_amended_liability ); const field = this.filing_dialog.get_field("liability_breakup_html"); - if (!amendment_table_html) return; + if (!liability_html) return; field.toggle(true); - field.df.options = amendment_table_html; + field.df.options = liability_html; this.filing_dialog.refresh(); }, }); @@ -2382,20 +2375,10 @@ class FileGSTR1Dialog { update_actions_for_filing(pan) { this.filing_dialog.set_primary_action("File", () => { - if (!this.filing_dialog.get_value("acknowledged")) { - frappe.msgprint( - __("Please acknowledge that you can not undo this action.") - ); - return; - } - this.perform_gstr1_action( "file", r => this.handle_filing_response(r.message), - { - pan: pan, - otp: this.filing_dialog.get_value("otp"), - } + { pan: pan, otp: this.filing_dialog.get_value("otp") } ); }); @@ -2434,6 +2417,7 @@ class FileGSTR1Dialog { this.frm.taxpayer_api_call("generate_gstr1", { message: "Verifying filed GSTR-1" }).then(r => { this.frm.doc.__gst_data = r.message; this.frm.trigger("load_gstr1_data"); + this.frm.gstr1.show_suggested_jv_dialog(); }); } } @@ -2472,10 +2456,12 @@ class GSTR1Action extends FileGSTR1Dialog { frappe.show_alert(__("Uploading data to GSTN")); this.perform_gstr1_action(action, (response) => { - if (response._server_messages) { + // No data to upload + if (response._server_messages && response._server_messages.length) { this.toggle_actions(true); return } + this.fetch_status_with_retry(action) }); } @@ -2519,13 +2505,14 @@ class GSTR1Action extends FileGSTR1Dialog { method: "india_compliance.gst_india.doctype.gstr_1_beta.gstr_1_beta.mark_as_unfiled", args: { filters }, callback: () => { - this.frm.doc.__gst_data.status = "Not Filed"; - this.frm.trigger("load_gstr1_data"); + this.frm.gstr1.status = "Not Filed"; + this.frm.refresh(); } }) } make_journal_entry() { + // TODO: Refactor const d = new frappe.ui.Dialog({ title: "Create Journal Entry", fields: [ @@ -2580,10 +2567,11 @@ class GSTR1Action extends FileGSTR1Dialog { if (message.status_cd === "IP" && retries < retry_intervals.length) return this.fetch_status_with_retry(action, retries + 1); + // Not IP + if (action == "upload") { if (message.status_cd == "P") return this.proceed_to_file(); else if (message.status_cd == "PE") this.show_errors(message); - // TODO: Highlight error tab } this.toggle_actions(true); @@ -2627,14 +2615,8 @@ class GSTR1Action extends FileGSTR1Dialog { ); this.frm.gstr1.tabs.filed_tab.set_active(); - this.frm.page.set_primary_action("File", () => - this.frm.gstr1.gstr1_action.file_gstr1_data() - ); - this.frm.add_custom_button(__("Mark as Unfiled"), () => { - this.frm.gstr1.gstr1_action.mark_as_unfiled(); - }); - this.frm.page.set_indicator("Ready to File", "orange"); this.frm.gstr1.status = "Ready to File"; + this.frm.refresh(); return; } @@ -2813,10 +2795,12 @@ function render_empty_state(frm) { if ($(".gst-ledger-difference").length) { $(".gst-ledger-difference").remove(); } - if (frm.doc.__gst_data) { + + if (frm.gstr1?.data) { frm.gstr1.data = null; frm.gstr1.status = null; } + frm.doc.__gst_data = null; frm.refresh(); }