From d8a3e622f2e0042dea2807058784bf04d8c87ac4 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 13 Jun 2024 18:21:37 +0530 Subject: [PATCH 01/41] fix: DoesNotExistError while processing return info (cherry picked from commit c62d33bd8ef23e568e4b1bc9c8b962bd3167414f) --- india_compliance/gst_india/doctype/gstr_1_log/gstr_1_log.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_1_log/gstr_1_log.py b/india_compliance/gst_india/doctype/gstr_1_log/gstr_1_log.py index 069dfb83f..fd70ad355 100644 --- a/india_compliance/gst_india/doctype/gstr_1_log/gstr_1_log.py +++ b/india_compliance/gst_india/doctype/gstr_1_log/gstr_1_log.py @@ -850,8 +850,9 @@ def process_gstr_1_returns_info(company, gstin, response): ) # update gstr-1 filed upto - gstin_doc = frappe.get_doc("GSTIN", gstin) - if not gstin_doc: + if frappe.db.exists("GSTIN", gstin): + gstin_doc = frappe.get_doc("GSTIN", gstin) + else: gstin_doc = frappe.new_doc("GSTIN", gstin=gstin, status="Active") def _update_gstr_1_filed_upto(filing_date): From 16b8f2923037ae6d0576ea7b867b0e8d5cc8b589 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 13 Jun 2024 19:02:00 +0530 Subject: [PATCH 02/41] fix: show message for missing creds while generating gstr-1 (cherry picked from commit 50cc4f1dd15cba8cafa2ec8c290c3e5ff408d2bc) --- .../doctype/gstr_1_beta/gstr_1_beta.js | 4 +++ .../doctype/gstr_1_log/gstr_1_log.py | 34 ++++++++++++++----- 2 files changed, 30 insertions(+), 8 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 5b2a81a9e..83d0972d9 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 @@ -141,6 +141,10 @@ frappe.ui.form.on(DOCTYPE, { ); }); + frappe.realtime.on("show_message", message => { + frappe.msgprint(message); + }); + frappe.realtime.on("gstr1_generation_failed", message => { const { error, filters } = message; let alert = `GSTR-1 Generation Failed for ${filters.company_gstin} - ${filters.month_or_quarter} - ${filters.year}.

${error}`; diff --git a/india_compliance/gst_india/doctype/gstr_1_log/gstr_1_log.py b/india_compliance/gst_india/doctype/gstr_1_log/gstr_1_log.py index fd70ad355..f29d6271e 100644 --- a/india_compliance/gst_india/doctype/gstr_1_log/gstr_1_log.py +++ b/india_compliance/gst_india/doctype/gstr_1_log/gstr_1_log.py @@ -5,7 +5,7 @@ from datetime import datetime import frappe -from frappe import unscrub +from frappe import _, unscrub from frappe.model.document import Document from frappe.utils import flt, get_datetime, get_datetime_str, get_last_day, getdate @@ -503,7 +503,7 @@ def generate_gstr1_data(self, filters, callback=None): data = {} # APIs Disabled - if not self.is_gstr1_api_enabled(): + if not self.is_gstr1_api_enabled(warn_for_missing_credentials=True): return self.generate_only_books_data(data, filters, callback) # APIs Enabled @@ -773,15 +773,33 @@ def remove_json_for(self, file_field): self.remove_json_for("unfiled") # GSTR 1 UTILITY - def is_gstr1_api_enabled(self, settings=None): + def is_gstr1_api_enabled(self, settings=None, warn_for_missing_credentials=False): if not settings: settings = frappe.get_cached_doc("GST Settings") - return ( - is_production_api_enabled(settings) - and settings.compare_gstr_1_data - and settings.has_valid_credentials(self.gstin, "Returns") - ) + if not is_production_api_enabled(settings): + return False + + if not settings.compare_gstr_1_data: + return False + + if not settings.has_valid_credentials(self.gstin, "Returns"): + if warn_for_missing_credentials: + frappe.publish_realtime( + "show_message", + dict( + message=_( + "Credentials are missing for GSTIN {0} for service" + " Returns in GST Settings" + ).format(self.gstin), + title=_("Missing Credentials"), + ), + user=frappe.session.user, + ) + + return False + + return True def is_sek_needed(self, settings=None): if not self.is_gstr1_api_enabled(settings): From 1f6870d7e1552cc18539639790ff5ca88fed9ca9 Mon Sep 17 00:00:00 2001 From: Vishakh Desai Date: Wed, 29 May 2024 15:06:03 +0530 Subject: [PATCH 03/41] fix: gst-transporter-id refactor (cherry picked from commit a87d2cbca753f6f71d8719f979d5b605de75695a) # Conflicts: # india_compliance/gst_india/doctype/gstin/gstin.json # india_compliance/gst_india/doctype/gstin/gstin.py --- .../client_scripts/e_waybill_actions.js | 4 +- .../gst_india/client_scripts/supplier.js | 2 +- .../gst_india/doctype/gstin/gstin.js | 6 +- .../gst_india/doctype/gstin/gstin.json | 19 ++- .../gst_india/doctype/gstin/gstin.py | 110 ++++++++++++++---- .../gst_india/doctype/gstin/test_gstin.py | 70 ++++++----- .../gst_india/overrides/transaction.py | 11 +- india_compliance/gst_india/utils/__init__.py | 45 ++++--- .../gst_india/utils/gstin_info.py | 7 ++ india_compliance/public/js/transaction.js | 2 +- india_compliance/public/js/utils.js | 9 ++ 11 files changed, 193 insertions(+), 92 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/e_waybill_actions.js b/india_compliance/gst_india/client_scripts/e_waybill_actions.js index 473581f98..7140264d7 100644 --- a/india_compliance/gst_india/client_scripts/e_waybill_actions.js +++ b/india_compliance/gst_india/client_scripts/e_waybill_actions.js @@ -1162,9 +1162,7 @@ async function update_gst_tranporter_id(dialog) { } function set_gst_transporter_id_status(dialog) { - const gst_transporter_id_field = dialog.get_field("gst_transporter_id"); - - india_compliance.set_gstin_status(gst_transporter_id_field); + india_compliance.validate_gst_transporter_id(dialog.get_value("gst_transporter_id")); } function update_generation_dialog(dialog, doc) { diff --git a/india_compliance/gst_india/client_scripts/supplier.js b/india_compliance/gst_india/client_scripts/supplier.js index 05a0bfa11..2b36b5ac6 100644 --- a/india_compliance/gst_india/client_scripts/supplier.js +++ b/india_compliance/gst_india/client_scripts/supplier.js @@ -30,6 +30,6 @@ frappe.ui.form.on(DOCTYPE, { return; gst_transporter_id_field = frm.get_field("gst_transporter_id"); - india_compliance.set_gstin_status(gst_transporter_id_field); + india_compliance.validate_gst_transporter_id(gst_transporter_id_field.value); }, }); diff --git a/india_compliance/gst_india/doctype/gstin/gstin.js b/india_compliance/gst_india/doctype/gstin/gstin.js index 2abed01b8..b5c68b1b1 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.js +++ b/india_compliance/gst_india/doctype/gstin/gstin.js @@ -5,8 +5,12 @@ frappe.ui.form.on("GSTIN", { refresh(frm) { frm.disable_form(); - frm.add_custom_button(__("Refresh Now"), () => { + frm.add_custom_button(__("Refresh GSTIN Status"), () => { frm.call("update_gstin_status"); }); + + frm.add_custom_button(__("Refresh Transporter ID Status"), () => { + frm.call("update_transporter_id_status"); + }); }, }); diff --git a/india_compliance/gst_india/doctype/gstin/gstin.json b/india_compliance/gst_india/doctype/gstin/gstin.json index 234b1382c..0be1c14e1 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.json +++ b/india_compliance/gst_india/doctype/gstin/gstin.json @@ -13,8 +13,12 @@ "column_break_nrjd", "last_updated_on", "cancelled_date", +<<<<<<< HEAD "section_break_ttzc", "gstr_1_filed_upto" +======= + "transporter_id_status" +>>>>>>> a87d2cbc (fix: gst-transporter-id refactor) ], "fields": [ { @@ -30,8 +34,7 @@ "fieldtype": "Data", "in_list_view": 1, "in_standard_filter": 1, - "label": "Status", - "reqd": 1 + "label": "Status" }, { "fieldname": "registration_date", @@ -63,6 +66,7 @@ "label": "Is Blocked" }, { +<<<<<<< HEAD "fieldname": "section_break_ttzc", "fieldtype": "Section Break" }, @@ -71,12 +75,23 @@ "fieldtype": "Date", "hidden": 1, "label": "GSTR-1 Filed Upto" +======= + "fieldname": "transporter_id_status", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Transporter ID Status" +>>>>>>> a87d2cbc (fix: gst-transporter-id refactor) } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], +<<<<<<< HEAD "modified": "2024-05-17 15:38:05.867522", +======= + "modified": "2024-05-21 12:59:58.322776", +>>>>>>> a87d2cbc (fix: gst-transporter-id refactor) "modified_by": "Administrator", "module": "GST India", "name": "GSTIN", diff --git a/india_compliance/gst_india/doctype/gstin/gstin.py b/india_compliance/gst_india/doctype/gstin/gstin.py index 36dbb1170..435f420e6 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.py +++ b/india_compliance/gst_india/doctype/gstin/gstin.py @@ -6,6 +6,7 @@ from frappe.model.document import Document from frappe.utils import date_diff, format_date, get_datetime +from india_compliance.exceptions import GSPServerError from india_compliance.gst_india.api_classes.e_invoice import EInvoiceAPI from india_compliance.gst_india.api_classes.e_waybill import EWaybillAPI from india_compliance.gst_india.api_classes.public import PublicAPI @@ -40,7 +41,15 @@ def update_gstin_status(self): """ Permission check not required as GSTIN details are public and user has access to doc. """ - create_or_update_gstin_status(self.gstin) + # hard refresh will always use public API + create_or_update_gstin_status(self.gstin, use_public_api=True) + + @frappe.whitelist() + def update_transporter_id_status(self): + """ + Permission check not required as GSTIN details are public and user has access to doc. + """ + create_or_update_gstin_status(self.gstin, is_transporter_id=True) @frappe.whitelist() @@ -61,17 +70,15 @@ def get_gstin_status( return frappe.get_doc("GSTIN", gstin) - return get_updated_gstin(gstin, transaction_date, is_request_from_ui) + return get_updated_gstin(gstin, transaction_date, is_request_from_ui, force_update) -def get_updated_gstin(gstin, transaction_date=None, is_request_from_ui=0): +def get_updated_gstin( + gstin, transaction_date=None, is_request_from_ui=0, force_update=0 +): if is_request_from_ui: - return create_or_update_gstin_status(gstin) - - if gstin[:2] == "88": - callback = _validate_gst_transporter_id_info - else: - callback = _validate_gstin_info + # hard refresh from UI will always use public API + return create_or_update_gstin_status(gstin, use_public_api=force_update) frappe.enqueue( create_or_update_gstin_status, @@ -79,7 +86,7 @@ def get_updated_gstin(gstin, transaction_date=None, is_request_from_ui=0): queue="short", gstin=gstin, transaction_date=transaction_date, - callback=callback, + callback=_validate_gstin_info, ) @@ -88,13 +95,17 @@ def create_or_update_gstin_status( response=None, transaction_date=None, callback=None, + use_public_api=False, + is_transporter_id=False, ): doctype = "GSTIN" - if gstin and gstin[:2] == "88": + if is_transporter_id: response = get_transporter_id_info(gstin) else: - response = _get_gstin_info(gstin=gstin, response=response) + response = _get_gstin_info( + gstin=gstin, response=response, use_public_api=use_public_api + ) if not response: return @@ -113,16 +124,19 @@ def create_or_update_gstin_status( return doc -def _get_gstin_info(*, gstin=None, response=None): +def _get_gstin_info(*, gstin=None, response=None, use_public_api=False): if response: return get_formatted_response(response) validate_gstin(gstin) try: + if frappe.cache.get_value("gst_server_error"): + return + company_gstin = get_company_gstin() - if not company_gstin: + if use_public_api or not company_gstin: response = PublicAPI().get_gstin_info(gstin) return get_formatted_response(response) @@ -137,7 +151,10 @@ def _get_gstin_info(*, gstin=None, response=None): } ) - except Exception: + except Exception as e: + if isinstance(e, GSPServerError): + frappe.cache.set_value("gst_server_error", True, expires_in_sec=60) + frappe.log_error( title=_("Error fetching GSTIN status"), message=frappe.get_traceback(), @@ -197,7 +214,7 @@ def _throw(message): ) -def _validate_gst_transporter_id_info(transporter_id_info, **kwargs): +def _validate_gst_transporter_id_info(transporter_id_info, *args, **kwargs): if not transporter_id_info: return @@ -213,7 +230,7 @@ def _throw(message): message=message, ) - if transporter_id_info.status != "Active": + if transporter_id_info.transporter_id_status != "Active": return _throw( _( "Transporter ID {0} is not Active. Please make sure that transporter ID is valid." @@ -250,11 +267,6 @@ def is_status_refresh_required(gstin, transaction_date): if not doc: return True - # Transporter ID status is never cancelled - is_transporter_id = gstin[:2] == "88" - if is_transporter_id: - return False - if not transaction_date: # not from transactions return False @@ -301,13 +313,65 @@ def get_transporter_id_info(transporter_id): return frappe._dict( { "gstin": transporter_id, - "status": "Active" if response.transin else "Invalid", + "transporter_id_status": "Active" if response.transin else "Invalid", } ) +<<<<<<< HEAD def get_gstr_1_filed_upto(gstin): if not gstin: return return frappe.db.get_value("GSTIN", gstin, "gstr_1_filed_upto") +======= +@frappe.whitelist() +def validate_gst_transporter_id(transporter_id): + """ + Validates GST Transporter ID and warns user if transporter_id is not Active + + Args: + transporter_id (str): GST Transporter ID + """ + if not transporter_id: + return + + gstin = None + + # Check if GSTIN doc exists + if frappe.db.exists("GSTIN", transporter_id): + gstin = frappe.get_doc("GSTIN", transporter_id) + + # Check if transporter_id starts with 88 or is not valid GSTIN and use Transporter ID API + elif transporter_id[:2] == "88" or not validate_gstin(transporter_id, throw=False): + gstin = create_or_update_gstin_status( + transporter_id, + is_transporter_id=True, + callback=_validate_gst_transporter_id_info, + ) + + # Use GSTIN API + else: + gstin = create_or_update_gstin_status( + transporter_id, callback=_validate_gstin_info + ) + + # If GSTIN status is not Active and transporter_id_status is None, use Transporter ID API + if gstin.status and gstin.status != "Active" and not gstin.transporter_id_status: + gstin = create_or_update_gstin_status( + transporter_id, + is_transporter_id=True, + callback=_validate_gst_transporter_id_info, + ) + + # Return if GSTIN or transporter_id_status is Active + if gstin and (gstin.status == "Active" or gstin.transporter_id_status == "Active"): + return + + frappe.msgprint( + _("Transporter ID {0} seems to be {1}").format( + transporter_id, gstin.transporter_id_status if gstin else "Inactive" + ), + indicator="orange", + ) +>>>>>>> a87d2cbc (fix: gst-transporter-id refactor) diff --git a/india_compliance/gst_india/doctype/gstin/test_gstin.py b/india_compliance/gst_india/doctype/gstin/test_gstin.py index e948c983e..d9b495641 100644 --- a/india_compliance/gst_india/doctype/gstin/test_gstin.py +++ b/india_compliance/gst_india/doctype/gstin/test_gstin.py @@ -1,48 +1,44 @@ # Copyright (c) 2023, Resilient Tech and Contributors # See license.txt -import re +import responses +from responses import matchers -import frappe from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import now -from india_compliance.gst_india.utils.tests import create_transaction +from india_compliance.gst_india.doctype.gstin.gstin import validate_gst_transporter_id + +TEST_GSTIN = "24AANFA2641L1ZK" + +TRANSPORTER_ID_API_RESPONSE = { + "success": True, + "message": "Transporter details are fetched successfully", + "result": { + "transin": TEST_GSTIN, + "tradeName": "_Test Transporter ID Comapany", + "legalName": "_Test Transporter ID Comapany", + "address1": "address 1", + "address2": "address 2", + "stateCode": "24", + "pinCode": "390020", + }, +} class TestGSTIN(FrappeTestCase): + @responses.activate @change_settings("GST Settings", {"validate_gstin_status": 1, "sandbox_mode": 0}) - def test_validate_gst_transporter_id_info(self): - # customer gstin - frappe.get_doc( - { - "doctype": "GSTIN", - "gstin": "24AANFA2641L1ZF", - "registration_date": "2021-01-01", - "status": "Active", - "last_updated_on": now(), - } - ).insert(ignore_if_duplicate=True) - - # gst transporter id - frappe.get_doc( - { - "doctype": "GSTIN", - "gstin": "88AABCM9407D1ZS", - "status": "Invalid", - "last_updated_on": now(), - } - ).insert(ignore_if_duplicate=True) - - si = create_transaction( - doctype="Sales Invoice", - gst_transporter_id="88AABCM9407D1ZS", - do_not_save=True, - ) + def test_validate_gst_transporter_id(self): + self.mock_get_transporter_details_response() + + validate_gst_transporter_id(TEST_GSTIN) + + def mock_get_transporter_details_response(self): + url = "https://asp.resilient.tech/ewb/Master/GetTransporterDetails" - self.assertRaisesRegex( - frappe.ValidationError, - re.compile( - r"^(.*is not Active. Please make sure that transporter ID is valid.*)$" - ), - si.save, + responses.add( + responses.GET, + url, + json=TRANSPORTER_ID_API_RESPONSE, + match=[matchers.query_param_matcher({"trn_no": TEST_GSTIN})], + status=200, ) diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index f43160dd2..348ca8beb 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -20,10 +20,12 @@ restrict_gstr_1_transaction_for, ) from india_compliance.gst_india.doctype.gstin.gstin import ( - _validate_gst_transporter_id_info, _validate_gstin_info, get_gstin_status, ) +from india_compliance.gst_india.doctype.gstin.gstin import ( + validate_gst_transporter_id as _validate_gst_transporter_id, +) from india_compliance.gst_india.utils import ( get_all_gst_accounts, get_gst_accounts_by_tax_type, @@ -1446,12 +1448,7 @@ def validate_gst_transporter_id(doc): doc.gst_transporter_id, label="GST Transporter ID", is_transporter_id=True ) - gstin_doc = get_gstin_status(doc.gst_transporter_id) - - if not gstin_doc: - return - - _validate_gst_transporter_id_info(gstin_doc, throw=True) + _validate_gst_transporter_id(doc.gst_transporter_id) def validate_company_address_field(doc): diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index 0609e897a..a9dcd09e7 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -152,6 +152,7 @@ def validate_gstin( *, is_tcs_gstin=False, is_transporter_id=False, + throw=True, ): """ Validate GSTIN with following checks: @@ -166,21 +167,27 @@ def validate_gstin( gstin = gstin.upper().strip() if len(gstin) != 15: - frappe.throw( - _("{0} {1} must have 15 characters").format(label, frappe.bold(gstin)), - title=_("Invalid {0}").format(label), - ) + if throw: + frappe.throw( + _("{0} {1} must have 15 characters").format(label, frappe.bold(gstin)), + title=_("Invalid {0}").format(label), + ) + return False - if not is_transporter_id: - validate_gstin_check_digit(gstin, label) + is_valid = True + + if not (is_transporter_id and gstin.startswith("88")): + is_valid = validate_gstin_check_digit(gstin, label=label, throw=throw) if is_tcs_gstin and not TCS.match(gstin): - frappe.throw( - _("Invalid format for e-Commerce Operator (TCS) GSTIN"), - title=_("Invalid GSTIN"), - ) + if throw: + frappe.throw( + _("Invalid format for e-Commerce Operator (TCS) GSTIN"), + title=_("Invalid GSTIN"), + ) + return False - return gstin + return gstin if is_valid else is_valid def validate_gst_category(gst_category, gstin): @@ -323,7 +330,7 @@ def get_data_file_path(file_name): return frappe.get_app_path("india_compliance", "gst_india", "data", file_name) -def validate_gstin_check_digit(gstin, label="GSTIN"): +def validate_gstin_check_digit(gstin, label="GSTIN", throw=True): """ Function to validate the check digit of the GSTIN. """ @@ -338,11 +345,15 @@ def validate_gstin_check_digit(gstin, label="GSTIN"): total += digit factor = 2 if factor == 1 else 1 if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: - frappe.throw( - _( - """Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""" - ).format(label) - ) + if throw: + frappe.throw( + _( + """Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""" + ).format(label) + ) + return False + + return True def is_overseas_doc(doc): diff --git a/india_compliance/gst_india/utils/gstin_info.py b/india_compliance/gst_india/utils/gstin_info.py index 5c9ffdc4e..a22cf4f61 100644 --- a/india_compliance/gst_india/utils/gstin_info.py +++ b/india_compliance/gst_india/utils/gstin_info.py @@ -6,6 +6,7 @@ from frappe import _ from frappe.utils import getdate +from india_compliance.exceptions import GSPServerError from india_compliance.gst_india.api_classes.base import BASE_URL from india_compliance.gst_india.api_classes.public import PublicAPI from india_compliance.gst_india.doctype.gstr_1_log.gstr_1_log import ( @@ -46,6 +47,9 @@ def _get_gstin_info(gstin, *, throw_error=True): if not response: try: + if frappe.cache.get_value("gst_server_error"): + return + response = PublicAPI().get_gstin_info(gstin) frappe.enqueue( "india_compliance.gst_india.doctype.gstin.gstin.create_or_update_gstin_status", @@ -53,6 +57,9 @@ def _get_gstin_info(gstin, *, throw_error=True): response=response, ) except Exception as exc: + if isinstance(exc, GSPServerError): + frappe.cache.set_value("gst_server_error", True, expires_in_sec=60) + if throw_error: raise exc diff --git a/india_compliance/public/js/transaction.js b/india_compliance/public/js/transaction.js index 580c1b7f9..324e01d44 100644 --- a/india_compliance/public/js/transaction.js +++ b/india_compliance/public/js/transaction.js @@ -181,7 +181,7 @@ function set_and_validate_gstin_status(doctype) { }, gst_transporter_id(frm) { - _set_and_validate_gstin_status(frm, "gst_transporter_id"); + india_compliance.validate_gst_transporter_id(frm.get_field("gst_transporter_id").value); }, posting_date(frm) { diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 7f1dc236a..387d05b88 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -115,6 +115,15 @@ Object.assign(india_compliance, { return message; }, + async validate_gst_transporter_id(transporter_id) { + if (!transporter_id || transporter_id.length !== 15) return; + + await frappe.call({ + method: "india_compliance.gst_india.doctype.gstin.gstin.validate_gst_transporter_id", + args: { transporter_id }, + }); + }, + get_gstin_status_desc(status, datetime) { if (!status) return; const user_date = frappe.datetime.str_to_user(datetime); From 74c575a82333a4cdaae453d939fd6343b2fee5fe Mon Sep 17 00:00:00 2001 From: Vishakh Desai Date: Thu, 30 May 2024 12:00:38 +0530 Subject: [PATCH 04/41] fix: failing test case fixed (cherry picked from commit 0fc37d4318dd1c1d812fda9758a5c5caa3a5e640) --- india_compliance/gst_india/doctype/gstin/gstin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/gst_india/doctype/gstin/gstin.py b/india_compliance/gst_india/doctype/gstin/gstin.py index 435f420e6..83dc82fe6 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.py +++ b/india_compliance/gst_india/doctype/gstin/gstin.py @@ -357,7 +357,7 @@ def validate_gst_transporter_id(transporter_id): ) # If GSTIN status is not Active and transporter_id_status is None, use Transporter ID API - if gstin.status and gstin.status != "Active" and not gstin.transporter_id_status: + if gstin and gstin.status != "Active" and not gstin.transporter_id_status: gstin = create_or_update_gstin_status( transporter_id, is_transporter_id=True, From 0f2fc9a73e546bfb5e531ebfbe08026d3e19c4cc Mon Sep 17 00:00:00 2001 From: Vishakh Desai Date: Thu, 30 May 2024 12:28:27 +0530 Subject: [PATCH 05/41] chore: refactor (cherry picked from commit f7feff80226df01488e00268bf9fd79b3a7d5c6b) --- india_compliance/gst_india/doctype/gstin/gstin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstin/gstin.py b/india_compliance/gst_india/doctype/gstin/gstin.py index 83dc82fe6..29bdae885 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.py +++ b/india_compliance/gst_india/doctype/gstin/gstin.py @@ -356,8 +356,11 @@ def validate_gst_transporter_id(transporter_id): transporter_id, callback=_validate_gstin_info ) + if not gstin: + return + # If GSTIN status is not Active and transporter_id_status is None, use Transporter ID API - if gstin and gstin.status != "Active" and not gstin.transporter_id_status: + if gstin.status != "Active" and not gstin.transporter_id_status: gstin = create_or_update_gstin_status( transporter_id, is_transporter_id=True, @@ -365,13 +368,11 @@ def validate_gst_transporter_id(transporter_id): ) # Return if GSTIN or transporter_id_status is Active - if gstin and (gstin.status == "Active" or gstin.transporter_id_status == "Active"): + if gstin.status == "Active" or gstin.transporter_id_status == "Active": return frappe.msgprint( - _("Transporter ID {0} seems to be {1}").format( - transporter_id, gstin.transporter_id_status if gstin else "Inactive" - ), + _("Transporter ID {0} seems to be Inactive").format(transporter_id), indicator="orange", ) >>>>>>> a87d2cbc (fix: gst-transporter-id refactor) From 301b90157953e65979726c36f0f64a7b16c0b476 Mon Sep 17 00:00:00 2001 From: Vishakh Desai Date: Thu, 30 May 2024 12:48:14 +0530 Subject: [PATCH 06/41] fix: use validate_gstin_check_digit instead of validate_gstin in validate_gst_transporter_id (cherry picked from commit 3f2a83b42d8909241444e6695bada2865a67b1a9) --- .../gst_india/doctype/gstin/gstin.py | 5 ++- india_compliance/gst_india/utils/__init__.py | 42 ++++++++----------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstin/gstin.py b/india_compliance/gst_india/doctype/gstin/gstin.py index 29bdae885..98c520b4f 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.py +++ b/india_compliance/gst_india/doctype/gstin/gstin.py @@ -14,6 +14,7 @@ is_api_enabled, parse_datetime, validate_gstin, + validate_gstin_check_digit, ) GSTIN_STATUS = { @@ -343,7 +344,9 @@ def validate_gst_transporter_id(transporter_id): gstin = frappe.get_doc("GSTIN", transporter_id) # Check if transporter_id starts with 88 or is not valid GSTIN and use Transporter ID API - elif transporter_id[:2] == "88" or not validate_gstin(transporter_id, throw=False): + elif transporter_id[:2] == "88" or not validate_gstin_check_digit( + transporter_id, throw=False + ): gstin = create_or_update_gstin_status( transporter_id, is_transporter_id=True, diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index a9dcd09e7..7600a91f4 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -152,7 +152,6 @@ def validate_gstin( *, is_tcs_gstin=False, is_transporter_id=False, - throw=True, ): """ Validate GSTIN with following checks: @@ -167,27 +166,21 @@ def validate_gstin( gstin = gstin.upper().strip() if len(gstin) != 15: - if throw: - frappe.throw( - _("{0} {1} must have 15 characters").format(label, frappe.bold(gstin)), - title=_("Invalid {0}").format(label), - ) - return False - - is_valid = True + frappe.throw( + _("{0} {1} must have 15 characters").format(label, frappe.bold(gstin)), + title=_("Invalid {0}").format(label), + ) if not (is_transporter_id and gstin.startswith("88")): - is_valid = validate_gstin_check_digit(gstin, label=label, throw=throw) + validate_gstin_check_digit(gstin, label=label) if is_tcs_gstin and not TCS.match(gstin): - if throw: - frappe.throw( - _("Invalid format for e-Commerce Operator (TCS) GSTIN"), - title=_("Invalid GSTIN"), - ) - return False + frappe.throw( + _("Invalid format for e-Commerce Operator (TCS) GSTIN"), + title=_("Invalid GSTIN"), + ) - return gstin if is_valid else is_valid + return gstin def validate_gst_category(gst_category, gstin): @@ -345,13 +338,14 @@ def validate_gstin_check_digit(gstin, label="GSTIN", throw=True): total += digit factor = 2 if factor == 1 else 1 if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: - if throw: - frappe.throw( - _( - """Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""" - ).format(label) - ) - return False + if not throw: + return False + + frappe.throw( + _( + """Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""" + ).format(label) + ) return True From f8f357f9c53174bd0dd7752becdbc705cea80d8a Mon Sep 17 00:00:00 2001 From: Vishakh Desai Date: Thu, 30 May 2024 15:34:43 +0530 Subject: [PATCH 07/41] chore: refactor (cherry picked from commit dc8db1813cbb01783d43a1c3b22a60b328a929bc) --- india_compliance/gst_india/client_scripts/supplier.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/supplier.js b/india_compliance/gst_india/client_scripts/supplier.js index 2b36b5ac6..c42d12cb3 100644 --- a/india_compliance/gst_india/client_scripts/supplier.js +++ b/india_compliance/gst_india/client_scripts/supplier.js @@ -29,7 +29,6 @@ frappe.ui.form.on(DOCTYPE, { ) return; - gst_transporter_id_field = frm.get_field("gst_transporter_id"); - india_compliance.validate_gst_transporter_id(gst_transporter_id_field.value); + india_compliance.validate_gst_transporter_id(frm.doc.gst_transporter_id); }, }); From a4facb082dbfc70526af2017305c5344653ffb7d Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 14 Jun 2024 11:28:01 +0530 Subject: [PATCH 08/41] refactor: naming, usage etc (cherry picked from commit 6f3f5e84b95ccea3262fdc35fef5c87e5ab174c3) # Conflicts: # india_compliance/gst_india/doctype/gstin/gstin.py --- .../client_scripts/e_waybill_actions.js | 10 ++--- .../gst_india/client_scripts/supplier.js | 6 --- .../gst_india/doctype/gstin/gstin.py | 37 ++++++++++++------- .../gst_india/overrides/transaction.py | 4 +- india_compliance/gst_india/utils/__init__.py | 10 ++--- india_compliance/public/js/transaction.js | 2 +- india_compliance/public/js/utils.js | 4 +- 7 files changed, 37 insertions(+), 36 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/e_waybill_actions.js b/india_compliance/gst_india/client_scripts/e_waybill_actions.js index 7140264d7..166bb2757 100644 --- a/india_compliance/gst_india/client_scripts/e_waybill_actions.js +++ b/india_compliance/gst_india/client_scripts/e_waybill_actions.js @@ -295,7 +295,7 @@ function show_generate_e_waybill_dialog(frm) { ); d.show(); - set_gst_transporter_id_status(d); + validate_gst_transporter_id(d); //Alert if E-waybill cannot be generated using api if (!is_e_waybill_generatable(frm)) { @@ -374,7 +374,7 @@ function get_generate_e_waybill_dialog(opts, frm) { frm.doc.gst_transporter_id?.length == 15 ? frm.doc.gst_transporter_id : "", - onchange: () => set_gst_transporter_id_status(d), + onchange: () => validate_gst_transporter_id(d), }, // Sub Supply Type will be visible here for Delivery Note @@ -814,7 +814,7 @@ function show_update_transporter_dialog(frm) { frm.doc.gst_transporter_id.length == 15 ? frm.doc.gst_transporter_id : "", - onchange: () => set_gst_transporter_id_status(d), + onchange: () => validate_gst_transporter_id(d), }, { label: "Update e-Waybill Print/Data", @@ -841,7 +841,7 @@ function show_update_transporter_dialog(frm) { // To prevent triggering of change event on input twice frappe.ui.form.ControlData.trigger_change_on_input_event = true; d.show(); - set_gst_transporter_id_status(d); + validate_gst_transporter_id(d); } async function show_extend_validity_dialog(frm) { @@ -1161,7 +1161,7 @@ async function update_gst_tranporter_id(dialog) { dialog.set_value("gst_transporter_id", response.gst_transporter_id); } -function set_gst_transporter_id_status(dialog) { +function validate_gst_transporter_id(dialog) { india_compliance.validate_gst_transporter_id(dialog.get_value("gst_transporter_id")); } diff --git a/india_compliance/gst_india/client_scripts/supplier.js b/india_compliance/gst_india/client_scripts/supplier.js index c42d12cb3..750423c8e 100644 --- a/india_compliance/gst_india/client_scripts/supplier.js +++ b/india_compliance/gst_india/client_scripts/supplier.js @@ -23,12 +23,6 @@ frappe.ui.form.on(DOCTYPE, { }, gst_transporter_id(frm) { - if ( - !frm.doc.gst_transporter_id || - frm.doc.gst_transporter_id.length < 15 - ) - return; - india_compliance.validate_gst_transporter_id(frm.doc.gst_transporter_id); }, }); diff --git a/india_compliance/gst_india/doctype/gstin/gstin.py b/india_compliance/gst_india/doctype/gstin/gstin.py index 98c520b4f..7db18ee27 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.py +++ b/india_compliance/gst_india/doctype/gstin/gstin.py @@ -87,7 +87,7 @@ def get_updated_gstin( queue="short", gstin=gstin, transaction_date=transaction_date, - callback=_validate_gstin_info, + callback=_validate_gstin_status, ) @@ -102,9 +102,9 @@ def create_or_update_gstin_status( doctype = "GSTIN" if is_transporter_id: - response = get_transporter_id_info(gstin) + response = get_transporter_id_status(gstin) else: - response = _get_gstin_info( + response = _get_gstin_status( gstin=gstin, response=response, use_public_api=use_public_api ) @@ -125,7 +125,7 @@ def create_or_update_gstin_status( return doc -def _get_gstin_info(*, gstin=None, response=None, use_public_api=False): +def _get_gstin_status(*, gstin=None, response=None, use_public_api=False): if response: return get_formatted_response(response) @@ -166,7 +166,7 @@ def _get_gstin_info(*, gstin=None, response=None, use_public_api=False): frappe.cache.set_value(gstin, True, expires_in_sec=180) -def _validate_gstin_info(gstin_doc, transaction_date=None, throw=False): +def _validate_gstin_status(gstin_doc, transaction_date=None, throw=False): if not (gstin_doc and transaction_date): return @@ -215,7 +215,7 @@ def _throw(message): ) -def _validate_gst_transporter_id_info(transporter_id_info, *args, **kwargs): +def _validate_gst_transporter_id_status(transporter_id_info, *args, **kwargs): if not transporter_id_info: return @@ -296,7 +296,7 @@ def get_formatted_response(response): ) -def get_transporter_id_info(transporter_id): +def get_transporter_id_status(transporter_id): if not frappe.get_cached_value("GST Settings", None, "enable_e_waybill"): return @@ -344,19 +344,17 @@ def validate_gst_transporter_id(transporter_id): gstin = frappe.get_doc("GSTIN", transporter_id) # Check if transporter_id starts with 88 or is not valid GSTIN and use Transporter ID API - elif transporter_id[:2] == "88" or not validate_gstin_check_digit( - transporter_id, throw=False - ): + elif transporter_id[:2] == "88" or has_gstin_check_digit_failed(transporter_id): gstin = create_or_update_gstin_status( transporter_id, is_transporter_id=True, - callback=_validate_gst_transporter_id_info, + callback=_validate_gst_transporter_id_status, ) # Use GSTIN API else: gstin = create_or_update_gstin_status( - transporter_id, callback=_validate_gstin_info + transporter_id, callback=_validate_gstin_status ) if not gstin: @@ -367,7 +365,7 @@ def validate_gst_transporter_id(transporter_id): gstin = create_or_update_gstin_status( transporter_id, is_transporter_id=True, - callback=_validate_gst_transporter_id_info, + callback=_validate_gst_transporter_id_status, ) # Return if GSTIN or transporter_id_status is Active @@ -378,4 +376,17 @@ def validate_gst_transporter_id(transporter_id): _("Transporter ID {0} seems to be Inactive").format(transporter_id), indicator="orange", ) +<<<<<<< HEAD >>>>>>> a87d2cbc (fix: gst-transporter-id refactor) +======= + + +def has_gstin_check_digit_failed(gstin): + try: + validate_gstin_check_digit(gstin) + + except frappe.ValidationError: + return True + + return False +>>>>>>> 6f3f5e84 (refactor: naming, usage etc) diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 348ca8beb..d86f31f61 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -20,7 +20,7 @@ restrict_gstr_1_transaction_for, ) from india_compliance.gst_india.doctype.gstin.gstin import ( - _validate_gstin_info, + _validate_gstin_status, get_gstin_status, ) from india_compliance.gst_india.doctype.gstin.gstin import ( @@ -1433,7 +1433,7 @@ def validate_gstin_status(gstin, transaction_date): if not gstin_doc: return - _validate_gstin_info(gstin_doc, transaction_date, throw=True) + _validate_gstin_status(gstin_doc, transaction_date, throw=True) def validate_gst_transporter_id(doc): diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index 7600a91f4..3f41c8bdd 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -171,7 +171,8 @@ def validate_gstin( title=_("Invalid {0}").format(label), ) - if not (is_transporter_id and gstin.startswith("88")): + # eg: 29AAFCA7488L1Z0 invalid check digit for valid transporter id + if not is_transporter_id: validate_gstin_check_digit(gstin, label=label) if is_tcs_gstin and not TCS.match(gstin): @@ -323,7 +324,7 @@ def get_data_file_path(file_name): return frappe.get_app_path("india_compliance", "gst_india", "data", file_name) -def validate_gstin_check_digit(gstin, label="GSTIN", throw=True): +def validate_gstin_check_digit(gstin, label="GSTIN"): """ Function to validate the check digit of the GSTIN. """ @@ -338,17 +339,12 @@ def validate_gstin_check_digit(gstin, label="GSTIN", throw=True): total += digit factor = 2 if factor == 1 else 1 if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: - if not throw: - return False - frappe.throw( _( """Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""" ).format(label) ) - return True - def is_overseas_doc(doc): return is_overseas_transaction(doc.doctype, doc.gst_category, doc.place_of_supply) diff --git a/india_compliance/public/js/transaction.js b/india_compliance/public/js/transaction.js index 324e01d44..57909384a 100644 --- a/india_compliance/public/js/transaction.js +++ b/india_compliance/public/js/transaction.js @@ -181,7 +181,7 @@ function set_and_validate_gstin_status(doctype) { }, gst_transporter_id(frm) { - india_compliance.validate_gst_transporter_id(frm.get_field("gst_transporter_id").value); + india_compliance.validate_gst_transporter_id(frm.doc.gst_transporter_id); }, posting_date(frm) { diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 387d05b88..5e74c20cc 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -115,10 +115,10 @@ Object.assign(india_compliance, { return message; }, - async validate_gst_transporter_id(transporter_id) { + validate_gst_transporter_id(transporter_id) { if (!transporter_id || transporter_id.length !== 15) return; - await frappe.call({ + frappe.call({ method: "india_compliance.gst_india.doctype.gstin.gstin.validate_gst_transporter_id", args: { transporter_id }, }); From 89e3b902ab099bed4b0e583ecdc80c75e2cecdb3 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 15 Jun 2024 08:29:40 +0530 Subject: [PATCH 09/41] fix: changes as per reivew (cherry picked from commit 574b01605da4716a05b1c506498d391dbe247959) # Conflicts: # india_compliance/gst_india/doctype/gstin/gstin.json # india_compliance/gst_india/overrides/transaction.py # india_compliance/gst_india/utils/gstin_info.py --- .../doctype/gst_settings/gst_settings.py | 20 ++ .../gst_india/doctype/gstin/gstin.json | 19 +- .../gst_india/doctype/gstin/gstin.py | 230 +++++------------- .../gst_india/overrides/transaction.py | 17 +- india_compliance/gst_india/utils/__init__.py | 2 +- .../gst_india/utils/gstin_info.py | 113 ++++++++- india_compliance/public/js/utils.js | 11 +- 7 files changed, 216 insertions(+), 196 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_settings/gst_settings.py b/india_compliance/gst_india/doctype/gst_settings/gst_settings.py index a83961928..bb42b5bc0 100644 --- a/india_compliance/gst_india/doctype/gst_settings/gst_settings.py +++ b/india_compliance/gst_india/doctype/gst_settings/gst_settings.py @@ -90,6 +90,26 @@ def update_retry_e_invoice_e_waybill_scheduled_job(self): not self.enable_retry_einv_ewb_generation, ) + def get_gstin_with_credentials(self, service=None): + if not service: + return + + if service == "Returns" and not self: + return + + if service == "e-Waybill" and not self.enable_e_waybill: + return + + if service == "e-Invoice" and not self.enable_e_invoice: + return + + if service in ["e-Invoice", "e-Waybill"]: + service = "e-Waybill / e-Invoice" + + for row in self.credentials: + if row.service == service: + return row.gstin + def validate_gst_accounts(self): account_list = [] company_wise_account_types = {} diff --git a/india_compliance/gst_india/doctype/gstin/gstin.json b/india_compliance/gst_india/doctype/gstin/gstin.json index 0be1c14e1..845329c49 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.json +++ b/india_compliance/gst_india/doctype/gstin/gstin.json @@ -9,16 +9,20 @@ "gstin", "registration_date", "status", - "is_blocked", + "transporter_id_status", "column_break_nrjd", "last_updated_on", "cancelled_date", +<<<<<<< HEAD <<<<<<< HEAD "section_break_ttzc", "gstr_1_filed_upto" ======= "transporter_id_status" >>>>>>> a87d2cbc (fix: gst-transporter-id refactor) +======= + "is_blocked" +>>>>>>> 574b0160 (fix: changes as per reivew) ], "fields": [ { @@ -34,7 +38,7 @@ "fieldtype": "Data", "in_list_view": 1, "in_standard_filter": 1, - "label": "Status" + "label": "GSTIN Status" }, { "fieldname": "registration_date", @@ -79,7 +83,6 @@ "fieldname": "transporter_id_status", "fieldtype": "Data", "in_list_view": 1, - "in_standard_filter": 1, "label": "Transporter ID Status" >>>>>>> a87d2cbc (fix: gst-transporter-id refactor) } @@ -87,11 +90,15 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], +<<<<<<< HEAD <<<<<<< HEAD "modified": "2024-05-17 15:38:05.867522", ======= "modified": "2024-05-21 12:59:58.322776", >>>>>>> a87d2cbc (fix: gst-transporter-id refactor) +======= + "modified": "2024-06-14 13:59:53.998262", +>>>>>>> 574b0160 (fix: changes as per reivew) "modified_by": "Administrator", "module": "GST India", "name": "GSTIN", @@ -115,6 +122,12 @@ "report": 1, "role": "Accounts Manager", "share": 1 + }, + { + "export": 1, + "read": 1, + "report": 1, + "role": "Accounts User" } ], "sort_field": "modified", diff --git a/india_compliance/gst_india/doctype/gstin/gstin.py b/india_compliance/gst_india/doctype/gstin/gstin.py index 7db18ee27..d5ee6a9b9 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.py +++ b/india_compliance/gst_india/doctype/gstin/gstin.py @@ -6,15 +6,10 @@ from frappe.model.document import Document from frappe.utils import date_diff, format_date, get_datetime -from india_compliance.exceptions import GSPServerError -from india_compliance.gst_india.api_classes.e_invoice import EInvoiceAPI -from india_compliance.gst_india.api_classes.e_waybill import EWaybillAPI -from india_compliance.gst_india.api_classes.public import PublicAPI -from india_compliance.gst_india.utils import ( - is_api_enabled, - parse_datetime, - validate_gstin, - validate_gstin_check_digit, +from india_compliance.gst_india.utils import is_api_enabled, validate_gstin_check_digit +from india_compliance.gst_india.utils.gstin_info import ( + fetch_gstin_status, + fetch_transporter_id_status, ) GSTIN_STATUS = { @@ -43,7 +38,7 @@ def update_gstin_status(self): Permission check not required as GSTIN details are public and user has access to doc. """ # hard refresh will always use public API - create_or_update_gstin_status(self.gstin, use_public_api=True) + create_or_update_gstin_status(self.gstin, throw=True) @frappe.whitelist() def update_transporter_id_status(self): @@ -53,63 +48,24 @@ def update_transporter_id_status(self): create_or_update_gstin_status(self.gstin, is_transporter_id=True) -@frappe.whitelist() -def get_gstin_status( - gstin, transaction_date=None, is_request_from_ui=0, force_update=0 -): - """ - Permission check not required as GSTIN details are public where GSTIN is known. - """ - if not gstin: - return - - if not int(force_update) and not is_status_refresh_required( - gstin, transaction_date - ): - if not frappe.db.exists("GSTIN", gstin): - return - - return frappe.get_doc("GSTIN", gstin) - - return get_updated_gstin(gstin, transaction_date, is_request_from_ui, force_update) - - -def get_updated_gstin( - gstin, transaction_date=None, is_request_from_ui=0, force_update=0 -): - if is_request_from_ui: - # hard refresh from UI will always use public API - return create_or_update_gstin_status(gstin, use_public_api=force_update) - - frappe.enqueue( - create_or_update_gstin_status, - enqueue_after_commit=True, - queue="short", - gstin=gstin, - transaction_date=transaction_date, - callback=_validate_gstin_status, - ) - - def create_or_update_gstin_status( gstin=None, response=None, transaction_date=None, callback=None, - use_public_api=False, is_transporter_id=False, + throw=False, ): doctype = "GSTIN" - if is_transporter_id: - response = get_transporter_id_status(gstin) - else: - response = _get_gstin_status( - gstin=gstin, response=response, use_public_api=use_public_api - ) - if not response: - return + if is_transporter_id: + response = fetch_transporter_id_status(gstin, throw=throw) + else: + response = fetch_gstin_status(gstin=gstin, throw=throw) + + if not response: + return if frappe.db.exists(doctype, response.get("gstin")): doc = frappe.get_doc(doctype, response.pop("gstin")) @@ -125,48 +81,55 @@ def create_or_update_gstin_status( return doc -def _get_gstin_status(*, gstin=None, response=None, use_public_api=False): - if response: - return get_formatted_response(response) +### GSTIN Status Validation ### - validate_gstin(gstin) - try: - if frappe.cache.get_value("gst_server_error"): +def get_and_validate_gstin_status(gstin, transaction_date): + """ + Get and validate GSTIN status. + Enqueues fetching GSTIN status if required and hence best suited for Backend use. + """ + if not gstin: + return + + if not is_status_refresh_required(gstin, transaction_date): + if not frappe.db.exists("GSTIN", gstin): return - company_gstin = get_company_gstin() - - if use_public_api or not company_gstin: - response = PublicAPI().get_gstin_info(gstin) - return get_formatted_response(response) - - response = EInvoiceAPI(company_gstin=company_gstin).get_gstin_info(gstin) - return frappe._dict( - { - "gstin": gstin, - "registration_date": parse_datetime(response.DtReg, throw=False), - "cancelled_date": parse_datetime(response.DtDReg, throw=False), - "status": response.Status, - "is_blocked": response.BlkStatus, - } + doc = frappe.get_doc("GSTIN", gstin) + validate_gstin_status(doc, transaction_date, throw=True) + + else: + # Don't delay the response if API is required + frappe.enqueue( + create_or_update_gstin_status, + enqueue_after_commit=True, + queue="short", + gstin=gstin, + transaction_date=transaction_date, + callback=validate_gstin_status, ) - except Exception as e: - if isinstance(e, GSPServerError): - frappe.cache.set_value("gst_server_error", True, expires_in_sec=60) - frappe.log_error( - title=_("Error fetching GSTIN status"), - message=frappe.get_traceback(), - ) - frappe.clear_last_message() +@frappe.whitelist() +def get_gstin_status(gstin, transaction_date=None, force_update=False): + """ + Get GSTIN status. Responds immediately, and best suited for Frontend use. + Permission check not required as GSTIN details are public where GSTIN is known. + """ + if not gstin: + return + + if not force_update and not is_status_refresh_required(gstin, transaction_date): + if not frappe.db.exists("GSTIN", gstin): + return + + return frappe.get_doc("GSTIN", gstin) - finally: - frappe.cache.set_value(gstin, True, expires_in_sec=180) + return create_or_update_gstin_status(gstin, throw=force_update) -def _validate_gstin_status(gstin_doc, transaction_date=None, throw=False): +def validate_gstin_status(gstin_doc, transaction_date=None, throw=False): if not (gstin_doc and transaction_date): return @@ -215,41 +178,6 @@ def _throw(message): ) -def _validate_gst_transporter_id_status(transporter_id_info, *args, **kwargs): - if not transporter_id_info: - return - - throw = kwargs.get("throw", False) - - def _throw(message): - if throw: - frappe.throw(message) - - else: - frappe.log_error( - title=_("Invalid Transporter ID"), - message=message, - ) - - if transporter_id_info.transporter_id_status != "Active": - return _throw( - _( - "Transporter ID {0} is not Active. Please make sure that transporter ID is valid." - ).format(transporter_id_info.gstin) - ) - - -def get_company_gstin(): - gst_settings = frappe.get_cached_doc("GST Settings") - - if not gst_settings.enable_e_invoice: - return - - for row in gst_settings.credentials: - if row.service == "e-Waybill / e-Invoice": - return row.gstin - - def is_status_refresh_required(gstin, transaction_date): settings = frappe.get_cached_doc("GST Settings") @@ -257,7 +185,6 @@ def is_status_refresh_required(gstin, transaction_date): not settings.validate_gstin_status or not is_api_enabled(settings) or settings.sandbox_mode - or frappe.cache.get_value(gstin) ): return @@ -278,45 +205,7 @@ def is_status_refresh_required(gstin, transaction_date): return days_since_last_update >= settings.gstin_status_refresh_interval -def get_formatted_response(response): - """ - Format response from Public API - """ - return frappe._dict( - { - "gstin": response.gstin, - "registration_date": parse_datetime( - response.rgdt, day_first=True, throw=False - ), - "cancelled_date": parse_datetime( - response.cxdt, day_first=True, throw=False - ), - "status": response.sts, - } - ) - - -def get_transporter_id_status(transporter_id): - if not frappe.get_cached_value("GST Settings", None, "enable_e_waybill"): - return - - company_gstin = get_company_gstin() - if not company_gstin: - return - - response = EWaybillAPI(company_gstin=company_gstin).get_transporter_details( - transporter_id - ) - - if not response: - return - - return frappe._dict( - { - "gstin": transporter_id, - "transporter_id_status": "Active" if response.transin else "Invalid", - } - ) +### GST Transporter ID Validation ### <<<<<<< HEAD @@ -329,7 +218,10 @@ def get_gstr_1_filed_upto(gstin): @frappe.whitelist() def validate_gst_transporter_id(transporter_id): """ - Validates GST Transporter ID and warns user if transporter_id is not Active + Validates GST Transporter ID and warns user if transporter_id is not Active. + Just suggestive and not enforced. + + Only for Frontend use. Args: transporter_id (str): GST Transporter ID @@ -348,14 +240,11 @@ def validate_gst_transporter_id(transporter_id): gstin = create_or_update_gstin_status( transporter_id, is_transporter_id=True, - callback=_validate_gst_transporter_id_status, ) # Use GSTIN API else: - gstin = create_or_update_gstin_status( - transporter_id, callback=_validate_gstin_status - ) + gstin = create_or_update_gstin_status(transporter_id) if not gstin: return @@ -365,7 +254,6 @@ def validate_gst_transporter_id(transporter_id): gstin = create_or_update_gstin_status( transporter_id, is_transporter_id=True, - callback=_validate_gst_transporter_id_status, ) # Return if GSTIN or transporter_id_status is Active @@ -373,7 +261,7 @@ def validate_gst_transporter_id(transporter_id): return frappe.msgprint( - _("Transporter ID {0} seems to be Inactive").format(transporter_id), + _("GST Transporter ID {0} seems to be Invalid").format(transporter_id), indicator="orange", ) <<<<<<< HEAD diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index d86f31f61..999cd65f7 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -16,6 +16,7 @@ STATE_NUMBERS, ) from india_compliance.gst_india.constants.custom_fields import E_WAYBILL_INV_FIELDS +<<<<<<< HEAD from india_compliance.gst_india.doctype.gst_settings.gst_settings import ( restrict_gstr_1_transaction_for, ) @@ -26,6 +27,9 @@ from india_compliance.gst_india.doctype.gstin.gstin import ( validate_gst_transporter_id as _validate_gst_transporter_id, ) +======= +from india_compliance.gst_india.doctype.gstin.gstin import get_and_validate_gstin_status +>>>>>>> 574b0160 (fix: changes as per reivew) from india_compliance.gst_india.utils import ( get_all_gst_accounts, get_gst_accounts_by_tax_type, @@ -1428,28 +1432,17 @@ def validate_gstin_status(gstin, transaction_date): if not settings.validate_gstin_status: return - gstin_doc = get_gstin_status(gstin, transaction_date) - - if not gstin_doc: - return - - _validate_gstin_status(gstin_doc, transaction_date, throw=True) + get_and_validate_gstin_status(gstin, transaction_date) def validate_gst_transporter_id(doc): if not doc.get("gst_transporter_id"): return - settings = frappe.get_cached_doc("GST Settings") - if not settings.validate_gstin_status: - return - doc.gst_transporter_id = validate_gstin( doc.gst_transporter_id, label="GST Transporter ID", is_transporter_id=True ) - _validate_gst_transporter_id(doc.gst_transporter_id) - def validate_company_address_field(doc): if doc.doctype not in DOCTYPES_WITH_GST_DETAIL: diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index 3f41c8bdd..ad310859b 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -173,7 +173,7 @@ def validate_gstin( # eg: 29AAFCA7488L1Z0 invalid check digit for valid transporter id if not is_transporter_id: - validate_gstin_check_digit(gstin, label=label) + validate_gstin_check_digit(gstin, label) if is_tcs_gstin and not TCS.match(gstin): frappe.throw( diff --git a/india_compliance/gst_india/utils/gstin_info.py b/india_compliance/gst_india/utils/gstin_info.py index a22cf4f61..ad8789b6b 100644 --- a/india_compliance/gst_india/utils/gstin_info.py +++ b/india_compliance/gst_india/utils/gstin_info.py @@ -8,11 +8,17 @@ from india_compliance.exceptions import GSPServerError from india_compliance.gst_india.api_classes.base import BASE_URL +from india_compliance.gst_india.api_classes.e_invoice import EInvoiceAPI +from india_compliance.gst_india.api_classes.e_waybill import EWaybillAPI from india_compliance.gst_india.api_classes.public import PublicAPI +<<<<<<< HEAD from india_compliance.gst_india.doctype.gstr_1_log.gstr_1_log import ( process_gstr_1_returns_info, ) from india_compliance.gst_india.utils import titlecase, validate_gstin +======= +from india_compliance.gst_india.utils import parse_datetime, titlecase, validate_gstin +>>>>>>> 574b0160 (fix: changes as per reivew) GST_CATEGORIES = { "Regular": "Registered Regular", @@ -54,7 +60,7 @@ def _get_gstin_info(gstin, *, throw_error=True): frappe.enqueue( "india_compliance.gst_india.doctype.gstin.gstin.create_or_update_gstin_status", queue="long", - response=response, + response=get_formatted_response_for_status(response), ) except Exception as exc: if isinstance(exc, GSPServerError): @@ -175,6 +181,111 @@ def _extract_address_lines(address): return address_line1, address_line2 +def fetch_gstin_status(*, gstin=None, throw=True): + """ + Fetch GSTIN status from E-Invoice API or Public API + + Uses Public API if credentials are not available or its a user initiated request + + :param gstin: GSTIN to fetch status for + :param throw: Raise exception if error occurs (used for user initiated requests) + """ + validate_gstin(gstin) + + try: + if not throw and frappe.cache.get_value("gst_server_error"): + return + + gst_settings = frappe.get_cached_doc("GST Settings", None) + company_gstin = gst_settings.get_gstin_with_credentials(service="e-Invoice") + + if throw or not company_gstin: + response = PublicAPI().get_gstin_info(gstin) + return get_formatted_response_for_status(response) + + response = EInvoiceAPI(company_gstin=company_gstin).get_gstin_info(gstin) + return frappe._dict( + { + "gstin": gstin, + "registration_date": parse_datetime(response.DtReg, throw=False), + "cancelled_date": parse_datetime(response.DtDReg, throw=False), + "status": response.Status, + "is_blocked": response.BlkStatus, + } + ) + + except Exception as e: + if throw: + raise e + + if isinstance(e, GSPServerError): + frappe.cache.set_value("gst_server_error", True, expires_in_sec=60) + + frappe.log_error( + title=_("Error fetching GSTIN status"), + message=frappe.get_traceback(), + ) + frappe.clear_last_message() + + +def get_formatted_response_for_status(response): + """ + Format response from Public API + """ + return frappe._dict( + { + "gstin": response.gstin, + "registration_date": parse_datetime( + response.rgdt, day_first=True, throw=False + ), + "cancelled_date": parse_datetime( + response.cxdt, day_first=True, throw=False + ), + "status": response.sts, + } + ) + + +def fetch_transporter_id_status(transporter_id, throw=True): + """ + Fetch Transporter ID status from E-Waybill API + + :param transporter_id: GSTIN of the transporter + :param throw: Raise exception if error occurs (used for user initiated requests) + """ + if not frappe.get_cached_value("GST Settings", None, "enable_e_waybill"): + return + + gst_settings = frappe.get_cached_doc("GST Settings", None) + company_gstin = gst_settings.get_gstin_with_credentials(service="e-Waybill") + + if not company_gstin: + return + + try: + response = EWaybillAPI(company_gstin=company_gstin).get_transporter_details( + transporter_id + ) + + except Exception as e: + if throw: + raise e + + frappe.log_error( + title=_("Error fetching Transporter ID status"), + message=frappe.get_traceback(), + ) + frappe.clear_last_message() + return + + return frappe._dict( + { + "gstin": transporter_id, + "transporter_id_status": "Active" if response.transin else "Invalid", + } + ) + + # ####### SAMPLE DATA for GST_CATEGORIES ######## # "Composition" 36AASFP8573D2ZN # "Input Service Distributor (ISD)" 29AABCF8078M2ZW Flipkart diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 5e74c20cc..8dd170e7f 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -87,18 +87,13 @@ Object.assign(india_compliance, { return in_list(frappe.boot.sales_doctypes, doctype) ? "Customer" : "Supplier"; }, - async set_gstin_status(field, transaction_date, force_update = 0) { + async set_gstin_status(field, transaction_date, force_update) { const gstin = field.value; if (!gstin || gstin.length !== 15) return field.set_description(""); const { message } = await frappe.call({ method: "india_compliance.gst_india.doctype.gstin.gstin.get_gstin_status", - args: { - gstin, - transaction_date, - is_request_from_ui: 1, - force_update, - }, + args: { gstin, transaction_date, force_update }, }); if (!message) return field.set_description(""); @@ -156,7 +151,7 @@ Object.assign(india_compliance, { `).appendTo(field.$wrapper.find(".gstin-last-updated")); refresh_btn.on("click", async function () { - const force_update = 1; + const force_update = true; await india_compliance.set_gstin_status( field, transaction_date, From a2ee2cc236579ca465c27657af967ff91d7a969e Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 18 Jun 2024 13:13:12 +0530 Subject: [PATCH 10/41] chore: resolve merge conflicts --- .../gst_india/doctype/gstin/gstin.json | 32 +++++-------------- .../gst_india/doctype/gstin/gstin.py | 18 ++++------- .../gst_india/overrides/transaction.py | 10 ------ .../gst_india/utils/gstin_info.py | 4 --- 4 files changed, 15 insertions(+), 49 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstin/gstin.json b/india_compliance/gst_india/doctype/gstin/gstin.json index 845329c49..a5acf0c6f 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.json +++ b/india_compliance/gst_india/doctype/gstin/gstin.json @@ -13,16 +13,9 @@ "column_break_nrjd", "last_updated_on", "cancelled_date", -<<<<<<< HEAD -<<<<<<< HEAD + "is_blocked", "section_break_ttzc", "gstr_1_filed_upto" -======= - "transporter_id_status" ->>>>>>> a87d2cbc (fix: gst-transporter-id refactor) -======= - "is_blocked" ->>>>>>> 574b0160 (fix: changes as per reivew) ], "fields": [ { @@ -70,7 +63,12 @@ "label": "Is Blocked" }, { -<<<<<<< HEAD + "fieldname": "transporter_id_status", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Transporter ID Status" + }, + { "fieldname": "section_break_ttzc", "fieldtype": "Section Break" }, @@ -79,26 +77,12 @@ "fieldtype": "Date", "hidden": 1, "label": "GSTR-1 Filed Upto" -======= - "fieldname": "transporter_id_status", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Transporter ID Status" ->>>>>>> a87d2cbc (fix: gst-transporter-id refactor) } ], "in_create": 1, "index_web_pages_for_search": 1, "links": [], -<<<<<<< HEAD -<<<<<<< HEAD - "modified": "2024-05-17 15:38:05.867522", -======= - "modified": "2024-05-21 12:59:58.322776", ->>>>>>> a87d2cbc (fix: gst-transporter-id refactor) -======= "modified": "2024-06-14 13:59:53.998262", ->>>>>>> 574b0160 (fix: changes as per reivew) "modified_by": "Administrator", "module": "GST India", "name": "GSTIN", @@ -130,7 +114,7 @@ "role": "Accounts User" } ], - "sort_field": "modified", + "sort_field": "creation", "sort_order": "DESC", "states": [], "title_field": "gstin" diff --git a/india_compliance/gst_india/doctype/gstin/gstin.py b/india_compliance/gst_india/doctype/gstin/gstin.py index d5ee6a9b9..283ae0ff0 100644 --- a/india_compliance/gst_india/doctype/gstin/gstin.py +++ b/india_compliance/gst_india/doctype/gstin/gstin.py @@ -48,6 +48,13 @@ def update_transporter_id_status(self): create_or_update_gstin_status(self.gstin, is_transporter_id=True) +def get_gstr_1_filed_upto(gstin): + if not gstin: + return + + return frappe.db.get_value("GSTIN", gstin, "gstr_1_filed_upto") + + def create_or_update_gstin_status( gstin=None, response=None, @@ -208,13 +215,6 @@ def is_status_refresh_required(gstin, transaction_date): ### GST Transporter ID Validation ### -<<<<<<< HEAD -def get_gstr_1_filed_upto(gstin): - if not gstin: - return - - return frappe.db.get_value("GSTIN", gstin, "gstr_1_filed_upto") -======= @frappe.whitelist() def validate_gst_transporter_id(transporter_id): """ @@ -264,9 +264,6 @@ def validate_gst_transporter_id(transporter_id): _("GST Transporter ID {0} seems to be Invalid").format(transporter_id), indicator="orange", ) -<<<<<<< HEAD ->>>>>>> a87d2cbc (fix: gst-transporter-id refactor) -======= def has_gstin_check_digit_failed(gstin): @@ -277,4 +274,3 @@ def has_gstin_check_digit_failed(gstin): return True return False ->>>>>>> 6f3f5e84 (refactor: naming, usage etc) diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 999cd65f7..05d33be0e 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -16,20 +16,10 @@ STATE_NUMBERS, ) from india_compliance.gst_india.constants.custom_fields import E_WAYBILL_INV_FIELDS -<<<<<<< HEAD from india_compliance.gst_india.doctype.gst_settings.gst_settings import ( restrict_gstr_1_transaction_for, ) -from india_compliance.gst_india.doctype.gstin.gstin import ( - _validate_gstin_status, - get_gstin_status, -) -from india_compliance.gst_india.doctype.gstin.gstin import ( - validate_gst_transporter_id as _validate_gst_transporter_id, -) -======= from india_compliance.gst_india.doctype.gstin.gstin import get_and_validate_gstin_status ->>>>>>> 574b0160 (fix: changes as per reivew) from india_compliance.gst_india.utils import ( get_all_gst_accounts, get_gst_accounts_by_tax_type, diff --git a/india_compliance/gst_india/utils/gstin_info.py b/india_compliance/gst_india/utils/gstin_info.py index ad8789b6b..72534135d 100644 --- a/india_compliance/gst_india/utils/gstin_info.py +++ b/india_compliance/gst_india/utils/gstin_info.py @@ -11,14 +11,10 @@ from india_compliance.gst_india.api_classes.e_invoice import EInvoiceAPI from india_compliance.gst_india.api_classes.e_waybill import EWaybillAPI from india_compliance.gst_india.api_classes.public import PublicAPI -<<<<<<< HEAD from india_compliance.gst_india.doctype.gstr_1_log.gstr_1_log import ( process_gstr_1_returns_info, ) -from india_compliance.gst_india.utils import titlecase, validate_gstin -======= from india_compliance.gst_india.utils import parse_datetime, titlecase, validate_gstin ->>>>>>> 574b0160 (fix: changes as per reivew) GST_CATEGORIES = { "Regular": "Registered Regular", From 4c13a38107ae0a418cea83aa169773f87dd3feea Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 18 Jun 2024 10:54:55 +0530 Subject: [PATCH 11/41] fix(ux): show missing creds message only where relevant (cherry picked from commit 95d3f8692df4494c872ece24a5c8ee22a5feb654) --- .../gst_india/doctype/gst_settings/gst_settings.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_settings/gst_settings.py b/india_compliance/gst_india/doctype/gst_settings/gst_settings.py index bb42b5bc0..daba66012 100644 --- a/india_compliance/gst_india/doctype/gst_settings/gst_settings.py +++ b/india_compliance/gst_india/doctype/gst_settings/gst_settings.py @@ -206,9 +206,14 @@ def validate_credentials(self): _("Missing Required Field"), ) - if (self.enable_e_invoice or self.enable_e_waybill) and all( - credential.service != "e-Waybill / e-Invoice" - for credential in self.credentials + if ( + (self.enable_e_invoice or self.enable_e_waybill) + and not self.sandbox_mode + and not frappe.flags.in_setup_wizard + and all( + credential.service != "e-Waybill / e-Invoice" + for credential in self.credentials + ) ): frappe.msgprint( _( From 40ba3dd7e3f547b122044953f87cd6fb836f19e8 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 18 Jun 2024 10:58:42 +0530 Subject: [PATCH 12/41] fix: get gstins if param party is available (cherry picked from commit 0b8a84dbadaa3d6b0c8c3add7176c2c772dab736) --- india_compliance/gst_india/utils/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index ad310859b..86e4c2737 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -93,6 +93,8 @@ def get_gstin_list(party, party_type="Company"): """ Returns a list the party's GSTINs. """ + if not party: + return frappe.has_permission(party_type, doc=party, throw=True) From c951c0132ccfefa2367f2fd93d91b2ae71a465e5 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 18 Jun 2024 12:00:57 +0530 Subject: [PATCH 13/41] fix: show tax rate column only where required for GSTR-1 Beta (cherry picked from commit 9868d1394375117a57a81f3d9e2148c62c749e64) --- .../doctype/gstr_1_beta/gstr_1_beta.js | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 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 83d0972d9..35c04c52e 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 @@ -1182,7 +1182,7 @@ class GSTR1_TabManager extends TabManager { ]; } - get_document_columns() { + get_document_columns(with_tax_rate) { // `Transaction Type` + Invoice Columns with `Document` as title instead of `Invoice` return [ ...this.get_detail_view_column(), @@ -1227,7 +1227,7 @@ class GSTR1_TabManager extends TabManager { _value: (...args) => this.format_detailed_table_cell(args), }, ...this.get_match_columns(), - ...this.get_tax_columns(), + ...this.get_tax_columns(with_tax_rate), { name: "Document Value", fieldname: GSTR1_DataField.DOC_VALUE, @@ -1345,7 +1345,7 @@ class GSTR1_TabManager extends TabManager { return [ ...this.get_detail_view_column(), ...this.get_match_columns(), - ...this.get_tax_columns(), + ...this.get_tax_columns(true), ]; } @@ -1353,14 +1353,14 @@ class GSTR1_TabManager extends TabManager { return [ ...this.get_detail_view_column(), ...this.get_match_columns(), - ...this.get_tax_columns(), + ...this.get_tax_columns(true), ]; } // Common Columns - get_tax_columns() { - return [ + get_tax_columns(with_tax_rate) { + const columns = [ { name: "Place of Supply", fieldname: GSTR1_DataField.POS, @@ -1404,6 +1404,10 @@ class GSTR1_TabManager extends TabManager { width: 100, }, ]; + + if (!with_tax_rate) columns.splice(1, 1); + + return columns; } get_igst_tax_columns(with_pos) { @@ -1532,7 +1536,7 @@ class BooksTab extends GSTR1_TabManager { } get_b2cs_columns() { - let columns = this.get_document_columns(); + let columns = this.get_document_columns(true); columns = columns.filter( col => ![GSTR1_DataField.CUST_GSTIN, GSTR1_DataField.REVERSE_CHARGE].includes( @@ -1848,7 +1852,7 @@ class FiledTab extends GSTR1_TabManager { fieldname: GSTR1_DataField.DOC_TYPE, width: 100, }, - ...this.get_tax_columns(), + ...this.get_tax_columns(true), ...this.get_match_columns(), ]; } From 299ec403beb7da19c0cd626c90c47001d0ba7f4a Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 18 Jun 2024 12:56:09 +0530 Subject: [PATCH 14/41] fix: don't export zero values for gstr-1 beta (cherry picked from commit a497bd3d6ab91258e9befd00b009e960f2f15946) # Conflicts: # india_compliance/gst_india/overrides/transaction.py --- .../doctype/gst_settings/gst_settings.py | 4 +- .../gst_india/overrides/transaction.py | 47 +++++++++++++++++++ .../gst_india/utils/gstr_1/gstr_1_json_map.py | 3 ++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_settings/gst_settings.py b/india_compliance/gst_india/doctype/gst_settings/gst_settings.py index daba66012..74a6d8112 100644 --- a/india_compliance/gst_india/doctype/gst_settings/gst_settings.py +++ b/india_compliance/gst_india/doctype/gst_settings/gst_settings.py @@ -493,9 +493,9 @@ def restrict_gstr_1_transaction_for(posting_date, company_gstin, gst_settings=No gstr_1_filed_upto = get_gstr_1_filed_upto(company_gstin) if not gstr_1_filed_upto: - return False + restrict = False - if posting_date > getdate(gstr_1_filed_upto): + elif posting_date > getdate(gstr_1_filed_upto): restrict = False if ( diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 05d33be0e..a0a4df76f 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -1556,7 +1556,54 @@ def update_gst_details(doc, method=None): if doc.doctype in DOCTYPES_WITH_GST_DETAIL: ItemGSTDetails().update(doc) +<<<<<<< HEAD ItemGSTTreatment().set(doc) +======= + +def validate_item_tax_template(doc): + if not doc.items or not doc.taxes: + return + + non_taxable_items_with_tax = [] + taxable_items_with_no_tax = [] + + for item in doc.items: + if item.taxable_value == 0: + continue + + if item.gst_treatment == "Zero-Rated" and not doc.get("is_export_with_gst"): + continue + + total_taxes = abs(item.igst_amount + item.cgst_amount + item.sgst_amount) + + if total_taxes and item.gst_treatment in ("Nil-Rated", "Exempted", "Non-GST"): + non_taxable_items_with_tax.append(item.idx) + + if not total_taxes and item.gst_treatment in ("Taxable", "Zero-Rated"): + taxable_items_with_no_tax.append(item.idx) + + # Case: Zero Tax template with taxes or missing GST Accounts + if non_taxable_items_with_tax: + frappe.throw( + _( + "Cannot charge GST on Non-Taxable Items.
" + "Are the taxes setup correctly in Item Tax Template? Please select" + " the correct Item Tax Template for following row numbers:
{0}" + ).format(", ".join(bold(row_no) for row_no in non_taxable_items_with_tax)), + title=_("Invalid Items"), + ) + + # Case: Taxable template with missing GST Accounts + if taxable_items_with_no_tax: + frappe.throw( + _( + "No GST is being charged on Taxable Items.
" + "Are there missing GST accounts in Item Tax Template? Please" + " verify the Item Tax Template for following row numbers:
{0}" + ).format(", ".join(bold(row_no) for row_no in taxable_items_with_no_tax)), + title=_("Invalid Items"), + ) +>>>>>>> a497bd3d (fix: don't export zero values for gstr-1 beta) def after_mapping(target_doc, method=None, source_doc=None): 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 20666dac1..d57409262 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 @@ -2271,6 +2271,9 @@ def prepare_mapped_data(self): _class.process_invoices(data) for invoice in data: + if invoice.get("taxable_value") == 0: + continue + if invoice["invoice_category"] in ( GSTR1_Category.B2B.value, GSTR1_Category.EXP.value, From fa4f0d2b0e3100728099ead7f102b2efb2f75e2d Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 18 Jun 2024 14:31:23 +0530 Subject: [PATCH 15/41] chore: resolve conflicts --- .../gst_india/overrides/transaction.py | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index a0a4df76f..05d33be0e 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -1556,54 +1556,7 @@ def update_gst_details(doc, method=None): if doc.doctype in DOCTYPES_WITH_GST_DETAIL: ItemGSTDetails().update(doc) -<<<<<<< HEAD ItemGSTTreatment().set(doc) -======= - -def validate_item_tax_template(doc): - if not doc.items or not doc.taxes: - return - - non_taxable_items_with_tax = [] - taxable_items_with_no_tax = [] - - for item in doc.items: - if item.taxable_value == 0: - continue - - if item.gst_treatment == "Zero-Rated" and not doc.get("is_export_with_gst"): - continue - - total_taxes = abs(item.igst_amount + item.cgst_amount + item.sgst_amount) - - if total_taxes and item.gst_treatment in ("Nil-Rated", "Exempted", "Non-GST"): - non_taxable_items_with_tax.append(item.idx) - - if not total_taxes and item.gst_treatment in ("Taxable", "Zero-Rated"): - taxable_items_with_no_tax.append(item.idx) - - # Case: Zero Tax template with taxes or missing GST Accounts - if non_taxable_items_with_tax: - frappe.throw( - _( - "Cannot charge GST on Non-Taxable Items.
" - "Are the taxes setup correctly in Item Tax Template? Please select" - " the correct Item Tax Template for following row numbers:
{0}" - ).format(", ".join(bold(row_no) for row_no in non_taxable_items_with_tax)), - title=_("Invalid Items"), - ) - - # Case: Taxable template with missing GST Accounts - if taxable_items_with_no_tax: - frappe.throw( - _( - "No GST is being charged on Taxable Items.
" - "Are there missing GST accounts in Item Tax Template? Please" - " verify the Item Tax Template for following row numbers:
{0}" - ).format(", ".join(bold(row_no) for row_no in taxable_items_with_no_tax)), - title=_("Invalid Items"), - ) ->>>>>>> a497bd3d (fix: don't export zero values for gstr-1 beta) def after_mapping(target_doc, method=None, source_doc=None): From 209afe50c2819ce59bb2f2e8b4320e6efb878389 Mon Sep 17 00:00:00 2001 From: Sanket322 <113279972+Sanket322@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:33:02 +0530 Subject: [PATCH 16/41] fix: better print format for `gst tax invoice` (#2106) * fix: changed gst tax invoice print format * fix: give css to erpnext elements * fix: more scalable design * fix: minor changes as per review * fix: style company_details and separate div * fix: changing style in heading * fix: created doctype for print format * fix: added new tab in company * fix: add data field for image url * fix: update minor html * fix: changes as per discussion * fix: changes as per review * chore: rename fields --------- Co-authored-by: Smit Vora (cherry picked from commit 224ddc849f9d0408d90ba37fded7ebec0caa2978) --- .../gst_india/client_scripts/company.js | 17 ++++++++ .../gst_india/constants/custom_fields.py | 33 +++++++++++++++ .../doctype/company_print_options/__init__.py | 0 .../company_print_options.json | 40 +++++++++++++++++++ .../company_print_options.py | 9 +++++ .../gst_india/overrides/company.py | 8 ++++ .../gst_tax_invoice/gst_tax_invoice.json | 7 ++-- .../gst_india/web_template/__init__.py | 0 .../e_invoice_qr_code/__init__.py | 0 .../e_invoice_qr_code/e_invoice_qr_code.html | 1 + .../e_invoice_qr_code/e_invoice_qr_code.json | 23 +++++++++++ .../web_template/upi_qr_code/__init__.py | 0 .../web_template/upi_qr_code/upi_qr_code.html | 1 + .../web_template/upi_qr_code/upi_qr_code.json | 23 +++++++++++ 14 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 india_compliance/gst_india/doctype/company_print_options/__init__.py create mode 100644 india_compliance/gst_india/doctype/company_print_options/company_print_options.json create mode 100644 india_compliance/gst_india/doctype/company_print_options/company_print_options.py create mode 100644 india_compliance/gst_india/web_template/__init__.py create mode 100644 india_compliance/gst_india/web_template/e_invoice_qr_code/__init__.py create mode 100644 india_compliance/gst_india/web_template/e_invoice_qr_code/e_invoice_qr_code.html create mode 100644 india_compliance/gst_india/web_template/e_invoice_qr_code/e_invoice_qr_code.json create mode 100644 india_compliance/gst_india/web_template/upi_qr_code/__init__.py create mode 100644 india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.html create mode 100644 india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json diff --git a/india_compliance/gst_india/client_scripts/company.js b/india_compliance/gst_india/client_scripts/company.js index 25663d16b..a0594e0de 100644 --- a/india_compliance/gst_india/client_scripts/company.js +++ b/india_compliance/gst_india/client_scripts/company.js @@ -10,6 +10,10 @@ set_gstin_options_and_status(DOCTYPE); frappe.ui.form.off(DOCTYPE, "make_default_tax_template"); frappe.ui.form.on(DOCTYPE, { + onload(frm){ + frm.fields_dict.bank_details_for_printing.grid.fields_map.autofield.ignore_validation = 1 + frm.fields_dict.registration_details_for_printing.grid.fields_map.autofield.ignore_validation = 1 + }, setup(frm) { erpnext.company.set_custom_query(frm, [ "default_customs_expense_account", @@ -19,6 +23,19 @@ frappe.ui.form.on(DOCTYPE, { "default_customs_payable_account", { root_type: "Liability" }, ]); + + frm.set_query("autofield", "bank_details_for_printing", (_, cdt, cdn) => { + return { + query: "india_compliance.gst_india.overrides.company.get_default_print_options", + params : {for_bank : true} + } + }); + frm.set_query("autofield", "registration_details_for_printing", (_, cdt, cdn) => { + return { + query: "india_compliance.gst_india.overrides.company.get_default_print_options", + params : {for_bank : false} + } + }); }, make_default_tax_template: function (frm) { diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index 187ae02e8..c395295ea 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -86,6 +86,39 @@ "options": "Account", "insert_after": "default_customs_expense_account", }, + { + "fieldname": "print_options", + "label": "Print Options", + "fieldtype": "Tab Break", + "insert_after": "dashboard_tab", + }, + { + "label": "Enable Physical Signature", + "fieldname": "show_physical_signature", + "insert_after": "print_options", + "fieldtype": "Check", + }, + { + "label": "Company Logo", + "fieldname": "logo_for_printing", + "insert_after": "show_physical_signature", + "fieldtype": "Attach", + "translatable": 0, + }, + { + "label": "Bank Details", + "fieldname": "bank_details_for_printing", + "insert_after": "logo_for_printing", + "fieldtype": "Table", + "options": "Company Print Options", + }, + { + "label": "Registration Details", + "fieldname": "registration_details_for_printing", + "insert_after": "bank_details_for_printing", + "fieldtype": "Table", + "options": "Company Print Options", + }, ], ("Customer", "Supplier"): party_fields, # Purchase Fields diff --git a/india_compliance/gst_india/doctype/company_print_options/__init__.py b/india_compliance/gst_india/doctype/company_print_options/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/india_compliance/gst_india/doctype/company_print_options/company_print_options.json b/india_compliance/gst_india/doctype/company_print_options/company_print_options.json new file mode 100644 index 000000000..a462904d7 --- /dev/null +++ b/india_compliance/gst_india/doctype/company_print_options/company_print_options.json @@ -0,0 +1,40 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-05-24 10:38:39.591721", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "autofield", + "autofield_value" + ], + "fields": [ + { + "allow_in_quick_entry": 1, + "columns": 2, + "fieldname": "autofield", + "fieldtype": "Autocomplete", + "in_list_view": 1, + "search_index": 1 + }, + { + "columns": 2, + "fieldname": "autofield_value", + "fieldtype": "Data", + "in_list_view": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-06-05 14:29:47.327389", + "modified_by": "Administrator", + "module": "GST India", + "name": "Company Print Options", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/india_compliance/gst_india/doctype/company_print_options/company_print_options.py b/india_compliance/gst_india/doctype/company_print_options/company_print_options.py new file mode 100644 index 000000000..a4d295639 --- /dev/null +++ b/india_compliance/gst_india/doctype/company_print_options/company_print_options.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 CompanyPrintOptions(Document): + pass diff --git a/india_compliance/gst_india/overrides/company.py b/india_compliance/gst_india/overrides/company.py index 321109dd2..0b87aba92 100644 --- a/india_compliance/gst_india/overrides/company.py +++ b/india_compliance/gst_india/overrides/company.py @@ -226,3 +226,11 @@ def create_default_company_account( frappe.db.set_value( "Company", company, default_fieldname, account.name, update_modified=False ) + + +@frappe.whitelist() +def get_default_print_options(for_bank=True): + if for_bank: + return ["Account No.", "Bank Name", "Branch", "IFSC Code", "UPI ID"] + else: + return ["MSME No.", "MSME Type", "LLPIN", "LUT No."] diff --git a/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json b/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json index 8a30c8a99..5eba8216e 100644 --- a/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json +++ b/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json @@ -2,6 +2,7 @@ "absolute_value": 0, "align_labels_right": 0, "creation": "2017-07-04 16:26:21.120187", + "css": "/*css for pdf*/\n@media print{\n .payment_qr img {\n width:81px !important;\n height: 81px;\n }\n}\n\n.payment_qr img {\n width:81px !important;\n height: 81px;\n}\n.payment_qr{\n float:left;\n}\n \n \n/*general css*/ \nb{\n color : initial;\n}\n\np{\n font-size : 12px !important;\n word-break:break-all;\n margin:0px !important;\n}\n\ntable{\n width:100%;\n}\n\n\n/*section after heading of print format*/\ndiv[data-label=\"First Section\"] > div:nth-child(1){\n width:40% !important;\n padding-right:0px !important;\n padding-top:20px !important;\n}\ndiv[data-label=\"First Section\"] > div:nth-child(2) {\n width:34% !important;\n padding-right:0px !important;\n padding-left:6px !important;\n padding-top:20px !important;\n}\ndiv[data-label=\"First Section\"] > div:nth-child(3) {\n width:26% !important;\n padding-left:0px !important;\n}\n\n\n/*css for place of supply*/\ndiv[data-fieldname=\"place_of_supply\"]{\n padding-top:5px !important;\n padding-bottom:5px !important;\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div > label{\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div:nth-child(1){\n width:50% !important;\n line-height: 1;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div:nth-child(2){\n width:50% !important;\n line-height: 1;\n}\n\n\n/*css for posting date*/\ndiv[data-fieldname=\"posting_date\"]{\n margin-top:0px;\n margin-bottom:0px;\n}\ndiv[data-fieldname=\"posting_date\"] > div:nth-child(1){\n width:50% !important;\n line-height:1.6;\n}\ndiv[data-fieldname=\"posting_date\"] > div:nth-child(2){\n width:50% !important;\n line-height:1.6;\n}\n\n\n/*css for first section after heading second column*/\n.first_section_second_column{\n padding-bottom:5px !important;\n padding-top:5px !important;\n}\n\n\n/*css for first section after heading third column - qr code column*/\n.paid_unpaid{\n padding-bottom:5px !important;\n text-align:end;\n}\n.paid_unpaid > p{\n font-size:120% !important;\n line-height:1;\n}\n\n\n/*css for irn*/\n.print-format p.field-property{\n padding-top:5px !important;\n padding-bottom:5px !important;\n}\n\n\n/*css for due_date adn paid amount*/\ndiv[data-fieldname=\"due_date\"] > div{\n width:50%;\n}\ndiv[data-fieldname=\"due_date\"] > div > label {\n margin:0px ;\n}\ndiv[data-fieldname=\"due_date\"] > div:nth-child(2){\n width:50%;\n text-align:end;\n}\ndiv[data-fieldname=\"due_date\"]{\n margin-top:0px;\n margin-bottom:0px;\n}\ndiv[data-fieldname=\"paid_amount\"]{\n margin-top:0px;\n margin-bottom:0px;\n padding-top:5px;\n padding-bottom:5px;\n line-height:1;\n}\ndiv[data-fieldname=\"paid_amount\"] > div > label {\n margin:0px;\n}\n\n.data-field{\n margin-top:0px;\n margin-bottom:0px;\n}\n\n\n/*changing css of erpnext elements*/\n#taxes-section > .row > .col-xs-6:first-child {\n display: none;\n}\n\n#taxes-section > .row > .col-xs-6 {\n width: 100%;\n}\n\n#taxes-section > .row > .col-xs-6 > .row {\n padding-top:5px !important;\n padding-bottom:5px !important;\n line-height:1;\n}\n\n#taxes-section > .row > .col-xs-6 > .row > .col-xs-5 > label {\n margin:0px !important;\n}\n\ndiv[data-label=\"erpnext\"] > div:nth-child(2) > .row > .col-xs-5 > label {\n margin:0px !important;\n}\n\n.column-break > .row:first-child {\n line-height:1;\n padding-bottom:5px !important;\n}\n\ndiv[data-fieldname=\"grand_total\"] {\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"rounded_total\"] {\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"in_words\"] {\n margin-top:0px ;\n margin-bottom:0px;\n line-height:1;\n padding-top:5px;\n}\n\n\n/* last section of print format*/\n", "custom_format": 0, "disabled": 0, "doc_type": "Sales Invoice", @@ -9,19 +10,19 @@ "doctype": "Print Format", "font": "Default", "font_size": 14, - "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\\n\\t

\\n\\t\\tTAX INVOICE
\\n\\t\\t{{ doc.name }}\\n\\t

\\n
\\n

\\n\\t{% if doc.invoice_copy -%}\\n\\t\\t{{ doc.invoice_copy }}\\n\\t{% endif -%}\\n

\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company\", \"label\": \"Company\"}, {\"print_hide\": 0, \"fieldname\": \"company_address_display\", \"label\": \"Company Address\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"posting_date\", \"label\": \"Date\"}, {\"print_hide\": 0, \"fieldname\": \"due_date\", \"label\": \"Payment Due Date\"}, {\"print_hide\": 0, \"fieldname\": \"reverse_charge\", \"label\": \"Reverse Charge\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldname\": \"_custom_html\", \"options\": \"
\", \"fieldtype\": \"HTML\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Address\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"customer_name\", \"label\": \"Customer Name\"}, {\"print_hide\": 0, \"fieldname\": \"address_display\", \"label\": \"Address\"}, {\"print_hide\": 0, \"fieldname\": \"contact_display\", \"label\": \"Contact\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"shipping_address\", \"label\": \"Shipping Address\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"item_code\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"200px\"}, {\"print_hide\": 0, \"fieldname\": \"gst_hsn_code\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"serial_no\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"qty\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"uom\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"rate\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"amount\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"items\", \"label\": \"Items\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"total\", \"label\": \"Total\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"description\", \"print_width\": \"300px\"}], \"print_hide\": 0, \"fieldname\": \"taxes\", \"label\": \"Sales Taxes and Charges\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"grand_total\", \"label\": \"Grand Total\"}, {\"print_hide\": 0, \"fieldname\": \"rounded_total\", \"label\": \"Rounded Total\"}, {\"print_hide\": 0, \"fieldname\": \"in_words\", \"label\": \"In Words\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"other_charges_calculation\", \"align\": \"left\", \"label\": \"Tax Breakup\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Terms\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"terms\", \"label\": \"Terms and Conditions Details\"}]", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"{% set logo_for_printing = frappe.db.get_value(\\\"Company\\\",doc.company,\\\"logo_for_printing\\\") %}\\n\\n\\n \\n \\n \\n \\n \\n \\n
\\n {% if logo_for_printing != \\\"\\\" %}\\n
\\n \\n
\\n {% endif %}\\n
\\n

{{ doc.company }}

\\n
\\n
\\n

\\n {{ doc.select_print_heading if doc.select_print_heading else 'Tax Invoice' }}
\\n {{ doc.name }} \\n

\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"First Section\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n\\t\\n\\t{% if not doc.shipping_address or doc.address_display == doc.shipping_address %}\\n\\t\\t

Billing and Shipping Address

\\n\\t{% else %}\\n\\t\\t

Billed To

\\n\\t{% endif %} \\n\\t{% if doc.customer_name %}\\n\\t\\t

{{ doc.customer_name }}

\\n\\t{% endif %}\\n\\n\\t

{{ doc.address_display if doc.address_display else '' }}

\\n\\n\\t\\n\\t{% if doc.shipping_address and doc.address_display !=\\n\\t\\tdoc.shipping_address %}\\n\\t\\t

Shipped To

\\n\\t\\t\\t{% if doc.dispatch_address_name %}\\n\\t\\t\\t\\t

{{ doc.dispatch_address_name }}

\\n\\t\\t\\t{% endif %}\\n\\t\\t

{{ doc.shipping_address }}

\\n\\t{% endif %}\\n
\"}, {\"fieldname\": \"place_of_supply\", \"print_hide\": 0, \"label\": \"Place of Supply\"}, {\"fieldname\": \"is_reverse_charge\", \"print_hide\": 0, \"align\": \"left\", \"label\": \"Reverse Charge\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.contact_display or doc.contact_mobile or doc.contact_email %}\\n
\\n

Contact Person

\\n {% if doc.contact_display %}\\n

{{ doc.contact_display }}

\\n {% endif %} {% if doc.contact_mobile %}\\n

{{ doc.contact_mobile }}

\\n {% endif %} {% if doc.contact_email %}\\n

{{ doc.contact_email }}

\\n {% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.po_no %}\\n
\\n\\t

Purchase Order Reference

\\n\\t

{{ doc.po_no }}

\\n\\t{% if doc.po_date %}\\n\\t

dtd. {{ doc.get_formatted(\\\"po_date\\\") }}

\\n\\t{% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\r\\n{% set delivery_note_list = [] %}\\r\\n{% for item in doc.items %}\\r\\n {% if item.delivery_note and item.delivery_note not in delivery_note_list %}\\r\\n {% set _ = delivery_note_list.append(item.delivery_note) %}\\r\\n {% endif %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if delivery_note_list %}\\r\\n
\\r\\n

Delivery Note Reference

\\r\\n {% for dn in delivery_note_list %}\\r\\n {% set dn_date = frappe.db.get_value('Delivery Note', dn, 'posting_date') %}\\r\\n

{{ dn }} dtd. {{ frappe.utils.formatdate(dn_date, 'dd-MM-yyyy') }}

\\r\\n {% endfor %}\\r\\n
\\r\\n{% endif %}\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.transporter_name or doc.vehicle_no or doc.lr_no or doc.ewaybill %}\\n
\\n

Transporter Details

\\n {% if doc.transporter_name %}\\n

{{ doc.transporter_name }}

\\n {% endif %} \\n {% if doc.vehicle_no %}\\n

Vehicle No: {{ doc.vehicle_no }}

\\n {% endif %} \\n {% if doc.lr_no %}\\n

LR No: {{ doc.lr_no }}

\\n {% endif %} \\n {% if doc.ewaybill %}\\n

e-Waybill No: {{ doc.ewaybill }}

\\n {% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n \\n {% if doc.outstanding_amount !=0 %}\\n

\\n Unpaid Amount\\n

\\n

\\n {{ doc.get_formatted(\\\"outstanding_amount\\\") }}\\n

\\n {% else %}\\n

\\n Fully Paid\\n

\\n {% endif %}\\n
\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% if doc.irn %}\\r\\n{% set e_invoice_log = frappe.db.get_value( \\\"e-Invoice Log\\\", doc.irn,\\r\\n(\\\"invoice_data\\\", \\\"signed_qr_code\\\"), as_dict=True ) %}\\r\\n\\r\\n{% if e_invoice_log %}\\r\\n{% set invoice_data = json.loads(e_invoice_log.invoice_data) %}\\r\\n{% set date_format = frappe.db.get_single_value(\\\"System Settings\\\",'date_format').replace(\\\"mm\\\",\\\"MM\\\")\\r\\n%}\\r\\n\\r\\n
\\r\\n\\t\\r\\n\\t
\\r\\n\\t\\t{{ web_block('e-Invoice QR Code', values={'e_invoice_qr_text':\\r\\n\\t\\te_invoice_log.signed_qr_code }) }}\\r\\n\\t
\\r\\n\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tIRN : {{ doc.irn }}\\r\\n\\t

\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tAck No. {{ invoice_data.get(\\\"AckNo\\\") if doc.irn and invoice_data.get(\\\"AckNo\\\")\\r\\n\\t\\telse '' }}\\r\\n\\t

\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tAck Dt.\\r\\n\\t\\t{{ frappe.utils.format_datetime(invoice_data.get(\\\"AckDt\\\"),date_format) if doc.irn and\\r\\n\\t\\tinvoice_data.get(\\\"AckDt\\\") else '' }}\\r\\n\\t

\\r\\n
\\r\\n{% endif %}\\r\\n{% endif %}\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"items\", \"print_hide\": 0, \"label\": \"Items\", \"visible_columns\": [{\"fieldname\": \"serial_no\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"gst_hsn_code\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"description\", \"print_width\": \"200px\", \"print_hide\": 0}, {\"fieldname\": \"qty\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"amount\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"erpnext\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"align\": \"left\", \"label\": \"Due Date\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% set company_doc = frappe.get_doc(\\\"Company\\\",doc.company) %}\\r\\n{% set currency_format = frappe.db.get_single_value(\\\"System Settings\\\",\\\"number_format\\\") %}\\r\\n\\r\\n{% set bank_details = {} %}\\r\\n{% set upi_id = namespace(value=\\\"\\\") %}\\r\\n{% set bank_name = namespace(value=\\\"\\\") %}\\r\\n{% set paid_amount = namespace(value=0) %}\\r\\n\\r\\n{% for doc in company_doc.bank_details_for_printing %}\\r\\n\\t{% if doc.autofield == \\\"UPI ID\\\" %}\\r\\n\\t\\t{% set upi_id.value = doc.autofield_value %}\\r\\n\\t{% elif doc.autofield == \\\"Bank Name\\\" %}\\r\\n\\t\\t{% set bank_name.value = doc.autofield_value %}\\r\\n\\t{% else %}\\r\\n\\t\\t{% set _ = bank_details.update({doc.autofield: doc.autofield_value}) %}\\r\\n\\t{% endif %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if doc.rounded_total %}\\r\\n\\t{% set paid_amount.value = frappe.utils.cint(doc.rounded_total) - frappe.utils.cint(doc.outstanding_amount) %}\\r\\n{% else %}\\r\\n\\t{% set paid_amount.value = frappe.utils.cint(doc.grand_total) - frappe.utils.cint(doc.outstanding_amount) %}\\r\\n{% endif %}\\r\\n{% set paid_amount.value = frappe.utils.fmt_money(amount=paid_amount.value, format=currency_format, currency=company_doc.default_currency) %}\\r\\n\\r\\n\\r\\n
\\r\\n\\t
\\r\\n\\t\\t\\r\\n\\t
\\r\\n\\t
{{ paid_amount.value }}
\\r\\n
\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t{% if doc.outstanding_amount != 0 and upi_id.value != '' %}\\r\\n\\t\\t\\t\\r\\n\\t\\t{% endif %}\\r\\n\\t\\t{% if bank_details %}\\r\\n\\t\\t\\t\\r\\n\\t\\t{% endif %}\\r\\n\\t\\r\\n
\\r\\n\\t\\t\\t\\t
\\r\\n\\t\\t\\t\\t\\t{{ web_block('UPI QR Code', values={'upi_qr_text': 'upi://pay?pa=' ~ upi_id.value ~ '&am=' ~ doc.outstanding_amount ~ '&tn=' ~ doc.name ~ '&cu=INR' }) }}\\r\\n\\t\\t\\t\\t
\\r\\n\\t\\t\\t\\t

UPI QR

\\r\\n\\t\\t\\t
\\r\\n\\t\\t\\t\\t

Bank Details

\\r\\n\\t\\t\\t\\t

{{ bank_name.value }}

\\r\\n\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t{% for key,value in bank_details.items() %}\\r\\n\\t\\t\\t\\t\\t{% if value != None %}\\r\\n\\t\\t\\t\\t\\t\\t

{{ key }} : {{ value }}

\\r\\n\\t\\t\\t\\t\\t{% endif %}\\r\\n\\t\\t\\t\\t{% endfor %}\\r\\n\\t\\t\\t
\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total\", \"print_hide\": 0, \"label\": \"Total\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n {%- set data = doc.taxes -%}\\n {%- set print_settings = frappe.get_doc(\\\"Print Settings\\\") -%}\\n {% include \\\"erpnext/templates/print_formats/includes/taxes.html\\\" %}\\n
\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldname\": \"rounded_total\", \"print_hide\": 0, \"label\": \"Rounded Total\"}, {\"fieldname\": \"in_words\", \"print_hide\": 0, \"label\": \"In Words\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"gst_breakup_table\", \"print_hide\": 0, \"label\": \"GST Breakup Table\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% set all_details = {} %}\\r\\n\\r\\n{% set company_doc = frappe.get_doc(\\\"Company\\\",doc.company) %}\\r\\n\\r\\n{% for doc in company_doc.registration_details_for_printing %}\\r\\n\\t{% set _ = all_details.update({doc.autofield: doc.autofield_value}) %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if doc.company_gstin %}\\r\\n\\t{% set _ = all_details.update({ \\\"Our GSTIN\\\": doc.company_gstin, }) %}\\r\\n{% endif %}\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t{% for key,value in all_details.items() %}\\r\\n\\t\\t\\t{% if value != None %}\\r\\n\\t\\t\\t \\r\\n\\t\\t\\t{% endif %}\\r\\n\\t\\t\\t{% if loop.index % 2 == 0 and not loop.last %}\\r\\n\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t{% endif %}\\r\\n\\t\\t{% endfor %}\\r\\n\\t\\r\\n
\\r\\n\\t\\t\\t\\t

{{ key }} : {{ value }}

\\r\\n\\t\\t\\t
\\r\\n\\r\\n{% if company_doc.show_physical_signature == 0 %}\\r\\n{% set first_name = frappe.db.get_value(\\\"User\\\",doc.owner,\\\"first_name\\\") %}\\r\\n{% set last_name = frappe.db.get_value(\\\"User\\\",doc.owner,\\\"last_name\\\") %}\\r\\n{% set generated_by = first_name ~ (' ' ~ last_name if last_name else '') %}\\r\\n{% set generated_on = frappe.utils.getdate(doc.get_formatted(\\\"creation\\\")) %}\\r\\n\\r\\n\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n

Generated By : {{ generated_by }}

Generated On: {{ generated_on }}

\\r\\n{% else %}\\r\\n\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n

For {{ doc.company }}

Authorized By

\\r\\n{% endif %}\\r\\n\\r\\n{% if company_doc.show_physical_signature == 0 %}\\r\\n\\t

This is computer-generated document and no signature is required

\\r\\n{% endif %}\"}]", "idx": 0, "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2022-02-24 18:11:22.419118", + "modified": "2024-06-18 15:53:21.428025", "modified_by": "Administrator", "module": "GST India", "name": "GST Tax Invoice", "owner": "Administrator", - "page_number": "Hide", + "page_number": "Top Left", "print_format_builder": 1, "print_format_builder_beta": 0, "print_format_type": "Jinja", diff --git a/india_compliance/gst_india/web_template/__init__.py b/india_compliance/gst_india/web_template/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/india_compliance/gst_india/web_template/e_invoice_qr_code/__init__.py b/india_compliance/gst_india/web_template/e_invoice_qr_code/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/india_compliance/gst_india/web_template/e_invoice_qr_code/e_invoice_qr_code.html b/india_compliance/gst_india/web_template/e_invoice_qr_code/e_invoice_qr_code.html new file mode 100644 index 000000000..e1b478aaf --- /dev/null +++ b/india_compliance/gst_india/web_template/e_invoice_qr_code/e_invoice_qr_code.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/india_compliance/gst_india/web_template/e_invoice_qr_code/e_invoice_qr_code.json b/india_compliance/gst_india/web_template/e_invoice_qr_code/e_invoice_qr_code.json new file mode 100644 index 000000000..9e21d63f0 --- /dev/null +++ b/india_compliance/gst_india/web_template/e_invoice_qr_code/e_invoice_qr_code.json @@ -0,0 +1,23 @@ +{ + "__unsaved": 1, + "creation": "2024-04-25 12:45:52.102883", + "docstatus": 0, + "doctype": "Web Template", + "fields": [ + { + "fieldname": "e_invoice_qr_text", + "fieldtype": "Text", + "label": "QR Text", + "reqd": 0 + } + ], + "idx": 0, + "modified": "2024-06-03 13:20:45.766056", + "modified_by": "Administrator", + "module": "GST India", + "name": "e-Invoice QR Code", + "owner": "Administrator", + "standard": 1, + "template": "", + "type": "Component" +} \ No newline at end of file diff --git a/india_compliance/gst_india/web_template/upi_qr_code/__init__.py b/india_compliance/gst_india/web_template/upi_qr_code/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.html b/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.html new file mode 100644 index 000000000..608aea379 --- /dev/null +++ b/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json b/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json new file mode 100644 index 000000000..c4dfabea0 --- /dev/null +++ b/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json @@ -0,0 +1,23 @@ +{ + "__unsaved": 1, + "creation": "2024-05-03 10:34:51.575300", + "docstatus": 0, + "doctype": "Web Template", + "fields": [ + { + "fieldname": "upi_qr_text", + "fieldtype": "Text", + "label": "UPI Code", + "reqd": 0 + } + ], + "idx": 0, + "modified": "2024-06-03 13:19:21.517387", + "modified_by": "Administrator", + "module": "GST India", + "name": "UPI QR", + "owner": "Administrator", + "standard": 1, + "template": "", + "type": "Component" +} \ No newline at end of file From 3e86d56c7249efc5c9b08551b227d027d9489230 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Wed, 19 Jun 2024 13:56:22 +0530 Subject: [PATCH 17/41] fix: specify parenttype for purchase taxes (cherry picked from commit 287813a77c43b8294c47ef43e89c1f4753d252bc) --- .../gst_india/doctype/purchase_reconciliation_tool/__init__.py | 1 + .../gst_india/report/gstr_3b_details/gstr_3b_details.py | 1 + 2 files changed, 2 insertions(+) diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py index 3beaffd8a..0af13d91d 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py @@ -434,6 +434,7 @@ def get_query(self, additional_fields=None, is_return=False): .where(self.PI.docstatus == 1) .where(IfNull(self.PI.reconciliation_status, "") != "Not Applicable") .where(self.PI.is_opening == "NO") + .where(self.PI_TAX.parenttype == "Purchase Invoice") .groupby(self.PI.name) .select( *fields, diff --git a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py index 35a808298..6bd5d5bbc 100644 --- a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py +++ b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py @@ -525,6 +525,7 @@ def get_tax_case_statement(account, alias): IfNull(pi.ineligibility_reason, "") == "ITC restricted due to PoS rules" ) .where(pi.name.isin(ineligible_transactions)) + .where(taxes.parenttype == "Purchase Invoice") .groupby(pi[group_by]) .run(as_dict=True) ) From 0fd1da72cc1da0e497141745817d90e4b49be59c Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 20 Jun 2024 11:57:37 +0530 Subject: [PATCH 18/41] fix: wait for otp authentication, link instead of button for ux (cherry picked from commit e1958e81492463526170092f70b465cd4c8773a7) --- .../purchase_reconciliation_tool.js | 13 ++++--------- india_compliance/public/js/utils.js | 3 ++- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js index 91487550f..a44294349 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js @@ -17,15 +17,9 @@ const ALERT_HTML = ` ${ api_enabled - ? `` + ? ` + Download 2B + ` : "" } @@ -1465,6 +1459,7 @@ async function fetch_date_range(frm, field_prefix, method) { function set_date_range_description(frm, field_prefixs) { if (!field_prefixs) field_prefixs = ["inward_supply", "purchase"]; + else field_prefixs = [field_prefixs]; field_prefixs.forEach(prefix => { const period_field = prefix + "_period"; diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 8dd170e7f..a2ad26cde 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -388,7 +388,8 @@ Object.assign(india_compliance, { args: { company_gstin: gstin }, }); - this.authenticate_otp(gstin); + // wait for OTP to be authenticated to proceed + await this.authenticate_otp(gstin); }, async authenticate_otp(gstin) { From 213958531a018d4f5f1948fbcd04dc3791a74663 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 20 Jun 2024 13:19:16 +0530 Subject: [PATCH 19/41] fix(e-waybill): conditional checks for purchase receipt (cherry picked from commit 2017f65b23f9874468a834a5c5bbe25429fce638) --- india_compliance/gst_india/utils/e_waybill.py | 2 +- india_compliance/gst_india/utils/transaction_data.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index f59f47284..3fd75026f 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -1521,7 +1521,7 @@ def set_party_address_details(self): to_party = self.transaction_details.party_name from_party = self.transaction_details.company_name - if self.doc.doctype == "Purchase Invoice": + if self.doc.doctype in ("Purchase Invoice", "Purchase Receipt"): to_party, from_party = from_party, to_party if self.doc.is_return: diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index 33748d3a8..9983ea7af 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -40,7 +40,7 @@ def __init__(self, doc): gst_type = "Output" self.party_name_field = "customer_name" - if self.doc.doctype == "Purchase Invoice": + if self.doc.doctype in ("Purchase Invoice", "Purchase Receipt"): self.party_name_field = "supplier_name" if self.doc.is_reverse_charge != 1: # for with reverse charge, gst_type is Output From 2a5780b49bb12bc3d2dbc474f23938ddd8c2105d Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 20 Jun 2024 13:23:33 +0530 Subject: [PATCH 20/41] fix(reco-tool): support date to last month for filtered mismatches (cherry picked from commit 714fdcf5153fa6a95228a801cd252754326bdf39) # Conflicts: # india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json --- .../purchase_reconciliation_tool.json | 8 ++++++-- india_compliance/gst_india/utils/__init__.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json index 1801ce2d8..bb13ef3ee 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json @@ -47,7 +47,7 @@ "fieldname": "purchase_period", "fieldtype": "Select", "label": "Purchase Period", - "options": "\nThis Month\nThis Quarter\nThis Fiscal Year\nLast Month\nLast Quarter\nLast Fiscal Year\nCustom" + "options": "\nThis Month\nThis Quarter to Last Month\nThis Quarter\nThis Fiscal Year to Last Month\nThis Fiscal Year\nLast Month\nLast Quarter\nLast Fiscal Year\nCustom" }, { "depends_on": "eval: doc.purchase_period == 'Custom'", @@ -71,7 +71,7 @@ "fieldname": "inward_supply_period", "fieldtype": "Select", "label": "Inward Supply Period", - "options": "\nThis Month\nThis Quarter\nThis Fiscal Year\nLast Month\nLast Quarter\nLast Fiscal Year\nCustom" + "options": "\nThis Month\nThis Quarter to Last Month\nThis Quarter\nThis Fiscal Year to Last Month\nThis Fiscal Year\nLast Month\nLast Quarter\nLast Fiscal Year\nCustom" }, { "depends_on": "eval: doc.inward_supply_period == 'Custom'", @@ -150,7 +150,11 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2023-10-12 16:20:03.501277", +======= + "modified": "2024-06-20 13:19:57.316043", +>>>>>>> 714fdcf5 (fix(reco-tool): support date to last month for filtered mismatches) "modified_by": "Administrator", "module": "GST India", "name": "Purchase Reconciliation Tool", diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index 86e4c2737..6fdc02bfb 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -15,7 +15,9 @@ cint, cstr, get_datetime, + get_last_day, get_link_to_form, + get_quarter_start, get_system_timezone, getdate, ) @@ -773,6 +775,15 @@ def get_timespan_date_range(timespan: str, company: str | None = None) -> tuple fiscal_year = get_fiscal_year(date, company=company) return (fiscal_year[1], fiscal_year[2]) + if timespan == "this fiscal year to last month": + date = getdate() + fiscal_year = get_fiscal_year(date, company=company) + return (fiscal_year[1], get_last_day(add_to_date(date, months=-1))) + + if timespan == "this quarter to last month": + date = getdate() + return (get_quarter_start(date), get_last_day(add_to_date(date, months=-1))) + return From 2b8c2268e7ab4063fead705f5d0db8cd0294c8d8 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 20 Jun 2024 16:40:48 +0530 Subject: [PATCH 21/41] test: add test case for timespan date range (cherry picked from commit 9ac5c4edc8a1828ca182368c5e52014c1b10f75e) --- india_compliance/gst_india/utils/__init__.py | 15 +++++++++-- .../gst_india/utils/test_utils.py | 27 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 india_compliance/gst_india/utils/test_utils.py diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index 6fdc02bfb..9a2d21858 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -778,11 +778,22 @@ def get_timespan_date_range(timespan: str, company: str | None = None) -> tuple if timespan == "this fiscal year to last month": date = getdate() fiscal_year = get_fiscal_year(date, company=company) - return (fiscal_year[1], get_last_day(add_to_date(date, months=-1))) + last_month = add_to_date(date, months=-1) + + if fiscal_year[1] > last_month: + return (fiscal_year[1], fiscal_year[1]) + + return (fiscal_year[1], get_last_day(last_month)) if timespan == "this quarter to last month": date = getdate() - return (get_quarter_start(date), get_last_day(add_to_date(date, months=-1))) + quarter_start = get_quarter_start(date) + last_month = get_last_day(add_to_date(date, months=-1)) + + if quarter_start > last_month: + return (quarter_start, quarter_start) + + return (quarter_start, get_last_day(last_month)) return diff --git a/india_compliance/gst_india/utils/test_utils.py b/india_compliance/gst_india/utils/test_utils.py new file mode 100644 index 000000000..fa81b6518 --- /dev/null +++ b/india_compliance/gst_india/utils/test_utils.py @@ -0,0 +1,27 @@ +from datetime import date +from unittest.mock import patch + +from frappe.tests.utils import FrappeTestCase +from frappe.utils import getdate + + +class TestUtils(FrappeTestCase): + + @patch( + "india_compliance.gst_india.utils.getdate", return_value=getdate("2023-06-20") + ) + def test_timespan_date_range(self, getdate_mock): + from india_compliance.gst_india.utils import get_timespan_date_range + + timespan_date_range_map = { + "this fiscal year": (date(2023, 4, 1), date(2024, 3, 31)), + "last fiscal year": (date(2022, 4, 1), date(2023, 3, 31)), + "this fiscal year to last month": (date(2023, 4, 1), date(2023, 5, 31)), + "this quarter to last month": (date(2023, 4, 1), date(2023, 5, 31)), + } + + for timespan, expected_date_range in timespan_date_range_map.items(): + actual_date_range = get_timespan_date_range(timespan) + + for i, expected_date in enumerate(expected_date_range): + self.assertEqual(expected_date, actual_date_range[i]) From ab6ff6821aa627a03a18febc2d85be658b577e0c Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 20 Jun 2024 16:48:37 +0530 Subject: [PATCH 22/41] fix: don't try downloading data if there are no return periods (#2264) (cherry picked from commit 20a95027c9fb60d6471774ae7b0c450f1fab546b) --- .../purchase_reconciliation_tool.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py index 1558e70d5..003b0f1cf 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py @@ -490,6 +490,9 @@ def _download_gstr_2a( if not force: periods = get_periods_to_download(company_gstin, return_type, periods) + if not periods: + return + return download_gstr_2a(company_gstin, periods, otp, gst_categories) @@ -498,6 +501,10 @@ def _download_gstr_2b(date_range, company_gstin, otp=None): periods = get_periods_to_download( company_gstin, return_type, BaseUtil.get_periods(date_range, return_type) ) + + if not periods: + return + return download_gstr_2b(company_gstin, periods, otp) From 4eafb2eb6866c1dd208ad339db26d7b922d201aa Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 20 Jun 2024 17:09:12 +0530 Subject: [PATCH 23/41] refactor: import and use the function for scheduler (#2262) (cherry picked from commit d8f91c0a46066143b26d8e71efb5a039b3069037) --- india_compliance/gst_india/api_classes/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py index b61a2539b..221d2cf3b 100644 --- a/india_compliance/gst_india/api_classes/base.py +++ b/india_compliance/gst_india/api_classes/base.py @@ -5,6 +5,7 @@ import frappe from frappe import _ from frappe.utils import sbool +from frappe.utils.scheduler import is_scheduler_disabled from india_compliance.exceptions import GatewayTimeoutError, GSPServerError from india_compliance.gst_india.utils import is_api_enabled @@ -290,7 +291,7 @@ def check_scheduler_status(): if frappe.flags.in_test or frappe.conf.developer_mode: return - if frappe.utils.scheduler.is_scheduler_disabled(): + if is_scheduler_disabled(): frappe.throw( _( "The Scheduler is currently disabled, which needs to be enabled to use e-Invoicing and e-Waybill features. " From 3e99cef70b54a30e539bc2912ccbaef5ea709771 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 21 Jun 2024 16:24:18 +0530 Subject: [PATCH 24/41] test: create old fiscal if missing (cherry picked from commit b36fbb5e0adad8c95ee4bef1ceabe9d1c1b3dd6a) --- india_compliance/gst_india/utils/test_utils.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/india_compliance/gst_india/utils/test_utils.py b/india_compliance/gst_india/utils/test_utils.py index fa81b6518..3bb931cc0 100644 --- a/india_compliance/gst_india/utils/test_utils.py +++ b/india_compliance/gst_india/utils/test_utils.py @@ -1,12 +1,27 @@ from datetime import date from unittest.mock import patch +import frappe from frappe.tests.utils import FrappeTestCase from frappe.utils import getdate class TestUtils(FrappeTestCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + # create 2023-2024 fiscal year + fiscal_year = frappe.new_doc("Fiscal Year") + fiscal_year.update( + { + "year_start_date": "2023-04-01", + "year_end_date": "2024-03-31", + "year": "2023-2024", + } + ).insert(ignore_if_duplicate=True) + @patch( "india_compliance.gst_india.utils.getdate", return_value=getdate("2023-06-20") ) From 5fea9bdc216bee76d244b5240cf936d145df6933 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 21 Jun 2024 16:33:35 +0530 Subject: [PATCH 25/41] fix: use correct field name for auth token auto refresh (#2272) (cherry picked from commit 3822cc9f2c019d4b73ba2d32aef5db2448e48435) --- .../purchase_reconciliation_tool.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py index 003b0f1cf..dfc3f6324 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py @@ -609,11 +609,11 @@ def wrapper(*args, **kwargs): def auto_refresh_authtoken(): - is_auto_refresh_enabled = frappe.db.get_single_value( - "GST Settings", "auto_refresh_auth_token" + enable_auto_reconciliation = frappe.get_cached_value( + "GST Settings", "GST Settings", "enable_auto_reconciliation" ) - if not is_auto_refresh_enabled: + if not enable_auto_reconciliation: return for credential in frappe.get_all( From ebd069591366467e8eaadc02d476ba2bee2d4c3c Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 21 Jun 2024 16:35:30 +0530 Subject: [PATCH 26/41] test: create old fiscal years as required for testing (cherry picked from commit 32fe493ab2787a843ade8f5aadaaa1403eb55fdd) --- india_compliance/gst_india/utils/test_utils.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/india_compliance/gst_india/utils/test_utils.py b/india_compliance/gst_india/utils/test_utils.py index 3bb931cc0..cea70bd5f 100644 --- a/india_compliance/gst_india/utils/test_utils.py +++ b/india_compliance/gst_india/utils/test_utils.py @@ -12,7 +12,7 @@ class TestUtils(FrappeTestCase): def setUpClass(cls): super().setUpClass() - # create 2023-2024 fiscal year + # create old fiscal years fiscal_year = frappe.new_doc("Fiscal Year") fiscal_year.update( { @@ -22,6 +22,15 @@ def setUpClass(cls): } ).insert(ignore_if_duplicate=True) + fiscal_year = frappe.new_doc("Fiscal Year") + fiscal_year.update( + { + "year_start_date": "2022-04-01", + "year_end_date": "2023-03-31", + "year": "2022-2023", + } + ).insert(ignore_if_duplicate=True) + @patch( "india_compliance.gst_india.utils.getdate", return_value=getdate("2023-06-20") ) From b96c7d86f6ca7a0a28fea6758df5c97b6e061919 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 21 Jun 2024 17:15:46 +0530 Subject: [PATCH 27/41] chore: resolve conflicts --- .../purchase_reconciliation_tool.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json index bb13ef3ee..ed9329843 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json @@ -150,11 +150,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], -<<<<<<< HEAD - "modified": "2023-10-12 16:20:03.501277", -======= "modified": "2024-06-20 13:19:57.316043", ->>>>>>> 714fdcf5 (fix(reco-tool): support date to last month for filtered mismatches) "modified_by": "Administrator", "module": "GST India", "name": "Purchase Reconciliation Tool", From 7bd55826f3fd080fa3e37a4afc2ae8e26d7635ef Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 12:13:04 +0530 Subject: [PATCH 28/41] feat: support ecommerce sale from GSTR-1 (backport: #2125) (#2281) * fix: custom fields and validations for section 14 * fix: failing linters * fix: failing linter in js file * fix: update patch numbering * fix: minor changes ton validations * fix: supply_liable field to virtual field and remove all backend validations * fix: depends on for supply_liable_to field * fix: added section-14 in gstr-1 report * fix: download json and excel for section-14 * fix: failing linters * fix: as per review * fix: as per review * fix: modified query for section_14 data * fix: as per review * fix: changes as per review * fix: gstr-1 columns * fix: re-write section-14 query * fix: query for section-14 * fix: escape account name for query * refactor: field names and functions related to ecommerce supply type in GST compliance modules * fix: formatting fix * chore: better label * refactor: section-14 query * chore: rename func, comments * fix: supecommerce categories --------- Co-authored-by: priyanshshah2442 Co-authored-by: ljain112 --- .../gst_india/constants/custom_fields.py | 12 ++ .../doctype/gst_settings/gst_settings.json | 11 +- .../gst_india/overrides/transaction.py | 21 +- .../gst_india/report/gstr_1/gstr_1.js | 1 + .../gst_india/report/gstr_1/gstr_1.py | 179 +++++++++++++++++- .../gst_india/utils/gstr_1/__init__.py | 5 + india_compliance/patches.txt | 3 +- .../post_install/set_default_gst_settings.py | 11 ++ ...enable_sales_through_ecommerce_operator.py | 14 ++ india_compliance/public/js/transaction.js | 29 +++ 10 files changed, 281 insertions(+), 5 deletions(-) create mode 100644 india_compliance/patches/v14/enable_sales_through_ecommerce_operator.py diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index c395295ea..628a026af 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -221,10 +221,22 @@ "label": "E-commerce GSTIN", "length": 15, "fieldtype": "Data", + "depends_on": "eval:gst_settings.enable_sales_through_ecommerce_operators", "insert_after": "gst_section", "print_hide": 1, "translatable": 0, }, + { + "fieldname": "ecommerce_supply_type", + "label": "E-commerce Supply Type", + "fieldtype": "Data", + "depends_on": "eval:gst_settings.enable_sales_through_ecommerce_operators && doc.ecommerce_gstin", + "insert_after": "ecommerce_gstin", + "print_hide": 1, + "translatable": 0, + "is_virtual": 1, + "read_only": 1, + }, { "fieldname": "gst_col_break", "fieldtype": "Column Break", diff --git a/india_compliance/gst_india/doctype/gst_settings/gst_settings.json b/india_compliance/gst_india/doctype/gst_settings/gst_settings.json index aee01737c..33e84782d 100644 --- a/india_compliance/gst_india/doctype/gst_settings/gst_settings.json +++ b/india_compliance/gst_india/doctype/gst_settings/gst_settings.json @@ -9,6 +9,7 @@ "hsn_wise_tax_breakup", "enable_reverse_charge_in_sales", "enable_overseas_transactions", + "enable_sales_through_ecommerce_operators", "round_off_gst_values", "require_supplier_invoice_no", "column_break_4", @@ -567,6 +568,12 @@ "hidden": 1, "label": "Is Retry e-Invoice/e-Waybill Pending" }, + { + "default": "0", + "fieldname": "enable_sales_through_ecommerce_operators", + "fieldtype": "Check", + "label": "Enable Sales through E-commerce Operators" + }, { "fieldname": "gstr_1_section_break", "fieldtype": "Section Break", @@ -609,7 +616,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-06-09 17:27:54.720233", + "modified": "2024-06-09 17:28:54.720233", "modified_by": "Administrator", "module": "GST India", "name": "GST Settings", @@ -635,7 +642,7 @@ } ], "quick_entry": 1, - "sort_field": "modified", + "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 05d33be0e..c510ba0f3 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -32,6 +32,7 @@ validate_gst_category, validate_gstin, ) +from india_compliance.gst_india.utils.gstr_1 import SUPECOM from india_compliance.income_tax_india.overrides.tax_withholding_category import ( get_tax_withholding_accounts, ) @@ -1515,7 +1516,6 @@ def validate_transaction(doc, method=None): validate_ecommerce_gstin(doc) validate_gst_category(doc.gst_category, gstin) - valid_accounts = validate_gst_accounts(doc, is_sales_transaction) or () update_taxable_values(doc, valid_accounts) validate_item_wise_tax_detail(doc, valid_accounts) @@ -1529,10 +1529,12 @@ def before_print(doc, method=None, print_settings=None): ): return + set_ecommerce_supply_type(doc) set_gst_breakup(doc) def onload(doc, method=None): + if ( ignore_gst_validations(doc, throw=False) or not doc.place_of_supply @@ -1540,6 +1542,7 @@ def onload(doc, method=None): ): return + set_ecommerce_supply_type(doc) set_gst_breakup(doc) @@ -1615,3 +1618,19 @@ def before_update_after_submit(doc, method=None): update_taxable_values(doc, valid_accounts) validate_item_wise_tax_detail(doc, valid_accounts) update_gst_details(doc) + + +def set_ecommerce_supply_type(doc): + """ + - Set GSTR-1 E-commerce section for virtual field ecommerce_supply_type + """ + if doc.doctype not in ("Sales Order", "Sales Invoice", "Delivery Note"): + return + + if not doc.ecommerce_gstin: + return + + if doc.is_reverse_charge: + doc.ecommerce_supply_type = SUPECOM.US_9_5.value + else: + doc.ecommerce_supply_type = SUPECOM.US_52.value diff --git a/india_compliance/gst_india/report/gstr_1/gstr_1.js b/india_compliance/gst_india/report/gstr_1/gstr_1.js index e9b8286f1..e382c657a 100644 --- a/india_compliance/gst_india/report/gstr_1/gstr_1.js +++ b/india_compliance/gst_india/report/gstr_1/gstr_1.js @@ -12,6 +12,7 @@ const TYPES_OF_BUSINESS = { "NIL Rated": __("NIL RATED/EXEMPTED Invoices"), "Document Issued Summary": __("Document Issued Summary"), HSN: __("HSN-wise-summary of outward supplies"), + "Section 14": __("Supplies through E-Commerce Operators"), }; const url = "india_compliance.gst_india.report.gstr_1.gstr_1"; diff --git a/india_compliance/gst_india/report/gstr_1/gstr_1.py b/india_compliance/gst_india/report/gstr_1/gstr_1.py index 297bea0b9..ff4017fe1 100644 --- a/india_compliance/gst_india/report/gstr_1/gstr_1.py +++ b/india_compliance/gst_india/report/gstr_1/gstr_1.py @@ -8,7 +8,7 @@ import frappe from frappe import _ from frappe.query_builder import Criterion -from frappe.query_builder.functions import IfNull, Sum +from frappe.query_builder.functions import Date, IfNull, Sum from frappe.utils import cint, flt, formatdate, getdate from india_compliance.gst_india.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies import ( @@ -23,6 +23,7 @@ ) from india_compliance.gst_india.utils import get_escaped_name, get_gst_accounts_by_type from india_compliance.gst_india.utils.exporter import ExcelExporter +from india_compliance.gst_india.utils.gstr_1 import SUPECOM B2C_LIMIT = 2_50_000 @@ -38,6 +39,7 @@ "NIL Rated": "nil", "Document Issued Summary": "doc_issue", "HSN": "hsn", + "Section 14": "supeco", } INDEX_FOR_NIL_EXEMPT_DICT = {"Nil-Rated": 0, "Exempted": 1, "Non-GST": 2} @@ -107,6 +109,8 @@ def get_data(self): self.get_documents_issued_data() elif self.filters.get("type_of_business") == "HSN": self.data = get_hsn_data(self.filters, self.columns, self.gst_accounts) + elif self.filters.get("type_of_business") == "Section 14": + self.data = self.get_data_for_supplies_through_ecommerce_operators() elif self.invoices: for inv, items_based_on_rate in self.invoice_tax_rate_info.items(): invoice_details = self.invoices.get(inv) @@ -566,6 +570,106 @@ def get_invoice_wise_tax_details(self): return invoice_item_wise_tax_details + def get_data_for_supplies_through_ecommerce_operators(self): + si = frappe.qb.DocType("Sales Invoice") + si_item = frappe.qb.DocType("Sales Invoice Item") + taxes = frappe.qb.DocType("Sales Taxes and Charges") + igst_account = get_escaped_name(self.gst_accounts.igst_account) + cgst_account = get_escaped_name(self.gst_accounts.cgst_account) + sgst_account = get_escaped_name(self.gst_accounts.sgst_account) + cess_account = get_escaped_name(self.gst_accounts.cess_account) + cess_non_advol = get_escaped_name(self.gst_accounts.cess_non_advol_account) + + # subquery to get total taxable value + taxable_value_query = ( + frappe.qb.from_(si_item) + .select( + si_item.parent, + Sum(si_item.taxable_value).as_("total_taxable_value"), + ) + .groupby(si_item.parent) + ) + + # subquery to get total taxes + taxes_query = ( + frappe.qb.from_(taxes) + .select( + taxes.parent, + Sum( + Case() + .when( + taxes.account_head == igst_account, + taxes.tax_amount, + ) + .else_(0) + ).as_("total_igst_amount"), + Sum( + Case() + .when( + taxes.account_head == cgst_account, + taxes.tax_amount, + ) + .else_(0) + ).as_("total_cgst_amount"), + Sum( + Case() + .when( + taxes.account_head == sgst_account, + taxes.tax_amount, + ) + .else_(0) + ).as_("total_sgst_amount"), + Sum( + Case() + .when( + taxes.account_head.isin([cess_account, cess_non_advol]), + taxes.tax_amount, + ) + .else_(0) + ).as_("total_cess_amount"), + ) + .groupby(taxes.parent) + ) + + query = ( + frappe.qb.from_(si) + .left_join(taxable_value_query) + .on(si.name == taxable_value_query.parent) + .left_join(taxes_query) + .on(si.name == taxes_query.parent) + .select( + si.ecommerce_gstin, + Sum(IfNull(taxable_value_query.total_taxable_value, 0)).as_( + "total_taxable_value" + ), + Sum(IfNull(taxes_query.total_igst_amount, 0)).as_("total_igst_amount"), + Sum(IfNull(taxes_query.total_cgst_amount, 0)).as_("total_cgst_amount"), + Sum(IfNull(taxes_query.total_sgst_amount, 0)).as_("total_sgst_amount"), + Sum(IfNull(taxes_query.total_cess_amount, 0)).as_("total_cess_amount"), + Case() + .when(si.is_reverse_charge == 1, SUPECOM.US_9_5.value) + .else_(SUPECOM.US_52.value) + .as_("ecommerce_supply_type"), + ) + .where(si.is_opening == "No") + .where(si.docstatus == 1) + .where(IfNull(si.ecommerce_gstin, "") != "") + .where(IfNull(si.billing_address_gstin, "") != si.company_gstin) + .where( + Date(si.posting_date).between( + self.filters.from_date, self.filters.to_date + ) + ) + .where(si.company == self.filters.company) + .groupby(si.is_reverse_charge, si.ecommerce_gstin) + .orderby(si.ecommerce_gstin, si.is_reverse_charge) + ) + + if self.filters.company_gstin: + query = query.where(si.company_gstin == self.filters.company_gstin) + + return query.run(as_dict=True) + def get_columns(self): self.other_columns = [] self.tax_columns = [] @@ -1065,9 +1169,58 @@ def get_columns(self): elif self.filters.get("type_of_business") == "HSN": self.columns = get_hsn_columns() return + elif self.filters.get("type_of_business") == "Section 14": + self.columns = self.get_section_14_columns() + return self.columns = self.invoice_columns + self.tax_columns + self.other_columns + def get_section_14_columns(self): + return [ + { + "fieldname": "ecommerce_gstin", + "label": _("GSTIN of E-Commerce Operator"), + "fieldtype": "Data", + "width": 180, + }, + { + "fieldname": "total_taxable_value", + "label": _("Net value of supplies"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "total_igst_amount", + "label": _("Integrated tax"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "total_cgst_amount", + "label": _("Central tax"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "total_sgst_amount", + "label": _("State/UT tax"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "total_cess_amount", + "label": _("Cess"), + "fieldtype": "Currency", + "width": 120, + }, + { + "fieldname": "ecommerce_supply_type", + "label": _("Nature of Supply"), + "fieldtype": "Data", + "width": 180, + }, + ] + class GSTR11A11BData: def __init__(self, filters, gst_accounts): @@ -1499,6 +1652,10 @@ def get_json(type_of_business, gstin, data, filters): if type_of_business == "HSN": return get_hsn_wise_json_data(filters, data) + if type_of_business == "Section 14": + res.setdefault("superco", {}) + return get_section_14_json(res, data) + def set_gst_defaults(filters): if isinstance(filters, str): @@ -1836,6 +1993,26 @@ def get_document_issued_summary_json(data): return {"doc_det": doc_det} +def get_section_14_json(res, data): + out = res["superco"] + for item in data: + key = ( + "clttx" if item["ecommerce_supply_type"] == SUPECOM.US_52.value else "paytx" + ) + out.setdefault(key, []).append( + { + "etin": item["ecommerce_gstin"], + "suppval": item["total_taxable_value"], + "igst": item["total_igst_amount"], + "cgst": item["total_cgst_amount"], + "sgst": item["total_sgst_amount"], + "cess": item["total_cess_amount"], + } + ) + + return out + + def get_invoice_type(row): invoice_type = row.get("invoice_type") return ( diff --git a/india_compliance/gst_india/utils/gstr_1/__init__.py b/india_compliance/gst_india/utils/gstr_1/__init__.py index 378c110eb..0a9cb2f49 100644 --- a/india_compliance/gst_india/utils/gstr_1/__init__.py +++ b/india_compliance/gst_india/utils/gstr_1/__init__.py @@ -53,6 +53,11 @@ class GSTR1_SubCategory(Enum): SUPECOM_9_5 = "GST Payable on RCM by E-commerce Operator u/s 9(5)" +class SUPECOM(Enum): + US_9_5 = "Liable to pay tax u/s 9(5)" + US_52 = "Liable to collect tax u/s 52(TCS)" + + CATEGORY_SUB_CATEGORY_MAPPING = { GSTR1_Category.B2B: ( GSTR1_SubCategory.B2B_REGULAR, diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 5f104a13a..aafcbe972 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -3,7 +3,7 @@ execute:import frappe; frappe.delete_doc_if_exists("DocType", "GSTIN") [post_model_sync] india_compliance.patches.v14.set_default_for_overridden_accounts_setting -execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #47 +execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #48 execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() #7 execute:from india_compliance.income_tax_india.setup import create_custom_fields; create_custom_fields() #1 india_compliance.patches.post_install.remove_old_fields #1 @@ -51,3 +51,4 @@ india_compliance.patches.v14.update_default_gstr1_settings india_compliance.patches.v14.add_match_found_in_purchase_reconciliation_status india_compliance.patches.v14.unset_inward_supply_link_for_cancelled_purchase india_compliance.patches.v14.delete_not_generated_gstr_import_log +india_compliance.patches.v14.enable_sales_through_ecommerce_operator diff --git a/india_compliance/patches/post_install/set_default_gst_settings.py b/india_compliance/patches/post_install/set_default_gst_settings.py index 040bd7ba6..c0c276c3f 100644 --- a/india_compliance/patches/post_install/set_default_gst_settings.py +++ b/india_compliance/patches/post_install/set_default_gst_settings.py @@ -16,6 +16,7 @@ def execute(): enable_overseas_transactions(new_settings) enable_reverse_charge_in_sales(new_settings) enable_e_waybill_from_dn(new_settings) + enable_sales_through_ecommerce_operators(new_settings) if new_settings: frappe.db.set_single_value("GST Settings", new_settings) @@ -50,3 +51,13 @@ def enable_reverse_charge_in_sales(settings): settings["enable_reverse_charge_in_sales"] = 1 toggle_custom_fields(SALES_REVERSE_CHARGE_FIELDS, True) + + +def enable_sales_through_ecommerce_operators(settings=None): + if not frappe.db.exists( + "Sales Invoice", + {"ecommerce_gstin": ("not in", ("", None)), **POSTING_DATE_CONDITION}, + ): + return + + settings["enable_sales_through_ecommerce_operators"] = 1 diff --git a/india_compliance/patches/v14/enable_sales_through_ecommerce_operator.py b/india_compliance/patches/v14/enable_sales_through_ecommerce_operator.py new file mode 100644 index 000000000..0e2198432 --- /dev/null +++ b/india_compliance/patches/v14/enable_sales_through_ecommerce_operator.py @@ -0,0 +1,14 @@ +import frappe + +from india_compliance.patches.post_install.set_default_gst_settings import ( + enable_sales_through_ecommerce_operators, +) + + +def execute(): + new_settings = {} + + enable_sales_through_ecommerce_operators(new_settings) + + if new_settings: + frappe.db.set_single_value("GST Settings", new_settings) diff --git a/india_compliance/public/js/transaction.js b/india_compliance/public/js/transaction.js index 57909384a..646400498 100644 --- a/india_compliance/public/js/transaction.js +++ b/india_compliance/public/js/transaction.js @@ -22,6 +22,10 @@ for (const doctype of ["Sales Invoice", "Delivery Note"]) { ignore_port_code_validation(doctype); } +for (const doctype of ["Sales Invoice", "Sales Order", "Delivery Note"]) { + set_e_commerce_ecommerce_supply_type(doctype); +} + function fetch_gst_details(doctype) { const event_fields = [ "tax_category", @@ -298,3 +302,28 @@ function is_invoice_no_validation_required(transaction_type) { gst_settings.enable_e_waybill_from_pr) ); } + +function set_e_commerce_ecommerce_supply_type(doctype) { + const event_fields = ["ecommerce_gstin", "is_reverse_charge"]; + + const events = Object.fromEntries( + event_fields.map(field => [field, frm => _set_e_commerce_ecommerce_supply_type(frm)]) + ); + + frappe.ui.form.on(doctype, events); +} + +function _set_e_commerce_ecommerce_supply_type(frm) { + if (!gst_settings.enable_sales_through_ecommerce_operators) return; + + if (!frm.doc.ecommerce_gstin) { + frm.set_value("ecommerce_supply_type", ""); + return; + } + + if (frm.doc.is_reverse_charge) { + frm.set_value("ecommerce_supply_type", "Liable to pay tax u/s 9(5)"); + } else { + frm.set_value("ecommerce_supply_type", "Liable to collect tax u/s 52(TCS)"); + } +} From cf1a43e107aeda1d6a395af61350bfadf29b0ab5 Mon Sep 17 00:00:00 2001 From: Sanket322 <113279972+Sanket322@users.noreply.github.com> Date: Fri, 21 Jun 2024 17:49:52 +0530 Subject: [PATCH 29/41] fix: add gst_tax_type field (backport #2241) * fix: add gst_tax_type field * fix: added patch * fix: corrections based on test-cases * fix: changes as per review * resolved test-case * fix: fixing some codacy issues * refactor: patch, and usage of tax types * fix: changes as per review * fix: changes as per review * chore: rename and file position --------- Co-authored-by: ljain112 --- .../gst_india/constants/__init__.py | 4 + .../gst_india/constants/custom_fields.py | 14 ++ .../doctype/bill_of_entry/bill_of_entry.py | 17 +-- .../bill_of_entry_taxes.json | 9 +- .../doctype/gstr_3b_report/gstr_3b_report.py | 23 ++- .../purchase_reconciliation_tool/__init__.py | 24 +-- .../gst_india/overrides/ineligible_itc.py | 5 +- .../gst_india/overrides/payment_entry.py | 12 +- .../gst_india/overrides/purchase_invoice.py | 23 ++- .../gst_india/overrides/transaction.py | 139 ++++++++---------- .../gst_india/report/gstr_1/gstr_1.py | 19 +-- .../report/gstr_3b_details/gstr_3b_details.py | 25 ++-- .../hsn_wise_summary_of_outward_supplies.py | 48 +++--- india_compliance/gst_india/utils/__init__.py | 26 ++++ .../gst_india/utils/transaction_data.py | 27 ++-- india_compliance/hooks.py | 28 +++- india_compliance/install.py | 1 + india_compliance/patches.txt | 3 +- .../patches/post_install/set_gst_tax_type.py | 46 ++++++ 19 files changed, 277 insertions(+), 216 deletions(-) create mode 100644 india_compliance/patches/post_install/set_gst_tax_type.py diff --git a/india_compliance/gst_india/constants/__init__.py b/india_compliance/gst_india/constants/__init__.py index eff4a5ae0..f0bae3ad3 100644 --- a/india_compliance/gst_india/constants/__init__.py +++ b/india_compliance/gst_india/constants/__init__.py @@ -16,6 +16,10 @@ GST_TAX_TYPES = tuple(field[:-8] for field in GST_ACCOUNT_FIELDS) +GST_RCM_TAX_TYPES = tuple(tax_type + "_rcm" for tax_type in GST_TAX_TYPES) + +TAX_TYPES = (*GST_TAX_TYPES, *GST_RCM_TAX_TYPES) + GST_PARTY_TYPES = ("Customer", "Supplier", "Company") # Map for e-Invoice Supply Type diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index 628a026af..ff1185b7b 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -592,6 +592,20 @@ "translatable": 0, }, ], + ( + "Sales Taxes and Charges", + "Purchase Taxes and Charges", + "Advance Taxes and Charges", + ): [ + { + "fieldname": "gst_tax_type", + "label": "GST Tax Type", + "fieldtype": "Data", + "insert_after": "rate", + "read_only": 1, + "translatable": 0, + }, + ], "Purchase Invoice": [ { "fieldname": "gst_section", diff --git a/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py b/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py index 0f0fc18bb..d7d39efa3 100644 --- a/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py +++ b/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py @@ -13,6 +13,7 @@ from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.taxes_and_totals import get_round_off_applicable_accounts +from india_compliance.gst_india.constants import GST_TAX_TYPES from india_compliance.gst_india.overrides.ineligible_itc import ( update_landed_cost_voucher_for_gst_expense, update_regional_gl_entries, @@ -21,6 +22,7 @@ from india_compliance.gst_india.overrides.transaction import ( ItemGSTDetails, ItemGSTTreatment, + set_gst_tax_type, validate_charge_type_for_cess_non_advol_accounts, ) from india_compliance.gst_india.utils import get_gst_accounts_by_type @@ -41,12 +43,11 @@ def set_item_wise_tax_details(self): if ( not row.tax_amount or not row.item_wise_tax_rates - or row.account_head not in self.gst_account_map + or row.gst_tax_type not in GST_TAX_TYPES ): continue - account_type = self.gst_account_map[row.account_head] - tax = account_type[:-8] + tax = row.gst_tax_type tax_rate_field = f"{tax}_rate" tax_amount_field = f"{tax}_amount" @@ -113,6 +114,7 @@ def onload(self): def before_validate(self): self.set_taxes_and_totals() + set_gst_tax_type(self) def before_save(self): update_gst_details(self) @@ -287,9 +289,7 @@ def validate_taxes(self): ).format(tax.idx) ) - validate_charge_type_for_cess_non_advol_accounts( - [input_accounts.cess_non_advol_account], tax - ) + validate_charge_type_for_cess_non_advol_accounts(tax) if tax.charge_type != "Actual": continue @@ -306,9 +306,7 @@ def validate_taxes(self): # validating total tax total_tax = 0 - is_non_cess_advol = ( - tax.account_head == input_accounts.cess_non_advol_account - ) + is_non_cess_advol = tax.gst_tax_type == "cess_non_advol" for item, rate in item_wise_tax_rates.items(): multiplier = ( @@ -516,6 +514,7 @@ def set_missing_values(source, target): "charge_type": "On Net Total", "account_head": input_igst_account, "rate": rate, + "gst_tax_type": "igst", "description": description, }, ) diff --git a/india_compliance/gst_india/doctype/bill_of_entry_taxes/bill_of_entry_taxes.json b/india_compliance/gst_india/doctype/bill_of_entry_taxes/bill_of_entry_taxes.json index 84b11a9d0..ca72c9c7f 100644 --- a/india_compliance/gst_india/doctype/bill_of_entry_taxes/bill_of_entry_taxes.json +++ b/india_compliance/gst_india/doctype/bill_of_entry_taxes/bill_of_entry_taxes.json @@ -13,6 +13,7 @@ "account_head", "section_break_10", "rate", + "gst_tax_type", "column_break_pipk", "tax_amount", "total", @@ -89,11 +90,17 @@ "fieldtype": "Code", "hidden": 1, "label": "Item Wise Tax Rates" + }, + { + "fieldname": "gst_tax_type", + "fieldtype": "Data", + "label": "GST Tax Type", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2024-03-19 19:21:46.068235", + "modified": "2024-06-12 18:03:36.955731", "modified_by": "Administrator", "module": "GST India", "name": "Bill of Entry Taxes", diff --git a/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py b/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py index db9c4e591..d85388a5f 100644 --- a/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py +++ b/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py @@ -17,7 +17,6 @@ from india_compliance.gst_india.report.gstr_3b_details.gstr_3b_details import ( IneligibleITC, ) -from india_compliance.gst_india.utils import get_gst_accounts_by_type VALUES_TO_UPDATE = ["iamt", "camt", "samt", "csamt"] @@ -230,7 +229,6 @@ def get_itc_details(self): def update_imports_from_bill_of_entry(self, itc_details): boe = frappe.qb.DocType("Bill of Entry") boe_taxes = frappe.qb.DocType("Bill of Entry Taxes") - gst_accounts = get_gst_accounts_by_type(self.company, "Input") def _get_tax_amount(account_type): return ( @@ -245,12 +243,12 @@ def _get_tax_amount(account_type): ) & boe.company_gstin.eq(self.gst_details.get("gstin")) & boe.docstatus.eq(1) - & boe_taxes.account_head.eq(gst_accounts[account_type]) + & boe_taxes.gst_tax_type.eq(account_type) ) .run() )[0][0] or 0 - igst, cess = _get_tax_amount("igst_account"), _get_tax_amount("cess_account") + igst, cess = _get_tax_amount("igst"), _get_tax_amount("cess") itc_details.setdefault("Import Of Goods", {"iamt": 0, "csamt": 0}) itc_details["Import Of Goods"]["iamt"] += igst itc_details["Import Of Goods"]["csamt"] += cess @@ -316,12 +314,13 @@ def get_invoice_item_wise_tax_details(self, doctype): def set_item_wise_tax_details(self, docs): self.invoice_item_wise_tax_details = {} item_wise_details = {} - account_head_gst_map = {} - - for key, values in self.account_heads.items(): - for value in values: - if value is not None: - account_head_gst_map[value] = key + gst_tax_type_map = { + "cgst": "camt", + "sgst": "samt", + "igst": "iamt", + "cess": "csamt", + "cess_non_advol": "csamt", + } item_defaults = frappe._dict( { @@ -357,7 +356,7 @@ def set_item_wise_tax_details(self, docs): # Process tax details for tax in details["taxes"]: - gst_tax_type = account_head_gst_map.get(tax.account_head) + gst_tax_type = gst_tax_type_map.get(tax.gst_tax_type) if not gst_tax_type: continue @@ -451,7 +450,7 @@ def get_outward_tax_details(self, doctype): tax_details = frappe.db.sql( f""" SELECT - parent, account_head, item_wise_tax_detail + parent, item_wise_tax_detail, gst_tax_type FROM `tab{tax_template}` WHERE parenttype = %s and docstatus = 1 diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py index 0af13d91d..03ca4afd2 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py @@ -13,11 +13,7 @@ from frappe.utils import add_months, format_date, getdate, rounded from india_compliance.gst_india.constants import GST_TAX_TYPES -from india_compliance.gst_india.utils import ( - get_escaped_name, - get_gst_accounts_by_type, - get_party_for_gstin, -) +from india_compliance.gst_india.utils import get_party_for_gstin from india_compliance.gst_india.utils.gstr_2 import IMPORT_CATEGORY, ReturnType @@ -454,10 +450,8 @@ def get_query(self, additional_fields=None, is_return=False): return query def get_fields(self, additional_fields=None, is_return=False): - gst_accounts = get_gst_accounts_by_type(self.company, "Input") tax_fields = [ - self.query_tax_amount(account).as_(tax[:-8]) - for tax, account in gst_accounts.items() + self.query_tax_amount(tax_type).as_(tax_type) for tax_type in GST_TAX_TYPES ] fields = [ @@ -490,13 +484,12 @@ def get_fields(self, additional_fields=None, is_return=False): return fields - def query_tax_amount(self, account): - account = get_escaped_name(account) + def query_tax_amount(self, gst_tax_type): return Abs( Sum( Case() .when( - self.PI_TAX.account_head == account, + self.PI_TAX.gst_tax_type == gst_tax_type, self.PI_TAX.base_tax_amount_after_discount_amount, ) .else_(0) @@ -600,10 +593,8 @@ def get_query(self, additional_fields=None): return query def get_fields(self, additional_fields=None): - gst_accounts = get_gst_accounts_by_type(self.company, "Input") tax_fields = [ - self.query_tax_amount(account).as_(tax[:-8]) - for tax, account in gst_accounts.items() + self.query_tax_amount(tax_type).as_(tax_type) for tax_type in GST_TAX_TYPES ] fields = [ @@ -638,13 +629,12 @@ def get_fields(self, additional_fields=None): return fields - def query_tax_amount(self, account): - account = get_escaped_name(account) + def query_tax_amount(self, gst_tax_type): return Abs( Sum( Case() .when( - self.BOE_TAX.account_head == account, + self.BOE_TAX.gst_tax_type == gst_tax_type, self.BOE_TAX.tax_amount, ) .else_(0) diff --git a/india_compliance/gst_india/overrides/ineligible_itc.py b/india_compliance/gst_india/overrides/ineligible_itc.py index c53a318e9..3c332d806 100644 --- a/india_compliance/gst_india/overrides/ineligible_itc.py +++ b/india_compliance/gst_india/overrides/ineligible_itc.py @@ -7,10 +7,10 @@ ) from erpnext.stock import get_warehouse_account_map +from india_compliance.gst_india.constants import GST_TAX_TYPES from india_compliance.gst_india.overrides.transaction import ( is_indian_registered_company, ) -from india_compliance.gst_india.utils import get_gst_accounts_by_type class IneligibleITC: @@ -272,11 +272,10 @@ def update_ineligible_taxes(self, item): "Input SGST - FC": 50, } """ - gst_accounts = get_gst_accounts_by_type(self.doc.company, "Input").values() ineligible_taxes = frappe._dict() for tax in self.doc.taxes: - if tax.account_head not in gst_accounts: + if tax.gst_tax_type not in GST_TAX_TYPES: continue ineligible_taxes[tax.account_head] = self.get_item_tax_amount(item, tax) diff --git a/india_compliance/gst_india/overrides/payment_entry.py b/india_compliance/gst_india/overrides/payment_entry.py index 62d961468..15962adfa 100644 --- a/india_compliance/gst_india/overrides/payment_entry.py +++ b/india_compliance/gst_india/overrides/payment_entry.py @@ -7,6 +7,7 @@ from erpnext.accounts.utils import create_payment_ledger_entry from erpnext.controllers.accounts_controller import get_advance_payment_entries +from india_compliance.gst_india.constants import TAX_TYPES from india_compliance.gst_india.overrides.transaction import get_gst_details from india_compliance.gst_india.overrides.transaction import ( validate_backdated_transaction as _validate_backdated_transaction, @@ -14,10 +15,7 @@ from india_compliance.gst_india.overrides.transaction import ( validate_transaction as validate_transaction_for_advance_payment, ) -from india_compliance.gst_india.utils import ( - get_all_gst_accounts, - get_gst_accounts_by_type, -) +from india_compliance.gst_india.utils import get_all_gst_accounts @frappe.whitelist() @@ -92,9 +90,8 @@ def validate(doc, method=None): validate_transaction_for_advance_payment(doc, method) else: - gst_accounts = get_all_gst_accounts(doc.company) for row in doc.taxes: - if row.account_head in gst_accounts and row.tax_amount != 0: + if row.gst_tax_type in TAX_TYPES and row.tax_amount != 0: frappe.throw( _("GST Taxes are not allowed for Supplier Advance Payment Entry") ) @@ -116,9 +113,8 @@ def before_cancel(doc, method=None): def validate_backdated_transaction(doc, action="create"): - gst_accounts = get_gst_accounts_by_type(doc.company, "Output").values() for row in doc.taxes: - if row.account_head in gst_accounts and row.tax_amount != 0: + if row.gst_tax_type in TAX_TYPES and row.tax_amount != 0: _validate_backdated_transaction(doc, action=action) break diff --git a/india_compliance/gst_india/overrides/purchase_invoice.py b/india_compliance/gst_india/overrides/purchase_invoice.py index 9b37fc63b..72ec9870c 100644 --- a/india_compliance/gst_india/overrides/purchase_invoice.py +++ b/india_compliance/gst_india/overrides/purchase_invoice.py @@ -6,7 +6,7 @@ update_dashboard_with_gst_logs, ) from india_compliance.gst_india.overrides.transaction import validate_transaction -from india_compliance.gst_india.utils import get_gst_accounts_by_type, is_api_enabled +from india_compliance.gst_india.utils import is_api_enabled from india_compliance.gst_india.utils.e_waybill import get_e_waybill_info @@ -97,19 +97,17 @@ def update_itc_totals(doc, method=None): if doc.ineligibility_reason == "ITC restricted due to PoS rules": return - gst_accounts = get_gst_accounts_by_type(doc.company, "Input") - for tax in doc.get("taxes"): - if tax.account_head == gst_accounts.igst_account: + if tax.gst_tax_type == "igst": doc.itc_integrated_tax += flt(tax.base_tax_amount_after_discount_amount) - if tax.account_head == gst_accounts.sgst_account: + if tax.gst_tax_type == "sgst": doc.itc_state_tax += flt(tax.base_tax_amount_after_discount_amount) - if tax.account_head == gst_accounts.cgst_account: + if tax.gst_tax_type == "cgst": doc.itc_central_tax += flt(tax.base_tax_amount_after_discount_amount) - if tax.account_head == gst_accounts.cess_account: + if tax.gst_tax_type == "cess": doc.itc_cess_amount += flt(tax.base_tax_amount_after_discount_amount) @@ -175,11 +173,10 @@ def validate_with_inward_supply(doc): mismatch_fields["Taxable Value"] = doc._inward_supply.get("taxable_value") # mismatch for taxes - gst_accounts = get_gst_accounts_by_type(doc.company, "Input") for tax in ["cgst", "sgst", "igst", "cess"]: - tax_amount = get_tax_amount(doc.taxes, gst_accounts[tax + "_account"]) + tax_amount = get_tax_amount(doc.taxes, tax) if tax == "cess": - tax_amount += get_tax_amount(doc.taxes, gst_accounts.cess_non_advol_account) + tax_amount += get_tax_amount(doc.taxes, "cess_non_advol") if tax_amount == doc._inward_supply.get(tax): continue @@ -207,15 +204,15 @@ def validate_with_inward_supply(doc): ) -def get_tax_amount(taxes, account_head): - if not (taxes or account_head): +def get_tax_amount(taxes, gst_tax_type): + if not (taxes or gst_tax_type): return 0 return sum( [ tax.base_tax_amount_after_discount_amount for tax in taxes - if tax.account_head == account_head + if tax.gst_tax_type == gst_tax_type ] ) diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index c510ba0f3..659dac000 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -11,9 +11,11 @@ ) from india_compliance.gst_india.constants import ( + GST_RCM_TAX_TYPES, GST_TAX_TYPES, SALES_DOCTYPES, STATE_NUMBERS, + TAX_TYPES, ) from india_compliance.gst_india.constants.custom_fields import E_WAYBILL_INV_FIELDS from india_compliance.gst_india.doctype.gst_settings.gst_settings import ( @@ -22,7 +24,7 @@ from india_compliance.gst_india.doctype.gstin.gstin import get_and_validate_gstin_status from india_compliance.gst_india.utils import ( get_all_gst_accounts, - get_gst_accounts_by_tax_type, + get_gst_account_gst_tax_type_map, get_gst_accounts_by_type, get_hsn_settings, get_place_of_supply, @@ -60,7 +62,8 @@ def set_gst_breakup(doc): doc.gst_breakup_table = gst_breakup_html.replace("\n", "").replace(" ", "") -def update_taxable_values(doc, valid_accounts): +def update_taxable_values(doc): + if doc.doctype not in DOCTYPES_WITH_GST_DETAIL: return @@ -73,7 +76,7 @@ def update_taxable_values(doc, valid_accounts): row for row in doc.taxes if row.base_tax_amount_after_discount_amount - and row.account_head in valid_accounts + and row.gst_tax_type in TAX_TYPES ): reference_row_index = next( ( @@ -81,7 +84,7 @@ def update_taxable_values(doc, valid_accounts): for row in doc.taxes if row.base_tax_amount_after_discount_amount and row.charge_type == "On Previous Row Total" - and row.account_head in valid_accounts + and row.gst_tax_type in TAX_TYPES ), None, # ignore accounts after GST accounts ) @@ -124,22 +127,20 @@ def update_taxable_values(doc, valid_accounts): item.taxable_value += total_charges - apportioned_charges -def validate_item_wise_tax_detail(doc, gst_accounts): +def validate_item_wise_tax_detail(doc): if doc.doctype not in DOCTYPES_WITH_GST_DETAIL: return item_taxable_values = defaultdict(float) item_qty_map = defaultdict(float) - cess_non_advol_account = get_gst_accounts_by_tax_type(doc.company, "cess_non_advol") - for row in doc.items: item_key = row.item_code or row.item_name item_taxable_values[item_key] += row.taxable_value item_qty_map[item_key] += row.qty for row in doc.taxes: - if row.account_head not in gst_accounts: + if not row.gst_tax_type: continue if row.charge_type != "Actual": @@ -160,7 +161,7 @@ def validate_item_wise_tax_detail(doc, gst_accounts): # Sales Invoice is created with manual tax amount. So, when a sales return is created, # the tax amount is not recalculated, causing the issue. - is_cess_non_advol = row.account_head in cess_non_advol_account + is_cess_non_advol = "cess_non_advol" in row.gst_tax_type multiplier = ( item_qty_map.get(item_name, 0) if is_cess_non_advol @@ -298,6 +299,17 @@ def get_valid_accounts(company, *, for_sales=False, for_purchase=False, throw=Tr return all_valid_accounts, intra_state_accounts, inter_state_accounts +def set_gst_tax_type(doc, method=None): + if not doc.taxes: + return + + gst_tax_account_map = get_gst_account_gst_tax_type_map() + + for tax in doc.taxes: + # Setting as None if not GST Account + tax.gst_tax_type = gst_tax_account_map.get(tax.account_head) + + def validate_gst_accounts(doc, is_sales_transaction=False): """ Validate GST accounts @@ -314,19 +326,18 @@ def validate_gst_accounts(doc, is_sales_transaction=False): rows_to_validate := [ row for row in doc.taxes - if row.tax_amount and row.account_head in get_all_gst_accounts(doc.company) + if row.tax_amount and row.gst_tax_type and row.gst_tax_type in TAX_TYPES ] ): return # Helper functions - - def _get_matched_idx(rows_to_search, account_head_list): + def _get_matched_idx(rows_to_search, gst_tax_type_list): return next( ( row.idx for row in rows_to_search - if row.account_head in account_head_list + if row.gst_tax_type in gst_tax_type_list ), None, ) @@ -334,15 +345,6 @@ def _get_matched_idx(rows_to_search, account_head_list): def _throw(message, title=None): frappe.throw(message, title=title or _("Invalid GST Account")) - all_valid_accounts, intra_state_accounts, inter_state_accounts = get_valid_accounts( - doc.company, - for_sales=is_sales_transaction, - for_purchase=not is_sales_transaction, - ) - cess_non_advol_accounts = get_gst_accounts_by_tax_type( - doc.company, "cess_non_advol" - ) - # Company GSTIN = Party GSTIN party_gstin = ( doc.billing_address_gstin if is_sales_transaction else doc.supplier_gstin @@ -350,7 +352,7 @@ def _throw(message, title=None): if ( party_gstin and doc.company_gstin == party_gstin - and (idx := _get_matched_idx(rows_to_validate, all_valid_accounts)) + and (idx := _get_matched_idx(rows_to_validate, TAX_TYPES)) ): _throw( _( @@ -362,7 +364,7 @@ def _throw(message, title=None): # Sales / Purchase Validations if is_sales_transaction: if is_export_without_payment_of_gst(doc) and ( - idx := _get_matched_idx(rows_to_validate, all_valid_accounts) + idx := _get_matched_idx(rows_to_validate, TAX_TYPES) ): _throw( _( @@ -372,7 +374,7 @@ def _throw(message, title=None): ) if doc.get("is_reverse_charge") and ( - idx := _get_matched_idx(rows_to_validate, all_valid_accounts) + idx := _get_matched_idx(rows_to_validate, TAX_TYPES) ): _throw( _( @@ -381,7 +383,7 @@ def _throw(message, title=None): ) elif doc.gst_category == "Registered Composition" and ( - idx := _get_matched_idx(rows_to_validate, all_valid_accounts) + idx := _get_matched_idx(rows_to_validate, TAX_TYPES) ): _throw( _( @@ -393,7 +395,7 @@ def _throw(message, title=None): elif not doc.is_reverse_charge: if idx := _get_matched_idx( rows_to_validate, - get_gst_accounts_by_type(doc.company, "Reverse Charge").values(), + GST_RCM_TAX_TYPES, ): _throw( _( @@ -403,7 +405,7 @@ def _throw(message, title=None): ) if not doc.supplier_gstin and ( - idx := _get_matched_idx(rows_to_validate, all_valid_accounts) + idx := _get_matched_idx(rows_to_validate, TAX_TYPES) ): _throw( _( @@ -415,6 +417,12 @@ def _throw(message, title=None): is_inter_state = is_inter_state_supply(doc) previous_row_references = set() + all_valid_accounts, intra_state_accounts, inter_state_accounts = get_valid_accounts( + doc.company, + for_sales=is_sales_transaction, + for_purchase=not is_sales_transaction, + ) + for row in rows_to_validate: account_head = row.account_head if account_head not in all_valid_accounts: @@ -449,12 +457,11 @@ def _throw(message, title=None): ).format(row.idx), title=_("Invalid Charge Type"), ) - if row.charge_type == "On Previous Row Total": previous_row_references.add(row.row_id) # validating charge type "On Item Quantity" and non_cess_advol_account - validate_charge_type_for_cess_non_advol_accounts(cess_non_advol_accounts, row) + validate_charge_type_for_cess_non_advol_accounts(row) used_accounts = set(row.account_head for row in rows_to_validate) if not is_inter_state: @@ -475,15 +482,12 @@ def _throw(message, title=None): ), title=_("Invalid Reference Row"), ) - for row in doc.get("items") or []: if not row.item_tax_template: continue - for account in used_accounts: if account in row.item_tax_rate: continue - frappe.msgprint( _( "Item Row #{0}: GST Account {1} is missing in Item Tax Template {2}" @@ -492,8 +496,6 @@ def _throw(message, title=None): indicator="orange", ) - return all_valid_accounts - def validate_tax_accounts_for_non_gst(doc): """GST Tax Accounts should not be charged for Non GST Items""" @@ -509,10 +511,9 @@ def validate_tax_accounts_for_non_gst(doc): ) -def validate_charge_type_for_cess_non_advol_accounts(cess_non_advol_accounts, tax_row): - if ( - tax_row.charge_type == "On Item Quantity" - and tax_row.account_head not in cess_non_advol_accounts +def validate_charge_type_for_cess_non_advol_accounts(tax_row): + if tax_row.charge_type == "On Item Quantity" and ( + tax_row.gst_tax_type not in ("cess_non_advol", "cess_non_advol_rcm") ): frappe.throw( _( @@ -522,9 +523,8 @@ def validate_charge_type_for_cess_non_advol_accounts(cess_non_advol_accounts, ta title=_("Invalid Charge Type"), ) - if ( - tax_row.charge_type not in ["On Item Quantity", "Actual"] - and tax_row.account_head in cess_non_advol_accounts + if tax_row.charge_type not in ["On Item Quantity", "Actual"] and ( + "cess_non_advol" in tax_row.gst_tax_type ): frappe.throw( _( @@ -1030,19 +1030,13 @@ def validate_reverse_charge_transaction(doc, method=None): if not doc.is_reverse_charge: return - reverse_charge_accounts = get_gst_accounts_by_type( - doc.company, "Reverse Charge" - ).values() - - input_gst_accounts = get_gst_accounts_by_type(doc.company, "Input").values() - for tax in doc.get("taxes"): - if tax.account_head in input_gst_accounts: + if tax.gst_tax_type in GST_TAX_TYPES: if tax.add_deduct_tax == "Add": base_gst_tax += tax.base_tax_amount_after_discount_amount else: base_gst_tax += tax.base_tax_amount_after_discount_amount - elif tax.account_head in reverse_charge_accounts: + elif tax.gst_tax_type in GST_RCM_TAX_TYPES: if tax.add_deduct_tax == "Add": base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount else: @@ -1076,14 +1070,11 @@ def get(self, docs, doctype, company): """ Return Item GST Details for a list of documents """ - self.set_gst_accounts_and_item_defaults(doctype, company) + self.get_item_defaults() self.set_tax_amount_precisions(doctype) response = frappe._dict() - if not self.gst_account_map: - return response - for doc in docs: self.doc = doc if not doc.get("items") or not doc.get("taxes"): @@ -1104,23 +1095,12 @@ def update(self, doc): if not self.doc.get("items"): return - self.set_gst_accounts_and_item_defaults(doc.doctype, doc.company) - if not self.gst_account_map: - return - + self.get_item_defaults() self.set_tax_amount_precisions(doc.doctype) self.set_item_wise_tax_details() self.update_item_tax_details() - def set_gst_accounts_and_item_defaults(self, doctype, company): - if doctype in SALES_DOCTYPES: - account_type = "Output" - else: - account_type = "Input" - - gst_account_map = get_gst_accounts_by_type(company, account_type, throw=False) - self.gst_account_map = {v: k for k, v in gst_account_map.items()} - + def get_item_defaults(self): item_defaults = frappe._dict(count=0) for row in GST_TAX_TYPES: @@ -1161,13 +1141,12 @@ def set_item_wise_tax_details(self): for row in self.doc.taxes: if ( not row.base_tax_amount_after_discount_amount + or row.gst_tax_type not in GST_TAX_TYPES or not row.item_wise_tax_detail - or row.account_head not in self.gst_account_map ): continue - account_type = self.gst_account_map[row.account_head] - tax = account_type[:-8] + tax = row.gst_tax_type tax_rate_field = f"{tax}_rate" tax_amount_field = f"{tax}_amount" @@ -1278,10 +1257,7 @@ def set(self, doc): self.set_for_overseas() return - self.gst_accounts = get_all_gst_accounts(self.doc.company) - has_gst_accounts = any( - row.account_head in self.gst_accounts for row in self.doc.taxes - ) + has_gst_accounts = any(row.gst_tax_type in TAX_TYPES for row in self.doc.taxes) if not has_gst_accounts: self.set_for_no_taxes() @@ -1340,7 +1316,7 @@ def get_default_treatment(self): if row.charge_type in ("Actual", "On Item Quantity"): continue - if row.account_head not in self.gst_accounts: + if row.gst_tax_type not in GST_TAX_TYPES: continue if row.rate == 0: @@ -1516,9 +1492,10 @@ def validate_transaction(doc, method=None): validate_ecommerce_gstin(doc) validate_gst_category(doc.gst_category, gstin) - valid_accounts = validate_gst_accounts(doc, is_sales_transaction) or () - update_taxable_values(doc, valid_accounts) - validate_item_wise_tax_detail(doc, valid_accounts) + + validate_gst_accounts(doc, is_sales_transaction) + update_taxable_values(doc) + validate_item_wise_tax_detail(doc) def before_print(doc, method=None, print_settings=None): @@ -1614,9 +1591,9 @@ def before_update_after_submit(doc, method=None): if is_sales_transaction := doc.doctype in SALES_DOCTYPES: validate_hsn_codes(doc) - valid_accounts = validate_gst_accounts(doc, is_sales_transaction) or () - update_taxable_values(doc, valid_accounts) - validate_item_wise_tax_detail(doc, valid_accounts) + validate_gst_accounts(doc, is_sales_transaction) + update_taxable_values(doc) + validate_item_wise_tax_detail(doc) update_gst_details(doc) diff --git a/india_compliance/gst_india/report/gstr_1/gstr_1.py b/india_compliance/gst_india/report/gstr_1/gstr_1.py index ff4017fe1..dc62aa8c6 100644 --- a/india_compliance/gst_india/report/gstr_1/gstr_1.py +++ b/india_compliance/gst_india/report/gstr_1/gstr_1.py @@ -11,6 +11,7 @@ from frappe.query_builder.functions import Date, IfNull, Sum from frappe.utils import cint, flt, formatdate, getdate +from india_compliance.gst_india.constants.__init__ import GST_TAX_TYPES from india_compliance.gst_india.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies import ( get_columns as get_hsn_columns, ) @@ -108,7 +109,7 @@ def get_data(self): elif self.filters.get("type_of_business") == "Document Issued Summary": self.get_documents_issued_data() elif self.filters.get("type_of_business") == "HSN": - self.data = get_hsn_data(self.filters, self.columns, self.gst_accounts) + self.data = get_hsn_data(self.filters, self.columns) elif self.filters.get("type_of_business") == "Section 14": self.data = self.get_data_for_supplies_through_ecommerce_operators() elif self.invoices: @@ -509,7 +510,7 @@ def get_invoice_wise_tax_details(self): invoice_tax_details = frappe.db.sql( """ select - parent, account_head, item_wise_tax_detail + parent, account_head, item_wise_tax_detail,gst_tax_type from `tab%s` where parenttype = %s and docstatus = 1 @@ -521,11 +522,11 @@ def get_invoice_wise_tax_details(self): ) invoice_item_wise_tax_details = frappe._dict() - for parent, account, item_wise_tax_detail in invoice_tax_details: + for parent, account, item_wise_tax_detail, gst_tax_type in invoice_tax_details: if not item_wise_tax_detail: continue - if account not in self.gst_accounts.values(): + if gst_tax_type not in GST_TAX_TYPES: if "gst" in account.lower(): unidentified_gst_accounts.add(account) continue @@ -535,14 +536,8 @@ def get_invoice_wise_tax_details(self): except ValueError: continue - is_cess = account in ( - self.gst_accounts.cess_account, - self.gst_accounts.cess_non_advol_account, - ) - is_cgst_or_sgst = ( - account == self.gst_accounts.cgst_account - or account == self.gst_accounts.sgst_account - ) + is_cess = "cess" in gst_tax_type + is_cgst_or_sgst = gst_tax_type in ("cgst", "sgst") parent_dict = invoice_item_wise_tax_details.setdefault(parent, {}) for item_code, invoice_tax_details in item_wise_tax_detail.items(): diff --git a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py index 6bd5d5bbc..18c4af864 100644 --- a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py +++ b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py @@ -8,6 +8,7 @@ from frappe.query_builder.functions import Extract, Ifnull, IfNull, LiteralValue, Sum from frappe.utils import cint, flt, get_first_day, get_last_day +from india_compliance.gst_india.constants.__init__ import GST_TAX_TYPES from india_compliance.gst_india.utils import get_escaped_gst_accounts @@ -181,7 +182,7 @@ def get_itc_from_boe(self): Sum( Case() .when( - boe_taxes.account_head == self.gst_accounts.igst_account, + boe_taxes.gst_tax_type == "igst", boe_taxes.tax_amount, ) .else_(0) @@ -189,7 +190,7 @@ def get_itc_from_boe(self): Sum( Case() .when( - boe_taxes.account_head == self.gst_accounts.cess_account, + boe_taxes.gst_tax_type == "cess", boe_taxes.tax_amount, ) .else_(0) @@ -489,11 +490,11 @@ def get_ineligible_itc_due_to_pos_for_purchase(self, group_by="name"): taxes = frappe.qb.DocType("Purchase Taxes and Charges") # utility function - def get_tax_case_statement(account, alias): + def get_tax_case_statement(gst_tax_types, alias): return Sum( Case() .when( - taxes.account_head.isin(account), + taxes.gst_tax_type.isin(gst_tax_types), taxes.base_tax_amount_after_discount_amount, ) .else_(0) @@ -509,18 +510,18 @@ def get_tax_case_statement(account, alias): pi.name.as_("voucher_no"), pi.posting_date, pi.ineligibility_reason.as_("itc_classification"), - get_tax_case_statement([self.gst_accounts.igst_account], "iamt"), - get_tax_case_statement([self.gst_accounts.cgst_account], "camt"), - get_tax_case_statement([self.gst_accounts.sgst_account], "camt"), + get_tax_case_statement(["igst"], "iamt"), + get_tax_case_statement(["cgst"], "camt"), + get_tax_case_statement(["sgst"], "camt"), get_tax_case_statement( [ - self.gst_accounts.cess_account, - self.gst_accounts.cess_non_advol_account, + "cess", + "cess_non_advol", ], "csamt", ), ) - .where(taxes.account_head.isin(list(self.gst_accounts.values()))) + .where(taxes.gst_tax_type.isin(GST_TAX_TYPES)) .where( IfNull(pi.ineligibility_reason, "") == "ITC restricted due to PoS rules" ) @@ -568,7 +569,7 @@ def get_for_bill_of_entry(self, group_by="name"): Sum( Case() .when( - boe_taxes.account_head == self.gst_accounts.igst_account, + boe_taxes.gst_tax_type == "igst", boe_taxes.tax_amount, ) .else_(0) @@ -576,7 +577,7 @@ def get_for_bill_of_entry(self, group_by="name"): Sum( Case() .when( - boe_taxes.account_head == self.gst_accounts.cess_account, + boe_taxes.gst_tax_type == "cess", boe_taxes.tax_amount, ) .else_(0) diff --git a/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 670446e0b..10fac50d3 100644 --- a/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -10,7 +10,8 @@ from frappe.utils import flt, getdate import erpnext -from india_compliance.gst_india.utils import get_gst_accounts_by_type, get_gst_uom +from india_compliance.gst_india.constants import GST_TAX_TYPES +from india_compliance.gst_india.utils import get_gst_uom def execute(filters=None): @@ -20,33 +21,19 @@ def execute(filters=None): validate_filters(filters) columns = get_columns() - output_gst_accounts_dict = get_gst_accounts_by_type(filters.company, "Output") - data = get_hsn_data(filters, columns, output_gst_accounts_dict) + data = get_hsn_data(filters, columns) return columns, data -def get_hsn_data(filters, columns, output_gst_accounts_dict): - output_gst_accounts = set() +def get_hsn_data(filters, columns): non_cess_accounts = ["igst_account", "cgst_account", "sgst_account"] tax_columns = non_cess_accounts + ["cess_account"] - for account_type, account_name in output_gst_accounts_dict.items(): - if not account_name: - continue - - output_gst_accounts.add(account_name) - company_currency = erpnext.get_company_currency(filters.company) item_list = get_items(filters) - itemised_tax = get_item_taxes( - item_list, - columns, - company_currency, - output_gst_accounts, - output_gst_accounts_dict, - ) + itemised_tax = get_item_taxes(item_list, company_currency) data = [] added_item = set() @@ -235,9 +222,7 @@ def get_items(filters): return items -def get_item_taxes( - item_list, columns, company_currency, output_gst_accounts, output_gst_accounts_dict -): +def get_item_taxes(item_list, company_currency): if not item_list: return [] @@ -260,19 +245,19 @@ def get_item_taxes( frappe.qb.from_(doctype) .select( doctype.parent, - doctype.account_head, + doctype.gst_tax_type, doctype.item_wise_tax_detail, doctype.base_tax_amount_after_discount_amount, ) .where(doctype.parenttype == "Sales Invoice") .where(doctype.docstatus == 1) .where(doctype.parent.isin(invoice_numbers)) - .where(doctype.account_head.isin(output_gst_accounts)) + .where(doctype.gst_tax_type.isin(GST_TAX_TYPES)) ).run() - gst_account_map = {value: key for key, value in output_gst_accounts_dict.items()} + gst_account_map = get_gst_account_tax_type_map() - for parent, account_head, item_wise_tax_detail, tax_amount in tax_details: + for parent, gst_tax_type, item_wise_tax_detail, tax_amount in tax_details: if not item_wise_tax_detail: continue @@ -284,7 +269,7 @@ def get_item_taxes( continue item_taxes = itemised_tax.setdefault((parent, item_code), {}) - item_taxes[gst_account_map.get(account_head)] = frappe._dict( + item_taxes[gst_account_map.get(gst_tax_type)] = frappe._dict( tax_rate=flt(tax_rate, 2), tax_amount=flt(tax_amount, tax_amount_precision), ) @@ -295,6 +280,17 @@ def get_item_taxes( return itemised_tax +def get_gst_account_tax_type_map(): + gst_account_map = {} + + for tax_type in GST_TAX_TYPES: + if "cess" in tax_type: + gst_account_map[tax_type] = "cess_account" + else: + gst_account_map[tax_type] = tax_type + "_account" + return gst_account_map + + def get_merged_data(columns, data): merged_hsn_dict = {} diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index 9a2d21858..f04c15f18 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -544,6 +544,32 @@ def get_gst_accounts_by_tax_type(company, tax_type, throw=True): ) +def get_gst_account_gst_tax_type_map(): + """ + - Returns gst_account by tax_type for all the companies + - Eg.: {"Input Tax SGST - _TIRC": "sgst", "Input Tax CGST - _TIRC": "cgst"} + + """ + + gst_account_map = frappe._dict() + settings = frappe.get_cached_doc("GST Settings", "GST Settings") + + for row in settings.gst_accounts: + for account in GST_ACCOUNT_FIELDS: + account_value = row.get(account) + + if not account_value: + continue + + account_key = account[:-8] + if "Reverse Charge" in row.get("account_type"): + account_key = account_key + "_rcm" + + gst_account_map[account_value] = account_key + + return gst_account_map + + @frappe.whitelist() def get_all_gst_accounts(company): """ diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index 9983ea7af..9db820ea2 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -14,7 +14,6 @@ VEHICLE_TYPES, ) from india_compliance.gst_india.utils import ( - get_gst_accounts_by_type, get_gst_uom, get_validated_country_code, validate_invoice_number, @@ -37,24 +36,17 @@ def __init__(self, doc): self.sandbox_mode = self.settings.sandbox_mode self.transaction_details = frappe._dict() - gst_type = "Output" self.party_name_field = "customer_name" + self.is_purchase_rcm = False if self.doc.doctype in ("Purchase Invoice", "Purchase Receipt"): self.party_name_field = "supplier_name" - if self.doc.is_reverse_charge != 1: - # for with reverse charge, gst_type is Output - # this will ensure zero taxes in transaction details - gst_type = "Input" + if self.doc.is_reverse_charge == 1: + # for with reverse charge in purchase, do not compute taxes + self.is_purchase_rcm = True self.party_name = self.doc.get(self.party_name_field) - # "CGST Account - TC": "cgst_account" - self.gst_accounts = { - v: k - for k, v in get_gst_accounts_by_type(self.doc.company, gst_type).items() - } - def set_transaction_details(self): rounding_adjustment = self.rounded(self.doc.base_rounding_adjustment) if self.doc.is_return: @@ -123,11 +115,12 @@ def update_transaction_tax_details(self): for row in self.doc.taxes: if ( not row.base_tax_amount_after_discount_amount - or row.account_head not in self.gst_accounts + or self.is_purchase_rcm + or row.gst_tax_type not in GST_TAX_TYPES ): continue - tax = self.gst_accounts[row.account_head][:-8] + tax = row.gst_tax_type self.transaction_details[f"total_{tax}_amount"] = abs( self.rounded(row.base_tax_amount_after_discount_amount) ) @@ -334,12 +327,12 @@ def update_item_tax_details(self, item_details, item): for row in self.doc.taxes: if ( not row.base_tax_amount_after_discount_amount - or row.account_head not in self.gst_accounts + or self.is_purchase_rcm + or row.gst_tax_type not in GST_TAX_TYPES ): continue - # Remove '_account' from 'cgst_account' - tax = self.gst_accounts[row.account_head][:-8] + tax = row.gst_tax_type tax_rate = self.rounded( frappe.parse_json(row.item_wise_tax_detail).get( item.item_code or item.item_name diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index 0924ffb11..05f7f4957 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -103,6 +103,7 @@ "before_print": "india_compliance.gst_india.overrides.transaction.before_print", "before_save": "india_compliance.gst_india.overrides.transaction.update_gst_details", "before_submit": "india_compliance.gst_india.overrides.transaction.update_gst_details", + "before_validate": "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -123,6 +124,7 @@ }, "Payment Entry": { "onload": "india_compliance.gst_india.overrides.payment_entry.onload", + "before_validate": "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", "validate": "india_compliance.gst_india.overrides.payment_entry.validate", "on_submit": "india_compliance.gst_india.overrides.payment_entry.on_submit", "on_update_after_submit": "india_compliance.gst_india.overrides.payment_entry.on_update_after_submit", @@ -134,7 +136,10 @@ "india_compliance.gst_india.overrides.transaction.onload", ], "before_print": "india_compliance.gst_india.overrides.transaction.before_print", - "before_validate": "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "before_validate": [ + "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", + ], "validate": "india_compliance.gst_india.overrides.purchase_invoice.validate", "before_save": "india_compliance.gst_india.overrides.transaction.update_gst_details", "before_submit": [ @@ -149,7 +154,10 @@ "Purchase Order": { "onload": "india_compliance.gst_india.overrides.transaction.onload", "before_print": "india_compliance.gst_india.overrides.transaction.before_print", - "before_validate": "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "before_validate": [ + "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", + ], "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -166,7 +174,10 @@ "india_compliance.gst_india.overrides.purchase_receipt.onload", ], "before_print": "india_compliance.gst_india.overrides.transaction.before_print", - "before_validate": "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "before_validate": [ + "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", + ], "validate": "india_compliance.gst_india.overrides.purchase_receipt.validate", "before_save": "india_compliance.gst_india.overrides.transaction.update_gst_details", "before_submit": [ @@ -182,6 +193,9 @@ "india_compliance.gst_india.overrides.transaction.onload", ], "before_print": "india_compliance.gst_india.overrides.transaction.before_print", + "before_validate": [ + "india_compliance.gst_india.overrides.transaction.set_gst_tax_type" + ], "validate": "india_compliance.gst_india.overrides.sales_invoice.validate", "before_save": "india_compliance.gst_india.overrides.transaction.update_gst_details", "before_submit": "india_compliance.gst_india.overrides.transaction.update_gst_details", @@ -197,6 +211,7 @@ "Sales Order": { "onload": "india_compliance.gst_india.overrides.transaction.onload", "before_print": "india_compliance.gst_india.overrides.transaction.before_print", + "before_validate": "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -228,6 +243,7 @@ "POS Invoice": { "onload": "india_compliance.gst_india.overrides.transaction.onload", "before_print": "india_compliance.gst_india.overrides.transaction.before_print", + "before_validate": "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -237,6 +253,7 @@ "Quotation": { "onload": "india_compliance.gst_india.overrides.transaction.onload", "before_print": "india_compliance.gst_india.overrides.transaction.before_print", + "before_validate": "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -246,7 +263,10 @@ "Supplier Quotation": { "onload": "india_compliance.gst_india.overrides.transaction.onload", "before_print": "india_compliance.gst_india.overrides.transaction.before_print", - "before_validate": "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "before_validate": [ + "india_compliance.gst_india.overrides.transaction.before_validate_transaction", + "india_compliance.gst_india.overrides.transaction.set_gst_tax_type", + ], "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), diff --git a/india_compliance/install.py b/india_compliance/install.py index 03db81b0f..ef954b442 100644 --- a/india_compliance/install.py +++ b/india_compliance/install.py @@ -21,6 +21,7 @@ "update_gst_accounts", # this is an India Compliance patch, but needs priority "update_itc_amounts", ## India Compliance + "set_gst_tax_type", "update_state_name_to_puducherry", "rename_import_of_capital_goods", "update_hsn_code", diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index aafcbe972..943468917 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -3,7 +3,7 @@ execute:import frappe; frappe.delete_doc_if_exists("DocType", "GSTIN") [post_model_sync] india_compliance.patches.v14.set_default_for_overridden_accounts_setting -execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #48 +execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #49 execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() #7 execute:from india_compliance.income_tax_india.setup import create_custom_fields; create_custom_fields() #1 india_compliance.patches.post_install.remove_old_fields #1 @@ -52,3 +52,4 @@ india_compliance.patches.v14.add_match_found_in_purchase_reconciliation_status india_compliance.patches.v14.unset_inward_supply_link_for_cancelled_purchase india_compliance.patches.v14.delete_not_generated_gstr_import_log india_compliance.patches.v14.enable_sales_through_ecommerce_operator +india_compliance.patches.post_install.set_gst_tax_type diff --git a/india_compliance/patches/post_install/set_gst_tax_type.py b/india_compliance/patches/post_install/set_gst_tax_type.py new file mode 100644 index 000000000..3a2580746 --- /dev/null +++ b/india_compliance/patches/post_install/set_gst_tax_type.py @@ -0,0 +1,46 @@ +import frappe +from frappe.query_builder import Case + +from india_compliance.gst_india.utils import get_gst_account_gst_tax_type_map + +TAX_DOCTYPES = [ + "Sales Taxes and Charges", + "Purchase Taxes and Charges", + "Advance Taxes and Charges", + "Bill of Entry Taxes", +] + + +def execute(): + gst_tax_type_account_map = get_gst_account_gst_tax_type_map() + + if not gst_tax_type_account_map: + return + + gst_accounts_by_tax_type = {} + for account, tax_type in gst_tax_type_account_map.items(): + gst_accounts_by_tax_type.setdefault(tax_type, []).append(account) + + for tax_doctype in TAX_DOCTYPES: + update_documents(tax_doctype, gst_accounts_by_tax_type) + + +def update_documents(taxes_doctype, gst_accounts_by_tax_type): + taxes_doctype = frappe.qb.DocType(taxes_doctype) + + update_query = frappe.qb.update(taxes_doctype).where( + taxes_doctype.parenttype.notin( + ["Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"] + ) + ) + + conditions = Case() + + for gst_tax_account, gst_tax_name in gst_accounts_by_tax_type.items(): + conditions = conditions.when( + taxes_doctype.account_head.isin(gst_tax_name), gst_tax_account + ) + + conditions = conditions.else_(None) + + update_query.set(taxes_doctype.gst_tax_type, conditions).run() From 9573130f76274a2e9e061f48311cf32b45bcdbc9 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 12:49:24 +0530 Subject: [PATCH 30/41] fix: update taxable values for non gst items --- india_compliance/gst_india/overrides/transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 659dac000..540b4e174 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -563,7 +563,7 @@ def validate_items(doc, throw): items_with_duplicate_taxes.append(bold(row.item_code)) if not has_gst_items: - update_taxable_values(doc, []) + update_taxable_values(doc) validate_tax_accounts_for_non_gst(doc) return False From b92b0bd9b76da983bb3b24933dc12f764b22d0b7 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 13:05:51 +0530 Subject: [PATCH 31/41] fix: parse param correctly in py (cherry picked from commit dd9d551ca387e2ecc3d946e278549aa346f2d719) --- india_compliance/gst_india/client_scripts/company.js | 4 ++-- india_compliance/gst_india/overrides/company.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/company.js b/india_compliance/gst_india/client_scripts/company.js index a0594e0de..7cf469a40 100644 --- a/india_compliance/gst_india/client_scripts/company.js +++ b/india_compliance/gst_india/client_scripts/company.js @@ -27,13 +27,13 @@ frappe.ui.form.on(DOCTYPE, { frm.set_query("autofield", "bank_details_for_printing", (_, cdt, cdn) => { return { query: "india_compliance.gst_india.overrides.company.get_default_print_options", - params : {for_bank : true} + params : {for_bank : 1} } }); frm.set_query("autofield", "registration_details_for_printing", (_, cdt, cdn) => { return { query: "india_compliance.gst_india.overrides.company.get_default_print_options", - params : {for_bank : false} + params : {for_bank : 0} } }); }, diff --git a/india_compliance/gst_india/overrides/company.py b/india_compliance/gst_india/overrides/company.py index 0b87aba92..bd22a2255 100644 --- a/india_compliance/gst_india/overrides/company.py +++ b/india_compliance/gst_india/overrides/company.py @@ -229,8 +229,8 @@ def create_default_company_account( @frappe.whitelist() -def get_default_print_options(for_bank=True): - if for_bank: +def get_default_print_options(for_bank=1) -> list: + if int(for_bank): return ["Account No.", "Bank Name", "Branch", "IFSC Code", "UPI ID"] else: return ["MSME No.", "MSME Type", "LLPIN", "LUT No."] From 78ee46b9c6f9d929f37af26411e2733e0840aff5 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 13:08:08 +0530 Subject: [PATCH 32/41] fix: get correct tax amount for 3b (cherry picked from commit 686c852ae530446826768a847185f0c1c0ba750a) --- .../gst_india/report/gstr_3b_details/gstr_3b_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py index 18c4af864..69180e4a8 100644 --- a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py +++ b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py @@ -512,7 +512,7 @@ def get_tax_case_statement(gst_tax_types, alias): pi.ineligibility_reason.as_("itc_classification"), get_tax_case_statement(["igst"], "iamt"), get_tax_case_statement(["cgst"], "camt"), - get_tax_case_statement(["sgst"], "camt"), + get_tax_case_statement(["sgst"], "samt"), get_tax_case_statement( [ "cess", From 062fea2fdee2cc48724942d4d173ccd896cf3558 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 13:09:14 +0530 Subject: [PATCH 33/41] fix: generate correct web template for upi qr code (cherry picked from commit e6f1d1b078e251494b9ce6e62d414b1a7a3d3f9e) --- .../gst_india/web_template/upi_qr_code/upi_qr_code.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json b/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json index c4dfabea0..2256439e2 100644 --- a/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json +++ b/india_compliance/gst_india/web_template/upi_qr_code/upi_qr_code.json @@ -7,15 +7,15 @@ { "fieldname": "upi_qr_text", "fieldtype": "Text", - "label": "UPI Code", + "label": "UPI QR Code", "reqd": 0 } ], "idx": 0, - "modified": "2024-06-03 13:19:21.517387", + "modified": "2024-06-03 13:19:22.517387", "modified_by": "Administrator", "module": "GST India", - "name": "UPI QR", + "name": "UPI QR Code", "owner": "Administrator", "standard": 1, "template": "", From db1758cf467d675ffb8a96099a7341bc73417a3b Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 13:44:31 +0530 Subject: [PATCH 34/41] fix: set default print format for Sales Invoice (cherry picked from commit 55178883833184ceee4090f291b62dc4e1edb59f) # Conflicts: # india_compliance/patches.txt --- india_compliance/gst_india/setup/__init__.py | 23 ++++++++++++++++++++ india_compliance/patches.txt | 4 ++++ 2 files changed, 27 insertions(+) diff --git a/india_compliance/gst_india/setup/__init__.py b/india_compliance/gst_india/setup/__init__.py index a4fa622a8..c33322696 100644 --- a/india_compliance/gst_india/setup/__init__.py +++ b/india_compliance/gst_india/setup/__init__.py @@ -32,6 +32,7 @@ def after_install(): create_email_template() set_default_gst_settings() set_default_accounts_settings() + set_default_print_settings() create_hsn_codes() add_fields_to_item_variant_settings() @@ -260,6 +261,28 @@ def set_default_accounts_settings(): frappe.db.set_default("add_taxes_from_item_tax_template", 0) +def set_default_print_settings(): + sales_invoice_format = frappe.get_meta("Sales Invoice").default_print_format + + if sales_invoice_format: + return + + # print style + frappe.db.set_value("Print Settings", None, "print_style", "Modern") + + # print format + frappe.make_property_setter( + { + "doctype": "Sales Invoice", + "doctype_or_field": "DocType", + "property": "default_print_format", + "value": "GST Tax Invoice", + }, + validate_fields_for_doctype=False, + is_system_generated=False, + ) + + def show_accounts_settings_override_warning(): """ Show warning if Determine Address Tax Category From is set to something diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 943468917..f92578d63 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -53,3 +53,7 @@ india_compliance.patches.v14.unset_inward_supply_link_for_cancelled_purchase india_compliance.patches.v14.delete_not_generated_gstr_import_log india_compliance.patches.v14.enable_sales_through_ecommerce_operator india_compliance.patches.post_install.set_gst_tax_type +<<<<<<< HEAD +======= +execute from india_compliance.gst_india.setup import set_default_print_settings; set_default_print_settings() +>>>>>>> 55178883 (fix: set default print format for Sales Invoice) From d83b46d243ef90f2338ffb96893d33f85c68ad43 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 14:02:27 +0530 Subject: [PATCH 35/41] fix: don't get gstin list if company is not available (cherry picked from commit 892bc597b84f6ef50211c30f2fe714353e8567a5) --- india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js | 2 ++ india_compliance/gst_india/utils/__init__.py | 3 --- 2 files changed, 2 insertions(+), 3 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 35c04c52e..0858bcb36 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 @@ -2102,6 +2102,8 @@ async function set_default_company_gstin(frm) { frm.set_value("company_gstin", ""); const company = frm.doc.company; + if (!company) return; + const { message: gstin_list } = await frappe.call( "india_compliance.gst_india.utils.get_gstin_list", { party: company } diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index f04c15f18..90a9db238 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -95,9 +95,6 @@ def get_gstin_list(party, party_type="Company"): """ Returns a list the party's GSTINs. """ - if not party: - return - frappe.has_permission(party_type, doc=party, throw=True) gstin_list = frappe.get_all( From d23d3d009ae247e40886b18b692a843069b84cb7 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 14:04:04 +0530 Subject: [PATCH 36/41] chore: apply semgrep suggestion (cherry picked from commit bc0599d754073dc7bbe0c60a65583a3efc5653c4) --- india_compliance/gst_india/setup/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/gst_india/setup/__init__.py b/india_compliance/gst_india/setup/__init__.py index c33322696..4ba25ba33 100644 --- a/india_compliance/gst_india/setup/__init__.py +++ b/india_compliance/gst_india/setup/__init__.py @@ -268,7 +268,7 @@ def set_default_print_settings(): return # print style - frappe.db.set_value("Print Settings", None, "print_style", "Modern") + frappe.db.set_single_value("Print Settings", "print_style", "Modern") # print format frappe.make_property_setter( From 186855e1f00733d03276ae92f9791bb72b23072a Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 14:08:12 +0530 Subject: [PATCH 37/41] chore: patch format (cherry picked from commit 7c11563ffd710ad99605892875abb9bbb24426d7) # Conflicts: # india_compliance/patches.txt --- india_compliance/patches.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index f92578d63..f290ac418 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -54,6 +54,10 @@ india_compliance.patches.v14.delete_not_generated_gstr_import_log india_compliance.patches.v14.enable_sales_through_ecommerce_operator india_compliance.patches.post_install.set_gst_tax_type <<<<<<< HEAD +<<<<<<< HEAD ======= execute from india_compliance.gst_india.setup import set_default_print_settings; set_default_print_settings() >>>>>>> 55178883 (fix: set default print format for Sales Invoice) +======= +execute:from india_compliance.gst_india.setup import set_default_print_settings; set_default_print_settings() +>>>>>>> 7c11563f (chore: patch format) From 96ea718144ccdf27e11e7994abb76c54a9b9d881 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 15:10:51 +0530 Subject: [PATCH 38/41] fix: enhance gst invoice format (cherry picked from commit 8677f4db6579cce8b468d5c9f8664897e4cac409) --- .../print_format/gst_tax_invoice/gst_tax_invoice.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json b/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json index 5eba8216e..9c036e4c7 100644 --- a/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json +++ b/india_compliance/gst_india/print_format/gst_tax_invoice/gst_tax_invoice.json @@ -2,7 +2,7 @@ "absolute_value": 0, "align_labels_right": 0, "creation": "2017-07-04 16:26:21.120187", - "css": "/*css for pdf*/\n@media print{\n .payment_qr img {\n width:81px !important;\n height: 81px;\n }\n}\n\n.payment_qr img {\n width:81px !important;\n height: 81px;\n}\n.payment_qr{\n float:left;\n}\n \n \n/*general css*/ \nb{\n color : initial;\n}\n\np{\n font-size : 12px !important;\n word-break:break-all;\n margin:0px !important;\n}\n\ntable{\n width:100%;\n}\n\n\n/*section after heading of print format*/\ndiv[data-label=\"First Section\"] > div:nth-child(1){\n width:40% !important;\n padding-right:0px !important;\n padding-top:20px !important;\n}\ndiv[data-label=\"First Section\"] > div:nth-child(2) {\n width:34% !important;\n padding-right:0px !important;\n padding-left:6px !important;\n padding-top:20px !important;\n}\ndiv[data-label=\"First Section\"] > div:nth-child(3) {\n width:26% !important;\n padding-left:0px !important;\n}\n\n\n/*css for place of supply*/\ndiv[data-fieldname=\"place_of_supply\"]{\n padding-top:5px !important;\n padding-bottom:5px !important;\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div > label{\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div:nth-child(1){\n width:50% !important;\n line-height: 1;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div:nth-child(2){\n width:50% !important;\n line-height: 1;\n}\n\n\n/*css for posting date*/\ndiv[data-fieldname=\"posting_date\"]{\n margin-top:0px;\n margin-bottom:0px;\n}\ndiv[data-fieldname=\"posting_date\"] > div:nth-child(1){\n width:50% !important;\n line-height:1.6;\n}\ndiv[data-fieldname=\"posting_date\"] > div:nth-child(2){\n width:50% !important;\n line-height:1.6;\n}\n\n\n/*css for first section after heading second column*/\n.first_section_second_column{\n padding-bottom:5px !important;\n padding-top:5px !important;\n}\n\n\n/*css for first section after heading third column - qr code column*/\n.paid_unpaid{\n padding-bottom:5px !important;\n text-align:end;\n}\n.paid_unpaid > p{\n font-size:120% !important;\n line-height:1;\n}\n\n\n/*css for irn*/\n.print-format p.field-property{\n padding-top:5px !important;\n padding-bottom:5px !important;\n}\n\n\n/*css for due_date adn paid amount*/\ndiv[data-fieldname=\"due_date\"] > div{\n width:50%;\n}\ndiv[data-fieldname=\"due_date\"] > div > label {\n margin:0px ;\n}\ndiv[data-fieldname=\"due_date\"] > div:nth-child(2){\n width:50%;\n text-align:end;\n}\ndiv[data-fieldname=\"due_date\"]{\n margin-top:0px;\n margin-bottom:0px;\n}\ndiv[data-fieldname=\"paid_amount\"]{\n margin-top:0px;\n margin-bottom:0px;\n padding-top:5px;\n padding-bottom:5px;\n line-height:1;\n}\ndiv[data-fieldname=\"paid_amount\"] > div > label {\n margin:0px;\n}\n\n.data-field{\n margin-top:0px;\n margin-bottom:0px;\n}\n\n\n/*changing css of erpnext elements*/\n#taxes-section > .row > .col-xs-6:first-child {\n display: none;\n}\n\n#taxes-section > .row > .col-xs-6 {\n width: 100%;\n}\n\n#taxes-section > .row > .col-xs-6 > .row {\n padding-top:5px !important;\n padding-bottom:5px !important;\n line-height:1;\n}\n\n#taxes-section > .row > .col-xs-6 > .row > .col-xs-5 > label {\n margin:0px !important;\n}\n\ndiv[data-label=\"erpnext\"] > div:nth-child(2) > .row > .col-xs-5 > label {\n margin:0px !important;\n}\n\n.column-break > .row:first-child {\n line-height:1;\n padding-bottom:5px !important;\n}\n\ndiv[data-fieldname=\"grand_total\"] {\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"rounded_total\"] {\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"in_words\"] {\n margin-top:0px ;\n margin-bottom:0px;\n line-height:1;\n padding-top:5px;\n}\n\n\n/* last section of print format*/\n", + "css": "/*css for pdf*/\n@media print{\n .payment_qr img {\n width:81px !important;\n height: 81px;\n }\n}\n\n.payment_qr img {\n width:81px !important;\n height: 81px;\n}\n.payment_qr{\n float:left;\n}\n \n \n/*general css*/ \nb{\n color : initial;\n}\n\np{\n font-size : 12px !important;\n word-break:break-all;\n margin:0px !important;\n}\n\ntable{\n width:100%;\n}\n\n\n/*section after heading of print format*/\ndiv[data-label=\"First Section\"] > div:nth-child(1){\n width:40% !important;\n padding-right:0px !important;\n padding-top:20px !important;\n}\ndiv[data-label=\"First Section\"] > div:nth-child(2) {\n width:34% !important;\n padding-right:0px !important;\n padding-left:6px !important;\n padding-top:20px !important;\n}\ndiv[data-label=\"First Section\"] > div:nth-child(3) {\n width:26% !important;\n padding-left:0px !important;\n}\n\n\n/*css for place of supply*/\ndiv[data-fieldname=\"place_of_supply\"]{\n padding-top:5px !important;\n padding-bottom:5px !important;\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div > label{\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div:nth-child(1){\n width:50% !important;\n line-height: 1;\n}\ndiv[data-fieldname=\"place_of_supply\"] > div:nth-child(2){\n width:50% !important;\n line-height: 1;\n}\n\n\n/*css for posting date*/\ndiv[data-fieldname=\"posting_date\"]{\n margin-top:0px;\n margin-bottom:0px;\n}\ndiv[data-fieldname=\"posting_date\"] > div:nth-child(1){\n width:50% !important;\n line-height:1.6;\n}\ndiv[data-fieldname=\"posting_date\"] > div:nth-child(2){\n width:50% !important;\n line-height:1.6;\n}\n\n\n/*css for first section after heading second column*/\n.first_section_second_column{\n padding-bottom:5px !important;\n padding-top:5px !important;\n}\n\n\n/*css for first section after heading third column - qr code column*/\n.paid_unpaid{\n padding-bottom:5px !important;\n text-align:end;\n}\n.paid_unpaid > p{\n font-size:120% !important;\n line-height:1;\n}\n\n\n/*css for irn*/\n.print-format p.field-property{\n padding-top:5px !important;\n padding-bottom:5px !important;\n}\n\n/*css for company-details*/\ntable.company-details {\n width:66%;\n display: inline-block;\n}\n\ntable.company-details tbody {\n display: table;\n width:100%;\n}\n\ntable.company-details:nth-child(2) {\n width: 32%;\n}\n\n/*css for due_date adn paid amount*/\ndiv[data-fieldname=\"due_date\"] > div{\n width:50%;\n}\ndiv[data-fieldname=\"due_date\"] > div > label {\n margin:0px ;\n}\ndiv[data-fieldname=\"due_date\"] > div:nth-child(2){\n width:50%;\n text-align:end;\n}\ndiv[data-fieldname=\"due_date\"]{\n margin-top:0px;\n margin-bottom:0px;\n}\ndiv[data-fieldname=\"paid_amount\"]{\n margin-top:0px;\n margin-bottom:0px;\n padding-top:5px;\n padding-bottom:5px;\n line-height:1;\n}\ndiv[data-fieldname=\"paid_amount\"] > div > label {\n margin:0px;\n}\n\n.data-field{\n margin-top:0px;\n margin-bottom:0px;\n}\n\n\n/*changing css of erpnext elements*/\n#taxes-section > .row > .col-xs-6:first-child {\n display: none;\n}\n\n#taxes-section > .row > .col-xs-6 {\n width: 100%;\n}\n\n#taxes-section > .row > .col-xs-6 > .row {\n padding-top:5px !important;\n padding-bottom:5px !important;\n line-height:1;\n}\n\n#taxes-section > .row > .col-xs-6 > .row > .col-xs-5 > label {\n margin:0px !important;\n}\n\ndiv[data-label=\"erpnext\"] > div:nth-child(2) > .row > .col-xs-5 > label {\n margin:0px !important;\n}\n\n.column-break > .row:first-child {\n line-height:1;\n padding-bottom:5px !important;\n}\n\ndiv[data-fieldname=\"grand_total\"] {\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"rounded_total\"] {\n margin-top:0px !important;\n margin-bottom:0px !important;\n}\ndiv[data-fieldname=\"in_words\"] {\n margin-top:0px ;\n margin-bottom:0px;\n line-height:1;\n padding-top:5px;\n}\n\n\n/* last section of print format*/\n", "custom_format": 0, "disabled": 0, "doc_type": "Sales Invoice", @@ -10,14 +10,14 @@ "doctype": "Print Format", "font": "Default", "font_size": 14, - "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"{% set logo_for_printing = frappe.db.get_value(\\\"Company\\\",doc.company,\\\"logo_for_printing\\\") %}\\n\\n\\n \\n \\n \\n \\n \\n \\n
\\n {% if logo_for_printing != \\\"\\\" %}\\n
\\n \\n
\\n {% endif %}\\n
\\n

{{ doc.company }}

\\n
\\n
\\n

\\n {{ doc.select_print_heading if doc.select_print_heading else 'Tax Invoice' }}
\\n {{ doc.name }} \\n

\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"First Section\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n\\t\\n\\t{% if not doc.shipping_address or doc.address_display == doc.shipping_address %}\\n\\t\\t

Billing and Shipping Address

\\n\\t{% else %}\\n\\t\\t

Billed To

\\n\\t{% endif %} \\n\\t{% if doc.customer_name %}\\n\\t\\t

{{ doc.customer_name }}

\\n\\t{% endif %}\\n\\n\\t

{{ doc.address_display if doc.address_display else '' }}

\\n\\n\\t\\n\\t{% if doc.shipping_address and doc.address_display !=\\n\\t\\tdoc.shipping_address %}\\n\\t\\t

Shipped To

\\n\\t\\t\\t{% if doc.dispatch_address_name %}\\n\\t\\t\\t\\t

{{ doc.dispatch_address_name }}

\\n\\t\\t\\t{% endif %}\\n\\t\\t

{{ doc.shipping_address }}

\\n\\t{% endif %}\\n
\"}, {\"fieldname\": \"place_of_supply\", \"print_hide\": 0, \"label\": \"Place of Supply\"}, {\"fieldname\": \"is_reverse_charge\", \"print_hide\": 0, \"align\": \"left\", \"label\": \"Reverse Charge\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.contact_display or doc.contact_mobile or doc.contact_email %}\\n
\\n

Contact Person

\\n {% if doc.contact_display %}\\n

{{ doc.contact_display }}

\\n {% endif %} {% if doc.contact_mobile %}\\n

{{ doc.contact_mobile }}

\\n {% endif %} {% if doc.contact_email %}\\n

{{ doc.contact_email }}

\\n {% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.po_no %}\\n
\\n\\t

Purchase Order Reference

\\n\\t

{{ doc.po_no }}

\\n\\t{% if doc.po_date %}\\n\\t

dtd. {{ doc.get_formatted(\\\"po_date\\\") }}

\\n\\t{% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\r\\n{% set delivery_note_list = [] %}\\r\\n{% for item in doc.items %}\\r\\n {% if item.delivery_note and item.delivery_note not in delivery_note_list %}\\r\\n {% set _ = delivery_note_list.append(item.delivery_note) %}\\r\\n {% endif %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if delivery_note_list %}\\r\\n
\\r\\n

Delivery Note Reference

\\r\\n {% for dn in delivery_note_list %}\\r\\n {% set dn_date = frappe.db.get_value('Delivery Note', dn, 'posting_date') %}\\r\\n

{{ dn }} dtd. {{ frappe.utils.formatdate(dn_date, 'dd-MM-yyyy') }}

\\r\\n {% endfor %}\\r\\n
\\r\\n{% endif %}\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.transporter_name or doc.vehicle_no or doc.lr_no or doc.ewaybill %}\\n
\\n

Transporter Details

\\n {% if doc.transporter_name %}\\n

{{ doc.transporter_name }}

\\n {% endif %} \\n {% if doc.vehicle_no %}\\n

Vehicle No: {{ doc.vehicle_no }}

\\n {% endif %} \\n {% if doc.lr_no %}\\n

LR No: {{ doc.lr_no }}

\\n {% endif %} \\n {% if doc.ewaybill %}\\n

e-Waybill No: {{ doc.ewaybill }}

\\n {% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n \\n {% if doc.outstanding_amount !=0 %}\\n

\\n Unpaid Amount\\n

\\n

\\n {{ doc.get_formatted(\\\"outstanding_amount\\\") }}\\n

\\n {% else %}\\n

\\n Fully Paid\\n

\\n {% endif %}\\n
\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% if doc.irn %}\\r\\n{% set e_invoice_log = frappe.db.get_value( \\\"e-Invoice Log\\\", doc.irn,\\r\\n(\\\"invoice_data\\\", \\\"signed_qr_code\\\"), as_dict=True ) %}\\r\\n\\r\\n{% if e_invoice_log %}\\r\\n{% set invoice_data = json.loads(e_invoice_log.invoice_data) %}\\r\\n{% set date_format = frappe.db.get_single_value(\\\"System Settings\\\",'date_format').replace(\\\"mm\\\",\\\"MM\\\")\\r\\n%}\\r\\n\\r\\n
\\r\\n\\t\\r\\n\\t
\\r\\n\\t\\t{{ web_block('e-Invoice QR Code', values={'e_invoice_qr_text':\\r\\n\\t\\te_invoice_log.signed_qr_code }) }}\\r\\n\\t
\\r\\n\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tIRN : {{ doc.irn }}\\r\\n\\t

\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tAck No. {{ invoice_data.get(\\\"AckNo\\\") if doc.irn and invoice_data.get(\\\"AckNo\\\")\\r\\n\\t\\telse '' }}\\r\\n\\t

\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tAck Dt.\\r\\n\\t\\t{{ frappe.utils.format_datetime(invoice_data.get(\\\"AckDt\\\"),date_format) if doc.irn and\\r\\n\\t\\tinvoice_data.get(\\\"AckDt\\\") else '' }}\\r\\n\\t

\\r\\n
\\r\\n{% endif %}\\r\\n{% endif %}\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"items\", \"print_hide\": 0, \"label\": \"Items\", \"visible_columns\": [{\"fieldname\": \"serial_no\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"gst_hsn_code\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"description\", \"print_width\": \"200px\", \"print_hide\": 0}, {\"fieldname\": \"qty\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"amount\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"erpnext\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"align\": \"left\", \"label\": \"Due Date\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% set company_doc = frappe.get_doc(\\\"Company\\\",doc.company) %}\\r\\n{% set currency_format = frappe.db.get_single_value(\\\"System Settings\\\",\\\"number_format\\\") %}\\r\\n\\r\\n{% set bank_details = {} %}\\r\\n{% set upi_id = namespace(value=\\\"\\\") %}\\r\\n{% set bank_name = namespace(value=\\\"\\\") %}\\r\\n{% set paid_amount = namespace(value=0) %}\\r\\n\\r\\n{% for doc in company_doc.bank_details_for_printing %}\\r\\n\\t{% if doc.autofield == \\\"UPI ID\\\" %}\\r\\n\\t\\t{% set upi_id.value = doc.autofield_value %}\\r\\n\\t{% elif doc.autofield == \\\"Bank Name\\\" %}\\r\\n\\t\\t{% set bank_name.value = doc.autofield_value %}\\r\\n\\t{% else %}\\r\\n\\t\\t{% set _ = bank_details.update({doc.autofield: doc.autofield_value}) %}\\r\\n\\t{% endif %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if doc.rounded_total %}\\r\\n\\t{% set paid_amount.value = frappe.utils.cint(doc.rounded_total) - frappe.utils.cint(doc.outstanding_amount) %}\\r\\n{% else %}\\r\\n\\t{% set paid_amount.value = frappe.utils.cint(doc.grand_total) - frappe.utils.cint(doc.outstanding_amount) %}\\r\\n{% endif %}\\r\\n{% set paid_amount.value = frappe.utils.fmt_money(amount=paid_amount.value, format=currency_format, currency=company_doc.default_currency) %}\\r\\n\\r\\n\\r\\n
\\r\\n\\t
\\r\\n\\t\\t\\r\\n\\t
\\r\\n\\t
{{ paid_amount.value }}
\\r\\n
\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t{% if doc.outstanding_amount != 0 and upi_id.value != '' %}\\r\\n\\t\\t\\t\\r\\n\\t\\t{% endif %}\\r\\n\\t\\t{% if bank_details %}\\r\\n\\t\\t\\t\\r\\n\\t\\t{% endif %}\\r\\n\\t\\r\\n
\\r\\n\\t\\t\\t\\t
\\r\\n\\t\\t\\t\\t\\t{{ web_block('UPI QR Code', values={'upi_qr_text': 'upi://pay?pa=' ~ upi_id.value ~ '&am=' ~ doc.outstanding_amount ~ '&tn=' ~ doc.name ~ '&cu=INR' }) }}\\r\\n\\t\\t\\t\\t
\\r\\n\\t\\t\\t\\t

UPI QR

\\r\\n\\t\\t\\t
\\r\\n\\t\\t\\t\\t

Bank Details

\\r\\n\\t\\t\\t\\t

{{ bank_name.value }}

\\r\\n\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t{% for key,value in bank_details.items() %}\\r\\n\\t\\t\\t\\t\\t{% if value != None %}\\r\\n\\t\\t\\t\\t\\t\\t

{{ key }} : {{ value }}

\\r\\n\\t\\t\\t\\t\\t{% endif %}\\r\\n\\t\\t\\t\\t{% endfor %}\\r\\n\\t\\t\\t
\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total\", \"print_hide\": 0, \"label\": \"Total\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n {%- set data = doc.taxes -%}\\n {%- set print_settings = frappe.get_doc(\\\"Print Settings\\\") -%}\\n {% include \\\"erpnext/templates/print_formats/includes/taxes.html\\\" %}\\n
\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldname\": \"rounded_total\", \"print_hide\": 0, \"label\": \"Rounded Total\"}, {\"fieldname\": \"in_words\", \"print_hide\": 0, \"label\": \"In Words\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"gst_breakup_table\", \"print_hide\": 0, \"label\": \"GST Breakup Table\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% set all_details = {} %}\\r\\n\\r\\n{% set company_doc = frappe.get_doc(\\\"Company\\\",doc.company) %}\\r\\n\\r\\n{% for doc in company_doc.registration_details_for_printing %}\\r\\n\\t{% set _ = all_details.update({doc.autofield: doc.autofield_value}) %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if doc.company_gstin %}\\r\\n\\t{% set _ = all_details.update({ \\\"Our GSTIN\\\": doc.company_gstin, }) %}\\r\\n{% endif %}\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t{% for key,value in all_details.items() %}\\r\\n\\t\\t\\t{% if value != None %}\\r\\n\\t\\t\\t \\r\\n\\t\\t\\t{% endif %}\\r\\n\\t\\t\\t{% if loop.index % 2 == 0 and not loop.last %}\\r\\n\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t{% endif %}\\r\\n\\t\\t{% endfor %}\\r\\n\\t\\r\\n
\\r\\n\\t\\t\\t\\t

{{ key }} : {{ value }}

\\r\\n\\t\\t\\t
\\r\\n\\r\\n{% if company_doc.show_physical_signature == 0 %}\\r\\n{% set first_name = frappe.db.get_value(\\\"User\\\",doc.owner,\\\"first_name\\\") %}\\r\\n{% set last_name = frappe.db.get_value(\\\"User\\\",doc.owner,\\\"last_name\\\") %}\\r\\n{% set generated_by = first_name ~ (' ' ~ last_name if last_name else '') %}\\r\\n{% set generated_on = frappe.utils.getdate(doc.get_formatted(\\\"creation\\\")) %}\\r\\n\\r\\n\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n

Generated By : {{ generated_by }}

Generated On: {{ generated_on }}

\\r\\n{% else %}\\r\\n\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n

For {{ doc.company }}

Authorized By

\\r\\n{% endif %}\\r\\n\\r\\n{% if company_doc.show_physical_signature == 0 %}\\r\\n\\t

This is computer-generated document and no signature is required

\\r\\n{% endif %}\"}]", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"{% set logo_for_printing = frappe.db.get_value(\\\"Company\\\",doc.company,\\\"logo_for_printing\\\") %}\\n\\n\\n \\n \\n \\n \\n \\n \\n
\\n {% if logo_for_printing != \\\"\\\" %}\\n
\\n \\n
\\n {% endif %}\\n
\\n

{{ doc.company }}

\\n
\\n
\\n

\\n {{ doc.select_print_heading if doc.select_print_heading else 'Tax Invoice' }}
\\n {{ doc.name }} \\n

\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"First Section\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n\\t\\n\\t{% if not doc.shipping_address or doc.address_display == doc.shipping_address %}\\n\\t\\t

Billing and Shipping Address

\\n\\t{% else %}\\n\\t\\t

Billed To

\\n\\t{% endif %} \\n\\t{% if doc.customer_name %}\\n\\t\\t

{{ doc.customer_name }}

\\n\\t{% endif %}\\n\\n\\t

{{ doc.address_display if doc.address_display else '' }}

\\n\\n\\t\\n\\t{% if doc.shipping_address and doc.address_display !=\\n\\t\\tdoc.shipping_address %}\\n\\t\\t

Shipped To

\\n\\t\\t\\t{% if doc.dispatch_address_name %}\\n\\t\\t\\t\\t

{{ doc.dispatch_address_name }}

\\n\\t\\t\\t{% endif %}\\n\\t\\t

{{ doc.shipping_address }}

\\n\\t{% endif %}\\n
\"}, {\"fieldname\": \"place_of_supply\", \"print_hide\": 0, \"label\": \"Place of Supply\"}, {\"fieldname\": \"is_reverse_charge\", \"print_hide\": 0, \"align\": \"left\", \"label\": \"Reverse Charge\"}, {\"fieldname\": \"posting_date\", \"print_hide\": 0, \"label\": \"Date\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.contact_display or doc.contact_mobile or doc.contact_email %}\\n
\\n

Contact Person

\\n {% if doc.contact_display %}\\n

{{ doc.contact_display }}

\\n {% endif %} {% if doc.contact_mobile %}\\n

{{ doc.contact_mobile }}

\\n {% endif %} {% if doc.contact_email %}\\n

{{ doc.contact_email }}

\\n {% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.po_no %}\\n
\\n\\t

Purchase Order Reference

\\n\\t

{{ doc.po_no }}

\\n\\t{% if doc.po_date %}\\n\\t

dtd. {{ doc.get_formatted(\\\"po_date\\\") }}

\\n\\t{% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\r\\n{% set delivery_note_list = [] %}\\r\\n{% for item in doc.items %}\\r\\n {% if item.delivery_note and item.delivery_note not in delivery_note_list %}\\r\\n {% set _ = delivery_note_list.append(item.delivery_note) %}\\r\\n {% endif %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if delivery_note_list %}\\r\\n
\\r\\n

Delivery Note Reference

\\r\\n {% for dn in delivery_note_list %}\\r\\n {% set dn_date = frappe.db.get_value('Delivery Note', dn, 'posting_date') %}\\r\\n

{{ dn }} dtd. {{ frappe.utils.formatdate(dn_date, 'dd-MM-yyyy') }}

\\r\\n {% endfor %}\\r\\n
\\r\\n{% endif %}\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"\\n{% if doc.transporter_name or doc.vehicle_no or doc.lr_no or doc.ewaybill %}\\n
\\n

Transporter Details

\\n {% if doc.transporter_name %}\\n

{{ doc.transporter_name }}

\\n {% endif %} \\n {% if doc.vehicle_no %}\\n

Vehicle No: {{ doc.vehicle_no }}

\\n {% endif %} \\n {% if doc.lr_no %}\\n

LR No: {{ doc.lr_no }}

\\n {% endif %} \\n {% if doc.ewaybill %}\\n

e-Waybill No: {{ doc.ewaybill }}

\\n {% endif %}\\n
\\n{% endif %}\\n\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n \\n {% if doc.outstanding_amount !=0 %}\\n

\\n Unpaid Amount\\n

\\n

\\n {{ doc.get_formatted(\\\"outstanding_amount\\\") }}\\n

\\n {% else %}\\n

\\n Fully Paid\\n

\\n {% endif %}\\n
\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% if doc.irn %}\\r\\n{% set e_invoice_log = frappe.db.get_value( \\\"e-Invoice Log\\\", doc.irn,\\r\\n(\\\"invoice_data\\\", \\\"signed_qr_code\\\"), as_dict=True ) %}\\r\\n\\r\\n{% if e_invoice_log %}\\r\\n{% set invoice_data = json.loads(e_invoice_log.invoice_data) %}\\r\\n{% set date_format = frappe.db.get_single_value(\\\"System Settings\\\",'date_format').replace(\\\"mm\\\",\\\"MM\\\")\\r\\n%}\\r\\n\\r\\n
\\r\\n\\t\\r\\n\\t
\\r\\n\\t\\t{{ web_block('e-Invoice QR Code', values={'e_invoice_qr_text':\\r\\n\\t\\te_invoice_log.signed_qr_code }) }}\\r\\n\\t
\\r\\n\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tIRN : {{ doc.irn }}\\r\\n\\t

\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tAck No. {{ invoice_data.get(\\\"AckNo\\\") if doc.irn and invoice_data.get(\\\"AckNo\\\")\\r\\n\\t\\telse '' }}\\r\\n\\t

\\r\\n\\t\\r\\n\\t

\\r\\n\\t\\tAck Dt.\\r\\n\\t\\t{{ frappe.utils.format_datetime(invoice_data.get(\\\"AckDt\\\"),date_format) if doc.irn and\\r\\n\\t\\tinvoice_data.get(\\\"AckDt\\\") else '' }}\\r\\n\\t

\\r\\n
\\r\\n{% endif %}\\r\\n{% endif %}\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"items\", \"print_hide\": 0, \"label\": \"Items\", \"visible_columns\": [{\"fieldname\": \"serial_no\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"gst_hsn_code\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"description\", \"print_width\": \"200px\", \"print_hide\": 0}, {\"fieldname\": \"qty\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"rate\", \"print_width\": \"\", \"print_hide\": 0}, {\"fieldname\": \"amount\", \"print_width\": \"\", \"print_hide\": 0}]}, {\"fieldtype\": \"Section Break\", \"label\": \"erpnext\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"due_date\", \"print_hide\": 0, \"align\": \"left\", \"label\": \"Due Date\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% set company_doc = frappe.get_doc(\\\"Company\\\",doc.company) %}\\r\\n{% set currency_format = frappe.db.get_single_value(\\\"System Settings\\\",\\\"number_format\\\") %}\\r\\n\\r\\n{% set bank_details = {} %}\\r\\n{% set upi_id = namespace(value=\\\"\\\") %}\\r\\n{% set bank_name = namespace(value=\\\"\\\") %}\\r\\n{% set paid_amount = namespace(value=0) %}\\r\\n\\r\\n{% for doc in company_doc.bank_details_for_printing %}\\r\\n\\t{% if doc.autofield == \\\"UPI ID\\\" %}\\r\\n\\t\\t{% set upi_id.value = doc.autofield_value %}\\r\\n\\t{% elif doc.autofield == \\\"Bank Name\\\" %}\\r\\n\\t\\t{% set bank_name.value = doc.autofield_value %}\\r\\n\\t{% else %}\\r\\n\\t\\t{% set _ = bank_details.update({doc.autofield: doc.autofield_value}) %}\\r\\n\\t{% endif %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if doc.rounded_total %}\\r\\n\\t{% set paid_amount.value = frappe.utils.cint(doc.rounded_total) - frappe.utils.cint(doc.outstanding_amount) %}\\r\\n{% else %}\\r\\n\\t{% set paid_amount.value = frappe.utils.cint(doc.grand_total) - frappe.utils.cint(doc.outstanding_amount) %}\\r\\n{% endif %}\\r\\n{% set paid_amount.value = frappe.utils.fmt_money(amount=paid_amount.value, format=currency_format, currency=company_doc.default_currency) %}\\r\\n\\r\\n\\r\\n
\\r\\n\\t
\\r\\n\\t\\t\\r\\n\\t
\\r\\n\\t
{{ paid_amount.value }}
\\r\\n
\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t{% if doc.outstanding_amount != 0 and upi_id.value != '' %}\\r\\n\\t\\t\\t\\r\\n\\t\\t{% endif %}\\r\\n\\t\\t{% if bank_details %}\\r\\n\\t\\t\\t\\r\\n\\t\\t{% endif %}\\r\\n\\t\\r\\n
\\r\\n\\t\\t\\t\\t
\\r\\n\\t\\t\\t\\t\\t{{ web_block('UPI QR Code', values={'upi_qr_text': 'upi://pay?pa=' ~ upi_id.value ~ '&am=' ~ doc.outstanding_amount ~ '&tn=' ~ doc.name ~ '&cu=INR' }) }}\\r\\n\\t\\t\\t\\t
\\r\\n\\t\\t\\t\\t

UPI QR

\\r\\n\\t\\t\\t
\\r\\n\\t\\t\\t\\t

Bank Details

\\r\\n\\t\\t\\t\\t

{{ bank_name.value }}

\\r\\n\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t{% for key,value in bank_details.items() %}\\r\\n\\t\\t\\t\\t\\t{% if value != None %}\\r\\n\\t\\t\\t\\t\\t\\t

{{ key }} : {{ value }}

\\r\\n\\t\\t\\t\\t\\t{% endif %}\\r\\n\\t\\t\\t\\t{% endfor %}\\r\\n\\t\\t\\t
\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"total\", \"print_hide\": 0, \"label\": \"Total\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\\n {%- set data = doc.taxes -%}\\n {%- set print_settings = frappe.get_doc(\\\"Print Settings\\\") -%}\\n {% include \\\"erpnext/templates/print_formats/includes/taxes.html\\\" %}\\n
\"}, {\"fieldname\": \"grand_total\", \"print_hide\": 0, \"label\": \"Grand Total\"}, {\"fieldname\": \"rounded_total\", \"print_hide\": 0, \"label\": \"Rounded Total\"}, {\"fieldname\": \"in_words\", \"print_hide\": 0, \"label\": \"In Words\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"gst_breakup_table\", \"print_hide\": 0, \"label\": \"GST Breakup Table\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"{% set all_details = {} %}\\r\\n\\r\\n{% set company_doc = frappe.get_doc(\\\"Company\\\",doc.company) %}\\r\\n\\r\\n{% for doc in company_doc.registration_details_for_printing %}\\r\\n\\t{% set _ = all_details.update({doc.autofield: doc.autofield_value}) %}\\r\\n{% endfor %}\\r\\n\\r\\n{% if doc.company_gstin %}\\r\\n\\t{% set _ = all_details.update({ \\\"Our GSTIN\\\": doc.company_gstin, }) %}\\r\\n{% endif %}\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t{% for key,value in all_details.items() %}\\r\\n\\t\\t\\t{% if value != None %}\\r\\n\\t\\t\\t \\r\\n\\t\\t\\t{% endif %}\\r\\n\\t\\t\\t{% if loop.index % 2 == 0 and not loop.last %}\\r\\n\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t{% endif %}\\r\\n\\t\\t{% endfor %}\\r\\n\\t\\r\\n
\\r\\n\\t\\t\\t\\t

{{ key }} : {{ value }}

\\r\\n\\t\\t\\t
\\r\\n\\r\\n{% if company_doc.show_physical_signature == 0 %}\\r\\n{% set first_name = frappe.db.get_value(\\\"User\\\",doc.owner,\\\"first_name\\\") %}\\r\\n{% set last_name = frappe.db.get_value(\\\"User\\\",doc.owner,\\\"last_name\\\") %}\\r\\n{% set generated_by = first_name ~ (' ' ~ last_name if last_name else '') %}\\r\\n\\r\\n\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n

Generated By : {{ generated_by }}

Generated On: {{ doc.get_formatted(\\\"creation\\\") }}

\\r\\n{% else %}\\r\\n\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n

For {{ doc.company }}

Authorized By

\\r\\n{% endif %}\\r\\n\\r\\n{% if company_doc.show_physical_signature == 0 %}\\r\\n\\t

This is computer-generated document and no signature is required

\\r\\n{% endif %}\"}]", "idx": 0, "line_breaks": 0, "margin_bottom": 15.0, "margin_left": 15.0, "margin_right": 15.0, "margin_top": 15.0, - "modified": "2024-06-18 15:53:21.428025", + "modified": "2024-06-22 15:07:56.623009", "modified_by": "Administrator", "module": "GST India", "name": "GST Tax Invoice", From 26fbbd9b48273aac389c085a49b96228f66def0a Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 16:03:40 +0530 Subject: [PATCH 39/41] chore: resolve conflicts --- india_compliance/patches.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index f290ac418..82e9f7655 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -53,11 +53,4 @@ india_compliance.patches.v14.unset_inward_supply_link_for_cancelled_purchase india_compliance.patches.v14.delete_not_generated_gstr_import_log india_compliance.patches.v14.enable_sales_through_ecommerce_operator india_compliance.patches.post_install.set_gst_tax_type -<<<<<<< HEAD -<<<<<<< HEAD -======= -execute from india_compliance.gst_india.setup import set_default_print_settings; set_default_print_settings() ->>>>>>> 55178883 (fix: set default print format for Sales Invoice) -======= execute:from india_compliance.gst_india.setup import set_default_print_settings; set_default_print_settings() ->>>>>>> 7c11563f (chore: patch format) From 3f2b7a598f232a7e327ed313148257768b139240 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 22 Jun 2024 15:50:34 +0530 Subject: [PATCH 40/41] fix: allow address in india with overseas category for high sea sales (cherry picked from commit 37231d64877b417dd9d826749e4fe3dd265050d5) --- india_compliance/gst_india/overrides/address.py | 6 +----- india_compliance/gst_india/overrides/party.py | 6 +++++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/india_compliance/gst_india/overrides/address.py b/india_compliance/gst_india/overrides/address.py index 5c4a2711e..ba50196a0 100644 --- a/india_compliance/gst_india/overrides/address.py +++ b/india_compliance/gst_india/overrides/address.py @@ -60,11 +60,7 @@ def validate(doc, method=None): def validate_overseas_gst_category(doc): - if doc.country == "India" and doc.gst_category == "Overseas": - frappe.throw( - _("Cannot set GST Category as Overseas for Indian Address"), - title=_("Invalid GST Category"), - ) + # High Seas Sales is possible (Indian Address with Overseas GST Category) if doc.country != "India" and doc.gst_category != "Overseas": frappe.throw( diff --git a/india_compliance/gst_india/overrides/party.py b/india_compliance/gst_india/overrides/party.py index dfb6d5433..23d8b1d68 100644 --- a/india_compliance/gst_india/overrides/party.py +++ b/india_compliance/gst_india/overrides/party.py @@ -41,9 +41,13 @@ def set_gst_category(doc): def fetch_or_guess_gst_category(doc): + # High Seas Sales + if doc.gst_category == "Overseas": + return doc.gst_category + # Any transaction can be treated as deemed export if doc.gstin and doc.gst_category == "Deemed Export": - return "Deemed Export" + return doc.gst_category if doc.gstin and is_autofill_party_info_enabled(): gstin_info = _get_gstin_info(doc.gstin, throw_error=False) or {} From 175b51a7611c2311e2d728ae20d91ff390cb9965 Mon Sep 17 00:00:00 2001 From: Lakshit Jain <108322669+ljain112@users.noreply.github.com> Date: Mon, 24 Jun 2024 15:50:16 +0530 Subject: [PATCH 41/41] fix(ineligible itc): update GL's with valuation hook, refactor (#2206) - Update valuation using regional override - Using GST Details for calculating ineligible amount - Modified Report Depends On: https://github.com/frappe/erpnext/pull/41842 Closes: #2193 Frappe Support: https://support.frappe.io/app/hd-ticket/15474 (cherry picked from commit f3133e3884332c45b8feb48035d0b5910760e21e) --- .../doctype/gstr_3b_report/gstr_3b_report.py | 23 +- .../gst_india/overrides/ineligible_itc.py | 104 +++--- .../gst_india/overrides/purchase_receipt.py | 5 +- .../overrides/test_ineligible_itc.py | 1 + .../gst_india/overrides/transaction.py | 5 +- .../report/gstr_3b_details/gstr_3b_details.py | 295 +++--------------- india_compliance/hooks.py | 9 +- .../patches/check_version_compatibility.py | 2 +- 8 files changed, 114 insertions(+), 330 deletions(-) diff --git a/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py b/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py index d85388a5f..956a3d5b8 100644 --- a/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py +++ b/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py @@ -120,24 +120,33 @@ def get_itc_reversal_entries(self): self.update_itc_reversal_from_bill_of_entry() def update_itc_reversal_from_purchase_invoice(self): + self.update_itc_reversal_for_purchase_us_17_4() + self.update_itc_reversal_for_purchase_due_to_pos() + + def update_itc_reversal_for_purchase_due_to_pos(self): ineligible_credit = IneligibleITC( self.company, self.gst_details.get("gstin"), self.month_no, self.year - ).get_ineligible_itc_us_17_5_for_purchase(group_by="ineligibility_reason") + ).get_for_purchase( + "ITC restricted due to PoS rules", group_by="ineligibility_reason" + ) - ineligible_credit_due_to_pos = IneligibleITC( - self.company, self.gst_details.get("gstin"), self.month_no, self.year - ).get_ineligible_itc_due_to_pos_for_purchase(group_by="ineligibility_reason") + self.process_ineligible_credit(ineligible_credit) - ineligible_credit.extend(ineligible_credit_due_to_pos) + def update_itc_reversal_for_purchase_us_17_4(self): + ineligible_credit = IneligibleITC( + self.company, self.gst_details.get("gstin"), self.month_no, self.year + ).get_for_purchase( + "Ineligible As Per Section 17(5)", group_by="ineligibility_reason" + ) - return self.process_ineligible_credit(ineligible_credit) + self.process_ineligible_credit(ineligible_credit) def update_itc_reversal_from_bill_of_entry(self): ineligible_credit = IneligibleITC( self.company, self.gst_details.get("gstin"), self.month_no, self.year ).get_for_bill_of_entry() - return self.process_ineligible_credit(ineligible_credit) + self.process_ineligible_credit(ineligible_credit) def process_ineligible_credit(self, ineligible_credit): if not ineligible_credit: diff --git a/india_compliance/gst_india/overrides/ineligible_itc.py b/india_compliance/gst_india/overrides/ineligible_itc.py index 3c332d806..7c1a9b957 100644 --- a/india_compliance/gst_india/overrides/ineligible_itc.py +++ b/india_compliance/gst_india/overrides/ineligible_itc.py @@ -1,6 +1,8 @@ +from collections import defaultdict + import frappe from frappe import _ -from frappe.utils import flt, get_link_to_form, rounded +from frappe.utils import flt, get_link_to_form from erpnext.assets.doctype.asset.asset import ( get_asset_account, is_cwip_accounting_enabled, @@ -32,41 +34,27 @@ def update_valuation_rate(self): - Only updates if its a stock item or fixed asset - No updates for expense items """ - self.doc._has_ineligible_itc_items = False - stock_items = self.doc.get_stock_items() - - for item in self.doc.items: - if ( - not self.is_eligibility_restricted_due_to_pos() - and not item.is_ineligible_for_itc - ): - continue - self.update_ineligible_taxes(item) + self.update_item_ineligibility() - if item._ineligible_tax_amount: - self.doc._has_ineligible_itc_items = True + if not self.doc.get("_has_ineligible_itc_items"): + return - if item.item_code in stock_items and self.is_perpetual: - item._is_stock_item = True + for item in self.doc.items: + if not item.get("_ineligible_tax_amount"): + continue if item.get("_is_stock_item") or item.get("is_fixed_asset"): ineligible_tax_amount = item._ineligible_tax_amount if self.doc.get("is_return"): ineligible_tax_amount = -ineligible_tax_amount - # TODO: handle rounding off of gst amount from gst settings self.update_item_valuation_rate(item, ineligible_tax_amount) def update_gl_entries(self, gl_entries): self.gl_entries = gl_entries - if ( - frappe.flags.through_repost_accounting_ledger - or frappe.flags.through_repost_item_valuation - ): - self.doc.update_valuation_rate() - self.update_valuation_rate() + self.update_item_ineligibility() if not self.doc.get("_has_ineligible_itc_items"): return gl_entries @@ -84,6 +72,34 @@ def update_gl_entries(self, gl_entries): self.update_item_gl_entries(item) + def update_item_ineligibility(self): + self.doc._has_ineligible_itc_items = False + stock_items = self.doc.get_stock_items() + + self.tax_account_dict = { + row.gst_tax_type: row.account_head + for row in self.doc.taxes + if row.gst_tax_type + } + + if not self.tax_account_dict: + return + + for item in self.doc.items: + if ( + not self.is_eligibility_restricted_due_to_pos() + and not item.is_ineligible_for_itc + ): + continue + + self.update_ineligible_taxes(item) + + if item._ineligible_tax_amount: + self.doc._has_ineligible_itc_items = True + + if item.item_code in stock_items and self.is_perpetual: + item._is_stock_item = True + def update_item_gl_entries(self, item): return @@ -272,39 +288,25 @@ def update_ineligible_taxes(self, item): "Input SGST - FC": 50, } """ - ineligible_taxes = frappe._dict() + ineligible_taxes = defaultdict(float) + ineligible_tax_amount = 0 - for tax in self.doc.taxes: - if tax.gst_tax_type not in GST_TAX_TYPES: + for tax_type in GST_TAX_TYPES: + tax_amount = abs(flt(item.get(f"{tax_type}_amount"))) + tax_account = self.tax_account_dict.get(tax_type) + + if not tax_amount: continue - ineligible_taxes[tax.account_head] = self.get_item_tax_amount(item, tax) + ineligible_taxes[tax_account] += tax_amount + ineligible_tax_amount += tax_amount item._ineligible_taxes = ineligible_taxes - item._ineligible_tax_amount = sum(ineligible_taxes.values()) + item._ineligible_tax_amount = ineligible_tax_amount def update_item_valuation_rate(self, item, ineligible_tax_amount): item.valuation_rate += flt(ineligible_tax_amount / item.stock_qty, 2) - def get_item_tax_amount(self, item, tax): - """ - Returns proportionate item tax amount for each tax component - """ - tax_rate = rounded( - frappe.parse_json(tax.item_wise_tax_detail).get( - item.item_code or item.item_name - )[0], - 3, - ) - - tax_amount = ( - tax_rate * item.qty - if tax.charge_type == "On Item Quantity" - else tax_rate * item.taxable_value / 100 - ) - - return abs(tax_amount) - def is_debit_entry_required(self, item): return True @@ -416,16 +418,6 @@ def update_valuation_rate(self): super().update_valuation_rate() - def get_item_tax_amount(self, item, tax): - tax_rate = frappe.parse_json(tax.item_wise_tax_rates).get(item.name) - if tax_rate is None: - return 0 - - tax_rate = rounded(tax_rate, 3) - tax_amount = tax_rate * item.taxable_value / 100 - - return abs(tax_amount) - def update_item_valuation_rate(self, item, ineligible_tax_amount): item.valuation_rate = ineligible_tax_amount diff --git a/india_compliance/gst_india/overrides/purchase_receipt.py b/india_compliance/gst_india/overrides/purchase_receipt.py index e062f4d08..e5de8e4f8 100644 --- a/india_compliance/gst_india/overrides/purchase_receipt.py +++ b/india_compliance/gst_india/overrides/purchase_receipt.py @@ -24,17 +24,14 @@ def onload(doc, method=None): if ignore_gst_validations(doc, throw=False): return - doc.flags.ignore_mandatory = True if ( validate_mandatory_fields( - doc, ("company_gstin", "place_of_supply", "gst_category") + doc, ("company_gstin", "place_of_supply", "gst_category"), throw=False ) is False ): return - doc.flags.ignore_mandatory = False - set_ineligibility_reason(doc, show_alert=False) diff --git a/india_compliance/gst_india/overrides/test_ineligible_itc.py b/india_compliance/gst_india/overrides/test_ineligible_itc.py index d9df89581..c8f749fd8 100644 --- a/india_compliance/gst_india/overrides/test_ineligible_itc.py +++ b/india_compliance/gst_india/overrides/test_ineligible_itc.py @@ -518,6 +518,7 @@ def test_purchase_returns_with_update_stock(self): doc = create_transaction(**transaction_details) doc = make_return_doc("Purchase Invoice", doc.name) + doc.save() doc.submit() self.assertGLEntry( diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 540b4e174..5805c3193 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -210,7 +210,7 @@ def is_indian_registered_company(doc): return True -def validate_mandatory_fields(doc, fields, error_message=None): +def validate_mandatory_fields(doc, fields, error_message=None, throw=True): if isinstance(fields, str): fields = (fields,) @@ -224,6 +224,9 @@ def validate_mandatory_fields(doc, fields, error_message=None): if doc.flags.ignore_mandatory: return False + if not throw: + return False + frappe.throw( error_message.format(bold(_(doc.meta.get_label(field)))), title=_("Missing Required Field"), diff --git a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py index 69180e4a8..7cdff286f 100644 --- a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py +++ b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py @@ -6,9 +6,8 @@ from frappe.query_builder import Case, DatePart from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.functions import Extract, Ifnull, IfNull, LiteralValue, Sum -from frappe.utils import cint, flt, get_first_day, get_last_day +from frappe.utils import cint, get_first_day, get_last_day -from india_compliance.gst_india.constants.__init__ import GST_TAX_TYPES from india_compliance.gst_india.utils import get_escaped_gst_accounts @@ -271,7 +270,7 @@ def get_itc_from_journal_entry(self): def get_ineligible_itc_from_purchase(self): ineligible_itc = IneligibleITC( self.company, self.company_gstin, self.filters.month, self.filters.year - ).get_ineligible_itc_us_17_5_for_purchase() + ).get_for_purchase("Ineligible As Per Section 17(5)") return self.process_ineligible_itc(ineligible_itc) @@ -419,275 +418,61 @@ def get_inward_nil_exempt(self): class IneligibleITC: def __init__(self, company, gstin, month, year) -> None: - self.gl_entry = frappe.qb.DocType("GL Entry") self.company = company self.gstin = gstin self.month = month self.year = year - self.gst_accounts = get_escaped_gst_accounts(company, "Input") - def get_ineligible_itc_us_17_5_for_purchase(self, group_by="name"): - """ - - Ineligible As Per Section 17(5) - - ITC restricted due to ineligible items in purchase invoice - """ - ineligible_transactions = self.get_vouchers_with_gst_expense("Purchase Invoice") + def get_for_purchase(self, ineligibility_reason, group_by="name"): + doctype = "Purchase Invoice" + dt = frappe.qb.DocType(doctype) + dt_item = frappe.qb.DocType(f"{doctype} Item") - if not ineligible_transactions: - return [] - - pi = frappe.qb.DocType("Purchase Invoice") - - credit_availed = ( - self.get_gl_entry_query("Purchase Invoice") - .inner_join(pi) - .on(pi.name == self.gl_entry.voucher_no) - .select(*self.select_net_gst_amount_from_gl_entry()) - .select( - pi.name.as_("voucher_no"), - pi.ineligibility_reason.as_("itc_classification"), - ) - .where( - IfNull(pi.ineligibility_reason, "") == "Ineligible As Per Section 17(5)" - ) - .where(pi.name.isin(ineligible_transactions)) - .groupby(pi[group_by]) - .run(as_dict=1) - ) - - credit_available = ( - frappe.qb.from_(pi) - .select( - ConstantColumn("Purchase Invoice").as_("voucher_type"), - pi.name.as_("voucher_no"), - pi.posting_date, - pi.ineligibility_reason.as_("itc_classification"), - Sum(pi.itc_integrated_tax).as_("iamt"), - Sum(pi.itc_central_tax).as_("camt"), - Sum(pi.itc_state_tax).as_("samt"), - Sum(pi.itc_cess_amount).as_("csamt"), - ) - .where( - IfNull(pi.ineligibility_reason, "") == "Ineligible As Per Section 17(5)" - ) - .where(pi.name.isin(ineligible_transactions)) - .groupby(pi[group_by]) - .run(as_dict=1) + query = ( + self.get_common_query(doctype, dt, dt_item) + .select((dt.ineligibility_reason).as_("itc_classification")) + .where((dt.is_opening == "No")) + .where(IfNull(dt.ineligibility_reason, "") == ineligibility_reason) ) - return self.get_ineligible_credit(credit_availed, credit_available, group_by) - - def get_ineligible_itc_due_to_pos_for_purchase(self, group_by="name"): - """ - - ITC restricted due to PoS rules - """ - ineligible_transactions = self.get_vouchers_with_gst_expense("Purchase Invoice") - - if not ineligible_transactions: - return [] + if ineligibility_reason == "Ineligible As Per Section 17(5)": + query = query.where(dt_item.is_ineligible_for_itc == 1) - pi = frappe.qb.DocType("Purchase Invoice") - taxes = frappe.qb.DocType("Purchase Taxes and Charges") - - # utility function - def get_tax_case_statement(gst_tax_types, alias): - return Sum( - Case() - .when( - taxes.gst_tax_type.isin(gst_tax_types), - taxes.base_tax_amount_after_discount_amount, - ) - .else_(0) - ).as_(alias) - - # Credit availed is not required as it will be always 0 for pos - - ineligible_credit = ( - frappe.qb.from_(pi) - .inner_join(taxes) - .on(pi.name == taxes.parent) - .select( - pi.name.as_("voucher_no"), - pi.posting_date, - pi.ineligibility_reason.as_("itc_classification"), - get_tax_case_statement(["igst"], "iamt"), - get_tax_case_statement(["cgst"], "camt"), - get_tax_case_statement(["sgst"], "samt"), - get_tax_case_statement( - [ - "cess", - "cess_non_advol", - ], - "csamt", - ), - ) - .where(taxes.gst_tax_type.isin(GST_TAX_TYPES)) - .where( - IfNull(pi.ineligibility_reason, "") == "ITC restricted due to PoS rules" - ) - .where(pi.name.isin(ineligible_transactions)) - .where(taxes.parenttype == "Purchase Invoice") - .groupby(pi[group_by]) - .run(as_dict=True) - ) - - return ineligible_credit + return query.groupby(dt[group_by]).run(as_dict=True) def get_for_bill_of_entry(self, group_by="name"): - ineligible_transactions = self.get_vouchers_with_gst_expense("Bill of Entry") - - if not ineligible_transactions: - return - - boe = frappe.qb.DocType("Bill of Entry") - boe_taxes = frappe.qb.DocType("Bill of Entry Taxes") - - credit_availed = ( - self.get_gl_entry_query("Bill of Entry") - .inner_join(boe) - .on(boe.name == self.gl_entry.voucher_no) - .select(*self.select_net_gst_amount_from_gl_entry()) - .select( - boe.name.as_("voucher_no"), - ConstantColumn("Ineligible As Per Section 17(5)").as_( - "itc_classification" - ), - ) - .where(boe.name.isin(ineligible_transactions)) - .groupby(boe[group_by]) - .run(as_dict=1) - ) - - credit_available = ( - frappe.qb.from_(boe) - .join(boe_taxes) - .on(boe_taxes.parent == boe.name) + doctype = "Bill of Entry" + dt = frappe.qb.DocType(doctype) + dt_item = frappe.qb.DocType(f"{doctype} Item") + query = ( + self.get_common_query(doctype, dt, dt_item) .select( - ConstantColumn("Bill of Entry").as_("voucher_type"), - boe.name.as_("voucher_no"), - boe.posting_date, - Sum( - Case() - .when( - boe_taxes.gst_tax_type == "igst", - boe_taxes.tax_amount, - ) - .else_(0) - ).as_("iamt"), - Sum( - Case() - .when( - boe_taxes.gst_tax_type == "cess", - boe_taxes.tax_amount, - ) - .else_(0) - ).as_("csamt"), - LiteralValue(0).as_("camt"), - LiteralValue(0).as_("samt"), ConstantColumn("Ineligible As Per Section 17(5)").as_( "itc_classification" - ), + ) ) - .where(boe.name.isin(ineligible_transactions)) - .groupby(boe[group_by]) - .run(as_dict=1) + .where(dt_item.is_ineligible_for_itc == 1) ) - return self.get_ineligible_credit(credit_availed, credit_available, group_by) + return query.groupby(dt[group_by]).run(as_dict=True) - def get_ineligible_credit(self, credit_availed, credit_available, group_by): - if group_by == "name": - group_by_field = "voucher_no" - elif group_by == "ineligibility_reason": - group_by_field = "itc_classification" - else: - group_by_field = group_by - - credit_availed_dict = frappe._dict( - {d[group_by_field]: d for d in credit_availed} - ) - ineligible_credit = [] - tax_amounts = ["camt", "samt", "iamt", "csamt"] - - for row in credit_available: - credit_availed = credit_availed_dict.get(row[group_by_field]) - if not credit_availed: - ineligible_credit.append(row) - continue - - for key in tax_amounts: - if key not in row: - continue - - row[key] -= flt(credit_availed.get(key, 0)) - - ineligible_credit.append(row) - - return ineligible_credit - - def get_vouchers_with_gst_expense(self, voucher_type): - gst_expense_account = frappe.get_cached_value( - "Company", self.company, "default_gst_expense_account" - ) - - data = ( - self.get_gl_entry_query(voucher_type) - .select(self.gl_entry.voucher_no) - .where(self.gl_entry.account == gst_expense_account) - .run(as_dict=1) - ) - - return set([d.voucher_no for d in data]) - - def select_net_gst_amount_from_gl_entry(self): - account_field_map = { - "cgst_account": "camt", - "sgst_account": "samt", - "igst_account": "iamt", - "cess_account": "csamt", - } - fields = [] - - for account_field, key in account_field_map.items(): - if ( - account_field not in self.gst_accounts - or not self.gst_accounts[account_field] - ): - continue - - fields.append( - ( - Sum( - Case() - .when( - self.gl_entry.account.eq(self.gst_accounts[account_field]), - self.gl_entry.debit_in_account_currency, - ) - .else_(0) - ) - - Sum( - Case() - .when( - self.gl_entry.account.eq(self.gst_accounts[account_field]), - self.gl_entry.credit_in_account_currency, - ) - .else_(0) - ) - ).as_(key) + def get_common_query(self, doctype, dt, dt_item): + return ( + frappe.qb.from_(dt) + .join(dt_item) + .on(dt.name == dt_item.parent) + .select( + ConstantColumn(doctype).as_("voucher_type"), + dt.name.as_("voucher_no"), + dt.posting_date, + Sum(dt_item.igst_amount).as_("iamt"), + Sum(dt_item.cgst_amount).as_("camt"), + Sum(dt_item.sgst_amount).as_("samt"), + Sum(dt_item.cess_amount + dt_item.cess_non_advol_amount).as_("csamt"), ) - - return fields - - def get_gl_entry_query(self, voucher_type): - query = ( - frappe.qb.from_(self.gl_entry) - .where(self.gl_entry.docstatus == 1) - .where(self.gl_entry.is_opening == "No") - .where(self.gl_entry.voucher_type == voucher_type) - .where(self.gl_entry.is_cancelled == 0) - .where(self.gl_entry.company_gstin == self.gstin) - .where(Extract(DatePart.month, self.gl_entry.posting_date).eq(self.month)) - .where(Extract(DatePart.year, self.gl_entry.posting_date).eq(self.year)) + .where(dt.docstatus == 1) + .where(dt.company_gstin == self.gstin) + .where(dt.company == self.company) + .where(Extract(DatePart.month, dt.posting_date).eq(self.month)) + .where(Extract(DatePart.year, dt.posting_date).eq(self.year)) ) - - return query diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index 05f7f4957..c380e6585 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -144,10 +144,7 @@ "before_save": "india_compliance.gst_india.overrides.transaction.update_gst_details", "before_submit": [ "india_compliance.gst_india.overrides.transaction.update_gst_details", - "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", ], - "before_gl_preview": "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", - "before_sl_preview": "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", "after_mapping": "india_compliance.gst_india.overrides.transaction.after_mapping", "on_cancel": "india_compliance.gst_india.overrides.purchase_invoice.on_cancel", }, @@ -182,10 +179,7 @@ "before_save": "india_compliance.gst_india.overrides.transaction.update_gst_details", "before_submit": [ "india_compliance.gst_india.overrides.transaction.update_gst_details", - "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", ], - "before_gl_preview": "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", - "before_sl_preview": "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate", }, "Sales Invoice": { "onload": [ @@ -302,6 +296,9 @@ "erpnext.controllers.accounts_controller.get_advance_payment_entries_for_regional": ( "india_compliance.gst_india.overrides.payment_entry.get_advance_payment_entries_for_regional" ), + "erpnext.controllers.buying_controller.update_regional_item_valuation_rate": ( + "india_compliance.gst_india.overrides.ineligible_itc.update_valuation_rate" + ), "erpnext.accounts.doctype.payment_reconciliation.payment_reconciliation.adjust_allocations_for_taxes": ( "india_compliance.gst_india.overrides.payment_entry.adjust_allocations_for_taxes_in_payment_reconciliation" ), diff --git a/india_compliance/patches/check_version_compatibility.py b/india_compliance/patches/check_version_compatibility.py index b0e31b094..2067317b3 100644 --- a/india_compliance/patches/check_version_compatibility.py +++ b/india_compliance/patches/check_version_compatibility.py @@ -18,7 +18,7 @@ { "app_name": "ERPNext", "current_version": version.parse(erpnext.__version__), - "required_versions": {"version-14": "14.66.5", "version-15": "15.23.2"}, + "required_versions": {"version-14": "14.70.7", "version-15": "15.27.7"}, }, ]