From c75c4b196cda1e521ebe21a69320a74af7d34d70 Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Wed, 30 Nov 2022 12:56:14 +0530 Subject: [PATCH 01/58] docs: note branch option for get-app command (cherry picked from commit 793a3f69b5bf5d6879cbb6f3cfef48626b175c37) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f2ada991..357a63d44 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,10 @@ For a detailed overview of these features, please [refer to the documentation](h Once you've [set up a Frappe site](https://frappeframework.com/docs/v14/user/en/installation/), installing India Compliance is simple: -1. Download the app using the Bench CLI +1. Download the app using the Bench CLI. The `--branch` option defaults to `next`. Use appropriate version as per your setup. ```bash - bench get-app https://github.com/resilient-tech/india-compliance.git + bench get-app --branch version-14 https://github.com/resilient-tech/india-compliance.git ``` 2. Install the app on your site From de8b1c425e4f927e21b59d4a5370944afe2a5c9a Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Wed, 9 Nov 2022 13:45:58 +0530 Subject: [PATCH 02/58] feat: validate zero distance in ewaybill if to and from pincodes are same (cherry picked from commit 46dcc827b7e8d2bcf3ef726449a88768b71aea88) --- india_compliance/gst_india/utils/e_waybill.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index b69e41af0..f104652f3 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -497,6 +497,7 @@ def get_data(self, *, with_irn=False): self.set_transaction_details() self.set_item_list() self.set_party_address_details() + self.validate_distance() return self.get_transaction_data() @@ -868,3 +869,15 @@ def get_item_data(self, item_details): "cessRate": item_details.cess_rate, "cessNonAdvol": item_details.cess_non_advol_rate, } + + def validate_distance(self): + if ( + self.transaction_details.distance == 0 + and self.dispatch_address.pincode == self.shipping_address.pincode + ): + frappe.throw( + _( + "Distance cannot be zero if Dispatch and Shipping address Pincodes are same" + ), + title=_("Invalid Distance"), + ) From e9eeecf7564ab0609fd6c14320d18bf50bc06f7f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 23 Nov 2022 04:27:49 +0000 Subject: [PATCH 03/58] fix: validate distance for invoice with irn (cherry picked from commit aa9c17a56993a597c7371b122318a226be66b161) --- india_compliance/gst_india/utils/e_waybill.py | 4 ++-- 1 file 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 f104652f3..8f756c833 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -478,6 +478,8 @@ def __init__(self, *args, **kwargs): def get_data(self, *, with_irn=False): self.validate_transaction() self.set_transporter_details() + self.set_party_address_details() + self.validate_distance() if with_irn: return self.sanitize_data( @@ -496,8 +498,6 @@ def get_data(self, *, with_irn=False): self.set_transaction_details() self.set_item_list() - self.set_party_address_details() - self.validate_distance() return self.get_transaction_data() From c9dc2e56b5597795c9f23cf4e76642d5249d6cbf Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 3 Dec 2022 07:26:32 +0000 Subject: [PATCH 04/58] fix: hardcode distance (cherry picked from commit 65e59ce490aaad33b97524091d81a44d4296a427) --- india_compliance/gst_india/utils/e_waybill.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 8f756c833..6be602042 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -770,6 +770,18 @@ def get_address_details(self, *args, **kwargs): return address_details + def validate_distance(self): + """ + e-Waybill portal doesn't return distance where from and to pincode is same. + Hardcode distance to 1 km to simplify and automate this. + Accuracy of distance is immaterial and used only for e-Waybill validity determination. + """ + if ( + self.transaction_details.distance == 0 + and self.dispatch_address.pincode == self.shipping_address.pincode + ): + self.transaction_details.distance = 1 + def get_transaction_data(self): if self.sandbox_mode: self.transaction_details.update( @@ -869,15 +881,3 @@ def get_item_data(self, item_details): "cessRate": item_details.cess_rate, "cessNonAdvol": item_details.cess_non_advol_rate, } - - def validate_distance(self): - if ( - self.transaction_details.distance == 0 - and self.dispatch_address.pincode == self.shipping_address.pincode - ): - frappe.throw( - _( - "Distance cannot be zero if Dispatch and Shipping address Pincodes are same" - ), - title=_("Invalid Distance"), - ) From 7ffe6092edd5c7745da2e3cc2166fddb240f5e41 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 6 Dec 2022 14:05:17 +0000 Subject: [PATCH 05/58] fix: reverse_charge field validation if present (POS Invoice exception) (cherry picked from commit e3374b63ec495ecaf6d983dd13e6487b213703f4) --- 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 ca8c97e1e..3b81d900f 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -185,7 +185,7 @@ def _throw(message, title=None): ).format(idx) ) - if doc.is_reverse_charge and ( + if doc.get("is_reverse_charge") and ( idx := _get_matched_idx(rows_to_validate, all_valid_accounts) ): _throw( From 7c9c78c6c86782d2d5135b1c2f3231b0e5795307 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 3 Dec 2022 09:31:12 +0000 Subject: [PATCH 06/58] fix: incorrect party field set for quotation (cherry picked from commit 652e5a833f96de2772b883914b9cbd2d763ff758) --- india_compliance/public/js/quick_entry.js | 2 +- india_compliance/public/js/transaction.js | 6 +++--- india_compliance/public/js/utils.js | 5 +++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/india_compliance/public/js/quick_entry.js b/india_compliance/public/js/quick_entry.js index f5042e83b..819fe1b67 100644 --- a/india_compliance/public/js/quick_entry.js +++ b/india_compliance/public/js/quick_entry.js @@ -263,7 +263,7 @@ class AddressQuickEntryForm extends GSTQuickEntryForm { return { party_type: doctype, party: name }; const party_type = ic.get_party_type(doctype); - const party = doc[party_type.toLowerCase()]; + const party = doc[ic.get_party_field(doctype)]; return { party_type, party }; } } diff --git a/india_compliance/public/js/transaction.js b/india_compliance/public/js/transaction.js index 9d457bf1d..44669524c 100644 --- a/india_compliance/public/js/transaction.js +++ b/india_compliance/public/js/transaction.js @@ -40,14 +40,14 @@ function fetch_gst_details(doctype) { async function update_gst_details(frm) { if (frm.__gst_update_triggered || frm.updating_party_details || !frm.doc.company) return; - const party_type = ic.get_party_type(frm.doc.doctype).toLowerCase(); - if (!frm.doc[party_type]) return; + const party_field = ic.get_party_field(frm.doc.doctype); + if (!frm.doc[party_field]) return; frm.__gst_update_triggered = true; // wait for GSTINs to get fetched await frappe.after_ajax().then(() => frm.__gst_update_triggered = false); - const party_fields = ["tax_category", "gst_category", "company_gstin", party_type]; + const party_fields = ["tax_category", "gst_category", "company_gstin", party_field]; if (in_list(frappe.boot.sales_doctypes, frm.doc.doctype)) { party_fields.push( diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index a5ce2b393..2331b6b54 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -39,6 +39,11 @@ Object.assign(ic, { return in_list(frappe.boot.sales_doctypes, doctype) ? "Customer" : "Supplier"; }, + get_party_field(doctype) { + if (doctype == "Quotation") return "party_name"; + return ic.get_party_type(doctype).toLowerCase(); + }, + set_state_options(frm) { const state_field = frm.get_field("state"); const country = frm.get_field("country").value; From 990b0313aa9439d0ab5a706cb9aaaf593e51fc54 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Mon, 28 Nov 2022 14:10:37 +0530 Subject: [PATCH 07/58] fix: display zero tax in e-invoice print format (cherry picked from commit 0297bd9119f25c93b5c8b52ca98d1c3442cbe3cf) --- .../print_format/e_invoice/e_invoice.html | 7 ++++--- india_compliance/gst_india/utils/jinja.py | 20 ++++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html index 0dc5f6533..9eab4ebac 100644 --- a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html +++ b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html @@ -17,7 +17,7 @@ "CgstVal": "CGST", "SgstVal": "SGST", "IgstVal": "IGST", - "CessVal": "CESS", + "CesVal": "CESS", "Discount": "Discount", "OthChrg": "Other Charges", "RndOffAmt": "Round Off", @@ -40,7 +40,7 @@ {% else %} {% set e_invoice_log = frappe.db.get_value( - "e-Invoice Log", doc.irn, ("invoice_data", "signed_qr_code"), as_dict=True + "e-Invoice Log", doc.irn, ("sales_invoice", "invoice_data", "signed_qr_code"), as_dict=True ) %} {% if not e_invoice_log %} @@ -51,6 +51,7 @@ {% else %} +{%- set invoice_no = e_invoice_log.sales_invoice -%} {%- set invoice_data = _dict(json.loads(e_invoice_log.invoice_data)) -%} {% macro get_address_html(address) %} @@ -205,7 +206,7 @@
3. Item Details
{%- set amounts = invoice_data.ValDtls -%} -{% set amount_fields = get_non_zero_fields(amounts, AMOUNT_FIELDS_MAP) %} +{% set amount_fields = get_non_zero_fields(amounts, AMOUNT_FIELDS_MAP, invoice_no) %}
4. Value Details
diff --git a/india_compliance/gst_india/utils/jinja.py b/india_compliance/gst_india/utils/jinja.py index d05126a4b..04ed3648e 100644 --- a/india_compliance/gst_india/utils/jinja.py +++ b/india_compliance/gst_india/utils/jinja.py @@ -6,12 +6,15 @@ from barcode import Code128 from barcode.writer import ImageWriter +import frappe + from india_compliance.gst_india.constants.e_waybill import ( SUB_SUPPLY_TYPES, SUPPLY_TYPES, TRANSPORT_MODES, TRANSPORT_TYPES, ) +from india_compliance.gst_india.overrides.transaction import is_inter_state_supply from india_compliance.gst_india.utils import as_ist @@ -81,17 +84,28 @@ def get_ewaybill_barcode(ewaybill): return barcode_base64 -def get_non_zero_fields(data, fields): +def get_non_zero_fields(data, fields, doc=None): """Returns a list of fields with non-zero values in order of fields specified""" if isinstance(data, dict): data = [data] non_zero_fields = [] + + # used doc to check wether it is inter state or not + if doc: + doc = frappe.get_doc("Sales Invoice", doc) + fields_to_skip = ( + ["CgstVal", "SgstVal"] if is_inter_state_supply(doc) else ["IgstVal"] + ) + for row in data: for field in fields: - if row.get(field, 0) != 0 and field not in non_zero_fields: - non_zero_fields.append(field) + if field in non_zero_fields: + continue + if doc and field in fields_to_skip: continue + if field.endswith("Rt") or field.endswith("Val") or row.get(field, 0) != 0: + non_zero_fields.append(field) return non_zero_fields From 5dbb5386decd1debbdae5d0250063b5e2e69adda Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 7 Dec 2022 05:59:03 +0000 Subject: [PATCH 08/58] fix: show mandatory fields in print format (cherry picked from commit 27ca78f5b26e2b8110b1a9860afdaa6569abf157) --- .../print_format/e_invoice/e_invoice.html | 5 ++-- india_compliance/gst_india/utils/jinja.py | 28 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html index 9eab4ebac..0cf1631f6 100644 --- a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html +++ b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html @@ -40,7 +40,7 @@ {% else %} {% set e_invoice_log = frappe.db.get_value( - "e-Invoice Log", doc.irn, ("sales_invoice", "invoice_data", "signed_qr_code"), as_dict=True + "e-Invoice Log", doc.irn, ("invoice_data", "signed_qr_code"), as_dict=True ) %} {% if not e_invoice_log %} @@ -51,7 +51,6 @@ {% else %} -{%- set invoice_no = e_invoice_log.sales_invoice -%} {%- set invoice_data = _dict(json.loads(e_invoice_log.invoice_data)) -%} {% macro get_address_html(address) %} @@ -206,7 +205,7 @@
3. Item Details
{%- set amounts = invoice_data.ValDtls -%} -{% set amount_fields = get_non_zero_fields(amounts, AMOUNT_FIELDS_MAP, invoice_no) %} +{% set amount_fields = get_non_zero_fields(amounts, AMOUNT_FIELDS_MAP, doc) %}
4. Value Details
diff --git a/india_compliance/gst_india/utils/jinja.py b/india_compliance/gst_india/utils/jinja.py index 04ed3648e..a57151803 100644 --- a/india_compliance/gst_india/utils/jinja.py +++ b/india_compliance/gst_india/utils/jinja.py @@ -6,8 +6,6 @@ from barcode import Code128 from barcode.writer import ImageWriter -import frappe - from india_compliance.gst_india.constants.e_waybill import ( SUB_SUPPLY_TYPES, SUPPLY_TYPES, @@ -85,27 +83,31 @@ def get_ewaybill_barcode(ewaybill): def get_non_zero_fields(data, fields, doc=None): - """Returns a list of fields with non-zero values in order of fields specified""" + """ + Returns a list of fields with non-zero values in order of fields specified + Always return mandatory fields even if they have zero value + """ if isinstance(data, dict): data = [data] non_zero_fields = [] + mandatory_fields = ["GstRt"] - # used doc to check wether it is inter state or not if doc: - doc = frappe.get_doc("Sales Invoice", doc) - fields_to_skip = ( - ["CgstVal", "SgstVal"] if is_inter_state_supply(doc) else ["IgstVal"] - ) + if is_inter_state_supply(doc): + mandatory_fields.extend(["IgstVal"]) + + else: + mandatory_fields.extend(["CgstVal", "SgstVal"]) for row in data: for field in fields: - if field in non_zero_fields: - continue - if doc and field in fields_to_skip: - continue - if field.endswith("Rt") or field.endswith("Val") or row.get(field, 0) != 0: + is_mandatory = field in mandatory_fields and field in row + + if ( + row.get(field, 0) != 0 or is_mandatory + ) and field not in non_zero_fields: non_zero_fields.append(field) return non_zero_fields From 38ef8dd153043ef6161405a01db0e783aeb72167 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Fri, 25 Nov 2022 11:08:25 +0530 Subject: [PATCH 09/58] fix: setup of ewaybill info on onload event (cherry picked from commit 76c1f84eb3e639f02a824dfb956ef93a25a3aa04) --- .../gst_india/overrides/delivery_note.py | 33 +++++++++++++++++++ .../gst_india/overrides/sales_invoice.py | 10 ------ .../gst_india/overrides/transaction.py | 10 ++++++ india_compliance/hooks.py | 6 +++- 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/india_compliance/gst_india/overrides/delivery_note.py b/india_compliance/gst_india/overrides/delivery_note.py index 1ecefdabb..c41ce921b 100644 --- a/india_compliance/gst_india/overrides/delivery_note.py +++ b/india_compliance/gst_india/overrides/delivery_note.py @@ -1,6 +1,39 @@ +import frappe + from india_compliance.gst_india.overrides.sales_invoice import ( update_dashboard_with_gst_logs, ) +from india_compliance.gst_india.utils import is_api_enabled + + +def onload(doc, method=None): + if not doc.get("ewaybill"): + return + + gst_settings = frappe.get_cached_value( + "GST Settings", + "GST Settings", + ("enable_api", "enable_e_waybill", "enable_e_waybill_from_dn", "api_secret"), + as_dict=1, + ) + + if not is_api_enabled(gst_settings): + return + + if ( + gst_settings.enable_e_waybill + and gst_settings.enable_e_waybill_from_dn + and doc.ewaybill + ): + doc.set_onload( + "e_waybill_info", + frappe.get_value( + "e-Waybill Log", + doc.ewaybill, + ("created_on", "valid_upto"), + as_dict=True, + ), + ) def get_dashboard_data(data): diff --git a/india_compliance/gst_india/overrides/sales_invoice.py b/india_compliance/gst_india/overrides/sales_invoice.py index 95c06c6f9..52880ecc3 100644 --- a/india_compliance/gst_india/overrides/sales_invoice.py +++ b/india_compliance/gst_india/overrides/sales_invoice.py @@ -1,6 +1,5 @@ import frappe from frappe import _, bold -from frappe.model import delete_doc from india_compliance.gst_india.constants import GST_INVOICE_NUMBER_FORMAT from india_compliance.gst_india.overrides.transaction import validate_transaction @@ -148,15 +147,6 @@ def on_submit(doc, method=None): ) -def ignore_logs_on_trash(doc, method=None): - # TODO: design better way to achieve this - if "e-Waybill Log" not in delete_doc.doctypes_to_skip: - delete_doc.doctypes_to_skip += ( - "e-Waybill Log", - "e-Invoice Log", - ) - - def get_dashboard_data(data): return update_dashboard_with_gst_logs( "Sales Invoice", data, "e-Waybill Log", "e-Invoice Log", "Integration Request" diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 3b81d900f..ef85135a7 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -2,6 +2,7 @@ import frappe from frappe import _, bold +from frappe.model import delete_doc from frappe.utils import cint, flt from erpnext.controllers.accounts_controller import get_taxes_and_charges @@ -723,3 +724,12 @@ def validate_transaction(doc, method=None): valid_accounts = validate_gst_accounts(doc, is_sales_transaction) or () update_taxable_values(doc, valid_accounts) + + +def ignore_logs_on_trash(doc, method=None): + # TODO: design better way to achieve this + if "e-Waybill Log" not in delete_doc.doctypes_to_skip: + delete_doc.doctypes_to_skip += ( + "e-Waybill Log", + "e-Invoice Log", + ) diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index 17db4908e..95aad0a7b 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -64,6 +64,10 @@ ), }, "Delivery Note": { + "on_trash": ( + "india_compliance.gst_india.overrides.transaction.ignore_logs_on_trash" + ), + "onload": "india_compliance.gst_india.overrides.delivery_note.onload", "validate": ( "india_compliance.gst_india.overrides.transaction.validate_transaction" ), @@ -89,7 +93,7 @@ }, "Sales Invoice": { "on_trash": ( - "india_compliance.gst_india.overrides.sales_invoice.ignore_logs_on_trash" + "india_compliance.gst_india.overrides.transaction.ignore_logs_on_trash" ), "onload": "india_compliance.gst_india.overrides.sales_invoice.onload", "validate": "india_compliance.gst_india.overrides.sales_invoice.validate", From c606d0c32e87556f30307c38be12be0914f90ac3 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Mon, 28 Nov 2022 10:37:58 +0530 Subject: [PATCH 10/58] fix: minor changes (cherry picked from commit 2549d6989385d08b3c956a598b2307f79b049e58) --- .../gst_india/overrides/delivery_note.py | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/india_compliance/gst_india/overrides/delivery_note.py b/india_compliance/gst_india/overrides/delivery_note.py index c41ce921b..961017b26 100644 --- a/india_compliance/gst_india/overrides/delivery_note.py +++ b/india_compliance/gst_india/overrides/delivery_note.py @@ -7,33 +7,23 @@ def onload(doc, method=None): - if not doc.get("ewaybill"): - return + gst_settings = frappe.get_cached_doc("GST Settings") - gst_settings = frappe.get_cached_value( - "GST Settings", - "GST Settings", - ("enable_api", "enable_e_waybill", "enable_e_waybill_from_dn", "api_secret"), - as_dict=1, - ) + if not (is_api_enabled(gst_settings) or gst_settings.enable_e_waybill_from_dn): + return - if not is_api_enabled(gst_settings): + if not doc.get("ewaybill"): return - if ( - gst_settings.enable_e_waybill - and gst_settings.enable_e_waybill_from_dn - and doc.ewaybill - ): - doc.set_onload( - "e_waybill_info", - frappe.get_value( - "e-Waybill Log", - doc.ewaybill, - ("created_on", "valid_upto"), - as_dict=True, - ), - ) + doc.set_onload( + "e_waybill_info", + frappe.get_value( + "e-Waybill Log", + doc.ewaybill, + ("created_on", "valid_upto"), + as_dict=True, + ), + ) def get_dashboard_data(data): From eff62651443f82b5fe95378abb6820871d0df033 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Mon, 28 Nov 2022 10:44:08 +0530 Subject: [PATCH 11/58] fix: condition order (cherry picked from commit 119dddda09807e4364d4edd3e27bd8686e506654) --- india_compliance/gst_india/overrides/delivery_note.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/india_compliance/gst_india/overrides/delivery_note.py b/india_compliance/gst_india/overrides/delivery_note.py index 961017b26..4aff3a2d6 100644 --- a/india_compliance/gst_india/overrides/delivery_note.py +++ b/india_compliance/gst_india/overrides/delivery_note.py @@ -7,14 +7,14 @@ def onload(doc, method=None): + if not doc.get("ewaybill"): + return + gst_settings = frappe.get_cached_doc("GST Settings") if not (is_api_enabled(gst_settings) or gst_settings.enable_e_waybill_from_dn): return - if not doc.get("ewaybill"): - return - doc.set_onload( "e_waybill_info", frappe.get_value( From 9e2d1a3cc776c15d02d1a3aecfd79728a1a9799f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 7 Dec 2022 11:52:45 +0000 Subject: [PATCH 12/58] fix: correct conditions for set_onload (cherry picked from commit f7d3301b39efb7b80de0cb3749ec5fa58be77313) --- india_compliance/gst_india/overrides/delivery_note.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/india_compliance/gst_india/overrides/delivery_note.py b/india_compliance/gst_india/overrides/delivery_note.py index 4aff3a2d6..d4c8d49d6 100644 --- a/india_compliance/gst_india/overrides/delivery_note.py +++ b/india_compliance/gst_india/overrides/delivery_note.py @@ -12,7 +12,11 @@ def onload(doc, method=None): gst_settings = frappe.get_cached_doc("GST Settings") - if not (is_api_enabled(gst_settings) or gst_settings.enable_e_waybill_from_dn): + if not ( + is_api_enabled(gst_settings) + and gst_settings.enable_e_waybill + and gst_settings.enable_e_waybill_from_dn + ): return doc.set_onload( From 85f16696df32e2532c9ca498259f6f7d908a9ffd Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 7 Dec 2022 11:53:21 +0000 Subject: [PATCH 13/58] fix: irn field absent in delivery notes (cherry picked from commit acfe0a268bd90fd62c23d6e2d96c6164f0522f63) --- india_compliance/gst_india/utils/e_waybill.py | 4 ++-- 1 file 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 6be602042..b4e01fe17 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -70,7 +70,7 @@ def _generate_e_waybill(doc, throw=True): # Via e-Invoice API if not Return or Debit Note # Handles following error when generating e-Waybill using IRN: # 4010: E-way Bill cannot generated for Debit Note, Credit Note and Services - with_irn = doc.irn and not (doc.is_return or doc.is_debit_note) + with_irn = doc.get("irn") and not (doc.is_return or doc.is_debit_note) data = EWaybillData(doc).get_data(with_irn=with_irn) except frappe.ValidationError as e: @@ -157,7 +157,7 @@ def _cancel_e_waybill(doc, values): # if e-Waybill has been created using IRN if ( frappe.conf.ic_api_sandbox_mode - and doc.irn + and doc.get("irn") and not (doc.is_return or doc.is_debit_note) ) else EWaybillAPI From 017f8e3d10f5becabb1c05bfa8d887c5fe8d3521 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Wed, 7 Dec 2022 18:46:12 +0530 Subject: [PATCH 14/58] feat: bulk e_invoice generation from list view (#345) * feat: bulk e_invoice generation from list view * fix: fix use of template string in msgprint * fix: changes as per review * fix: check permission and better link * fix: show bulk options only if enabled * chore: remove comments not required * chore: define constant doctype Co-authored-by: Smit Vora (cherry picked from commit 73a091097293c8cdd5127f4a1915a29b8a15ec52) --- .../client_scripts/sales_invoice_list.js | 81 ++++++++++++++----- .../gst_india/overrides/sales_invoice.py | 26 ++++++ 2 files changed, 87 insertions(+), 20 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/sales_invoice_list.js b/india_compliance/gst_india/client_scripts/sales_invoice_list.js index feda7ae94..0c1dc026d 100644 --- a/india_compliance/gst_india/client_scripts/sales_invoice_list.js +++ b/india_compliance/gst_india/client_scripts/sales_invoice_list.js @@ -1,29 +1,70 @@ -const erpnext_onload = frappe.listview_settings["Sales Invoice"].onload; -frappe.listview_settings["Sales Invoice"].onload = function (list_view) { +const DOCTYPE = "Sales Invoice"; +const erpnext_onload = frappe.listview_settings[DOCTYPE].onload; +frappe.listview_settings[DOCTYPE].onload = function (list_view) { if (erpnext_onload) { erpnext_onload(list_view); } - const action = async () => { - const selected_docs = list_view.get_checked_items(); - const docnames = list_view.get_checked_items(true); + if (!frappe.perm.has_perm(DOCTYPE, 0, "submit") || !ic.is_api_enabled()) + return; + + if (gst_settings.enable_e_waybill) + bulk_generate(list_view, "e-Waybill JSON", generate_e_waybill_json); + + if (gst_settings.enable_e_invoice) + bulk_generate(list_view, "e-Invoice", generate_e_invoice); +}; - for (let doc of selected_docs) { - if (doc.docstatus !== 1) { - frappe.throw( - __("e-Waybill JSON can only be generated from a submitted document") - ); - } - } +function bulk_generate(list_view, label, callback) { + list_view.page.add_actions_menu_item( + __("Generate {0}", [__(label)]), + () => { + const selected_docs = list_view.get_checked_items(); + const docnames = list_view.get_checked_items(true); + validate_draft_invoices(selected_docs, label); + callback(docnames); + }, + false + ); +} - const ewb_data = await frappe.xcall( - "india_compliance.gst_india.utils.e_waybill.generate_e_waybill_json", - { doctype: list_view.doctype, docnames } - ); +async function generate_e_waybill_json(docnames) { + const ewb_data = await frappe.xcall( + "india_compliance.gst_india.utils.e_waybill.generate_e_waybill_json", + { doctype: DOCTYPE, docnames } + ); - trigger_file_download(ewb_data, get_e_waybill_file_name()); - }; + trigger_file_download(ewb_data, get_e_waybill_file_name()); +} - list_view.page.add_actions_menu_item(__("Generate e-Waybill JSON"), action, false); +function generate_e_invoice(docnames) { + frappe.xcall( + "india_compliance.gst_india.overrides.sales_invoice.generate_e_invoice", + { docnames } + ); + + const today = frappe.datetime.get_today(); + const route = frappe.utils.generate_route({ + type: "doctype", + name: "Integration Request", + route_options: { + integration_request_service: "India Compliance API", + creation: `["Between",["${today}", "${today}"]]`, + }, + }); + frappe.msgprint( + __( + 'Bulk Generation is queued. Check the progress in Integration Request Log.', + [route] + ) + ); +} -}; +function validate_draft_invoices(selected_docs, label) { + const draft_invoices = selected_docs.filter(doc => doc.docstatus == 0); + if (!draft_invoices.length) return; + + frappe.throw( + __("{0} can only be generated from a submitted document.", [__(label)]) + ); +} diff --git a/india_compliance/gst_india/overrides/sales_invoice.py b/india_compliance/gst_india/overrides/sales_invoice.py index 95c06c6f9..9c3a13937 100644 --- a/india_compliance/gst_india/overrides/sales_invoice.py +++ b/india_compliance/gst_india/overrides/sales_invoice.py @@ -186,3 +186,29 @@ def update_dashboard_with_gst_logs(doctype, data, *log_doctypes): transactions.insert(2, {"label": _("GST Logs"), "items": log_doctypes}) return data + + +@frappe.whitelist() +def generate_e_invoice(docnames): + """ + Bulk generate e-Invoices for the given Sales Invoices. + Permission checks are done in the `generate_e_invoice` function. + """ + gst_settings = frappe.get_cached_doc("GST Settings") + if not is_api_enabled(gst_settings): + return + + docnames = frappe.parse_json(docnames) if docnames.startswith("[") else [docnames] + for doc in docnames: + doc = frappe.get_doc("Sales Invoice", doc) + if doc.docstatus != 1 or not validate_e_invoice_applicability( + doc, gst_settings, throw=False + ): + continue + + frappe.enqueue( + "india_compliance.gst_india.utils.e_invoice.generate_e_invoice", + queue="short", + docname=doc.name, + throw=False, + ) From d54449707adaab79d6e14b993e57188a277b4a84 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 7 Dec 2022 19:25:38 +0530 Subject: [PATCH 15/58] fix: `is_export_with_gst` and `is_reverse_charge` will be shown if applicable (#221) * fix: is_export_with_gst and and is_reverse_charge will be shown if applicable * fix: reverse charge setting and maintained column position * fix: indentation changes * fix: minor fix * fix: check gst_settings for each execute * chore: redefine constants * fix: copy list to keep it unaltered Co-authored-by: Daizy Co-authored-by: Sagar Vora (cherry picked from commit 2be4c2a0013b66deb425c0da130fd8be702c3702) --- .../gst_itemised_sales_register.py | 93 +++++-------- .../gst_sales_register/gst_sales_register.py | 128 ++++++++++-------- 2 files changed, 104 insertions(+), 117 deletions(-) diff --git a/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py b/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py index 2b74dfe76..cf0afd9b7 100644 --- a/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py +++ b/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py @@ -1,70 +1,41 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - - +import frappe from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import ( _execute, ) +from india_compliance.gst_india.report.gst_sales_register.gst_sales_register import ( + ADDITIONAL_QUERY_COLUMNS, + ADDITIONAL_TABLE_COLUMNS, + EXPORT_TYPE_COLUMNS, + REVERSE_CHARGE_COLUMNS, +) + def execute(filters=None): - return _execute( - filters, - additional_table_columns=[ - dict( - fieldtype="Data", - label="Billing Address GSTIN", - fieldname="billing_address_gstin", - width=140, - ), - dict( - fieldtype="Data", - label="Company GSTIN", - fieldname="company_gstin", - width=120, - ), - dict( - fieldtype="Data", - label="Place of Supply", - fieldname="place_of_supply", - width=120, - ), - dict( - fieldtype="Check", - label="Is Reverse Charge", - fieldname="is_reverse_charge", - width=120, - ), - dict( - fieldtype="Data", - label="GST Category", - fieldname="gst_category", - width=120, - ), - dict( - fieldtype="Check", - label="Is Export With GST", - fieldname="is_export_with_gst", - width=120, - ), - dict( - fieldtype="Data", - label="E-Commerce GSTIN", - fieldname="ecommerce_gstin", - width=130, - ), - dict( - fieldtype="Data", label="HSN Code", fieldname="gst_hsn_code", width=120 - ), - ], - additional_query_columns=[ - "billing_address_gstin", - "company_gstin", - "place_of_supply", - "is_reverse_charge", - "gst_category", - "is_export_with_gst", - "ecommerce_gstin", - "gst_hsn_code", - ], + overseas_enabled, reverse_charge_enabled = frappe.get_cached_value( + "GST Settings", + "GST Settings", + ("enable_overseas_transactions", "enable_reverse_charge_in_sales"), ) + + additional_table_columns = [ + *ADDITIONAL_TABLE_COLUMNS, + dict(fieldtype="Data", label="HSN Code", fieldname="gst_hsn_code", width=120), + ] + + additional_query_columns = [ + *ADDITIONAL_QUERY_COLUMNS, + "gst_hsn_code", + ] + + if reverse_charge_enabled: + additional_table_columns.insert(3, REVERSE_CHARGE_COLUMNS) + additional_query_columns.insert(3, "is_reverse_charge") + + if overseas_enabled: + additional_table_columns.insert(-3, EXPORT_TYPE_COLUMNS) + additional_query_columns.insert(-3, "is_export_with_gst") + + return _execute(filters, additional_table_columns, additional_query_columns) diff --git a/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py b/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py index ea24afb39..89b46875d 100644 --- a/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py +++ b/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py @@ -1,64 +1,80 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +import frappe +from erpnext.accounts.report.sales_register.sales_register import _execute +EXPORT_TYPE_COLUMNS = dict( + fieldtype="Check", + label="Is Export With GST", + fieldname="is_export_with_gst", + width=120, +) -from erpnext.accounts.report.sales_register.sales_register import _execute +REVERSE_CHARGE_COLUMNS = dict( + fieldtype="Check", + label="Is Reverse Charge", + fieldname="is_reverse_charge", + width=120, +) + +ADDITIONAL_TABLE_COLUMNS = [ + dict( + fieldtype="Data", + label="Billing Address GSTIN", + fieldname="billing_address_gstin", + width=140, + ), + dict( + fieldtype="Data", + label="Company GSTIN", + fieldname="company_gstin", + width=120, + ), + dict( + fieldtype="Data", + label="Place of Supply", + fieldname="place_of_supply", + width=120, + ), + dict( + fieldtype="Data", + label="GST Category", + fieldname="gst_category", + width=120, + ), + dict( + fieldtype="Data", + label="E-Commerce GSTIN", + fieldname="ecommerce_gstin", + width=130, + ), +] + +ADDITIONAL_QUERY_COLUMNS = [ + "billing_address_gstin", + "company_gstin", + "place_of_supply", + "gst_category", + "ecommerce_gstin", +] def execute(filters=None): - return _execute( - filters, - additional_table_columns=[ - dict( - fieldtype="Data", - label="Billing Address GSTIN", - fieldname="billing_address_gstin", - width=140, - ), - dict( - fieldtype="Data", - label="Company GSTIN", - fieldname="company_gstin", - width=120, - ), - dict( - fieldtype="Data", - label="Place of Supply", - fieldname="place_of_supply", - width=120, - ), - dict( - fieldtype="Check", - label="Is Reverse Charge", - fieldname="is_reverse_charge", - width=120, - ), - dict( - fieldtype="Data", - label="GST Category", - fieldname="gst_category", - width=120, - ), - dict( - fieldtype="Check", - label="Is Export With GST", - fieldname="is_export_with_gst", - width=120, - ), - dict( - fieldtype="Data", - label="E-Commerce GSTIN", - fieldname="ecommerce_gstin", - width=130, - ), - ], - additional_query_columns=[ - "billing_address_gstin", - "company_gstin", - "place_of_supply", - "is_reverse_charge", - "gst_category", - "is_export_with_gst", - "ecommerce_gstin", - ], + overseas_enabled, reverse_charge_enabled = frappe.get_cached_value( + "GST Settings", + "GST Settings", + ("enable_overseas_transactions", "enable_reverse_charge_in_sales"), ) + + additional_table_columns = ADDITIONAL_TABLE_COLUMNS.copy() + additional_query_columns = ADDITIONAL_QUERY_COLUMNS.copy() + + if reverse_charge_enabled: + additional_table_columns.insert(3, REVERSE_CHARGE_COLUMNS) + additional_query_columns.insert(3, "is_reverse_charge") + + if overseas_enabled: + additional_table_columns.insert(-2, EXPORT_TYPE_COLUMNS) + additional_query_columns.insert(-2, "is_export_with_gst") + + return _execute(filters, additional_table_columns, additional_query_columns) From 76bf78ad6948b0b1851f05935834fcab27962db0 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Sat, 10 Dec 2022 20:36:28 +0530 Subject: [PATCH 16/58] fix: added IRN and ewaybill details in respective print format (#383) * fix: added IRN and ewaybill details in respective print format * fix: spacing in list js * fix: show value only if it exists (cherry picked from commit cf4e3690469f4b690877427f9a9cc82d57924f01) --- .../gst_india/client_scripts/sales_invoice_list.js | 6 +++--- .../gst_india/print_format/e_invoice/e_invoice.html | 11 +++++++---- .../gst_india/print_format/e_waybill/e_waybill.html | 13 +++++++++++++ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/sales_invoice_list.js b/india_compliance/gst_india/client_scripts/sales_invoice_list.js index 0c1dc026d..71993d68d 100644 --- a/india_compliance/gst_india/client_scripts/sales_invoice_list.js +++ b/india_compliance/gst_india/client_scripts/sales_invoice_list.js @@ -7,10 +7,10 @@ frappe.listview_settings[DOCTYPE].onload = function (list_view) { if (!frappe.perm.has_perm(DOCTYPE, 0, "submit") || !ic.is_api_enabled()) return; - + if (gst_settings.enable_e_waybill) bulk_generate(list_view, "e-Waybill JSON", generate_e_waybill_json); - + if (gst_settings.enable_e_invoice) bulk_generate(list_view, "e-Invoice", generate_e_invoice); }; @@ -42,7 +42,7 @@ function generate_e_invoice(docnames) { "india_compliance.gst_india.overrides.sales_invoice.generate_e_invoice", { docnames } ); - + const today = frappe.datetime.get_today(); const route = frappe.utils.generate_route({ type: "doctype", diff --git a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html index 0cf1631f6..26c3ce2a3 100644 --- a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html +++ b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html @@ -96,13 +96,16 @@
1. Transaction Details
"Category": invoice_data.TranDtls.SupTyp, "Document Type": invoice_data.DocDtls.Typ, "Document No": invoice_data.DocDtls.No, + "e-Waybill": doc.ewaybill, } %} {% for key, value in transaction_details.items() %} -
-
-
{{ value }}
-
+ {% if value %} +
+
+
{{ value }}
+
+ {% endif %} {% endfor %}
diff --git a/india_compliance/gst_india/print_format/e_waybill/e_waybill.html b/india_compliance/gst_india/print_format/e_waybill/e_waybill.html index f98ec6f34..194a4f108 100644 --- a/india_compliance/gst_india/print_format/e_waybill/e_waybill.html +++ b/india_compliance/gst_india/print_format/e_waybill/e_waybill.html @@ -3,6 +3,7 @@ {% set data = _dict(json.loads(doc.data)) %} +{%- set irn = frappe.db.get_value("Sales Invoice", {'ewaybill': data.ewbNo}, 'irn') -%} @@ -81,6 +82,18 @@

e-Waybill

+ {% if irn %} + + + IRN: + + + + {{ irn }} + + + + {% endif %}
From 9a6b63e0699b24ca25bf65f18f41972e1c920036 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Tue, 13 Dec 2022 11:39:51 +0530 Subject: [PATCH 17/58] fix: leading zeros for state code with single number (cherry picked from commit 4c9f8c82dc5b33e372d259c5869d73cadb3c912e) --- india_compliance/gst_india/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index 099da6a09..c75cf8c78 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -26,7 +26,7 @@ def get_state(state_number): """Get state from State Number""" - state_number = str(state_number) + state_number = str(state_number).zfill(2) for state, code in STATE_NUMBERS.items(): if code == state_number: From e7c18cc135f3a1621a8c9ffb2c61134e0d62246b Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 15 Dec 2022 17:01:16 +0530 Subject: [PATCH 18/58] chore: improve branch info in README (cherry picked from commit 8919c1ee0b4ce62b9f0e1c5644042b520f4f56b7) --- README.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 357a63d44..92a22fea4 100644 --- a/README.md +++ b/README.md @@ -32,17 +32,26 @@ For a detailed overview of these features, please [refer to the documentation](h Once you've [set up a Frappe site](https://frappeframework.com/docs/v14/user/en/installation/), installing India Compliance is simple: -1. Download the app using the Bench CLI. The `--branch` option defaults to `next`. Use appropriate version as per your setup. +1. Download the app using the Bench CLI. - ```bash - bench get-app --branch version-14 https://github.com/resilient-tech/india-compliance.git - ``` + ```bash + bench get-app --branch [branch name] https://github.com/resilient-tech/india-compliance.git + ``` -2. Install the app on your site + Replace `[branch name]` with the appropriate branch as per your setup: - ```bash - bench --site [site name] install-app india_compliance - ``` + | Frappe Branch | India Compliance Branch | + |---------------|-------------------------| + | version-14 | version-14 | + | develop | next | + + If it isn't specified, the `--branch` option will default to `next`. + +2. Install the app on your site. + + ```bash + bench --site [site name] install-app india_compliance + ``` ## In-app Purchases From c69d6b7de4378a3b8db99312b27a2165692b9729 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 15 Dec 2022 11:54:14 +0000 Subject: [PATCH 19/58] fix: check api enabled only for e-Invoice (cherry picked from commit 6f442929dfff5f6a70b653d68e58f45a41483289) --- .../gst_india/client_scripts/sales_invoice_list.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/sales_invoice_list.js b/india_compliance/gst_india/client_scripts/sales_invoice_list.js index 71993d68d..ae1a50609 100644 --- a/india_compliance/gst_india/client_scripts/sales_invoice_list.js +++ b/india_compliance/gst_india/client_scripts/sales_invoice_list.js @@ -5,17 +5,17 @@ frappe.listview_settings[DOCTYPE].onload = function (list_view) { erpnext_onload(list_view); } - if (!frappe.perm.has_perm(DOCTYPE, 0, "submit") || !ic.is_api_enabled()) + if (!frappe.perm.has_perm(DOCTYPE, 0, "submit")) return; if (gst_settings.enable_e_waybill) - bulk_generate(list_view, "e-Waybill JSON", generate_e_waybill_json); + add_bulk_action(list_view, "e-Waybill JSON", generate_e_waybill_json); - if (gst_settings.enable_e_invoice) - bulk_generate(list_view, "e-Invoice", generate_e_invoice); + if (ic.is_e_invoice_enabled()) + add_bulk_action(list_view, "e-Invoice", generate_e_invoice); }; -function bulk_generate(list_view, label, callback) { +function add_bulk_action(list_view, label, callback) { list_view.page.add_actions_menu_item( __("Generate {0}", [__(label)]), () => { From 6359f3070984cc14b299c0631b23e6f85593256c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 15 Dec 2022 18:50:59 +0530 Subject: [PATCH 20/58] fix: Ignore company related GST settings on clearing transactions (cherry picked from commit a88abf2434dc6696454cc8572a78cf6a11862dfd) --- india_compliance/hooks.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index 95aad0a7b..d9f9266fd 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -170,6 +170,10 @@ ), } + +# DocTypes to be ignored while clearing transactions +company_data_to_be_ignored = ["GST Account", "GST Credential"] + # Includes in # ------------------ From 04002e4208a68385a2b111de6dbd3e4a07eb5b28 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Sat, 10 Dec 2022 11:38:48 +0530 Subject: [PATCH 21/58] fix: missing address error handling (cherry picked from commit f0be9e5b8a590cef2672f9db533c5d05db48b658) --- .../gst_india/utils/transaction_data.py | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index 41247f427..faffa0af0 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -314,12 +314,19 @@ def get_address_details(self, address_name, validate_gstin=False): { "gstin": address.get("gstin") or "URP", "state_number": address.gst_state_number, - "address_title": self.sanitize_value(address.address_title, 2), + "address_title": self.sanitize_value( + {"address_title": address.address_title}, 2, throw=True + ), "address_line1": self.sanitize_value( - address.address_line1, 3, min_length=1 + {"address_line1": address.address_line1}, + 3, + min_length=1, + throw=True, ), "address_line2": self.sanitize_value(address.address_line2, 3), - "city": self.sanitize_value(address.city, 3, max_length=50), + "city": self.sanitize_value( + {"city": address.city}, 3, max_length=50, throw=True + ), "pincode": int(address.pincode), } ) @@ -396,15 +403,50 @@ def sanitize_value( min_length=3, max_length=100, truncate=True, + throw=False, ): + """ + Sanitize a value for use in a GST document. + @param value: The value to be sanitized. Can be one of the following: + - A string + - A dict with a key matching the fieldname and a value + e.g. {"address_title": "Test Address"}, {"address_title": like object} + """ + + def _throw(error): + if throw: + frappe.throw(error, title=_("Missing Data")) + + key = value + if isinstance(value, dict): + key = list(value.keys())[0] + value = value[key] + + if isinstance(value, dict): + value = value.get(key) + if not value or len(value) < min_length: - return + return _throw( + _("{0} must be at least {1} characters long").format(key, min_length), + ) + + if isinstance(value, str) and not value.isascii(): + return _throw( + _( + "Unicode characters are not allowed. Please edit details in English only." + ) + ) if regex: value = re.sub(REGEX_MAP[regex], "", value) + if not value: + return _throw(_("{0} is mandatory.").format(key)) + if not truncate and len(value) > max_length: - return + return _throw( + _("Value must be at most {0} characters long.").format(max_length), + ) return value[:max_length] From 6a96fc528bfc0ad7db8617d6c9e75e5a61f8e3a3 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 10 Dec 2022 13:50:53 +0000 Subject: [PATCH 22/58] fix: handle erros while sanitizing mandatory value (cherry picked from commit 2e517469657905428c34f7405bc706c59e1dfb4a) --- .../gst_india/utils/transaction_data.py | 90 +++++++++++-------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index faffa0af0..d22f5601b 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -310,22 +310,35 @@ def get_address_details(self, address_name, validate_gstin=False): self.check_missing_address_fields(address, validate_gstin) + error_context = { + "doctype": "Address", + "docname": address.name, + } + return frappe._dict( { "gstin": address.get("gstin") or "URP", "state_number": address.gst_state_number, "address_title": self.sanitize_value( - {"address_title": address.address_title}, 2, throw=True + address.address_title, + regex=2, + throw=True, + error_context={**error_context, "fieldname": "Address Title"}, ), "address_line1": self.sanitize_value( - {"address_line1": address.address_line1}, - 3, + address.address_line1, + regex=3, min_length=1, throw=True, + error_context={**error_context, "fieldname": "Address Line 1"}, ), - "address_line2": self.sanitize_value(address.address_line2, 3), + "address_line2": self.sanitize_value(address.address_line2, regex=3), "city": self.sanitize_value( - {"city": address.city}, 3, max_length=50, throw=True + address.city, + regex=3, + max_length=50, + throw=True, + error_context={**error_context, "fieldname": "City"}, ), "pincode": int(address.pincode), } @@ -398,55 +411,62 @@ def rounded(value, precision=2): @staticmethod def sanitize_value( - value, + value: str, regex=None, min_length=3, max_length=100, truncate=True, throw=False, + error_context=None, ): """ - Sanitize a value for use in a GST document. - @param value: The value to be sanitized. Can be one of the following: - - A string - - A dict with a key matching the fieldname and a value - e.g. {"address_title": "Test Address"}, {"address_title": like object} - """ - - def _throw(error): - if throw: - frappe.throw(error, title=_("Missing Data")) - - key = value - if isinstance(value, dict): - key = list(value.keys())[0] - value = value[key] + Sanitize value to make it suitable for GST JSON sent for e-Waybill and e-Invoice. + + Parameters: + ---------- + @param value: Value to be sanitized + @param regex: Regex Key (from REGEX_MAP) to substitute unacceptable characters + @param min_length (default: 3): Minimum length of the value that is acceptable + @param max_length (default: 100): Maximum length of the value that is acceptable + @param truncate (default: True): Truncate the value if it exceeds max_length + @param throw (default: False): Throw an exception if the value is not acceptable. Used for mandatory fields. + @param error_context: Context to be used in the error message to help the user identify the field + example: error_context = {"fieldname": "Address Line 1", "doctype": "Address" , "docname": "Office Address"} + + Returns: + ---------- + @return: Sanitized value - if isinstance(value, dict): - value = value.get(key) + """ + throw = throw and error_context if not value or len(value) < min_length: - return _throw( - _("{0} must be at least {1} characters long").format(key, min_length), + if not throw: + return + + frappe.throw( + _( + "{fieldname} must be at least {min_length} characters long for {doctype} - {docname}" + ).format(**error_context, min_length=min_length), + title=_("Invalid Data"), ) - if isinstance(value, str) and not value.isascii(): - return _throw( + if not value.isascii(): + if not throw: + return + + frappe.throw( _( - "Unicode characters are not allowed. Please edit details in English only." - ) + "{fieldname} must be ASCII characters only for {doctype} - {docname}. (Non-ASCII characters are not allowed in GST JSON)" + ).format(**error_context), + title=_("Invalid Data"), ) if regex: value = re.sub(REGEX_MAP[regex], "", value) - if not value: - return _throw(_("{0} is mandatory.").format(key)) - if not truncate and len(value) > max_length: - return _throw( - _("Value must be at most {0} characters long.").format(max_length), - ) + return return value[:max_length] From 56eb05e39d474963d9f394025f0a687eb1ba635f Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Sat, 17 Dec 2022 20:28:23 +0530 Subject: [PATCH 23/58] test: test case for `generate_e_invoice` (#250) * wip: test case for ewaybill and einvoice * test: ewaybill test case * test: ewaybill test case * fix: add asserts in ewaybill test case * fix: assert in test case * test: validate e-invoice applicability and refactor other test cases * fix: minor dependency changes * fix: set value enable api * fix: refactor test case and change service item * fix: trial commit for config * fix:get_data validation test * test: test commit for site-config * fix: test case * fix: test case for validating e_invoice can be cancelled * refactor: use of request data from mock responses * ci: test setting config from yml * ci: trial check secrets * ci: add environment * ci: trial env variable * ci: trial secrets * ci: trial secrets * ci: print github * fix: allow mock tests without api key * fix: correct condition usage * fix: pass cancel param in load_doc * ci: trial commit to print * fix: remove print statement * fix: use of default functions instead test case function * fix: trial commit to check acknowledgement date * fix: acknowledgement date error * test: add assertions and request data to json * refactor: minor refactor while review * test: improved coverage * fix: site config with correct key Co-authored-by: Smit Vora (cherry picked from commit 803e5e29b089af540f11a8182bb86e7b3e809f98) --- .github/helper/site_config.json | 1 + .../gst_india/api_classes/base.py | 4 +- .../gst_india/data/test_e_invoice.json | 533 ++++++++++++++++++ india_compliance/gst_india/utils/e_invoice.py | 11 +- india_compliance/gst_india/utils/e_waybill.py | 5 +- .../gst_india/utils/test_e_invoice.py | 456 +++++++++++++++ india_compliance/tests/test_records.json | 54 +- 7 files changed, 1052 insertions(+), 12 deletions(-) create mode 100644 india_compliance/gst_india/data/test_e_invoice.json create mode 100644 india_compliance/gst_india/utils/test_e_invoice.py diff --git a/.github/helper/site_config.json b/.github/helper/site_config.json index 95c709e32..ffb215945 100644 --- a/.github/helper/site_config.json +++ b/.github/helper/site_config.json @@ -12,6 +12,7 @@ "root_password": "travis", "host_name": "http://test_site:8000", "install_apps": ["erpnext"], + "ic_api_secret": "*****", "ic_api_sandbox_mode": 1, "throttle_user_limit": 100 } \ No newline at end of file diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py index 8e7d6d535..90c3ecf80 100644 --- a/india_compliance/gst_india/api_classes/base.py +++ b/india_compliance/gst_india/api_classes/base.py @@ -33,7 +33,9 @@ def __init__(self, *args, **kwargs): or frappe.conf.ic_api_secret ) } - self.default_log_values = {} + self.default_log_values = { + "now": frappe.flags.in_test, + } self.setup(*args, **kwargs) diff --git a/india_compliance/gst_india/data/test_e_invoice.json b/india_compliance/gst_india/data/test_e_invoice.json new file mode 100644 index 000000000..75879fc09 --- /dev/null +++ b/india_compliance/gst_india/data/test_e_invoice.json @@ -0,0 +1,533 @@ +{ + "goods_item_with_ewaybill": { + "kwargs": { + "vehicle_no": "GJ07DL9009", + "customer_address": "_Test Registered Customer-Billing", + "shipping_address_name": "_Test Registered Customer-Billing" + }, + "request_data": { + "BuyerDtls": { + "Addr1": "Test Address - 3", + "Gstin": "36AMBPG7773M002", + "LglNm": "_Test Registered Customer", + "Loc": "Test City", + "Pin": 500055, + "Pos": "01", + "Stcd": "36", + "TrdNm": "Test Registered Customer" + }, + "DispDtls": { + "Addr1": "Test Address - 1", + "Loc": "Test City", + "Nm": "Test Indian Registered Company", + "Pin": 193501, + "Stcd": "01" + }, + "DocDtls": { + "Dt": "16/09/2022", + "No": "test_invoice_no", + "Typ": "INV" + }, + "EwbDtls": { + "Distance": 0, + "TransMode": "1", + "VehNo": "GJ07DL9009", + "VehType": "R" + }, + "ItemList": [ + { + "AssAmt": 100, + "CesAmt": 0, + "CesNonAdvlAmt": 0, + "CesRt": 0, + "CgstAmt": 0, + "Discount": 0, + "GstRt": 0, + "HsnCd": "61149090", + "IgstAmt": 0, + "IsServc": "N", + "PrdDesc": "Test Trading Goods 1", + "Qty": 1, + "SgstAmt": 0, + "SlNo": "1", + "TotAmt": 100, + "TotItemVal": 100, + "Unit": "NOS", + "UnitPrice": 100 + } + ], + "PayDtls": { + "CrDay": 0, + "PaidAmt": 0, + "PaymtDue": 100 + }, + "SellerDtls": { + "Addr1": "Test Address - 1", + "Gstin": "01AMBPG7773M002", + "LglNm": "_Test Indian Registered Company", + "Loc": "Test City", + "Pin": 193501, + "Stcd": "01", + "TrdNm": "Test Indian Registered Company" + }, + "ShipDtls": { + "Addr1": "Test Address - 3", + "Gstin": "36AMBPG7773M002", + "LglNm": "Test Registered Customer", + "Loc": "Test City", + "Pin": 500055, + "Stcd": "36", + "TrdNm": "Test Registered Customer" + }, + "TranDtls": { + "RegRev": "N", + "SupTyp": "B2B", + "TaxSch": "GST" + }, + "ValDtls": { + "AssVal": 100, + "CesVal": 0, + "CgstVal": 0, + "Discount": 0, + "IgstVal": 0, + "OthChrg": 0, + "RndOffAmt": 0, + "SgstVal": 0, + "TotInvVal": 100 + }, + "Version": "1.1" + }, + "response_data": { + "info": [ + { + "InfCd": "EWBPPD", + "Desc": "Pin-Pin calc distance: 2467KM" + } + ], + "message": "IRN generated successfully", + "result": { + "AckDt": "2022-09-16 19:29:00", + "AckNo": 232210036743849, + "EwbDt": "2022-09-16 19:29:00", + "EwbNo": 391009149369, + "EwbValidTill": "2022-09-29 23:59:00", + "Irn": "706daeccda0ef6f818da78f3a2a05a1288731057373002289b46c3229289a2e7", + "Remarks": null, + "SignedInvoice": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVEQzU3REUxMzU4QjMwMEJBOUY3OTM0MEE2Njk2ODMxRjNDODUwNDciLCJ0eXAiOiJKV1QiLCJ4NXQiOiI3Y1Y5NFRXTE1BdXA5NU5BcG1sb01mUElVRWMifQ.eyJkYXRhIjoie1wiQWNrTm9cIjoyMzIyMTAwMzY3NDM4NDksXCJBY2tEdFwiOlwiMjAyMi0wOS0xNiAxOToyOTowMFwiLFwiSXJuXCI6XCI3MDZkYWVjY2RhMGVmNmY4MThkYTc4ZjNhMmEwNWExMjg4NzMxMDU3MzczMDAyMjg5YjQ2YzMyMjkyODlhMmU3XCIsXCJWZXJzaW9uXCI6XCIxLjFcIixcIlRyYW5EdGxzXCI6e1wiVGF4U2NoXCI6XCJHU1RcIixcIlN1cFR5cFwiOlwiQjJCXCIsXCJSZWdSZXZcIjpcIk5cIn0sXCJEb2NEdGxzXCI6e1wiVHlwXCI6XCJJTlZcIixcIk5vXCI6XCJTSU5WLUNGWS0wMDA2N1wiLFwiRHRcIjpcIjE2LzA5LzIwMjJcIn0sXCJTZWxsZXJEdGxzXCI6e1wiR3N0aW5cIjpcIjAxQU1CUEc3NzczTTAwMlwiLFwiTGdsTm1cIjpcIl9UZXN0IEluZGlhbiBSZWdpc3RlcmVkIENvbXBhbnlcIixcIlRyZE5tXCI6XCJUZXN0IEluZGlhbiBSZWdpc3RlcmVkIENvbXBhbnlcIixcIkFkZHIxXCI6XCJUZXN0IEFkZHJlc3MgLSAxXCIsXCJMb2NcIjpcIlRlc3QgQ2l0eVwiLFwiUGluXCI6MTkzNTAxLFwiU3RjZFwiOlwiMDFcIn0sXCJCdXllckR0bHNcIjp7XCJHc3RpblwiOlwiMzZBTUJQRzc3NzNNMDAyXCIsXCJMZ2xObVwiOlwiX1Rlc3QgUmVnaXN0ZXJlZCBDdXN0b21lclwiLFwiVHJkTm1cIjpcIlRlc3QgUmVnaXN0ZXJlZCBDdXN0b21lclwiLFwiUG9zXCI6XCIwMVwiLFwiQWRkcjFcIjpcIlRlc3QgQWRkcmVzcyAtIDNcIixcIkxvY1wiOlwiVGVzdCBDaXR5XCIsXCJQaW5cIjo1MDAwNTUsXCJTdGNkXCI6XCIzNlwifSxcIkRpc3BEdGxzXCI6e1wiTm1cIjpcIlRlc3QgSW5kaWFuIFJlZ2lzdGVyZWQgQ29tcGFueVwiLFwiQWRkcjFcIjpcIlRlc3QgQWRkcmVzcyAtIDFcIixcIkxvY1wiOlwiVGVzdCBDaXR5XCIsXCJQaW5cIjoxOTM1MDEsXCJTdGNkXCI6XCIwMVwifSxcIlNoaXBEdGxzXCI6e1wiR3N0aW5cIjpcIjM2QU1CUEc3NzczTTAwMlwiLFwiTGdsTm1cIjpcIlRlc3QgUmVnaXN0ZXJlZCBDdXN0b21lclwiLFwiVHJkTm1cIjpcIlRlc3QgUmVnaXN0ZXJlZCBDdXN0b21lclwiLFwiQWRkcjFcIjpcIlRlc3QgQWRkcmVzcyAtIDNcIixcIkxvY1wiOlwiVGVzdCBDaXR5XCIsXCJQaW5cIjo1MDAwNTUsXCJTdGNkXCI6XCIzNlwifSxcIkl0ZW1MaXN0XCI6W3tcIkl0ZW1Ob1wiOjAsXCJTbE5vXCI6XCIxXCIsXCJJc1NlcnZjXCI6XCJOXCIsXCJQcmREZXNjXCI6XCJUZXN0IFRyYWRpbmcgR29vZHMgMVwiLFwiSHNuQ2RcIjpcIjYxMTQ5MDkwXCIsXCJRdHlcIjoxLjAsXCJVbml0XCI6XCJOT1NcIixcIlVuaXRQcmljZVwiOjEwMC4wLFwiVG90QW10XCI6MTAwLjAsXCJEaXNjb3VudFwiOjAsXCJBc3NBbXRcIjoxMDAuMCxcIkdzdFJ0XCI6MC4wLFwiSWdzdEFtdFwiOjAsXCJDZ3N0QW10XCI6MCxcIlNnc3RBbXRcIjowLFwiQ2VzUnRcIjowLFwiQ2VzQW10XCI6MCxcIkNlc05vbkFkdmxBbXRcIjowLFwiVG90SXRlbVZhbFwiOjEwMC4wfV0sXCJWYWxEdGxzXCI6e1wiQXNzVmFsXCI6MTAwLjAsXCJDZ3N0VmFsXCI6MCxcIlNnc3RWYWxcIjowLFwiSWdzdFZhbFwiOjAsXCJDZXNWYWxcIjowLFwiRGlzY291bnRcIjowLFwiT3RoQ2hyZ1wiOjAuMCxcIlJuZE9mZkFtdFwiOjAuMCxcIlRvdEludlZhbFwiOjEwMC4wfSxcIlBheUR0bHNcIjp7XCJDckRheVwiOjAsXCJQYWlkQW10XCI6MCxcIlBheW10RHVlXCI6MTAwLjB9LFwiRXdiRHRsc1wiOntcIlRyYW5zTW9kZVwiOlwiMVwiLFwiRGlzdGFuY2VcIjowLFwiVmVoTm9cIjpcIkdKMDdETDkwMDlcIixcIlZlaFR5cGVcIjpcIlJcIn19IiwiaXNzIjoiTklDIn0.ZOqrLJLsoXHf1QMRPBJoBesVluRB0a0ISsBGn6gqLuiJLfsAG1Oxmimqi9c7dboRnsW1eEj78Yps5D2A05WPMXwdkOy9Ahb_t4jXSGH-ijq_ed8z-xAtyiWH16YfIc9Zg020VkrlZiHdbfkx53hOwEA3aUhHIdPwQE5Kk-O9KWES3cttl9r5lrtzueTlTKB0GqXqiNlmuuQnCAJpWe34Coko1__kyPLLMdKgpOSB0EX2j7NjaZ5KPhu-GZHBtTvKczuSXvli6lwSQLaKpBm1IGvwMo2IzGW62pXp4XdMlcncLuc8wLTExSlKwHhsSspOxhMNBRx3NqcU0PQZOq050Q", + "SignedQRCode": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVEQzU3REUxMzU4QjMwMEJBOUY3OTM0MEE2Njk2ODMxRjNDODUwNDciLCJ0eXAiOiJKV1QiLCJ4NXQiOiI3Y1Y5NFRXTE1BdXA5NU5BcG1sb01mUElVRWMifQ.eyJkYXRhIjoie1wiU2VsbGVyR3N0aW5cIjpcIjAxQU1CUEc3NzczTTAwMlwiLFwiQnV5ZXJHc3RpblwiOlwiMzZBTUJQRzc3NzNNMDAyXCIsXCJEb2NOb1wiOlwiU0lOVi1DRlktMDAwNjdcIixcIkRvY1R5cFwiOlwiSU5WXCIsXCJEb2NEdFwiOlwiMTYvMDkvMjAyMlwiLFwiVG90SW52VmFsXCI6MTAwLjAsXCJJdGVtQ250XCI6MSxcIk1haW5Ic25Db2RlXCI6XCI2MTE0OTA5MFwiLFwiSXJuXCI6XCI3MDZkYWVjY2RhMGVmNmY4MThkYTc4ZjNhMmEwNWExMjg4NzMxMDU3MzczMDAyMjg5YjQ2YzMyMjkyODlhMmU3XCIsXCJJcm5EdFwiOlwiMjAyMi0wOS0xNiAxOToyOTowMFwifSIsImlzcyI6Ik5JQyJ9.j7Fpl3ol0G6akp1-ukVzOK-8Dqoey3iKLf9SCaXGfb3crIcpniezqevH1qBTgCtUDYnOa0tRk5Nhyi-ER-W8Hu2a4Ug28AJFp3S8Xv2RwdMe9HvJN1b8KBKz6N4_WcO7wD2VcXyoEKDuTP2KFlXRjuZx7tBh5ttjQ4vRNtVwpR2qy-lRtMquEbZsJ-JOPBLUTXimdpVwt9EW8xKUxRKT_7-8kwK-DGHePADVBUjD6kv-GSpbxgfM4UAPg1TRlRz_BHMbbi9adZVZn5l9GA-WRuSP_7C-Qd_ucYg1cmP2zswh1XClMEjwjmxpuFkhqdDsRfl8unnEi--FxA0lvn4nmw", + "Status": "ACT", + "distance": 2467 + }, + "success": true + } + }, + "service_item": { + "kwargs": { + "item_code": "_Test Service Item", + "customer_address": "_Test Registered Customer-Billing", + "shipping_address_name": "_Test Registered Customer-Billing" + }, + "request_data": { + "BuyerDtls": { + "Addr1": "Test Address - 3", + "Gstin": "36AMBPG7773M002", + "LglNm": "_Test Registered Customer", + "Loc": "Test City", + "Pin": 500055, + "Pos": "01", + "Stcd": "36", + "TrdNm": "Test Registered Customer" + }, + "DispDtls": { + "Addr1": "Test Address - 1", + "Loc": "Test City", + "Nm": "Test Indian Registered Company", + "Pin": 193501, + "Stcd": "01" + }, + "DocDtls": { + "Dt": "17/09/2022", + "No": "test_invoice_no", + "Typ": "INV" + }, + "EwbDtls": { + "Distance": 0 + }, + "ItemList": [ + { + "AssAmt": 100.0, + "CesAmt": 0, + "CesNonAdvlAmt": 0, + "CesRt": 0, + "CgstAmt": 0, + "Discount": 0, + "GstRt": 0.0, + "HsnCd": "999900", + "IgstAmt": 0, + "IsServc": "Y", + "PrdDesc": "Test Service Item", + "Qty": 1.0, + "SgstAmt": 0, + "SlNo": "1", + "TotAmt": 100.0, + "TotItemVal": 100.0, + "Unit": "NOS", + "UnitPrice": 100.0 + } + ], + "PayDtls": { + "CrDay": 0, + "PaidAmt": 0, + "PaymtDue": 100.0 + }, + "SellerDtls": { + "Addr1": "Test Address - 1", + "Gstin": "01AMBPG7773M002", + "LglNm": "_Test Indian Registered Company", + "Loc": "Test City", + "Pin": 193501, + "Stcd": "01", + "TrdNm": "Test Indian Registered Company" + }, + "ShipDtls": { + "Addr1": "Test Address - 3", + "Gstin": "36AMBPG7773M002", + "LglNm": "Test Registered Customer", + "Loc": "Test City", + "Pin": 500055, + "Stcd": "36", + "TrdNm": "Test Registered Customer" + }, + "TranDtls": { + "RegRev": "N", + "SupTyp": "B2B", + "TaxSch": "GST" + }, + "ValDtls": { + "AssVal": 100.0, + "CesVal": 0, + "CgstVal": 0, + "Discount": 0, + "IgstVal": 0, + "OthChrg": 0.0, + "RndOffAmt": 0.0, + "SgstVal": 0, + "TotInvVal": 100.0 + }, + "Version": "1.1" + }, + "response_data": { + "success": true, + "message": "IRN generated successfully", + "result": { + "AckNo": 232210036754863, + "AckDt": "2022-09-17 16:26:00", + "Irn": "68fb4fab44aee99fb23292478c4bd838e664837c9f1b04e3d9134ffed0b40b60", + "SignedInvoice": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVEQzU3REUxMzU4QjMwMEJBOUY3OTM0MEE2Njk2ODMxRjNDODUwNDciLCJ0eXAiOiJKV1QiLCJ4NXQiOiI3Y1Y5NFRXTE1BdXA5NU5BcG1sb01mUElVRWMifQ.eyJkYXRhIjoie1wiQWNrTm9cIjoyMzIyMTAwMzY3NTQ4NjMsXCJBY2tEdFwiOlwiMjAyMi0wOS0xNyAxNjoyNjowMFwiLFwiSXJuXCI6XCI2OGZiNGZhYjQ0YWVlOTlmYjIzMjkyNDc4YzRiZDgzOGU2NjQ4MzdjOWYxYjA0ZTNkOTEzNGZmZWQwYjQwYjYwXCIsXCJWZXJzaW9uXCI6XCIxLjFcIixcIlRyYW5EdGxzXCI6e1wiVGF4U2NoXCI6XCJHU1RcIixcIlN1cFR5cFwiOlwiQjJCXCIsXCJSZWdSZXZcIjpcIk5cIn0sXCJEb2NEdGxzXCI6e1wiVHlwXCI6XCJJTlZcIixcIk5vXCI6XCJnMnF4aFlcIixcIkR0XCI6XCIxNy8wOS8yMDIyXCJ9LFwiU2VsbGVyRHRsc1wiOntcIkdzdGluXCI6XCIwMUFNQlBHNzc3M00wMDJcIixcIkxnbE5tXCI6XCJfVGVzdCBJbmRpYW4gUmVnaXN0ZXJlZCBDb21wYW55XCIsXCJUcmRObVwiOlwiVGVzdCBJbmRpYW4gUmVnaXN0ZXJlZCBDb21wYW55XCIsXCJBZGRyMVwiOlwiVGVzdCBBZGRyZXNzIC0gMVwiLFwiTG9jXCI6XCJUZXN0IENpdHlcIixcIlBpblwiOjE5MzUwMSxcIlN0Y2RcIjpcIjAxXCJ9LFwiQnV5ZXJEdGxzXCI6e1wiR3N0aW5cIjpcIjM2QU1CUEc3NzczTTAwMlwiLFwiTGdsTm1cIjpcIl9UZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIlRyZE5tXCI6XCJUZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIlBvc1wiOlwiMDFcIixcIkFkZHIxXCI6XCJUZXN0IEFkZHJlc3MgLSAzXCIsXCJMb2NcIjpcIlRlc3QgQ2l0eVwiLFwiUGluXCI6NTAwMDU1LFwiU3RjZFwiOlwiMzZcIn0sXCJEaXNwRHRsc1wiOntcIk5tXCI6XCJUZXN0IEluZGlhbiBSZWdpc3RlcmVkIENvbXBhbnlcIixcIkFkZHIxXCI6XCJUZXN0IEFkZHJlc3MgLSAxXCIsXCJMb2NcIjpcIlRlc3QgQ2l0eVwiLFwiUGluXCI6MTkzNTAxLFwiU3RjZFwiOlwiMDFcIn0sXCJTaGlwRHRsc1wiOntcIkdzdGluXCI6XCIzNkFNQlBHNzc3M00wMDJcIixcIkxnbE5tXCI6XCJUZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIlRyZE5tXCI6XCJUZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIkFkZHIxXCI6XCJUZXN0IEFkZHJlc3MgLSAzXCIsXCJMb2NcIjpcIlRlc3QgQ2l0eVwiLFwiUGluXCI6NTAwMDU1LFwiU3RjZFwiOlwiMzZcIn0sXCJJdGVtTGlzdFwiOlt7XCJJdGVtTm9cIjowLFwiU2xOb1wiOlwiMVwiLFwiSXNTZXJ2Y1wiOlwiWVwiLFwiUHJkRGVzY1wiOlwiVGVzdCBTZXJ2aWNlIEl0ZW1cIixcIkhzbkNkXCI6XCI5OTU0MTFcIixcIlF0eVwiOjEuMCxcIlVuaXRcIjpcIk5PU1wiLFwiVW5pdFByaWNlXCI6MTAwLjAsXCJUb3RBbXRcIjoxMDAuMCxcIkRpc2NvdW50XCI6MCxcIkFzc0FtdFwiOjEwMC4wLFwiR3N0UnRcIjowLjAsXCJJZ3N0QW10XCI6MCxcIkNnc3RBbXRcIjowLFwiU2dzdEFtdFwiOjAsXCJDZXNSdFwiOjAsXCJDZXNBbXRcIjowLFwiQ2VzTm9uQWR2bEFtdFwiOjAsXCJUb3RJdGVtVmFsXCI6MTAwLjB9XSxcIlZhbER0bHNcIjp7XCJBc3NWYWxcIjoxMDAuMCxcIkNnc3RWYWxcIjowLFwiU2dzdFZhbFwiOjAsXCJJZ3N0VmFsXCI6MCxcIkNlc1ZhbFwiOjAsXCJEaXNjb3VudFwiOjAsXCJPdGhDaHJnXCI6MC4wLFwiUm5kT2ZmQW10XCI6MC4wLFwiVG90SW52VmFsXCI6MTAwLjB9LFwiUGF5RHRsc1wiOntcIkNyRGF5XCI6MCxcIlBhaWRBbXRcIjowLFwiUGF5bXREdWVcIjoxMDAuMH0sXCJFd2JEdGxzXCI6e1wiRGlzdGFuY2VcIjowfX0iLCJpc3MiOiJOSUMifQ.rcfXkciqDJypX-xCqaUU3xAk2gccHK_qBD_FBIUsEr-SyWVs4LStgXwQEWUhTYnEfcGGm_sWX15ewC0jn9iWVFCNNFnjKc8vsFQqnbzvi-bnr6CWkjRXOxVqQTfis6PbtTrblojBq2hhBaT1B_ZlgLePi5qFNWnxxaHItjYtBEeBzW5JxzXWQTqESrBy02iLQgQMOexmQ6jKGdUR3tRRG5MVB7QfXbL9BpQr-DXbHhbDGllT2S_xyj9SBsN6ICeluCG-ZJdHE_kCoIxc8iY_nXneb2PsBciij14cHb96h4ddNZtapxTPTh4CumVDmAgLBSVsBTugp8vm0L-jd7n3dg", + "SignedQRCode": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVEQzU3REUxMzU4QjMwMEJBOUY3OTM0MEE2Njk2ODMxRjNDODUwNDciLCJ0eXAiOiJKV1QiLCJ4NXQiOiI3Y1Y5NFRXTE1BdXA5NU5BcG1sb01mUElVRWMifQ.eyJkYXRhIjoie1wiU2VsbGVyR3N0aW5cIjpcIjAxQU1CUEc3NzczTTAwMlwiLFwiQnV5ZXJHc3RpblwiOlwiMzZBTUJQRzc3NzNNMDAyXCIsXCJEb2NOb1wiOlwiZzJxeGhZXCIsXCJEb2NUeXBcIjpcIklOVlwiLFwiRG9jRHRcIjpcIjE3LzA5LzIwMjJcIixcIlRvdEludlZhbFwiOjEwMC4wLFwiSXRlbUNudFwiOjEsXCJNYWluSHNuQ29kZVwiOlwiOTk1NDExXCIsXCJJcm5cIjpcIjY4ZmI0ZmFiNDRhZWU5OWZiMjMyOTI0NzhjNGJkODM4ZTY2NDgzN2M5ZjFiMDRlM2Q5MTM0ZmZlZDBiNDBiNjBcIixcIklybkR0XCI6XCIyMDIyLTA5LTE3IDE2OjI2OjAwXCJ9IiwiaXNzIjoiTklDIn0.A99BPXfKiGSNjnEcqmxc7RGutWakaeW0NMan9oC5yMw6zAoTNcVc34GtQKV7iajBZQhyiFrNwn5n6QtYOXafpitHcI_yrUWSojQBPPpPlslqj4hbnbCy7kmOZZ8mOKISHJrsIZJxpjRlSquIzfDN4aP1aT_qHDqwFqyA8RyJM-id5EpDaTrUFK12HwjKfAXHn4shEUBBgEWrHYOKK6VdpCNi6F_5I5bbJRivrvUJxMLjk3Ux9fyylqnEeyE2NThs9hFuV9EgoVzGE3FhfPZsooToAuG_npYEv3f6Q9KbOw3pNQ3NkqFwvmFjfNJLXdbxZIPe9fe9F1c-CRrIoNo_9w", + "Status": "ACT", + "EwbNo": null, + "EwbDt": null, + "EwbValidTill": null, + "Remarks": null + }, + "info": [ + { + "InfCd": "EWBERR", + "Desc": [ + { + "ErrorCode": "4019", + "ErrorMessage": "Provide Transporter ID in order to generate Part A of e-Way Bill" + } + ] + } + ] + } + }, + "return_invoice": { + "kwargs": { + "qty": -1, + "is_return": 1, + "customer_address": "_Test Registered Customer-Billing", + "shipping_address_name": "_Test Registered Customer-Billing" + }, + "request_data": { + "BuyerDtls": { + "Addr1": "Test Address - 3", + "Gstin": "36AMBPG7773M002", + "LglNm": "_Test Registered Customer", + "Loc": "Test City", + "Pin": 500055, + "Pos": "01", + "Stcd": "36", + "TrdNm": "Test Registered Customer" + }, + "DispDtls": { + "Addr1": "Test Address - 3", + "Loc": "Test City", + "Nm": "Test Registered Customer", + "Pin": 500055, + "Stcd": "36" + }, + "DocDtls": { + "Dt": "17/09/2022", + "No": "test_invoice_no", + "Typ": "CRN" + }, + "EwbDtls": { + "Distance": 0 + }, + "ItemList": [ + { + "AssAmt": 100.0, + "CesAmt": 0, + "CesNonAdvlAmt": 0, + "CesRt": 0, + "CgstAmt": 0, + "Discount": 0, + "GstRt": 0.0, + "HsnCd": "61149090", + "IgstAmt": 0, + "IsServc": "N", + "PrdDesc": "Test Trading Goods 1", + "Qty": 1.0, + "SgstAmt": 0, + "SlNo": "1", + "TotAmt": 100.0, + "TotItemVal": 100.0, + "Unit": "NOS", + "UnitPrice": 100.0 + } + ], + "PayDtls": { + "CrDay": 0, + "PaidAmt": 0, + "PaymtDue": 0.0 + }, + "RefDtls": { + "PrecDocDtls": [ + { + "InvDt": "17/09/2022", + "InvNo": "SINV-CFY-00092" + } + ] + }, + "SellerDtls": { + "Addr1": "Test Address - 1", + "Gstin": "01AMBPG7773M002", + "LglNm": "_Test Indian Registered Company", + "Loc": "Test City", + "Pin": 193501, + "Stcd": "01", + "TrdNm": "Test Indian Registered Company" + }, + "ShipDtls": { + "Addr1": "Test Address - 1", + "Gstin": "01AMBPG7773M002", + "LglNm": "Test Indian Registered Company", + "Loc": "Test City", + "Pin": 193501, + "Stcd": "01", + "TrdNm": "Test Indian Registered Company" + }, + "TranDtls": { + "RegRev": "N", + "SupTyp": "B2B", + "TaxSch": "GST" + }, + "ValDtls": { + "AssVal": 100.0, + "CesVal": 0, + "CgstVal": 0, + "Discount": 0, + "IgstVal": 0, + "OthChrg": 0.0, + "RndOffAmt": -0.0, + "SgstVal": 0, + "TotInvVal": 100.0 + }, + "Version": "1.1" + }, + "response_data": { + "success": true, + "message": "IRN generated successfully", + "result": { + "AckNo": 232210036755145, + "AckDt": "2022-09-17 17:05:00", + "Irn": "1c96258af085e45da556494ea5e5a7b401a598ab80af4136309c2dac7b54d795", + "SignedInvoice": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVEQzU3REUxMzU4QjMwMEJBOUY3OTM0MEE2Njk2ODMxRjNDODUwNDciLCJ0eXAiOiJKV1QiLCJ4NXQiOiI3Y1Y5NFRXTE1BdXA5NU5BcG1sb01mUElVRWMifQ.eyJkYXRhIjoie1wiQWNrTm9cIjoyMzIyMTAwMzY3NTUxNDUsXCJBY2tEdFwiOlwiMjAyMi0wOS0xNyAxNzowNTowMFwiLFwiSXJuXCI6XCIxYzk2MjU4YWYwODVlNDVkYTU1NjQ5NGVhNWU1YTdiNDAxYTU5OGFiODBhZjQxMzYzMDljMmRhYzdiNTRkNzk1XCIsXCJWZXJzaW9uXCI6XCIxLjFcIixcIlRyYW5EdGxzXCI6e1wiVGF4U2NoXCI6XCJHU1RcIixcIlN1cFR5cFwiOlwiQjJCXCIsXCJSZWdSZXZcIjpcIk5cIn0sXCJEb2NEdGxzXCI6e1wiVHlwXCI6XCJDUk5cIixcIk5vXCI6XCJnMnF4aFlcIixcIkR0XCI6XCIxNy8wOS8yMDIyXCJ9LFwiU2VsbGVyRHRsc1wiOntcIkdzdGluXCI6XCIwMUFNQlBHNzc3M00wMDJcIixcIkxnbE5tXCI6XCJfVGVzdCBJbmRpYW4gUmVnaXN0ZXJlZCBDb21wYW55XCIsXCJUcmRObVwiOlwiVGVzdCBJbmRpYW4gUmVnaXN0ZXJlZCBDb21wYW55XCIsXCJBZGRyMVwiOlwiVGVzdCBBZGRyZXNzIC0gMVwiLFwiTG9jXCI6XCJUZXN0IENpdHlcIixcIlBpblwiOjE5MzUwMSxcIlN0Y2RcIjpcIjAxXCJ9LFwiQnV5ZXJEdGxzXCI6e1wiR3N0aW5cIjpcIjM2QU1CUEc3NzczTTAwMlwiLFwiTGdsTm1cIjpcIl9UZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIlRyZE5tXCI6XCJUZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIlBvc1wiOlwiMDFcIixcIkFkZHIxXCI6XCJUZXN0IEFkZHJlc3MgLSAzXCIsXCJMb2NcIjpcIlRlc3QgQ2l0eVwiLFwiUGluXCI6NTAwMDU1LFwiU3RjZFwiOlwiMzZcIn0sXCJEaXNwRHRsc1wiOntcIk5tXCI6XCJUZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIkFkZHIxXCI6XCJUZXN0IEFkZHJlc3MgLSAzXCIsXCJMb2NcIjpcIlRlc3QgQ2l0eVwiLFwiUGluXCI6NTAwMDU1LFwiU3RjZFwiOlwiMzZcIn0sXCJTaGlwRHRsc1wiOntcIkdzdGluXCI6XCIwMUFNQlBHNzc3M00wMDJcIixcIkxnbE5tXCI6XCJUZXN0IEluZGlhbiBSZWdpc3RlcmVkIENvbXBhbnlcIixcIlRyZE5tXCI6XCJUZXN0IEluZGlhbiBSZWdpc3RlcmVkIENvbXBhbnlcIixcIkFkZHIxXCI6XCJUZXN0IEFkZHJlc3MgLSAxXCIsXCJMb2NcIjpcIlRlc3QgQ2l0eVwiLFwiUGluXCI6MTkzNTAxLFwiU3RjZFwiOlwiMDFcIn0sXCJJdGVtTGlzdFwiOlt7XCJJdGVtTm9cIjowLFwiU2xOb1wiOlwiMVwiLFwiSXNTZXJ2Y1wiOlwiTlwiLFwiUHJkRGVzY1wiOlwiVGVzdCBUcmFkaW5nIEdvb2RzIDFcIixcIkhzbkNkXCI6XCI2MTE0OTA5MFwiLFwiUXR5XCI6MS4wLFwiVW5pdFwiOlwiTk9TXCIsXCJVbml0UHJpY2VcIjoxMDAuMCxcIlRvdEFtdFwiOjEwMC4wLFwiRGlzY291bnRcIjowLFwiQXNzQW10XCI6MTAwLjAsXCJHc3RSdFwiOjAuMCxcIklnc3RBbXRcIjowLFwiQ2dzdEFtdFwiOjAsXCJTZ3N0QW10XCI6MCxcIkNlc1J0XCI6MCxcIkNlc0FtdFwiOjAsXCJDZXNOb25BZHZsQW10XCI6MCxcIlRvdEl0ZW1WYWxcIjoxMDAuMH1dLFwiVmFsRHRsc1wiOntcIkFzc1ZhbFwiOjEwMC4wLFwiQ2dzdFZhbFwiOjAsXCJTZ3N0VmFsXCI6MCxcIklnc3RWYWxcIjowLFwiQ2VzVmFsXCI6MCxcIkRpc2NvdW50XCI6MCxcIk90aENocmdcIjowLjAsXCJSbmRPZmZBbXRcIjowLjAsXCJUb3RJbnZWYWxcIjoxMDAuMH0sXCJQYXlEdGxzXCI6e1wiQ3JEYXlcIjowLFwiUGFpZEFtdFwiOjAsXCJQYXltdER1ZVwiOjAuMH0sXCJSZWZEdGxzXCI6e1wiUHJlY0RvY0R0bHNcIjpbe1wiSW52Tm9cIjpcIlNJTlYtQ0ZZLTAwMDkyXCIsXCJJbnZEdFwiOlwiMTcvMDkvMjAyMlwifV19LFwiRXdiRHRsc1wiOntcIkRpc3RhbmNlXCI6MH19IiwiaXNzIjoiTklDIn0.OZpYYN2pXIPJmLTi79NuVxVWWWGwdUzdnIEKXpgXyCEtXqgfwm9opf6EbyEt6wjwiYV4b2HWAfmkOXYJ-TO3dMc_98nOW8SeupGC6k-aV5kcDK9DNiRNWZBCiTMCRhlfmYC3oEZtd5f6yVoHvESl-QKeBlXcK2L8M1C0Guuq3QYqyhFp_gFqz_xY6ImKi65hEMDBghCaKINatRvPtFiwmtIspxUaHid5YcyqXdBxerEDjz-D2RjHOnhq1K_JX07X9xqcncLa106XltEeVFswJiEfqwtZTKDzkd4klymGJL5je7L1tgO8sNTKXGtFETKZfKmFSvxsMrBy1Dlp0LXXtg", + "SignedQRCode": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVEQzU3REUxMzU4QjMwMEJBOUY3OTM0MEE2Njk2ODMxRjNDODUwNDciLCJ0eXAiOiJKV1QiLCJ4NXQiOiI3Y1Y5NFRXTE1BdXA5NU5BcG1sb01mUElVRWMifQ.eyJkYXRhIjoie1wiU2VsbGVyR3N0aW5cIjpcIjAxQU1CUEc3NzczTTAwMlwiLFwiQnV5ZXJHc3RpblwiOlwiMzZBTUJQRzc3NzNNMDAyXCIsXCJEb2NOb1wiOlwiZzJxeGhZXCIsXCJEb2NUeXBcIjpcIkNSTlwiLFwiRG9jRHRcIjpcIjE3LzA5LzIwMjJcIixcIlRvdEludlZhbFwiOjEwMC4wLFwiSXRlbUNudFwiOjEsXCJNYWluSHNuQ29kZVwiOlwiNjExNDkwOTBcIixcIklyblwiOlwiMWM5NjI1OGFmMDg1ZTQ1ZGE1NTY0OTRlYTVlNWE3YjQwMWE1OThhYjgwYWY0MTM2MzA5YzJkYWM3YjU0ZDc5NVwiLFwiSXJuRHRcIjpcIjIwMjItMDktMTcgMTc6MDU6MDBcIn0iLCJpc3MiOiJOSUMifQ.fgLb2ILeWK-MHxs61jZeo3kgy6w-sFXkVSh9GbzH0yWCeVx8MTsRwkKEXklPlmdwudG1lUPVjWsOioCgLL1Q8EE_wqnpGY00JEWr5X74GduBLe0lo5ZKtsSIVf10REy1E7_JJV8qitSZa3JeSqdjPlTUowFpxvPiw-nu3fyBP92IdVFbGO6oMvodI66kanqEyKj26Rn0nfnHP3KT3u61RBUSaVm_gch79cnPQanDPrJtd_0Ra2Vn7FoopdfcNIEdASB71IDRHsMFCNs8LyHTtFoJVI2LqU8wic_A6oWZkswOTBHdsBauMa_CMF9-2QbwTHFv60yvS7KuS2HvBw-oyQ", + "Status": "ACT", + "EwbNo": null, + "EwbDt": null, + "EwbValidTill": null, + "Remarks": null + }, + "info": [ + { + "InfCd": "EWBERR", + "Desc": [ + { + "ErrorCode": "4019", + "ErrorMessage": "Provide Transporter ID in order to generate Part A of e-Way Bill" + } + ] + } + ] + } + }, + "debit_invoice": { + "kwargs": { + "is_debit_note": 1, + "qty": 0, + "customer_address": "_Test Registered Customer-Billing", + "shipping_address_name": "_Test Registered Customer-Billing" + }, + "request_data": { + "BuyerDtls": { + "Addr1": "Test Address - 3", + "Gstin": "36AMBPG7773M002", + "LglNm": "_Test Registered Customer", + "Loc": "Test City", + "Pin": 500055, + "Pos": "01", + "Stcd": "36", + "TrdNm": "Test Registered Customer" + }, + "DispDtls": { + "Addr1": "Test Address - 1", + "Loc": "Test City", + "Nm": "Test Indian Registered Company", + "Pin": 193501, + "Stcd": "01" + }, + "DocDtls": { + "Dt": "17/09/2022", + "No": "test_invoice_no", + "Typ": "DBN" + }, + "EwbDtls": { + "Distance": 0 + }, + "ItemList": [ + { + "AssAmt": 100.0, + "CesAmt": 0, + "CesNonAdvlAmt": 0, + "CesRt": 0, + "CgstAmt": 0, + "Discount": 0, + "GstRt": 0.0, + "HsnCd": "61149090", + "IgstAmt": 0, + "IsServc": "N", + "PrdDesc": "Test Trading Goods 1", + "Qty": 0.0, + "SgstAmt": 0, + "SlNo": "1", + "TotAmt": 100.0, + "TotItemVal": 100.0, + "Unit": "NOS", + "UnitPrice": 100.0 + } + ], + "PayDtls": { + "CrDay": 0, + "PaidAmt": 0, + "PaymtDue": 100.0 + }, + "SellerDtls": { + "Addr1": "Test Address - 1", + "Gstin": "01AMBPG7773M002", + "LglNm": "_Test Indian Registered Company", + "Loc": "Test City", + "Pin": 193501, + "Stcd": "01", + "TrdNm": "Test Indian Registered Company" + }, + "ShipDtls": { + "Addr1": "Test Address - 3", + "Gstin": "36AMBPG7773M002", + "LglNm": "Test Registered Customer", + "Loc": "Test City", + "Pin": 500055, + "Stcd": "36", + "TrdNm": "Test Registered Customer" + }, + "TranDtls": { + "RegRev": "N", + "SupTyp": "B2B", + "TaxSch": "GST" + }, + "ValDtls": { + "AssVal": 100.0, + "CesVal": 0, + "CgstVal": 0, + "Discount": 0, + "IgstVal": 0, + "OthChrg": 0.0, + "RndOffAmt": 0.0, + "SgstVal": 0, + "TotInvVal": 100.0 + }, + "Version": "1.1" + }, + "response_data": { + "success": true, + "message": "IRN generated successfully", + "result": { + "AckNo": 232210036755701, + "AckDt": "2022-09-17 17:50:00", + "Irn": "24f37c80532583c6894d8153e2b12494daa80ddbb197f0fc2c1bac07db67f933", + "SignedInvoice": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVEQzU3REUxMzU4QjMwMEJBOUY3OTM0MEE2Njk2ODMxRjNDODUwNDciLCJ0eXAiOiJKV1QiLCJ4NXQiOiI3Y1Y5NFRXTE1BdXA5NU5BcG1sb01mUElVRWMifQ.eyJkYXRhIjoie1wiQWNrTm9cIjoyMzIyMTAwMzY3NTU3MDEsXCJBY2tEdFwiOlwiMjAyMi0wOS0xNyAxNzo1MDowMFwiLFwiSXJuXCI6XCIyNGYzN2M4MDUzMjU4M2M2ODk0ZDgxNTNlMmIxMjQ5NGRhYTgwZGRiYjE5N2YwZmMyYzFiYWMwN2RiNjdmOTMzXCIsXCJWZXJzaW9uXCI6XCIxLjFcIixcIlRyYW5EdGxzXCI6e1wiVGF4U2NoXCI6XCJHU1RcIixcIlN1cFR5cFwiOlwiQjJCXCIsXCJSZWdSZXZcIjpcIk5cIn0sXCJEb2NEdGxzXCI6e1wiVHlwXCI6XCJEQk5cIixcIk5vXCI6XCJnMnF4aFlcIixcIkR0XCI6XCIxNy8wOS8yMDIyXCJ9LFwiU2VsbGVyRHRsc1wiOntcIkdzdGluXCI6XCIwMUFNQlBHNzc3M00wMDJcIixcIkxnbE5tXCI6XCJfVGVzdCBJbmRpYW4gUmVnaXN0ZXJlZCBDb21wYW55XCIsXCJUcmRObVwiOlwiVGVzdCBJbmRpYW4gUmVnaXN0ZXJlZCBDb21wYW55XCIsXCJBZGRyMVwiOlwiVGVzdCBBZGRyZXNzIC0gMVwiLFwiTG9jXCI6XCJUZXN0IENpdHlcIixcIlBpblwiOjE5MzUwMSxcIlN0Y2RcIjpcIjAxXCJ9LFwiQnV5ZXJEdGxzXCI6e1wiR3N0aW5cIjpcIjM2QU1CUEc3NzczTTAwMlwiLFwiTGdsTm1cIjpcIl9UZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIlRyZE5tXCI6XCJUZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIlBvc1wiOlwiMDFcIixcIkFkZHIxXCI6XCJUZXN0IEFkZHJlc3MgLSAzXCIsXCJMb2NcIjpcIlRlc3QgQ2l0eVwiLFwiUGluXCI6NTAwMDU1LFwiU3RjZFwiOlwiMzZcIn0sXCJEaXNwRHRsc1wiOntcIk5tXCI6XCJUZXN0IEluZGlhbiBSZWdpc3RlcmVkIENvbXBhbnlcIixcIkFkZHIxXCI6XCJUZXN0IEFkZHJlc3MgLSAxXCIsXCJMb2NcIjpcIlRlc3QgQ2l0eVwiLFwiUGluXCI6MTkzNTAxLFwiU3RjZFwiOlwiMDFcIn0sXCJTaGlwRHRsc1wiOntcIkdzdGluXCI6XCIzNkFNQlBHNzc3M00wMDJcIixcIkxnbE5tXCI6XCJUZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIlRyZE5tXCI6XCJUZXN0IFJlZ2lzdGVyZWQgQ3VzdG9tZXJcIixcIkFkZHIxXCI6XCJUZXN0IEFkZHJlc3MgLSAzXCIsXCJMb2NcIjpcIlRlc3QgQ2l0eVwiLFwiUGluXCI6NTAwMDU1LFwiU3RjZFwiOlwiMzZcIn0sXCJJdGVtTGlzdFwiOlt7XCJJdGVtTm9cIjowLFwiU2xOb1wiOlwiMVwiLFwiSXNTZXJ2Y1wiOlwiTlwiLFwiUHJkRGVzY1wiOlwiVGVzdCBUcmFkaW5nIEdvb2RzIDFcIixcIkhzbkNkXCI6XCI2MTE0OTA5MFwiLFwiUXR5XCI6MS4wLFwiVW5pdFwiOlwiTk9TXCIsXCJVbml0UHJpY2VcIjoxMDAuMCxcIlRvdEFtdFwiOjEwMC4wLFwiRGlzY291bnRcIjowLFwiQXNzQW10XCI6MTAwLjAsXCJHc3RSdFwiOjAuMCxcIklnc3RBbXRcIjowLFwiQ2dzdEFtdFwiOjAsXCJTZ3N0QW10XCI6MCxcIkNlc1J0XCI6MCxcIkNlc0FtdFwiOjAsXCJDZXNOb25BZHZsQW10XCI6MCxcIlRvdEl0ZW1WYWxcIjoxMDAuMH1dLFwiVmFsRHRsc1wiOntcIkFzc1ZhbFwiOjEwMC4wLFwiQ2dzdFZhbFwiOjAsXCJTZ3N0VmFsXCI6MCxcIklnc3RWYWxcIjowLFwiQ2VzVmFsXCI6MCxcIkRpc2NvdW50XCI6MCxcIk90aENocmdcIjowLjAsXCJSbmRPZmZBbXRcIjowLjAsXCJUb3RJbnZWYWxcIjoxMDAuMH0sXCJQYXlEdGxzXCI6e1wiQ3JEYXlcIjowLFwiUGFpZEFtdFwiOjAsXCJQYXltdER1ZVwiOjEwMC4wfSxcIkV3YkR0bHNcIjp7XCJEaXN0YW5jZVwiOjB9fSIsImlzcyI6Ik5JQyJ9.ad1NfDA-H8FgBHr_kaTeiVWUj-f8T6NXuLFWa1gprxGhACXoI9h6sU47U9PBxHcZ7qVXcYAKzA9CbNvAfxWCLKtjur5p85uIrksZYDD494lodGn3QeXbyjXMJeh7eM0mcKKN3Chp0TxaUfi9C7mA9W0R8HYKNnXIOT1CVlD-brrAw09_QiDsNgMhLBX5QfpHKIHPKCIEl_DgmWlnMzduy1iKYPpNreNPCV-J-ZaVQjxl93LjKBUb5AF1XWyWvPw_e8ePZEttviX_bU_Nnm1M4zCj-QWYzj8A0bauzl7kjp5UajEM7_7CLAI4sjZnqonGKYFfR5rf2Qj76exbs_pWGw", + "SignedQRCode": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVEQzU3REUxMzU4QjMwMEJBOUY3OTM0MEE2Njk2ODMxRjNDODUwNDciLCJ0eXAiOiJKV1QiLCJ4NXQiOiI3Y1Y5NFRXTE1BdXA5NU5BcG1sb01mUElVRWMifQ.eyJkYXRhIjoie1wiU2VsbGVyR3N0aW5cIjpcIjAxQU1CUEc3NzczTTAwMlwiLFwiQnV5ZXJHc3RpblwiOlwiMzZBTUJQRzc3NzNNMDAyXCIsXCJEb2NOb1wiOlwiZzJxeGhZXCIsXCJEb2NUeXBcIjpcIkRCTlwiLFwiRG9jRHRcIjpcIjE3LzA5LzIwMjJcIixcIlRvdEludlZhbFwiOjEwMC4wLFwiSXRlbUNudFwiOjEsXCJNYWluSHNuQ29kZVwiOlwiNjExNDkwOTBcIixcIklyblwiOlwiMjRmMzdjODA1MzI1ODNjNjg5NGQ4MTUzZTJiMTI0OTRkYWE4MGRkYmIxOTdmMGZjMmMxYmFjMDdkYjY3ZjkzM1wiLFwiSXJuRHRcIjpcIjIwMjItMDktMTcgMTc6NTA6MDBcIn0iLCJpc3MiOiJOSUMifQ.TUE7iIvF9Orc1bEuOaxtSuj0D6vP1MwV-hcoh3IZqV7EKXwMmm0PkVrN87vRzSu97NMKfQIHJDRgPYv4prhuT0dshWJ9A_kC4jiRSm5Naj_R1egBKsv5ykTojOKKrkGy35DtdUcJK_FyiD0qFfmMmInFD8u6D8W83eEo93i99RONgVKUyCkd_uqs-cz1P-PTlsi2xWbeDVVIcRoAmf-lcsbwkl2Hn6ECHgorKJHPJC1FGo1jRQ2Ktq0ODiJdncplbxbdYN19vUz61JJB4DPzWtf8wOkX11N0fDhdUdEINfJURWEOIGQRYip5GIJuA2qdqxZieVk0CetnsckcGgfE8g", + "Status": "ACT", + "EwbNo": null, + "EwbDt": null, + "EwbValidTill": null, + "Remarks": null + }, + "info": [ + { + "InfCd": "EWBERR", + "Desc": [ + { + "ErrorCode": "4019", + "ErrorMessage": "Provide Transporter ID in order to generate Part A of e-Way Bill" + } + ] + } + ] + } + }, + "cancel_e_invoice": { + "request_data": { + "Cnlrem": "Data Entry Mistake", + "Cnlrsn": "2", + "Irn": "706daeccda0ef6f818da78f3a2a05a1288731057373002289b46c3229289a2e7" + }, + "response_data": { + "message": "E-Invoice is cancelled successfully", + "result": { + "CancelDate": "2022-09-17 18:09:00", + "Irn": "706daeccda0ef6f818da78f3a2a05a1288731057373002289b46c3229289a2e7" + }, + "success": true + } + }, + "cancel_e_waybill": { + "request_data": { + "cancelRmrk": "Data Entry Mistake", + "cancelRsnCode": "3", + "ewbNo": "391009149369" + }, + "response_data": { + "message": "E-Way bill cancelled successfully", + "result": { + "cancelDate": "20/09/2022 12:10:00 PM", + "ewayBillNo": "391009149369" + }, + "success": true + } + } +} \ No newline at end of file diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index 7ad4e5b6d..99349f200 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -142,7 +142,13 @@ def cancel_e_invoice(docname, values): def log_e_invoice(doc, log_data): - frappe.enqueue(_log_e_invoice, queue="short", at_front=True, log_data=log_data) + frappe.enqueue( + _log_e_invoice, + queue="short", + at_front=True, + log_data=log_data, + now=frappe.flags.in_test, + ) update_onload(doc, "e_invoice_info", log_data) @@ -398,6 +404,9 @@ def get_invoice_data(self): self.dispatch_address.update(seller) self.transaction_details.name = random_string(6).lstrip("0") + if frappe.flags.in_test: + self.transaction_details.name = "test_invoice_no" + # For overseas transactions, dummy GSTIN is not needed if self.doc.gst_category != "Overseas": buyer = { diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index b4e01fe17..a11545cde 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -102,7 +102,6 @@ def _generate_e_waybill(doc, throw=True): indicator="green", alert=True, ) - return send_updated_doc(doc) @@ -389,6 +388,7 @@ def log_and_process_e_waybill(doc, log_data, fetch=False, comment=None): log_data=log_data, fetch=fetch, comment=comment, + now=frappe.flags.in_test, ) update_onload(doc, "e_waybill_info", log_data) @@ -627,6 +627,9 @@ def check_e_waybill_validity(self): frappe.throw(_("e-Waybill cannot be modified after its validity is over")) def validate_if_ewaybill_can_be_cancelled(self): + if not self.doc.get_onload(): + self.doc.run_method("onload") + cancel_upto = add_to_date( # this works because we do run_onload in load_doc above get_datetime( diff --git a/india_compliance/gst_india/utils/test_e_invoice.py b/india_compliance/gst_india/utils/test_e_invoice.py new file mode 100644 index 000000000..a9fef1290 --- /dev/null +++ b/india_compliance/gst_india/utils/test_e_invoice.py @@ -0,0 +1,456 @@ +import json +import re + +import responses +from responses import matchers + +import frappe +from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils import add_to_date, get_datetime, getdate, now_datetime +from frappe.utils.data import format_date + +from india_compliance.gst_india.api_classes.base import BASE_URL +from india_compliance.gst_india.utils import load_doc +from india_compliance.gst_india.utils.e_invoice import ( + EInvoiceData, + cancel_e_invoice, + generate_e_invoice, + validate_e_invoice_applicability, + validate_if_e_invoice_can_be_cancelled, +) +from india_compliance.gst_india.utils.e_waybill import EWaybillData +from india_compliance.gst_india.utils.tests import create_sales_invoice + + +class TestEInvoice(FrappeTestCase): + @classmethod + def setUpClass(cls): + frappe.db.set_value( + "GST Settings", + "GST Settings", + { + "enable_api": 1, + "enable_e_invoice": 1, + "auto_generate_e_invoice": 0, + "enable_e_waybill": 1, + "fetch_e_waybill_data": 0, + }, + ) + cls.e_invoice_test_data = frappe._dict( + frappe.get_file_json( + frappe.get_app_path( + "india_compliance", "gst_india", "data", "test_e_invoice.json" + ) + ) + ) + update_dates_for_test_data(cls.e_invoice_test_data) + + @change_settings("Selling Settings", {"allow_multiple_items": 1}) + def test_get_data(self): + """Validation test for more than 1000 items in sales invoice""" + si = create_sales_invoice(do_not_submit=True) + item_row = si.get("items")[0] + + for index in range(0, 1000): + si.append( + "items", + { + "item_code": item_row.item_code, + "qty": item_row.qty, + "rate": item_row.rate, + }, + ) + si.save() + + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"^(e-Invoice can only be generated.*)$"), + EInvoiceData(si).get_data, + ) + + @responses.activate + def test_generate_e_invoice_with_goods_item(self): + """Generate test e-Invoice for goods item""" + test_data = self.e_invoice_test_data.get("goods_item_with_ewaybill") + si = create_sales_invoice(**test_data.get("kwargs")) + + # Assert if request data given in Json + self.assertDictEqual(test_data.get("request_data"), EInvoiceData(si).get_data()) + + # Mock response for generating irn + self._mock_e_invoice_response(data=test_data) + + generate_e_invoice(si.name) + + # Assert if Integration Request Log generated + self.assertDocumentEqual( + { + "output": frappe.as_json(test_data.get("response_data"), indent=4), + }, + frappe.get_doc( + "Integration Request", + {"reference_doctype": "Sales Invoice", "reference_docname": si.name}, + ), + ) + + # Assert if Sales Doc updated + self.assertDocumentEqual( + { + "irn": test_data.get("response_data").get("result").get("Irn"), + "ewaybill": test_data.get("response_data").get("result").get("EwbNo"), + "einvoice_status": "Generated", + }, + frappe.get_doc("Sales Invoice", si.name), + ) + + self.assertDocumentEqual( + {"name": test_data.get("response_data").get("result").get("Irn")}, + frappe.get_doc("e-Invoice Log", {"sales_invoice": si.name}), + ) + self.assertDocumentEqual( + {"name": test_data.get("response_data").get("result").get("EwbNo")}, + frappe.get_doc("e-Waybill Log", {"reference_name": si.name}), + ) + + @responses.activate + def test_generate_e_invoice_with_service_item(self): + """Generate test e-Invoice for Service Item""" + test_data = self.e_invoice_test_data.get("service_item") + si = create_sales_invoice(**test_data.get("kwargs")) + + # Assert if request data given in Json + self.assertDictEqual(test_data.get("request_data"), EInvoiceData(si).get_data()) + + # Mock response for generating irn + self._mock_e_invoice_response(data=test_data) + + generate_e_invoice(si.name) + + # Assert if Integration Request Log generated + self.assertDocumentEqual( + { + "output": frappe.as_json(test_data.get("response_data"), indent=4), + }, + frappe.get_doc( + "Integration Request", + {"reference_doctype": "Sales Invoice", "reference_docname": si.name}, + ), + ) + + # Assert if Sales Doc updated + self.assertDocumentEqual( + { + "irn": test_data.get("response_data").get("result").get("Irn"), + "einvoice_status": "Generated", + }, + frappe.get_doc("Sales Invoice", si.name), + ) + + self.assertDocumentEqual( + {"name": test_data.get("response_data").get("result").get("Irn")}, + frappe.get_doc("e-Invoice Log", {"sales_invoice": si.name}), + ) + + self.assertFalse( + frappe.db.get_value("e-Waybill Log", {"reference_name": si.name}, "name") + ) + + @responses.activate + def test_return_e_invoice_with_goods_item(self): + """Generate test e-Invoice for returned Sales Invoices""" + test_data = self.e_invoice_test_data.get("return_invoice") + + si = create_sales_invoice( + customer_address="_Test Registered Customer-Billing", + shipping_address_name="_Test Registered Customer-Billing", + ) + + test_data.get("kwargs").update({"return_against": si.name}) + + for data in test_data.get("request_data").get("RefDtls").get("PrecDocDtls"): + data.update( + { + "InvDt": format_date(si.posting_date, "dd/mm/yyyy"), + "InvNo": si.name, + } + ) + + return_si = create_sales_invoice( + **test_data.get("kwargs"), + ) + + # Assert if request data given in Json + self.assertDictEqual( + test_data.get("request_data"), + EInvoiceData(frappe.get_doc("Sales Invoice", return_si.name)).get_data(), + ) + + # Mock response for generating irn + self._mock_e_invoice_response(data=test_data) + + generate_e_invoice(return_si.name) + + # Assert if Integration Request Log generated + self.assertDocumentEqual( + { + "output": frappe.as_json(test_data.get("response_data"), indent=4), + }, + frappe.get_doc( + "Integration Request", + { + "reference_doctype": "Sales Invoice", + "reference_docname": return_si.name, + }, + ), + ) + + # Assert if Sales Doc updated + self.assertDocumentEqual( + { + "irn": test_data.get("response_data").get("result").get("Irn"), + "einvoice_status": "Generated", + }, + frappe.get_doc("Sales Invoice", return_si.name), + ) + + self.assertDocumentEqual( + {"name": test_data.get("response_data").get("result").get("Irn")}, + frappe.get_doc("e-Invoice Log", {"sales_invoice": return_si.name}), + ) + + self.assertFalse( + frappe.db.get_value( + "e-Waybill Log", {"reference_name": return_si.name}, "name" + ) + ) + + @responses.activate + def test_debit_note_e_invoice_with_goods_item(self): + """Generate test e-Invoice for debit note with zero quantity""" + test_data = self.e_invoice_test_data.get("debit_invoice") + si = create_sales_invoice( + customer_address="_Test Registered Customer-Billing", + shipping_address_name="_Test Registered Customer-Billing", + ) + + test_data.get("kwargs").update({"return_against": si.name}) + debit_note = create_sales_invoice(**test_data.get("kwargs"), do_not_submit=True) + + debit_note.items[0].qty = 0 + debit_note.save() + debit_note.submit() + + # Assert if request data given in Json + self.assertDictEqual( + test_data.get("request_data"), EInvoiceData(debit_note).get_data() + ) + + # Mock response for generating irn + self._mock_e_invoice_response(data=test_data) + + generate_e_invoice(debit_note.name) + + # Assert if Integration Request Log generated + self.assertDocumentEqual( + { + "output": frappe.as_json(test_data.get("response_data"), indent=4), + }, + frappe.get_doc( + "Integration Request", + { + "reference_doctype": "Sales Invoice", + "reference_docname": debit_note.name, + }, + ), + ) + + # Assert if Sales Doc updated + self.assertDocumentEqual( + { + "irn": test_data.get("response_data").get("result").get("Irn"), + "einvoice_status": "Generated", + }, + frappe.get_doc("Sales Invoice", debit_note.name), + ) + + self.assertDocumentEqual( + {"name": test_data.get("response_data").get("result").get("Irn")}, + frappe.get_doc("e-Invoice Log", {"sales_invoice": debit_note.name}), + ) + + self.assertFalse( + frappe.db.get_value( + "e-Waybill Log", {"reference_name": debit_note.name}, "name" + ) + ) + + @responses.activate + def test_cancel_e_invoice(self): + """Test for generate and cancel e-Invoice + - Test function `validate_if_e_invoice_can_be_cancelled` + """ + + test_data = self.e_invoice_test_data.get("goods_item_with_ewaybill") + si = create_sales_invoice(**test_data.get("kwargs")) + + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"^(IRN not found)$"), + validate_if_e_invoice_can_be_cancelled, + si, + ) + + test_data.get("response_data").get("result").update( + {"AckDt": str(now_datetime())} + ) + + # Assert if request data given in Json + self.assertDictEqual(test_data.get("request_data"), EInvoiceData(si).get_data()) + + # Mock response for generating irn + self._mock_e_invoice_response(data=test_data) + + generate_e_invoice(si.name) + + si_doc = load_doc("Sales Invoice", si.name, "cancel") + si_doc.get_onload().get("e_invoice_info", {}).update({"acknowledged_on": None}) + + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"^(e-Invoice can only be cancelled.*)$"), + validate_if_e_invoice_can_be_cancelled, + si_doc, + ) + + cancelled_doc = self._cancel_e_invoice(si.name) + + self.assertDocumentEqual( + {"einvoice_status": "Cancelled", "irn": ""}, + cancelled_doc, + ) + self.assertDocumentEqual({"ewaybill": ""}, cancelled_doc) + + def test_validate_e_invoice_applicability(self): + """Test if e_invoicing is applicable""" + + si = create_sales_invoice( + customer="_Test Unregistered Customer", + gst_category="Unregistered", + do_not_submit=True, + ) + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"^(e-Invoice is not applicable for invoices.*)$"), + validate_e_invoice_applicability, + si, + ) + + si.update( + { + "gst_category": "Registered Regular", + "customer": "_Test Registered Customer", + } + ) + si.save(ignore_permissions=True) + frappe.db.set_single_value( + "GST Settings", "e_invoice_applicable_from", "2045-05-18" + ) + + self.assertRaisesRegex( + frappe.exceptions.ValidationError, + re.compile(r"^(e-Invoice is not applicable for invoices before.*)$"), + validate_e_invoice_applicability, + si, + ) + + frappe.db.set_single_value( + "GST Settings", "e_invoice_applicable_from", get_datetime() + ) + + def _cancel_e_invoice(self, invoice_no): + values = frappe._dict( + {"reason": "Data Entry Mistake", "remark": "Data Entry Mistake"} + ) + doc = frappe.get_doc("Sales Invoice", invoice_no) + + # Prepared e_waybill cancel data + cancel_e_waybill = self.e_invoice_test_data.get("cancel_e_waybill") + cancel_e_waybill.get("response_data").get("result").update( + {"ewayBillNo": doc.ewaybill} + ) + + # Assert for Mock request data + self.assertDictEqual( + cancel_e_waybill.get("request_data"), + EWaybillData(doc).get_e_waybill_cancel_data(values), + ) + + # Prepared e_invoice cancel data + cancel_irn_test_data = self.e_invoice_test_data.get("cancel_e_invoice") + cancel_irn_test_data.get("response_data").get("result").update({"Irn": doc.irn}) + + # Assert for Mock request data + self.assertTrue( + cancel_e_waybill.get("request_data"), + ) + + # Mock response for cancel e_waybill + self._mock_e_invoice_response( + data=cancel_e_waybill, + api="ei/api/ewayapi", + ) + + # Mock response for cancel e_invoice + self._mock_e_invoice_response( + data=cancel_irn_test_data, + api="ei/api/invoice/cancel", + ) + + cancel_e_invoice(doc.name, values=values) + return frappe.get_doc("Sales Invoice", doc.name) + + def _mock_e_invoice_response(self, data, api="ei/api/invoice"): + """Mock response for e-Invoice API""" + url = BASE_URL + "/test/" + api + + responses.add( + responses.POST, + url, + body=json.dumps(data.get("response_data")), + match=[matchers.json_params_matcher(data.get("request_data"))], + status=200, + ) + + +def update_dates_for_test_data(test_data): + """Update test data for e-invoice and e-waybill""" + today = format_date(frappe.utils.today(), "dd/mm/yyyy") + now = now_datetime().strftime("%Y-%m-%d %H:%M:%S") + validity = add_to_date(getdate(), days=1).strftime("%Y-%m-%d %I:%M:%S %p") + + # Update test data for goods_item_with_ewaybill + goods_item = test_data.get("goods_item_with_ewaybill") + goods_item.get("response_data").get("result").update( + { + "EwbDt": now, + "EwbValidTill": validity, + } + ) + + # Update Document Date in given test data + for key in ( + "goods_item_with_ewaybill", + "service_item", + "return_invoice", + "debit_invoice", + ): + test_data.get(key).get("request_data").get("DocDtls")["Dt"] = today + test_data.get(key).get("response_data").get("result")["AckDt"] = now + + response = test_data.cancel_e_waybill.get("response_data") + response.get("result")["cancelDate"] = now_datetime().strftime( + "%d/%m/%Y %I:%M:%S %p" + ) + + response = test_data.cancel_e_invoice.get("response_data") + response.get("result")["CancelDate"] = now diff --git a/india_compliance/tests/test_records.json b/india_compliance/tests/test_records.json index b6c82b8bc..dd82d1495 100644 --- a/india_compliance/tests/test_records.json +++ b/india_compliance/tests/test_records.json @@ -89,6 +89,32 @@ "income_account": "Sales - _TIRC" } ] + }, + { + "description": "_Test Service Item", + "doctype": "Item", + "item_code": "_Test Service Item", + "item_name": "_Test Service Item", + "valuation_rate": 100, + "item_group": "Services", + "gst_hsn_code": "999900", + "uoms": [ + { + "conversion_factor": 1, + "uom": "Nos", + "name": "_Test Service Item" + } + ], + "item_defaults": [ + { + "name": "_Test Service Item", + "company": "_Test Indian Registered Company", + "default_warehouse": "Stores - _TIRC", + "buying_cost_center": "Main - _TIRC", + "selling_cost_center": "Main - _TIRC", + "income_account": "Service - _TIRC" + } + ] } ], "Customer": [ @@ -123,21 +149,21 @@ "gst_category": "Registered Regular" }, { - "name":"_Test Registered Composition Supplier", + "name": "_Test Registered Composition Supplier", "supplier_name": "_Test Registered Composition Supplier", "supplier_type": "Individual", "gstin": "33AAAAR6720M1ZG", "gst_category": "Registered Composition" }, { - "name":"_Test Registered InterState Supplier", + "name": "_Test Registered InterState Supplier", "supplier_name": "_Test Registered InterState Supplier", "supplier_type": "Individual", "gstin": "33AAAAR6720M1ZG", "gst_category": "Registered Regular" }, { - "name":"_Test Unregistered Supplier", + "name": "_Test Unregistered Supplier", "supplier_name": "_Test Unregistered Supplier", "supplier_type": "Individual", "gstin": "", @@ -198,7 +224,10 @@ "is_primary_address": 1, "is_shipping_address": 1, "links": [ - { "link_doctype": "Customer", "link_name": "_Test Registered Customer" } + { + "link_doctype": "Customer", + "link_name": "_Test Registered Customer" + } ] }, { @@ -212,7 +241,10 @@ "gstin": "24AANCA4892J1Z8", "gst_category": "SEZ", "links": [ - { "link_doctype": "Customer", "link_name": "_Test Registered Customer" } + { + "link_doctype": "Customer", + "link_name": "_Test Registered Customer" + } ] }, { @@ -247,7 +279,10 @@ "is_primary_address": 1, "is_shipping_address": 1, "links": [ - { "link_doctype": "Supplier", "link_name": "_Test Registered Supplier" } + { + "link_doctype": "Supplier", + "link_name": "_Test Registered Supplier" + } ] }, { @@ -261,7 +296,10 @@ "gstin": "", "gst_category": "Overseas", "links": [ - { "link_doctype": "Supplier", "link_name": "_Test Registered Supplier" } + { + "link_doctype": "Supplier", + "link_name": "_Test Registered Supplier" + } ] }, { @@ -327,7 +365,5 @@ } ] } - ] - } \ No newline at end of file From 4963c2abfa5988c0064afb5b0472887a120cf245 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sun, 18 Dec 2022 08:04:04 +0000 Subject: [PATCH 24/58] fix: improved error with link to doc (cherry picked from commit 040b94f671a3c57630e38a97158918646fda8a1c) --- .../gst_india/utils/transaction_data.py | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index d22f5601b..f841fb9e8 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -2,7 +2,7 @@ import frappe from frappe import _ -from frappe.utils import format_date, getdate, rounded +from frappe.utils import format_date, get_url_to_form, getdate, rounded from india_compliance.gst_india.constants import GST_TAX_TYPES, PINCODE_FORMAT from india_compliance.gst_india.constants.e_waybill import ( @@ -440,27 +440,23 @@ def sanitize_value( """ throw = throw and error_context - if not value or len(value) < min_length: + def _throw(message): if not throw: return + url = get_url_to_form(error_context["doctype"], error_context["docname"]) frappe.throw( _( - "{fieldname} must be at least {min_length} characters long for {doctype} - {docname}" - ).format(**error_context, min_length=min_length), + "{fieldname} {message} for {doctype} - {docname}" + ).format(**error_context, message=message, url=url), title=_("Invalid Data"), ) - if not value.isascii(): - if not throw: - return + if not value or len(value) < min_length: + return _throw(f"must be at least {min_length} characters long") - frappe.throw( - _( - "{fieldname} must be ASCII characters only for {doctype} - {docname}. (Non-ASCII characters are not allowed in GST JSON)" - ).format(**error_context), - title=_("Invalid Data"), - ) + if not value.isascii(): + return _throw("must be ASCII characters only") if regex: value = re.sub(REGEX_MAP[regex], "", value) From 9f17bff2641739e3f768d842f039c5bdddca5b87 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sun, 18 Dec 2022 09:15:26 +0000 Subject: [PATCH 25/58] fix: irn not visible for Registered Composition (cherry picked from commit daecbdc7b61fbc347a83efab63ca4bceed6b9279) --- india_compliance/gst_india/constants/custom_fields.py | 5 +---- india_compliance/patches.txt | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index 08771dbd8..e0a848ec7 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -600,10 +600,7 @@ "insert_after": "customer", "no_copy": 1, "print_hide": 1, - "depends_on": ( - 'eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed' - ' Export"], doc.gst_category)' - ), + "depends_on": 'eval:doc.gst_category != "Unregistered"', "translatable": 0, }, { diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 784c353aa..2f9698035 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -3,7 +3,7 @@ [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() #2 +execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #3 execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() india_compliance.patches.post_install.update_custom_role_for_e_invoice_summary india_compliance.patches.v14.remove_ecommerce_gstin_from_purchase_invoice From 5107fb3acfc9d19baa9177a28926bc550a4f87d6 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sun, 18 Dec 2022 15:33:35 +0530 Subject: [PATCH 26/58] fix: reduce modification of source code for tests (cherry picked from commit 43accec2ab3df66aa5069fbc5d8e08d36d7bbfe5) --- india_compliance/gst_india/api_classes/base.py | 4 +--- india_compliance/gst_india/utils/api.py | 3 ++- india_compliance/gst_india/utils/e_invoice.py | 1 - india_compliance/gst_india/utils/e_waybill.py | 4 ---- india_compliance/gst_india/utils/test_e_invoice.py | 2 +- india_compliance/tests/__init__.py | 3 +++ 6 files changed, 7 insertions(+), 10 deletions(-) diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py index 90c3ecf80..8e7d6d535 100644 --- a/india_compliance/gst_india/api_classes/base.py +++ b/india_compliance/gst_india/api_classes/base.py @@ -33,9 +33,7 @@ def __init__(self, *args, **kwargs): or frappe.conf.ic_api_secret ) } - self.default_log_values = { - "now": frappe.flags.in_test, - } + self.default_log_values = {} self.setup(*args, **kwargs) diff --git a/india_compliance/gst_india/utils/api.py b/india_compliance/gst_india/utils/api.py index 2efb0d385..8ea61b165 100644 --- a/india_compliance/gst_india/utils/api.py +++ b/india_compliance/gst_india/utils/api.py @@ -3,7 +3,8 @@ def enqueue_integration_request(**kwargs): frappe.enqueue( - "india_compliance.gst_india.utils.api.create_integration_request", **kwargs + "india_compliance.gst_india.utils.api.create_integration_request", + **kwargs, ) diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index 99349f200..6072f167f 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -147,7 +147,6 @@ def log_e_invoice(doc, log_data): queue="short", at_front=True, log_data=log_data, - now=frappe.flags.in_test, ) update_onload(doc, "e_invoice_info", log_data) diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index a11545cde..59c687d6d 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -388,7 +388,6 @@ def log_and_process_e_waybill(doc, log_data, fetch=False, comment=None): log_data=log_data, fetch=fetch, comment=comment, - now=frappe.flags.in_test, ) update_onload(doc, "e_waybill_info", log_data) @@ -627,9 +626,6 @@ def check_e_waybill_validity(self): frappe.throw(_("e-Waybill cannot be modified after its validity is over")) def validate_if_ewaybill_can_be_cancelled(self): - if not self.doc.get_onload(): - self.doc.run_method("onload") - cancel_upto = add_to_date( # this works because we do run_onload in load_doc above get_datetime( diff --git a/india_compliance/gst_india/utils/test_e_invoice.py b/india_compliance/gst_india/utils/test_e_invoice.py index a9fef1290..477dffb54 100644 --- a/india_compliance/gst_india/utils/test_e_invoice.py +++ b/india_compliance/gst_india/utils/test_e_invoice.py @@ -371,7 +371,7 @@ def _cancel_e_invoice(self, invoice_no): values = frappe._dict( {"reason": "Data Entry Mistake", "remark": "Data Entry Mistake"} ) - doc = frappe.get_doc("Sales Invoice", invoice_no) + doc = load_doc("Sales Invoice", invoice_no, "cancel") # Prepared e_waybill cancel data cancel_e_waybill = self.e_invoice_test_data.get("cancel_e_waybill") diff --git a/india_compliance/tests/__init__.py b/india_compliance/tests/__init__.py index d8ffa6eef..2020d9384 100644 --- a/india_compliance/tests/__init__.py +++ b/india_compliance/tests/__init__.py @@ -1,3 +1,5 @@ +from functools import partial + import frappe from frappe.desk.page.setup_wizard.setup_wizard import setup_complete from frappe.test_runner import make_test_objects @@ -38,6 +40,7 @@ def before_tests(): frappe.flags.country = "India" frappe.flags.skip_test_records = True + frappe.enqueue = partial(frappe.enqueue, now=True) def set_default_settings_for_tests(): From b687e96cc943292709d26fb525630c120ed788c3 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sun, 18 Dec 2022 16:16:00 +0530 Subject: [PATCH 27/58] fix: ensure `get_gst_details` gets correct party details (cherry picked from commit 9bbda24837ec690e0247aea89d4d9c6253598611) --- india_compliance/public/js/quick_entry.js | 2 +- india_compliance/public/js/transaction.js | 22 ++++++++++++++-------- india_compliance/public/js/utils.js | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/india_compliance/public/js/quick_entry.js b/india_compliance/public/js/quick_entry.js index 819fe1b67..e867f226a 100644 --- a/india_compliance/public/js/quick_entry.js +++ b/india_compliance/public/js/quick_entry.js @@ -263,7 +263,7 @@ class AddressQuickEntryForm extends GSTQuickEntryForm { return { party_type: doctype, party: name }; const party_type = ic.get_party_type(doctype); - const party = doc[ic.get_party_field(doctype)]; + const party = doc[ic.get_party_fieldname(doctype)]; return { party_type, party }; } } diff --git a/india_compliance/public/js/transaction.js b/india_compliance/public/js/transaction.js index 44669524c..93cbb36bb 100644 --- a/india_compliance/public/js/transaction.js +++ b/india_compliance/public/js/transaction.js @@ -40,29 +40,35 @@ function fetch_gst_details(doctype) { async function update_gst_details(frm) { if (frm.__gst_update_triggered || frm.updating_party_details || !frm.doc.company) return; - const party_field = ic.get_party_field(frm.doc.doctype); - if (!frm.doc[party_field]) return; + const party = frm.doc[ic.get_party_fieldname(frm.doc.doctype)]; + if (!party) return; frm.__gst_update_triggered = true; + // wait for GSTINs to get fetched await frappe.after_ajax().then(() => frm.__gst_update_triggered = false); - const party_fields = ["tax_category", "gst_category", "company_gstin", party_field]; + const party_details = {}; + + // fieldname may be "party_name" for Quotation, but "customer" is expected by get_gst_details + party_details[ic.get_party_type(frm.doc.doctype).toLowerCase()] = party; + + const fieldnames_to_set = ["tax_category", "gst_category", "company_gstin"]; if (in_list(frappe.boot.sales_doctypes, frm.doc.doctype)) { - party_fields.push( + fieldnames_to_set.push( "customer_address", "billing_address_gstin", "is_export_with_gst", "is_reverse_charge" ); } else { - party_fields.push("supplier_address", "supplier_gstin"); + fieldnames_to_set.push("supplier_address", "supplier_gstin"); } - const party_details = Object.fromEntries( - party_fields.map(field => [field, frm.doc[field]]) - ); + for (const fieldname of fieldnames_to_set) { + party_details[fieldname] = frm.doc[fieldname]; + } frappe.call({ method: "india_compliance.gst_india.overrides.transaction.get_gst_details", diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 2331b6b54..86e1c927a 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -39,7 +39,7 @@ Object.assign(ic, { return in_list(frappe.boot.sales_doctypes, doctype) ? "Customer" : "Supplier"; }, - get_party_field(doctype) { + get_party_fieldname(doctype) { if (doctype == "Quotation") return "party_name"; return ic.get_party_type(doctype).toLowerCase(); }, From d10dc4a8004d8ccdc062333315e0bf4c66d2d18d Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 21 Dec 2022 16:01:07 +0530 Subject: [PATCH 28/58] fix: handle error 2283 on `get_e_invoice_by_irn` (#400) * fix: handle error 2283 on get_e_invoice_by_irn * fix: e-Invoice print where invoice data is missing (cherry picked from commit 11800ecd9e5582350cb5cf08fb074aacbcb0164b) --- .../gst_india/api_classes/e_invoice.py | 11 +++++++--- .../print_format/e_invoice/e_invoice.html | 9 ++++++++ india_compliance/gst_india/utils/e_invoice.py | 22 +++++++++++++------ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/india_compliance/gst_india/api_classes/e_invoice.py b/india_compliance/gst_india/api_classes/e_invoice.py index 5e44657b4..62550b832 100644 --- a/india_compliance/gst_india/api_classes/e_invoice.py +++ b/india_compliance/gst_india/api_classes/e_invoice.py @@ -11,6 +11,10 @@ class EInvoiceAPI(BaseAPI): API_NAME = "e-Invoice" BASE_PATH = "ei/api" SENSITIVE_HEADERS = BaseAPI.SENSITIVE_HEADERS + ("password",) + IGNORED_ERROR_CODES = { + "2150": "Duplicate IRN", + "2283": "IRN details cannot be provided as it is generated more than 2 days ago", + } def setup(self, doc=None, *, company_gstin=None): if not self.settings.enable_e_invoice: @@ -43,9 +47,10 @@ def setup(self, doc=None, *, company_gstin=None): ) def handle_failed_response(self, response_json): - # Don't fail in case of Duplicate IRN - if response_json.get("message").startswith("2150"): - return True + # Don't fail if the error is ignored + for error_code in self.IGNORED_ERROR_CODES: + if error_code in response_json.get("message"): + return True def get_e_invoice_by_irn(self, irn): return self.get(endpoint="invoice/irn", params={"irn": irn}) diff --git a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html index 26c3ce2a3..7a7783649 100644 --- a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html +++ b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html @@ -51,6 +51,14 @@ {% else %} +{% if not e_invoice_log.invoice_data %} + +
+ Invoice Data is unavailable in the {{ frappe.utils.get_link_to_form("e-Invoice Log", doc.irn, "e-Invoice Log") }} to generate print preview. +
+ +{% else %} + {%- set invoice_data = _dict(json.loads(e_invoice_log.invoice_data)) -%} {% macro get_address_html(address) %} @@ -235,3 +243,4 @@
4. Value Details
{% endif %} {% endif %} +{% endif %} diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index 6072f167f..0ed903268 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -47,7 +47,10 @@ def generate_e_invoice(docname, throw=True): # Handle Duplicate IRN if result.InfCd == "DUPIRN": - result = api.get_e_invoice_by_irn(result.Desc.get("Irn")) + response = api.get_e_invoice_by_irn(result.Desc.get("Irn")) + + # Handle: IRN details cannot be provided as it is generated more than 2 days ago + result = result.Desc if "2283" in response.get("message", "") else response except frappe.ValidationError as e: if throw: @@ -71,9 +74,14 @@ def generate_e_invoice(docname, throw=True): } ) - decoded_invoice = frappe.parse_json( - jwt.decode(result.SignedInvoice, options={"verify_signature": False})["data"] - ) + invoice_data = None + if result.get("SignedInvoice"): + decoded_invoice = frappe.parse_json( + jwt.decode(result.SignedInvoice, options={"verify_signature": False})[ + "data" + ] + ) + invoice_data = frappe.as_json(decoded_invoice, indent=4) log_e_invoice( doc, @@ -82,9 +90,9 @@ def generate_e_invoice(docname, throw=True): "sales_invoice": docname, "acknowledgement_number": result.AckNo, "acknowledged_on": parse_datetime(result.AckDt), - "signed_invoice": result.SignedInvoice, - "signed_qr_code": result.SignedQRCode, - "invoice_data": frappe.as_json(decoded_invoice, indent=4), + "signed_invoice": result.get("SignedInvoice"), + "signed_qr_code": result.get("SignedQRCode"), + "invoice_data": invoice_data, }, ) From 89a4ed54239e4740d6ef310855a58dacfebf3135 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Wed, 21 Dec 2022 13:20:06 +0530 Subject: [PATCH 29/58] fix: removed custom field and property setters after uninstall app (cherry picked from commit cfaada28b718bd0c7a6ba72c994158f01fffbf8e) --- india_compliance/gst_india/setup/__init__.py | 4 +-- india_compliance/hooks.py | 2 ++ india_compliance/uninstall.py | 37 ++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 india_compliance/uninstall.py diff --git a/india_compliance/gst_india/setup/__init__.py b/india_compliance/gst_india/setup/__init__.py index 4aa8e54fd..0c694f5d3 100644 --- a/india_compliance/gst_india/setup/__init__.py +++ b/india_compliance/gst_india/setup/__init__.py @@ -30,7 +30,7 @@ def create_custom_fields(): # Will not fail if a core field with same name already exists (!) # Will update a custom field if it already exists _create_custom_fields( - _get_custom_fields_to_create( + _get_custom_fields_map( CUSTOM_FIELDS, SALES_REVERSE_CHARGE_FIELDS, E_INVOICE_FIELDS, @@ -184,7 +184,7 @@ def show_accounts_settings_override_warning(): ) -def _get_custom_fields_to_create(*custom_fields_list): +def _get_custom_fields_map(*custom_fields_list): result = {} for custom_fields in custom_fields_list: diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index d9f9266fd..7e12a82ee 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -14,6 +14,8 @@ before_tests = "india_compliance.tests.before_tests" boot_session = "india_compliance.boot.set_bootinfo" +after_uninstall = "india_compliance.uninstall.after_uninstall" + app_include_js = "gst_india.bundle.js" doctype_js = { diff --git a/india_compliance/uninstall.py b/india_compliance/uninstall.py new file mode 100644 index 000000000..fa5485c2f --- /dev/null +++ b/india_compliance/uninstall.py @@ -0,0 +1,37 @@ +import frappe + +from india_compliance.gst_india.constants.custom_fields import ( + CUSTOM_FIELDS, + E_INVOICE_FIELDS, + E_WAYBILL_FIELDS, + SALES_REVERSE_CHARGE_FIELDS, +) +from india_compliance.gst_india.setup import _get_custom_fields_map +from india_compliance.gst_india.setup.property_setters import get_property_setters +from india_compliance.patches.post_install.update_e_invoice_fields_and_logs import ( + delete_custom_fields as _delete_custom_fields, +) + + +def after_uninstall(): + delete_custom_fields() + delete_property_setters() + + +def delete_custom_fields(): + _delete_custom_fields( + _get_custom_fields_map( + CUSTOM_FIELDS, + SALES_REVERSE_CHARGE_FIELDS, + E_INVOICE_FIELDS, + E_WAYBILL_FIELDS, + ), + ignore_validate=True, + ) + + +def delete_property_setters(): + for property_setter in get_property_setters(): + keys_to_update = ["doc_type", "field_name", "property", "value"] + filters = dict(zip(keys_to_update, list(property_setter.values()))) + frappe.db.delete("Property Setter", filters) From 7302598877181191228ac72174ef3c3419538166 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Fri, 23 Dec 2022 22:11:59 +0530 Subject: [PATCH 30/58] fix: remove customizations for incom tax fields (cherry picked from commit 15b99723624529691d57b83525d8814c69dddf1e) --- india_compliance/gst_india/setup/__init__.py | 27 +++++++++++ india_compliance/income_tax_india/setup.py | 7 +++ india_compliance/uninstall.py | 48 ++++++++------------ 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/india_compliance/gst_india/setup/__init__.py b/india_compliance/gst_india/setup/__init__.py index 0c694f5d3..a40b99b5b 100644 --- a/india_compliance/gst_india/setup/__init__.py +++ b/india_compliance/gst_india/setup/__init__.py @@ -14,6 +14,9 @@ ) from india_compliance.gst_india.setup.property_setters import get_property_setters from india_compliance.gst_india.utils import get_data_file_path, toggle_custom_fields +from india_compliance.patches.post_install.update_e_invoice_fields_and_logs import ( + delete_custom_fields as _delete_custom_fields, +) def after_install(): @@ -25,6 +28,11 @@ def after_install(): create_hsn_codes() +def after_uninstall(): + delete_custom_fields() + delete_property_setters() + + def create_custom_fields(): # Validation ignored for faster creation # Will not fail if a core field with same name already exists (!) @@ -45,6 +53,25 @@ def create_property_setters(): frappe.make_property_setter(property_setter) +def delete_custom_fields(): + _delete_custom_fields( + _get_custom_fields_map( + CUSTOM_FIELDS, + SALES_REVERSE_CHARGE_FIELDS, + E_INVOICE_FIELDS, + E_WAYBILL_FIELDS, + ) + ) + + +def delete_property_setters(): + for property_setter in get_property_setters(): + keys_to_update = ["doc_type", "field_name", "property", "value"] + # Update the keys to match with the property setter fields + filters = dict(zip(keys_to_update, list(property_setter.values()))) + frappe.db.delete("Property Setter", filters) + + def create_address_template(): if frappe.db.exists("Address Template", "India"): return diff --git a/india_compliance/income_tax_india/setup.py b/india_compliance/income_tax_india/setup.py index a09c7c656..1f70160da 100644 --- a/india_compliance/income_tax_india/setup.py +++ b/india_compliance/income_tax_india/setup.py @@ -1,7 +1,14 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from india_compliance.income_tax_india.constants.custom_fields import CUSTOM_FIELDS +from india_compliance.patches.post_install.update_e_invoice_fields_and_logs import ( + delete_custom_fields, +) def after_install(): create_custom_fields(CUSTOM_FIELDS, update=True) + + +def after_uninstall(): + delete_custom_fields(CUSTOM_FIELDS) diff --git a/india_compliance/uninstall.py b/india_compliance/uninstall.py index fa5485c2f..a1ce7669e 100644 --- a/india_compliance/uninstall.py +++ b/india_compliance/uninstall.py @@ -1,37 +1,27 @@ -import frappe +import click -from india_compliance.gst_india.constants.custom_fields import ( - CUSTOM_FIELDS, - E_INVOICE_FIELDS, - E_WAYBILL_FIELDS, - SALES_REVERSE_CHARGE_FIELDS, -) -from india_compliance.gst_india.setup import _get_custom_fields_map -from india_compliance.gst_india.setup.property_setters import get_property_setters -from india_compliance.patches.post_install.update_e_invoice_fields_and_logs import ( - delete_custom_fields as _delete_custom_fields, +from india_compliance.gst_india.constants import BUG_REPORT_URL +from india_compliance.gst_india.setup import after_uninstall as remove_gst_custom_fields +from india_compliance.income_tax_india.setup import ( + after_uninstall as remove_income_tax_fields, ) def after_uninstall(): - delete_custom_fields() - delete_property_setters() - + try: + print("Removing Income Tax customizations...") + remove_income_tax_fields() -def delete_custom_fields(): - _delete_custom_fields( - _get_custom_fields_map( - CUSTOM_FIELDS, - SALES_REVERSE_CHARGE_FIELDS, - E_INVOICE_FIELDS, - E_WAYBILL_FIELDS, - ), - ignore_validate=True, - ) + print("Removing GST customizations...") + remove_gst_custom_fields() + except Exception as e: + click.secho( + "Removing Customizations for India Compliance failed due to an error." + " Please try again or" + f" report the issue on {BUG_REPORT_URL} if not resolved.", + fg="bright_red", + ) + raise e -def delete_property_setters(): - for property_setter in get_property_setters(): - keys_to_update = ["doc_type", "field_name", "property", "value"] - filters = dict(zip(keys_to_update, list(property_setter.values()))) - frappe.db.delete("Property Setter", filters) + click.secho("Customizations has been removed Successfully...", fg="green") From bf791e2d8c8d14e76b851a1f5f9fc2e983ccdf9f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 26 Dec 2022 10:09:43 +0000 Subject: [PATCH 31/58] fix: run before_uninstall instead of after_uninstall (cherry picked from commit 4f968ece6ad5bfb457630da96ec7fcf288ee59ec) --- india_compliance/gst_india/setup/__init__.py | 2 +- india_compliance/hooks.py | 2 +- india_compliance/income_tax_india/setup.py | 2 +- india_compliance/uninstall.py | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/india_compliance/gst_india/setup/__init__.py b/india_compliance/gst_india/setup/__init__.py index a40b99b5b..07c5df240 100644 --- a/india_compliance/gst_india/setup/__init__.py +++ b/india_compliance/gst_india/setup/__init__.py @@ -28,7 +28,7 @@ def after_install(): create_hsn_codes() -def after_uninstall(): +def before_uninstall(): delete_custom_fields() delete_property_setters() diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index 7e12a82ee..c95205a59 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -14,7 +14,7 @@ before_tests = "india_compliance.tests.before_tests" boot_session = "india_compliance.boot.set_bootinfo" -after_uninstall = "india_compliance.uninstall.after_uninstall" +before_uninstall = "india_compliance.uninstall.before_uninstall" app_include_js = "gst_india.bundle.js" diff --git a/india_compliance/income_tax_india/setup.py b/india_compliance/income_tax_india/setup.py index 1f70160da..66eed0fe4 100644 --- a/india_compliance/income_tax_india/setup.py +++ b/india_compliance/income_tax_india/setup.py @@ -10,5 +10,5 @@ def after_install(): create_custom_fields(CUSTOM_FIELDS, update=True) -def after_uninstall(): +def before_uninstall(): delete_custom_fields(CUSTOM_FIELDS) diff --git a/india_compliance/uninstall.py b/india_compliance/uninstall.py index a1ce7669e..44e8ebf52 100644 --- a/india_compliance/uninstall.py +++ b/india_compliance/uninstall.py @@ -1,19 +1,19 @@ import click from india_compliance.gst_india.constants import BUG_REPORT_URL -from india_compliance.gst_india.setup import after_uninstall as remove_gst_custom_fields +from india_compliance.gst_india.setup import before_uninstall as remove_gst from india_compliance.income_tax_india.setup import ( - after_uninstall as remove_income_tax_fields, + before_uninstall as remove_income_tax, ) -def after_uninstall(): +def before_uninstall(): try: print("Removing Income Tax customizations...") - remove_income_tax_fields() + remove_income_tax() print("Removing GST customizations...") - remove_gst_custom_fields() + remove_gst() except Exception as e: click.secho( From 174b417f3554626dc08ed3192062a19a26bf00bc Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 26 Dec 2022 17:38:01 +0530 Subject: [PATCH 32/58] chore: specify organisation in required apps to avoid flaky github API in CI (cherry picked from commit 551b0761dc4443a781188e378121a79e185318bb) --- india_compliance/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index d9f9266fd..c05d46182 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -8,7 +8,7 @@ app_color = "grey" app_email = "hello@indiacompliance.app" app_license = "GNU General Public License (v3)" -required_apps = ["erpnext"] +required_apps = ["frappe/erpnext"] after_install = "india_compliance.install.after_install" before_tests = "india_compliance.tests.before_tests" From 5325c7d46e7b77231a00c03c333639e48e35e983 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 27 Dec 2022 09:29:26 +0530 Subject: [PATCH 33/58] fix: Error on e-Way Bill generation (#419) (cherry picked from commit e9e3e7e2e2e7b8f92a0d325b9a04da0dce07c945) Co-authored-by: Deepesh Garg --- india_compliance/gst_india/utils/transaction_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index f841fb9e8..c8384ed1f 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -58,6 +58,7 @@ def set_transaction_details(self): "discount_amount": 0, "company_gstin": self.doc.company_gstin, "name": self.doc.name, + "other_charges": 0, } ) self.update_transaction_details() From 67ec643a12c12225efe717201f8a81106dc7af62 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 28 Dec 2022 15:42:22 +0000 Subject: [PATCH 34/58] fix: use new hooks to ignore links on delete (#425) (cherry picked from commit 8c77ba2b685f9f1ae245a168e2867540d2ea8c11) --- india_compliance/gst_india/overrides/transaction.py | 10 ---------- india_compliance/hooks.py | 6 +++++- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index ef85135a7..3b81d900f 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -2,7 +2,6 @@ import frappe from frappe import _, bold -from frappe.model import delete_doc from frappe.utils import cint, flt from erpnext.controllers.accounts_controller import get_taxes_and_charges @@ -724,12 +723,3 @@ def validate_transaction(doc, method=None): valid_accounts = validate_gst_accounts(doc, is_sales_transaction) or () update_taxable_values(doc, valid_accounts) - - -def ignore_logs_on_trash(doc, method=None): - # TODO: design better way to achieve this - if "e-Waybill Log" not in delete_doc.doctypes_to_skip: - delete_doc.doctypes_to_skip += ( - "e-Waybill Log", - "e-Invoice Log", - ) diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index d9f9266fd..262a54010 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -171,9 +171,13 @@ } -# DocTypes to be ignored while clearing transactions +# DocTypes to be ignored while clearing transactions of a Company company_data_to_be_ignored = ["GST Account", "GST Credential"] +# Links to these doctypes will be ignored when deleting a document +ignore_links_on_delete = ["e-Waybill Log", "e-Invoice Log"] + + # Includes in # ------------------ From c78295b15ca1ba86d6d4f65b5eacf9e501234f64 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 28 Dec 2022 21:16:44 +0530 Subject: [PATCH 35/58] chore: keep back older function for backwards compatibility --- .../gst_india/overrides/transaction.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 3b81d900f..c6536b698 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -2,6 +2,7 @@ import frappe from frappe import _, bold +from frappe.model import delete_doc from frappe.utils import cint, flt from erpnext.controllers.accounts_controller import get_taxes_and_charges @@ -723,3 +724,17 @@ def validate_transaction(doc, method=None): valid_accounts = validate_gst_accounts(doc, is_sales_transaction) or () update_taxable_values(doc, valid_accounts) + + +# Note: This is kept for backwards compatibility with Frappe versions < 14.21.0 +def ignore_logs_on_trash(doc, method=None): + if ( + not hasattr(delete_doc, "doctypes_to_skip") + or "e-Waybill Log" in delete_doc.doctypes_to_skip + ): + return + + delete_doc.doctypes_to_skip += ( + "e-Waybill Log", + "e-Invoice Log", + ) From 62ed1c4b626c012c31aebea47fc4dd55187a2836 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 3 Jan 2023 13:28:34 +0530 Subject: [PATCH 36/58] feat: allow configuring Sandbox Mode in GST Settings (cherry picked from commit 450c16ecd323d5c0f20de9ba8e43e0bb4ee98dff) --- .github/helper/site_config.json | 1 - .../gst_india/api_classes/base.py | 2 +- .../doctype/gst_settings/gst_settings.json | 24 +++++++++++- india_compliance/gst_india/setup/__init__.py | 37 ++++++++++--------- india_compliance/gst_india/utils/e_waybill.py | 8 ++-- .../gst_india/utils/test_e_invoice.py | 2 +- .../gst_india/utils/transaction_data.py | 2 +- india_compliance/patches.txt | 1 + .../v14/set_sandbox_mode_in_gst_settings.py | 6 +++ india_compliance/tests/__init__.py | 5 +++ 10 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 india_compliance/patches/v14/set_sandbox_mode_in_gst_settings.py diff --git a/.github/helper/site_config.json b/.github/helper/site_config.json index ffb215945..507a00776 100644 --- a/.github/helper/site_config.json +++ b/.github/helper/site_config.json @@ -13,6 +13,5 @@ "host_name": "http://test_site:8000", "install_apps": ["erpnext"], "ic_api_secret": "*****", - "ic_api_sandbox_mode": 1, "throttle_user_limit": 100 } \ No newline at end of file diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py index 8e7d6d535..77ad6f64a 100644 --- a/india_compliance/gst_india/api_classes/base.py +++ b/india_compliance/gst_india/api_classes/base.py @@ -26,7 +26,7 @@ def __init__(self, *args, **kwargs): ) ) - self.sandbox_mode = frappe.conf.ic_api_sandbox_mode + self.sandbox_mode = self.settings.sandbox_mode self.default_headers = { "x-api-key": ( (self.settings.api_secret and self.settings.get_password("api_secret")) 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 9e3074ab6..1a5661200 100644 --- a/india_compliance/gst_india/doctype/gst_settings/gst_settings.json +++ b/india_compliance/gst_india/doctype/gst_settings/gst_settings.json @@ -6,7 +6,6 @@ "engine": "InnoDB", "field_order": [ "general_tab", - "enable_api", "hsn_wise_tax_breakup", "enable_reverse_charge_in_sales", "enable_overseas_transactions", @@ -14,6 +13,10 @@ "column_break_4", "validate_hsn_code", "min_hsn_digits", + "api_section", + "enable_api", + "column_break_rk3h", + "sandbox_mode", "e_waybill_section", "enable_e_waybill", "enable_e_waybill_from_dn", @@ -225,12 +228,29 @@ "fieldname": "autofill_party_info", "fieldtype": "Check", "label": "Autofill Party Information based on GSTIN" + }, + { + "fieldname": "api_section", + "fieldtype": "Section Break", + "label": "API" + }, + { + "fieldname": "column_break_rk3h", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval: ic.is_api_enabled(doc)", + "description": "Enable this to explore GST API features in a test environment. API actions executed in sandbox mode won't affect your actual business data on the GST portal.", + "fieldname": "sandbox_mode", + "fieldtype": "Check", + "label": "Use API in Sandbox Mode?" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-09-07 09:49:38.261631", + "modified": "2023-01-03 02:39:59.034386", "modified_by": "Administrator", "module": "GST India", "name": "GST Settings", diff --git a/india_compliance/gst_india/setup/__init__.py b/india_compliance/gst_india/setup/__init__.py index 4aa8e54fd..28ec72239 100644 --- a/india_compliance/gst_india/setup/__init__.py +++ b/india_compliance/gst_india/setup/__init__.py @@ -101,23 +101,26 @@ def create_hsn_codes(): def set_default_gst_settings(): settings = frappe.get_doc("GST Settings") - settings.db_set( - { - "hsn_wise_tax_breakup": 1, - "enable_reverse_charge_in_sales": 0, - "validate_hsn_code": 1, - "min_hsn_digits": 6, - "enable_e_waybill": 1, - "e_waybill_threshold": 50000, - # Default API Settings - "fetch_e_waybill_data": 1, - "attach_e_waybill_print": 1, - "auto_generate_e_waybill": 1, - "auto_generate_e_invoice": 1, - "e_invoice_applicable_from": nowdate(), - "auto_fill_party_info": 1, - } - ) + default_settings = { + "hsn_wise_tax_breakup": 1, + "enable_reverse_charge_in_sales": 0, + "validate_hsn_code": 1, + "min_hsn_digits": 6, + "enable_e_waybill": 1, + "e_waybill_threshold": 50000, + # Default API Settings + "fetch_e_waybill_data": 1, + "attach_e_waybill_print": 1, + "auto_generate_e_waybill": 1, + "auto_generate_e_invoice": 1, + "e_invoice_applicable_from": nowdate(), + "auto_fill_party_info": 1, + } + + if frappe.conf.developer_mode: + default_settings["sandbox_mode"] = 1 + + settings.db_set(default_settings) # Hide the fields as not enabled by default for fields in (E_INVOICE_FIELDS, SALES_REVERSE_CHARGE_FIELDS): diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 59c687d6d..56ca86714 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -149,20 +149,20 @@ def cancel_e_waybill(*, doctype, docname, values): def _cancel_e_waybill(doc, values): """Separate function, since called in backend from e-invoice utils""" - data = EWaybillData(doc).get_e_waybill_cancel_data(values) + e_waybill_data = EWaybillData(doc) api = ( EInvoiceAPI # Use EInvoiceAPI only for sandbox environment # if e-Waybill has been created using IRN if ( - frappe.conf.ic_api_sandbox_mode + e_waybill_data.sandbox_mode and doc.get("irn") and not (doc.is_return or doc.is_debit_note) ) else EWaybillAPI ) - result = api(doc).cancel_e_waybill(data) + result = api(doc).cancel_e_waybill(e_waybill_data.get_data_for_cancellation(values)) log_and_process_e_waybill( doc, @@ -500,7 +500,7 @@ def get_data(self, *, with_irn=False): return self.get_transaction_data() - def get_e_waybill_cancel_data(self, values): + def get_data_for_cancellation(self, values): self.validate_if_e_waybill_is_set() self.validate_if_ewaybill_can_be_cancelled() diff --git a/india_compliance/gst_india/utils/test_e_invoice.py b/india_compliance/gst_india/utils/test_e_invoice.py index 477dffb54..d9e662bf3 100644 --- a/india_compliance/gst_india/utils/test_e_invoice.py +++ b/india_compliance/gst_india/utils/test_e_invoice.py @@ -382,7 +382,7 @@ def _cancel_e_invoice(self, invoice_no): # Assert for Mock request data self.assertDictEqual( cancel_e_waybill.get("request_data"), - EWaybillData(doc).get_e_waybill_cancel_data(values), + EWaybillData(doc).get_data_for_cancellation(values), ) # Prepared e_invoice cancel data diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index c8384ed1f..01facb159 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -24,8 +24,8 @@ class GSTTransactionData: def __init__(self, doc): self.doc = doc - self.sandbox_mode = frappe.conf.ic_api_sandbox_mode self.settings = frappe.get_cached_doc("GST Settings") + self.sandbox_mode = self.settings.sandbox_mode self.transaction_details = frappe._dict() # "CGST Account - TC": "cgst_account" diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 2f9698035..4af0a97e2 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -7,3 +7,4 @@ execute:from india_compliance.gst_india.setup import create_custom_fields; creat execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() india_compliance.patches.post_install.update_custom_role_for_e_invoice_summary india_compliance.patches.v14.remove_ecommerce_gstin_from_purchase_invoice +india_compliance.patches.v14.set_sandbox_mode_in_gst_settings diff --git a/india_compliance/patches/v14/set_sandbox_mode_in_gst_settings.py b/india_compliance/patches/v14/set_sandbox_mode_in_gst_settings.py new file mode 100644 index 000000000..cd8e2a602 --- /dev/null +++ b/india_compliance/patches/v14/set_sandbox_mode_in_gst_settings.py @@ -0,0 +1,6 @@ +import frappe + + +def execute(): + if frappe.conf.ic_api_sandbox_mode: + frappe.db.set_single_value("GST Settings", "sandbox_mode", 1) diff --git a/india_compliance/tests/__init__.py b/india_compliance/tests/__init__.py index 2020d9384..c86cb8a0c 100644 --- a/india_compliance/tests/__init__.py +++ b/india_compliance/tests/__init__.py @@ -44,11 +44,16 @@ def before_tests(): def set_default_settings_for_tests(): + # e.g. set "All Customer Groups" as the default Customer Group for key in ("Customer Group", "Supplier Group", "Item Group", "Territory"): frappe.db.set_default(frappe.scrub(key), get_root_of(key)) + # Allow Negative Stock frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1) + # Enable Sandbox Mode in GST Settings + frappe.db.set_single_value("GST Settings", "sandbox_mode", 1) + def create_test_records(): test_records = frappe.get_file_json( From 878bdaa322a3c7e23417bb0804774c5c7427afeb Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 4 Jan 2023 07:38:56 +0000 Subject: [PATCH 37/58] fix(ux): show info alert when API call is done in sandbox mode (#437) (cherry picked from commit 183f5a707c512d2c9f014eb93f64554b7041076b) --- india_compliance/gst_india/api_classes/base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py index 77ad6f64a..239b98d34 100644 --- a/india_compliance/gst_india/api_classes/base.py +++ b/india_compliance/gst_india/api_classes/base.py @@ -171,6 +171,13 @@ def _make_request( log.output = response_json enqueue_integration_request(**log) + if self.sandbox_mode and not frappe.flags.ic_sandbox_message_shown: + frappe.msgprint( + _("GST API request was made in Sandbox Mode"), + alert=True, + ) + frappe.flags.ic_sandbox_message_shown = True + def handle_failed_response(self, response_json): # Override in subclass, return truthy value to stop frappe.throw pass From ec9f415d1a0a560bea60c229378896ad9e2f8879 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Fri, 23 Dec 2022 21:30:24 +0530 Subject: [PATCH 38/58] fix: move transporter info section into `More Info` tab (cherry picked from commit 3bc1053c59fa037114515d2cf299fcd32f7973ee) --- india_compliance/gst_india/constants/custom_fields.py | 2 +- india_compliance/patches.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index e0a848ec7..18d208c70 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -678,7 +678,7 @@ "fieldname": "transporter_info", "label": "Transporter Info", "fieldtype": "Section Break", - "insert_after": "terms", + "insert_after": "po_date", "collapsible": 1, "collapsible_depends_on": "transporter", "print_hide": 1, diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 4af0a97e2..3506d67f2 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -3,7 +3,7 @@ [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() #3 +execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #4 execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() india_compliance.patches.post_install.update_custom_role_for_e_invoice_summary india_compliance.patches.v14.remove_ecommerce_gstin_from_purchase_invoice From 3e21849eaa82dd54b6b0ae01c398f67ad943aad8 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 26 Dec 2022 09:32:10 +0000 Subject: [PATCH 39/58] fix: transporter section immediately below print settings (cherry picked from commit f48b7d1e2ec74a0b41e94fe06d400dbde8b1001a) --- india_compliance/gst_india/constants/custom_fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index 18d208c70..34b44e911 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -116,7 +116,7 @@ "fieldname": "gst_section", "label": "GST Details", "fieldtype": "Section Break", - "insert_after": "language", + "insert_after": "gst_vehicle_type", "print_hide": 1, "collapsible": 1, }, @@ -678,7 +678,7 @@ "fieldname": "transporter_info", "label": "Transporter Info", "fieldtype": "Section Break", - "insert_after": "po_date", + "insert_after": "language", "collapsible": 1, "collapsible_depends_on": "transporter", "print_hide": 1, From e8f1e9dd6d23f001baa1a1e11f99ed688efc1f46 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 10 Jan 2023 10:45:54 +0530 Subject: [PATCH 40/58] fix(quick entry): dynamic link for all doctypes in address quick entry (#444) * fix: missing link in new address * fix: check if dynamic link exists * fix: remove code duplication * chore: compare `doc` instead Co-authored-by: safvanhuzain (cherry picked from commit 854dd87213a6baa061c82570988a901597cc18fe) --- .../gst_india/client_scripts/item.js | 2 +- india_compliance/public/js/quick_entry.js | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/item.js b/india_compliance/gst_india/client_scripts/item.js index cceb1ec89..1bc374f7f 100644 --- a/india_compliance/gst_india/client_scripts/item.js +++ b/india_compliance/gst_india/client_scripts/item.js @@ -3,7 +3,7 @@ frappe.ui.form.on('Item', { if ((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) { frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => { $.each(hsn_doc.taxes || [], function(i, tax) { - let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes'); + let a = frappe.model.add_child(frm.doc, 'Item Tax', 'taxes'); a.item_tax_template = tax.item_tax_template; a.tax_category = tax.tax_category; a.valid_from = tax.valid_from; diff --git a/india_compliance/public/js/quick_entry.js b/india_compliance/public/js/quick_entry.js index e867f226a..7ccfd2214 100644 --- a/india_compliance/public/js/quick_entry.js +++ b/india_compliance/public/js/quick_entry.js @@ -256,15 +256,16 @@ class AddressQuickEntryForm extends GSTQuickEntryForm { get_default_party() { const doc = cur_frm && cur_frm.doc; - if (!doc) return; - - const { doctype, name } = doc; - if (in_list(frappe.boot.gst_party_types, doctype)) - return { party_type: doctype, party: name }; - - const party_type = ic.get_party_type(doctype); - const party = doc[ic.get_party_fieldname(doctype)]; - return { party_type, party }; + if ( + doc && + frappe.dynamic_link && + frappe.dynamic_link.doc === doc + ) { + return { + party_type: frappe.dynamic_link.doctype, + party: frappe.dynamic_link.doc[frappe.dynamic_link.fieldname] + }; + } } } From dd3faf1956937677401c46c6762c2b5a3208e5d3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 11 Jan 2023 13:19:04 +0530 Subject: [PATCH 41/58] fix: Unable to download GSTIN wise GSTR-1 JSON (cherry picked from commit 70514a8dc354d74393cca022c75d6f9a8170afde) --- india_compliance/gst_india/report/gstr_1/gstr_1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 313bdbe1f..4459ddc87 100644 --- a/india_compliance/gst_india/report/gstr_1/gstr_1.py +++ b/india_compliance/gst_india/report/gstr_1/gstr_1.py @@ -909,7 +909,7 @@ def get_json(filters, report_name, data): filters = json.loads(filters) report_data = json.loads(data) - gstin = get_company_gstin_number( + gstin = filters.get("company_gstin") or get_company_gstin_number( filters.get("company"), filters.get("company_address") ) From 4609ab759e6ab44b87d85b79461c12f764fb2574 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 11 Jan 2023 08:27:09 +0000 Subject: [PATCH 42/58] ci: correct node version, allow dispatch for releases (#450) --- .github/workflows/on_release.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/on_release.yml b/.github/workflows/on_release.yml index 0b3410c93..6feed6775 100644 --- a/.github/workflows/on_release.yml +++ b/.github/workflows/on_release.yml @@ -1,6 +1,7 @@ name: Generate Semantic Release on: + workflow_dispatch: push: branches: - version-14 @@ -18,7 +19,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v2 with: - node-version: 16 + node-version: 18 - name: Setup dependencies run: | @@ -31,4 +32,4 @@ jobs: GIT_AUTHOR_EMAIL: "bot@indiacompliance.app" GIT_COMMITTER_NAME: "India Compliance Bot" GIT_COMMITTER_EMAIL: "bot@indiacompliance.app" - run: npx semantic-release \ No newline at end of file + run: npx semantic-release From 31d9940342c93dde4a08fedcca28f33602c040d3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:48:03 +0530 Subject: [PATCH 43/58] fix: hsn field in new item quick entry (backport #452) * fix: hsn field in new item quick entry Co-authored-by: ljain112 Co-authored-by: Smit Vora --- india_compliance/gst_india/constants/custom_fields.py | 1 + india_compliance/patches.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index e0a848ec7..fb1ff9714 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -554,6 +554,7 @@ "fieldtype": "Link", "options": "GST HSN Code", "insert_after": "item_group", + "allow_in_quick_entry": 1, }, { "fieldname": "is_nil_exempt", diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 4af0a97e2..16dfb5189 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -3,7 +3,7 @@ [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() #3 +execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #5 execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() india_compliance.patches.post_install.update_custom_role_for_e_invoice_summary india_compliance.patches.v14.remove_ecommerce_gstin_from_purchase_invoice From 33f47e90156beedbd52c9d7dc2b8ee5851c5fed7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 17 Jan 2023 09:45:09 +0530 Subject: [PATCH 44/58] fix: Negative credit days error whilegenerating e-Invoice (cherry picked from commit df101479c75e3216ee1f633d8efdd1ec29b16c65) --- india_compliance/gst_india/utils/e_invoice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index 6072f167f..463e0cd5d 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -315,7 +315,9 @@ def update_payment_details(self): credit_days = 0 paid_amount = 0 - if self.doc.due_date: + if self.doc.due_date and getdate(self.doc.due_date) > getdate( + self.doc.posting_date + ): credit_days = ( getdate(self.doc.due_date) - getdate(self.doc.posting_date) ).days From 9734920b8de14e909e6dd39452aa32f2418b1f88 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 19 Jan 2023 14:42:04 +0530 Subject: [PATCH 45/58] fix: copy GST Item fields to its variants (#458) * fix: missing HSN for variant * chore: run pre-commit * fix: avoid duplicate field entry * fix: remove variant hsn mapping on uninstall * fix: map non-gst and exempt fields to variant * chore: change patch ordering * chore: linting issues * chore: improve code readability * chore: remove line from `install.py` Co-authored-by: Dany Robert Co-authored-by: Sagar Vora (cherry picked from commit bd9ec6da0cc497d6716fd48a2e0b54f44fd5325d) # Conflicts: # india_compliance/gst_india/setup/__init__.py --- india_compliance/gst_india/setup/__init__.py | 32 ++++++++++++++++++++ india_compliance/patches.txt | 1 + 2 files changed, 33 insertions(+) diff --git a/india_compliance/gst_india/setup/__init__.py b/india_compliance/gst_india/setup/__init__.py index 28ec72239..f3307ab8e 100644 --- a/india_compliance/gst_india/setup/__init__.py +++ b/india_compliance/gst_india/setup/__init__.py @@ -15,6 +15,8 @@ from india_compliance.gst_india.setup.property_setters import get_property_setters from india_compliance.gst_india.utils import get_data_file_path, toggle_custom_fields +ITEM_VARIANT_FIELDNAMES = frozenset(("gst_hsn_code", "is_nil_exempt", "is_non_gst")) + def after_install(): create_custom_fields() @@ -23,8 +25,18 @@ def after_install(): set_default_gst_settings() set_default_accounts_settings() create_hsn_codes() + add_fields_to_item_variant_settings() + + +<<<<<<< HEAD +======= +def before_uninstall(): + delete_custom_fields() + delete_property_setters() + remove_fields_from_item_variant_settings() +>>>>>>> bd9ec6da (fix: copy GST Item fields to its variants (#458)) def create_custom_fields(): # Validation ignored for faster creation # Will not fail if a core field with same name already exists (!) @@ -99,6 +111,18 @@ def create_hsn_codes(): ) +def add_fields_to_item_variant_settings(): + settings = frappe.get_doc("Item Variant Settings") + fields_to_add = ITEM_VARIANT_FIELDNAMES - { + row.field_name for row in settings.fields + } + + for fieldname in fields_to_add: + settings.append("fields", {"field_name": fieldname}) + + settings.save() + + def set_default_gst_settings(): settings = frappe.get_doc("GST Settings") default_settings = { @@ -198,3 +222,11 @@ def _get_custom_fields_to_create(*custom_fields_list): result.setdefault(doctypes, []).extend(fields) return result + + +def remove_fields_from_item_variant_settings(): + settings = frappe.get_doc("Item Variant Settings") + settings.fields = [ + row for row in settings.fields if row.field_name not in ITEM_VARIANT_FIELDNAMES + ] + settings.save() diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 16dfb5189..81b6124a4 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -8,3 +8,4 @@ execute:from india_compliance.gst_india.setup import create_property_setters; cr india_compliance.patches.post_install.update_custom_role_for_e_invoice_summary india_compliance.patches.v14.remove_ecommerce_gstin_from_purchase_invoice india_compliance.patches.v14.set_sandbox_mode_in_gst_settings +execute:from india_compliance.gst_india.setup import add_fields_to_item_variant_settings; add_fields_to_item_variant_settings() From 0ce74c725bf59bb02970423922f48dc831dfb069 Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Wed, 25 Jan 2023 19:17:18 +0530 Subject: [PATCH 46/58] fix: `account_type` mandatory error while installing IC (cherry picked from commit 0a35f6c981829b800f0a560910ba49f4f5bca0e9) --- india_compliance/gst_india/overrides/company.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/india_compliance/gst_india/overrides/company.py b/india_compliance/gst_india/overrides/company.py index 3d68944c4..baf4a4631 100644 --- a/india_compliance/gst_india/overrides/company.py +++ b/india_compliance/gst_india/overrides/company.py @@ -15,6 +15,7 @@ def delete_gst_settings_for_company(doc, method=None): row for row in gst_settings.get("gst_accounts", []) if row.company != doc.name ] + gst_settings.flags.ignore_mandatory = True gst_settings.save() @@ -94,6 +95,10 @@ def update_gst_settings(company): "Reverse Charge", ) + # Ignore mandatory during install, some values may not be set by post install patch + if frappe.flags.in_install: + gst_settings.flags.ignore_mandatory = True + gst_settings.save() From 0a52b6072e9ff0c8b7fb2a469c7196757995d5ce Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 18 Feb 2023 17:04:56 +0530 Subject: [PATCH 47/58] fix: misc issues noticed during review (cherry picked from commit e8b1bffc912edcf25cc66b738c792a9a75aa2a27) --- .../gst_india/api_classes/e_invoice.py | 10 +++++++--- .../gst_india/api_classes/returns.py | 12 +++++------- .../print_format/e_invoice/e_invoice.html | 8 ++++---- .../print_format/e_waybill/e_waybill.html | 2 +- india_compliance/gst_india/utils/e_invoice.py | 17 ++++++++++------- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/india_compliance/gst_india/api_classes/e_invoice.py b/india_compliance/gst_india/api_classes/e_invoice.py index 62550b832..f7d16a17e 100644 --- a/india_compliance/gst_india/api_classes/e_invoice.py +++ b/india_compliance/gst_india/api_classes/e_invoice.py @@ -13,7 +13,9 @@ class EInvoiceAPI(BaseAPI): SENSITIVE_HEADERS = BaseAPI.SENSITIVE_HEADERS + ("password",) IGNORED_ERROR_CODES = { "2150": "Duplicate IRN", - "2283": "IRN details cannot be provided as it is generated more than 2 days ago", + "2283": ( + "IRN details cannot be provided as it is generated more than 2 days ago" + ), } def setup(self, doc=None, *, company_gstin=None): @@ -47,9 +49,11 @@ def setup(self, doc=None, *, company_gstin=None): ) def handle_failed_response(self, response_json): - # Don't fail if the error is ignored + message = response_json.get("message", "").strip() + for error_code in self.IGNORED_ERROR_CODES: - if error_code in response_json.get("message"): + if message.startswith(error_code): + response_json.error_code = error_code return True def get_e_invoice_by_irn(self, irn): diff --git a/india_compliance/gst_india/api_classes/returns.py b/india_compliance/gst_india/api_classes/returns.py index eedf3ee2a..d0baaccdf 100644 --- a/india_compliance/gst_india/api_classes/returns.py +++ b/india_compliance/gst_india/api_classes/returns.py @@ -28,11 +28,14 @@ def setup(self, company_gstin): ) def handle_failed_response(self, response_json): - if response_json.get("errorCode") in self.IGNORED_ERROR_CODES: + error_code = response_json.get("errorCode") + + if error_code in self.IGNORED_ERROR_CODES: + response_json.error_type = self.IGNORED_ERROR_CODES[error_code] return True def get(self, action, return_period, otp=None, params=None): - response = super().get( + return super().get( params={"action": action, "gstin": self.company_gstin, **(params or {})}, headers={ "requestid": self.generate_request_id(), @@ -41,11 +44,6 @@ def get(self, action, return_period, otp=None, params=None): }, ) - if error_type := self.IGNORED_ERROR_CODES.get(response.errorCode): - response.error_type = error_type - - return response - class GSTR2bAPI(ReturnsAPI): API_NAME = "GSTR-2B" diff --git a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html index 7a7783649..658242267 100644 --- a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html +++ b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html @@ -54,7 +54,7 @@ {% if not e_invoice_log.invoice_data %}
- Invoice Data is unavailable in the {{ frappe.utils.get_link_to_form("e-Invoice Log", doc.irn, "e-Invoice Log") }} to generate print preview. + Invoice Data is not available in the e-Invoice Log linked to this invoice.
{% else %} @@ -97,14 +97,14 @@
1. Transaction Details
{% set transaction_details = { "IRN": invoice_data.Irn, - "Ack. No": invoice_data.AckNo, + "Ack. No.": invoice_data.AckNo, "Ack. Date": frappe.utils.format_datetime( invoice_data.AckDt, "dd/MM/yyyy hh:mm:ss" ), "Category": invoice_data.TranDtls.SupTyp, "Document Type": invoice_data.DocDtls.Typ, - "Document No": invoice_data.DocDtls.No, - "e-Waybill": doc.ewaybill, + "Document No.": invoice_data.DocDtls.No, + "e-Waybill No.": doc.ewaybill, } %} {% for key, value in transaction_details.items() %} diff --git a/india_compliance/gst_india/print_format/e_waybill/e_waybill.html b/india_compliance/gst_india/print_format/e_waybill/e_waybill.html index 194a4f108..6bc95f65e 100644 --- a/india_compliance/gst_india/print_format/e_waybill/e_waybill.html +++ b/india_compliance/gst_india/print_format/e_waybill/e_waybill.html @@ -3,7 +3,7 @@ {% set data = _dict(json.loads(doc.data)) %} -{%- set irn = frappe.db.get_value("Sales Invoice", {'ewaybill': data.ewbNo}, 'irn') -%} +{%- set irn = frappe.db.get_value("Sales Invoice", {"ewaybill": data.ewbNo}, "irn") -%} diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index a6cd66e8d..f8542ca56 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -1,3 +1,5 @@ +import json + import jwt import frappe @@ -47,10 +49,11 @@ def generate_e_invoice(docname, throw=True): # Handle Duplicate IRN if result.InfCd == "DUPIRN": - response = api.get_e_invoice_by_irn(result.Desc.get("Irn")) + response = api.get_e_invoice_by_irn(result.Desc.Irn) - # Handle: IRN details cannot be provided as it is generated more than 2 days ago - result = result.Desc if "2283" in response.get("message", "") else response + # Handle error 2283: + # IRN details cannot be provided as it is generated more than 2 days ago + result = result.Desc if response.error_code == "2283" else response except frappe.ValidationError as e: if throw: @@ -75,8 +78,8 @@ def generate_e_invoice(docname, throw=True): ) invoice_data = None - if result.get("SignedInvoice"): - decoded_invoice = frappe.parse_json( + if result.SignedInvoice: + decoded_invoice = json.loads( jwt.decode(result.SignedInvoice, options={"verify_signature": False})[ "data" ] @@ -90,8 +93,8 @@ def generate_e_invoice(docname, throw=True): "sales_invoice": docname, "acknowledgement_number": result.AckNo, "acknowledged_on": parse_datetime(result.AckDt), - "signed_invoice": result.get("SignedInvoice"), - "signed_qr_code": result.get("SignedQRCode"), + "signed_invoice": result.SignedInvoice, + "signed_qr_code": result.SignedQRCode, "invoice_data": invoice_data, }, ) From d483f782a3e840268888817e08382b0c65972dcc Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 18 Feb 2023 17:09:36 +0530 Subject: [PATCH 48/58] chore: bump isort (cherry picked from commit e41b1800b6ec323bba0762e7b2ed6d9c560e8309) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f096c513f..e646da466 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,8 +20,8 @@ repos: hooks: - id: black - - repo: https://github.com/timothycrosley/isort - rev: 5.10.1 + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 hooks: - id: isort From 78f1c97b31e2f9de9b1d579510fbc6dcc879367d Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 3 Mar 2023 19:20:47 +0530 Subject: [PATCH 49/58] fix: multiple fixes to SI bulk actions (cherry picked from commit a47dd39c79cb24eb34342e5bc3b6875999d1c14d) --- .../client_scripts/sales_invoice_list.js | 84 ++++++++++++------- .../gst_india/overrides/sales_invoice.py | 26 ------ india_compliance/gst_india/utils/e_invoice.py | 46 ++++++++++ india_compliance/gst_india/utils/e_waybill.py | 6 +- 4 files changed, 106 insertions(+), 56 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/sales_invoice_list.js b/india_compliance/gst_india/client_scripts/sales_invoice_list.js index ae1a50609..f08180023 100644 --- a/india_compliance/gst_india/client_scripts/sales_invoice_list.js +++ b/india_compliance/gst_india/client_scripts/sales_invoice_list.js @@ -5,27 +5,29 @@ frappe.listview_settings[DOCTYPE].onload = function (list_view) { erpnext_onload(list_view); } - if (!frappe.perm.has_perm(DOCTYPE, 0, "submit")) - return; + if (!frappe.perm.has_perm(DOCTYPE, 0, "submit")) return; if (gst_settings.enable_e_waybill) - add_bulk_action(list_view, "e-Waybill JSON", generate_e_waybill_json); + add_bulk_action_for_submitted_invoices( + list_view, + __("Generate e-Waybill JSON"), + generate_e_waybill_json + ); if (ic.is_e_invoice_enabled()) - add_bulk_action(list_view, "e-Invoice", generate_e_invoice); + add_bulk_action_for_submitted_invoices( + list_view, + __("Enqueue Bulk e-Invoice Generation"), + enqueue_bulk_e_invoice_generation + ); }; -function add_bulk_action(list_view, label, callback) { - list_view.page.add_actions_menu_item( - __("Generate {0}", [__(label)]), - () => { - const selected_docs = list_view.get_checked_items(); - const docnames = list_view.get_checked_items(true); - validate_draft_invoices(selected_docs, label); - callback(docnames); - }, - false - ); +function add_bulk_action_for_submitted_invoices(list_view, label, callback) { + list_view.page.add_actions_menu_item(label, async () => { + const selected_docs = list_view.get_checked_items(); + const submitted_docs = await validate_if_submitted(selected_docs); + if (submitted_docs) callback(submitted_docs); + }); } async function generate_e_waybill_json(docnames) { @@ -37,34 +39,58 @@ async function generate_e_waybill_json(docnames) { trigger_file_download(ewb_data, get_e_waybill_file_name()); } -function generate_e_invoice(docnames) { - frappe.xcall( - "india_compliance.gst_india.overrides.sales_invoice.generate_e_invoice", +async function enqueue_bulk_e_invoice_generation(docnames) { + const now = frappe.datetime.system_datetime(); + + const job_id = await frappe.xcall( + "india_compliance.gst_india.utils.e_invoice.enqueue_bulk_e_invoice_generation", { docnames } ); - const today = frappe.datetime.get_today(); - const route = frappe.utils.generate_route({ + const api_requests_link = frappe.utils.generate_route({ type: "doctype", name: "Integration Request", route_options: { integration_request_service: "India Compliance API", - creation: `["Between",["${today}", "${today}"]]`, + creation: `[">", "${now}"]`, }, }); + frappe.msgprint( __( - 'Bulk Generation is queued. Check the progress in Integration Request Log.', - [route] + "Bulk e-Invoice Generation has been queued. You can track the Background Job and API Requests.", + [frappe.utils.get_form_link("RQ Job", job_id), api_requests_link] ) ); } -function validate_draft_invoices(selected_docs, label) { - const draft_invoices = selected_docs.filter(doc => doc.docstatus == 0); - if (!draft_invoices.length) return; +async function validate_if_submitted(selected_docs) { + const valid_docs = []; + const invalid_docs = []; - frappe.throw( - __("{0} can only be generated from a submitted document.", [__(label)]) - ); + for (const doc of selected_docs) { + if (doc.docstatus != 1) { + invalid_docs.push(doc.name); + } else { + valid_docs.push(doc.name); + } + } + + if (!invalid_docs.length) return valid_docs; + + if (!valid_docs.length) { + frappe.throw(__("This action can only be performed on submitted documents")); + } + + const confirmed = await new Promise(resolve => { + frappe.confirm( + __( + "This action can only be performed on submitted documents. Do you want to continue without the following documents?

{0}", + [invalid_docs.join("
")] + ), + () => resolve(true) + ); + }); + + return confirmed ? valid_docs : false; } diff --git a/india_compliance/gst_india/overrides/sales_invoice.py b/india_compliance/gst_india/overrides/sales_invoice.py index 8586aa58d..52880ecc3 100644 --- a/india_compliance/gst_india/overrides/sales_invoice.py +++ b/india_compliance/gst_india/overrides/sales_invoice.py @@ -176,29 +176,3 @@ def update_dashboard_with_gst_logs(doctype, data, *log_doctypes): transactions.insert(2, {"label": _("GST Logs"), "items": log_doctypes}) return data - - -@frappe.whitelist() -def generate_e_invoice(docnames): - """ - Bulk generate e-Invoices for the given Sales Invoices. - Permission checks are done in the `generate_e_invoice` function. - """ - gst_settings = frappe.get_cached_doc("GST Settings") - if not is_api_enabled(gst_settings): - return - - docnames = frappe.parse_json(docnames) if docnames.startswith("[") else [docnames] - for doc in docnames: - doc = frappe.get_doc("Sales Invoice", doc) - if doc.docstatus != 1 or not validate_e_invoice_applicability( - doc, gst_settings, throw=False - ): - continue - - frappe.enqueue( - "india_compliance.gst_india.utils.e_invoice.generate_e_invoice", - queue="short", - docname=doc.name, - throw=False, - ) diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index f8542ca56..e4a941274 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -24,6 +24,7 @@ ITEM_LIMIT, ) from india_compliance.gst_india.utils import ( + is_api_enabled, load_doc, parse_datetime, send_updated_doc, @@ -39,6 +40,51 @@ ) +@frappe.whitelist() +def enqueue_bulk_e_invoice_generation(docnames): + """ + Enqueue bulk generation of e-Invoices for the given Sales Invoices. + """ + + frappe.has_permission("Sales Invoice", "submit", throw=True) + + gst_settings = frappe.get_cached_doc("GST Settings") + if not is_api_enabled(gst_settings) or not gst_settings.enable_e_invoice: + frappe.throw(_("Please enable e-Invoicing in GST Settings first")) + + docnames = frappe.parse_json(docnames) if docnames.startswith("[") else [docnames] + rq_job = frappe.enqueue( + "india_compliance.gst_india.utils.e_invoice.generate_e_invoices", + queue="long", + timeout=len(docnames) * 240, # 4 mins per e-Invoice + docnames=docnames, + ) + + return rq_job.id + + +def generate_e_invoices(docnames): + """ + Bulk generate e-Invoices for the given Sales Invoices. + Permission checks are done in the `generate_e_invoice` function. + """ + + for docname in docnames: + try: + generate_e_invoice(docname) + + except Exception: + frappe.log_error( + title=_("e-Invoice generation failed for Sales Invoice {0}").format( + docname + ), + message=frappe.get_traceback(), + ) + + finally: + frappe.db.commit() + + @frappe.whitelist() def generate_e_invoice(docname, throw=True): doc = load_doc("Sales Invoice", docname, "submit") diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 56ca86714..1cbaf06e2 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -551,7 +551,11 @@ def validate_transaction(self): super().validate_transaction() if self.doc.ewaybill: - frappe.throw(_("e-Waybill already generated for this document")) + frappe.throw( + _("e-Waybill already generated for {0} {1}").format( + _(self.doc.doctype), frappe.bold(self.doc.name) + ) + ) self.validate_applicability() From b467ab5e97818deae545daf0bd3420074b43e618 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 3 Mar 2023 19:35:40 +0530 Subject: [PATCH 50/58] fix: add link to error log(s) (cherry picked from commit 074029f0eaf4ebfd209ed17f58f3e95bcc5b7025) --- .../client_scripts/sales_invoice_list.js | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/india_compliance/gst_india/client_scripts/sales_invoice_list.js b/india_compliance/gst_india/client_scripts/sales_invoice_list.js index f08180023..426b2d648 100644 --- a/india_compliance/gst_india/client_scripts/sales_invoice_list.js +++ b/india_compliance/gst_india/client_scripts/sales_invoice_list.js @@ -47,19 +47,34 @@ async function enqueue_bulk_e_invoice_generation(docnames) { { docnames } ); + const creation_filter = `[">", "${now}"]`; const api_requests_link = frappe.utils.generate_route({ type: "doctype", name: "Integration Request", route_options: { integration_request_service: "India Compliance API", - creation: `[">", "${now}"]`, + creation: creation_filter, + }, + }); + const error_logs_link = frappe.utils.generate_route({ + type: "doctype", + name: "Error Log", + route_options: { + creation: creation_filter, }, }); frappe.msgprint( __( - "Bulk e-Invoice Generation has been queued. You can track the Background Job and API Requests.", - [frappe.utils.get_form_link("RQ Job", job_id), api_requests_link] + `Bulk e-Invoice Generation has been queued. You can track the + Background Job, + API Request(s), + and Error Log(s).`, + [ + frappe.utils.get_form_link("RQ Job", job_id), + api_requests_link, + error_logs_link, + ] ) ); } From 52f1898721fbcada370d35fd8e6a4fe9993a917a Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 3 Mar 2023 19:44:34 +0530 Subject: [PATCH 51/58] chore: add comment explaining manual commit (cherry picked from commit 598ddc5318168c03b088fbeb70557f75da06bd39) --- india_compliance/gst_india/utils/e_invoice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index e4a941274..c70281ff5 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -82,6 +82,8 @@ def generate_e_invoices(docnames): ) finally: + # each e-Invoice needs to be committed individually + # nosemgrep frappe.db.commit() From 1b14f3d35e08cb044a33bfc081a717e7fe7a9cef Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 4 Mar 2023 13:47:21 +0530 Subject: [PATCH 52/58] fix: improvements to e-Invoice print format (cherry picked from commit 65467097213f9bd656a41cd0c17803fe482a8cb7) --- .../print_format/e_invoice/e_invoice.html | 39 ++-------- india_compliance/gst_india/utils/jinja.py | 75 ++++++++++++++----- india_compliance/hooks.py | 3 +- 3 files changed, 63 insertions(+), 54 deletions(-) diff --git a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html index 658242267..7ebbf3764 100644 --- a/india_compliance/gst_india/print_format/e_invoice/e_invoice.html +++ b/india_compliance/gst_india/print_format/e_invoice/e_invoice.html @@ -1,30 +1,3 @@ -{% set ITEM_FIELDS_MAP = { - "SlNo": "Sr.", - "PrdDesc": "Product Description", - "HsnCd": "HSN Code", - "Qty": "Qty", - "Unit": "UOM", - "UnitPrice": "Rate", - "Discount": "Discount", - "AssAmt": "Taxable Amount", - "GstRt": "Tax Rate", - "CesRt": "Cess Rate", - "TotItemVal": "Total", -} %} - -{% set AMOUNT_FIELDS_MAP = { - "AssVal": "Taxable Value", - "CgstVal": "CGST", - "SgstVal": "SGST", - "IgstVal": "IGST", - "CesVal": "CESS", - "Discount": "Discount", - "OthChrg": "Other Charges", - "RndOffAmt": "Round Off", - "TotInvVal": "Total Value", -} %} - - @@ -166,19 +139,19 @@
Shipped To
{% set items = invoice_data.ItemList %} -{% set item_fields = get_non_zero_fields(items, ITEM_FIELDS_MAP) %} +{% set item_fields = get_e_invoice_item_fields(items) %}
3. Item Details
- {% for field in item_fields %} + {% for field, label in item_fields.items() %} + >{{ label }} {% endfor %} @@ -216,15 +189,15 @@
3. Item Details
{%- set amounts = invoice_data.ValDtls -%} -{% set amount_fields = get_non_zero_fields(amounts, AMOUNT_FIELDS_MAP, doc) %} +{% set amount_fields = get_e_invoice_amount_fields(amounts, doc) %}
4. Value Details
{{ ITEM_FIELDS_MAP[field] }}
- {% for field in amount_fields %} - + {% for field, label in amount_fields.items() %} + {% endfor %} diff --git a/india_compliance/gst_india/utils/jinja.py b/india_compliance/gst_india/utils/jinja.py index a57151803..c96089363 100644 --- a/india_compliance/gst_india/utils/jinja.py +++ b/india_compliance/gst_india/utils/jinja.py @@ -15,6 +15,32 @@ from india_compliance.gst_india.overrides.transaction import is_inter_state_supply from india_compliance.gst_india.utils import as_ist +E_INVOICE_ITEM_FIELDS = { + "SlNo": "Sr.", + "PrdDesc": "Product Description", + "HsnCd": "HSN Code", + "Qty": "Qty", + "Unit": "UOM", + "UnitPrice": "Rate", + "Discount": "Discount", + "AssAmt": "Taxable Amount", + "GstRt": "Tax Rate", + "CesRt": "Cess Rate", + "TotItemVal": "Total", +} + +E_INVOICE_AMOUNT_FIELDS = { + "AssVal": "Taxable Value", + "CgstVal": "CGST", + "SgstVal": "SGST", + "IgstVal": "IGST", + "CesVal": "CESS", + "Discount": "Discount", + "OthChrg": "Other Charges", + "RndOffAmt": "Round Off", + "TotInvVal": "Total Value", +} + def add_spacing(string, interval): """ @@ -82,32 +108,41 @@ def get_ewaybill_barcode(ewaybill): return barcode_base64 -def get_non_zero_fields(data, fields, doc=None): - """ - Returns a list of fields with non-zero values in order of fields specified - Always return mandatory fields even if they have zero value - """ +def get_non_zero_fields(data, fields): + """Returns a list of fields with non-zero values""" if isinstance(data, dict): data = [data] - non_zero_fields = [] - mandatory_fields = ["GstRt"] - - if doc: - if is_inter_state_supply(doc): - mandatory_fields.extend(["IgstVal"]) - - else: - mandatory_fields.extend(["CgstVal", "SgstVal"]) + non_zero_fields = set() for row in data: for field in fields: - is_mandatory = field in mandatory_fields and field in row - - if ( - row.get(field, 0) != 0 or is_mandatory - ) and field not in non_zero_fields: - non_zero_fields.append(field) + if field not in non_zero_fields and row.get(field, 0) != 0: + non_zero_fields.add(field) return non_zero_fields + + +def get_fields_to_display(data, field_map, mandatory_fields=None): + fields_to_display = get_non_zero_fields(data, field_map) + if mandatory_fields: + fields_to_display.update(mandatory_fields) + + return { + field: label for field, label in field_map.items() if field in fields_to_display + } + + +def get_e_invoice_item_fields(data): + return get_fields_to_display(data, E_INVOICE_ITEM_FIELDS, {"GstRt"}) + + +def get_e_invoice_amount_fields(data, doc): + mandatory_fields = set() + if is_inter_state_supply(doc): + mandatory_fields.add("IgstVal") + else: + mandatory_fields.update(("CgstVal", "SgstVal")) + + return get_fields_to_display(data, E_INVOICE_AMOUNT_FIELDS, mandatory_fields) diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index c81758749..071acf88a 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -159,7 +159,8 @@ "india_compliance.gst_india.utils.jinja.get_transport_type", "india_compliance.gst_india.utils.jinja.get_transport_mode", "india_compliance.gst_india.utils.jinja.get_ewaybill_barcode", - "india_compliance.gst_india.utils.jinja.get_non_zero_fields", + "india_compliance.gst_india.utils.jinja.get_e_invoice_item_fields", + "india_compliance.gst_india.utils.jinja.get_e_invoice_amount_fields", ], } From cf5875abf1e347576953641bb72729c70ff544f8 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 4 Mar 2023 14:11:25 +0530 Subject: [PATCH 53/58] chore: improve column adding logic in GST sales reports (cherry picked from commit 4fb9c36bd4f9f47fec93399d3491f04f2bd79eba) --- .../gst_itemised_sales_register.py | 47 +++---- .../gst_sales_register/gst_sales_register.py | 118 +++++++++--------- 2 files changed, 79 insertions(+), 86 deletions(-) diff --git a/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py b/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py index cf0afd9b7..bac1ccfc9 100644 --- a/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py +++ b/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py @@ -1,41 +1,30 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -import frappe + +from frappe import _ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import ( _execute, ) from india_compliance.gst_india.report.gst_sales_register.gst_sales_register import ( - ADDITIONAL_QUERY_COLUMNS, - ADDITIONAL_TABLE_COLUMNS, - EXPORT_TYPE_COLUMNS, - REVERSE_CHARGE_COLUMNS, + get_additional_table_columns, + get_column_names, ) +HSN_CODE_COLUMN = { + "fieldtype": "Data", + "label": _("HSN Code"), + "fieldname": "gst_hsn_code", + "width": 120, +} -def execute(filters=None): - overseas_enabled, reverse_charge_enabled = frappe.get_cached_value( - "GST Settings", - "GST Settings", - ("enable_overseas_transactions", "enable_reverse_charge_in_sales"), - ) - - additional_table_columns = [ - *ADDITIONAL_TABLE_COLUMNS, - dict(fieldtype="Data", label="HSN Code", fieldname="gst_hsn_code", width=120), - ] - - additional_query_columns = [ - *ADDITIONAL_QUERY_COLUMNS, - "gst_hsn_code", - ] - - if reverse_charge_enabled: - additional_table_columns.insert(3, REVERSE_CHARGE_COLUMNS) - additional_query_columns.insert(3, "is_reverse_charge") - if overseas_enabled: - additional_table_columns.insert(-3, EXPORT_TYPE_COLUMNS) - additional_query_columns.insert(-3, "is_export_with_gst") +def execute(filters=None): + additional_table_columns = get_additional_table_columns() + additional_table_columns.append(HSN_CODE_COLUMN) - return _execute(filters, additional_table_columns, additional_query_columns) + return _execute( + filters, + additional_table_columns, + get_column_names(additional_table_columns), + ) diff --git a/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py b/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py index 89b46875d..376ed71a2 100644 --- a/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py +++ b/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py @@ -1,65 +1,58 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt import frappe +from frappe import _ from erpnext.accounts.report.sales_register.sales_register import _execute -EXPORT_TYPE_COLUMNS = dict( - fieldtype="Check", - label="Is Export With GST", - fieldname="is_export_with_gst", - width=120, -) +EXPORT_TYPE_COLUMN = { + "fieldtype": "Check", + "label": _("Is Export With GST"), + "fieldname": "is_export_with_gst", + "width": 120, +} -REVERSE_CHARGE_COLUMNS = dict( - fieldtype="Check", - label="Is Reverse Charge", - fieldname="is_reverse_charge", - width=120, -) +REVERSE_CHARGE_COLUMN = { + "fieldtype": "Check", + "label": _("Is Reverse Charge"), + "fieldname": "is_reverse_charge", + "width": 120, +} ADDITIONAL_TABLE_COLUMNS = [ - dict( - fieldtype="Data", - label="Billing Address GSTIN", - fieldname="billing_address_gstin", - width=140, - ), - dict( - fieldtype="Data", - label="Company GSTIN", - fieldname="company_gstin", - width=120, - ), - dict( - fieldtype="Data", - label="Place of Supply", - fieldname="place_of_supply", - width=120, - ), - dict( - fieldtype="Data", - label="GST Category", - fieldname="gst_category", - width=120, - ), - dict( - fieldtype="Data", - label="E-Commerce GSTIN", - fieldname="ecommerce_gstin", - width=130, - ), -] - -ADDITIONAL_QUERY_COLUMNS = [ - "billing_address_gstin", - "company_gstin", - "place_of_supply", - "gst_category", - "ecommerce_gstin", + { + "fieldtype": "Data", + "label": _("Billing Address GSTIN"), + "fieldname": "billing_address_gstin", + "width": 140, + }, + { + "fieldtype": "Data", + "label": _("Company GSTIN"), + "fieldname": "company_gstin", + "width": 120, + }, + { + "fieldtype": "Data", + "label": _("Place of Supply"), + "fieldname": "place_of_supply", + "width": 120, + }, + { + "fieldtype": "Data", + "label": _("GST Category"), + "fieldname": "gst_category", + "width": 120, + }, + { + "fieldtype": "Data", + "label": _("E-Commerce GSTIN"), + "fieldname": "ecommerce_gstin", + "width": 130, + }, ] -def execute(filters=None): +def get_additional_table_columns(): overseas_enabled, reverse_charge_enabled = frappe.get_cached_value( "GST Settings", "GST Settings", @@ -67,14 +60,25 @@ def execute(filters=None): ) additional_table_columns = ADDITIONAL_TABLE_COLUMNS.copy() - additional_query_columns = ADDITIONAL_QUERY_COLUMNS.copy() if reverse_charge_enabled: - additional_table_columns.insert(3, REVERSE_CHARGE_COLUMNS) - additional_query_columns.insert(3, "is_reverse_charge") + additional_table_columns.insert(-2, REVERSE_CHARGE_COLUMN) if overseas_enabled: - additional_table_columns.insert(-2, EXPORT_TYPE_COLUMNS) - additional_query_columns.insert(-2, "is_export_with_gst") + additional_table_columns.insert(-2, EXPORT_TYPE_COLUMN) + + return additional_table_columns + - return _execute(filters, additional_table_columns, additional_query_columns) +def get_column_names(additional_table_columns): + return [column["fieldname"] for column in additional_table_columns] + + +def execute(filters=None): + additional_table_columns = get_additional_table_columns() + + return _execute( + filters, + additional_table_columns, + get_column_names(additional_table_columns), + ) From e0ee2152b56733f9a04e08fb12b4a5036ce0ce68 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 4 Mar 2023 14:25:53 +0530 Subject: [PATCH 54/58] chore: better function naming (cherry picked from commit 07d23f6e45c44e4ebd3e3420c5125facb979bb1e) --- india_compliance/gst_india/utils/e_waybill.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 1cbaf06e2..dd595e369 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -478,7 +478,7 @@ def get_data(self, *, with_irn=False): self.validate_transaction() self.set_transporter_details() self.set_party_address_details() - self.validate_distance() + self.update_distance_if_zero() if with_irn: return self.sanitize_data( @@ -773,12 +773,13 @@ def get_address_details(self, *args, **kwargs): return address_details - def validate_distance(self): + def update_distance_if_zero(self): """ e-Waybill portal doesn't return distance where from and to pincode is same. Hardcode distance to 1 km to simplify and automate this. Accuracy of distance is immaterial and used only for e-Waybill validity determination. """ + if ( self.transaction_details.distance == 0 and self.dispatch_address.pincode == self.shipping_address.pincode From 290acde7779a235ab284d3a322ba8f680f179331 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 4 Mar 2023 14:33:50 +0530 Subject: [PATCH 55/58] fix: translate inside function to respect multitenancy (cherry picked from commit 7f2e9787dcb3d830203566169b6fc0a264bb8f2b) --- .../gst_itemised_sales_register.py | 16 +-- .../gst_sales_register/gst_sales_register.py | 100 +++++++++--------- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py b/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py index bac1ccfc9..b05c3d53d 100644 --- a/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py +++ b/india_compliance/gst_india/report/gst_itemised_sales_register/gst_itemised_sales_register.py @@ -11,17 +11,17 @@ get_column_names, ) -HSN_CODE_COLUMN = { - "fieldtype": "Data", - "label": _("HSN Code"), - "fieldname": "gst_hsn_code", - "width": 120, -} - def execute(filters=None): additional_table_columns = get_additional_table_columns() - additional_table_columns.append(HSN_CODE_COLUMN) + additional_table_columns.append( + { + "fieldtype": "Data", + "label": _("HSN Code"), + "fieldname": "gst_hsn_code", + "width": 120, + } + ) return _execute( filters, diff --git a/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py b/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py index 376ed71a2..45589c67d 100644 --- a/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py +++ b/india_compliance/gst_india/report/gst_sales_register/gst_sales_register.py @@ -4,53 +4,6 @@ from frappe import _ from erpnext.accounts.report.sales_register.sales_register import _execute -EXPORT_TYPE_COLUMN = { - "fieldtype": "Check", - "label": _("Is Export With GST"), - "fieldname": "is_export_with_gst", - "width": 120, -} - -REVERSE_CHARGE_COLUMN = { - "fieldtype": "Check", - "label": _("Is Reverse Charge"), - "fieldname": "is_reverse_charge", - "width": 120, -} - -ADDITIONAL_TABLE_COLUMNS = [ - { - "fieldtype": "Data", - "label": _("Billing Address GSTIN"), - "fieldname": "billing_address_gstin", - "width": 140, - }, - { - "fieldtype": "Data", - "label": _("Company GSTIN"), - "fieldname": "company_gstin", - "width": 120, - }, - { - "fieldtype": "Data", - "label": _("Place of Supply"), - "fieldname": "place_of_supply", - "width": 120, - }, - { - "fieldtype": "Data", - "label": _("GST Category"), - "fieldname": "gst_category", - "width": 120, - }, - { - "fieldtype": "Data", - "label": _("E-Commerce GSTIN"), - "fieldname": "ecommerce_gstin", - "width": 130, - }, -] - def get_additional_table_columns(): overseas_enabled, reverse_charge_enabled = frappe.get_cached_value( @@ -59,13 +12,60 @@ def get_additional_table_columns(): ("enable_overseas_transactions", "enable_reverse_charge_in_sales"), ) - additional_table_columns = ADDITIONAL_TABLE_COLUMNS.copy() + additional_table_columns = [ + { + "fieldtype": "Data", + "label": _("Billing Address GSTIN"), + "fieldname": "billing_address_gstin", + "width": 140, + }, + { + "fieldtype": "Data", + "label": _("Company GSTIN"), + "fieldname": "company_gstin", + "width": 120, + }, + { + "fieldtype": "Data", + "label": _("Place of Supply"), + "fieldname": "place_of_supply", + "width": 120, + }, + { + "fieldtype": "Data", + "label": _("GST Category"), + "fieldname": "gst_category", + "width": 120, + }, + { + "fieldtype": "Data", + "label": _("E-Commerce GSTIN"), + "fieldname": "ecommerce_gstin", + "width": 130, + }, + ] if reverse_charge_enabled: - additional_table_columns.insert(-2, REVERSE_CHARGE_COLUMN) + additional_table_columns.insert( + -2, + { + "fieldtype": "Check", + "label": _("Is Reverse Charge"), + "fieldname": "is_reverse_charge", + "width": 120, + }, + ) if overseas_enabled: - additional_table_columns.insert(-2, EXPORT_TYPE_COLUMN) + additional_table_columns.insert( + -2, + { + "fieldtype": "Check", + "label": _("Is Export With GST"), + "fieldname": "is_export_with_gst", + "width": 120, + }, + ) return additional_table_columns From 2826e25cb72d93052e880a5aa41bd35e1935fc9e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 4 Mar 2023 16:57:05 +0530 Subject: [PATCH 56/58] refactor: separate modules for uninstall and custom field utils (cherry picked from commit 089204ab08fc37aa870a0bd59a900d3ad2d093c9) --- .../doctype/gst_settings/gst_settings.py | 4 +- india_compliance/gst_india/setup/__init__.py | 64 ++++------------ india_compliance/gst_india/uninstall.py | 36 +++++++++ india_compliance/gst_india/utils/__init__.py | 47 ------------ .../gst_india/utils/custom_fields.py | 74 +++++++++++++++++++ india_compliance/income_tax_india/setup.py | 9 +-- .../income_tax_india/uninstall.py | 6 ++ ...rate_e_invoice_settings_to_gst_settings.py | 8 +- .../patches/post_install/remove_old_fields.py | 2 +- .../post_install/set_default_gst_settings.py | 2 +- .../patches/post_install/set_gst_category.py | 2 +- .../setup_custom_fields_for_gst.py | 2 +- .../update_e_invoice_fields_and_logs.py | 30 +------- .../update_reverse_charge_and_export_type.py | 2 +- ...e_ecommerce_gstin_from_purchase_invoice.py | 2 +- india_compliance/uninstall.py | 14 ++-- 16 files changed, 152 insertions(+), 152 deletions(-) create mode 100644 india_compliance/gst_india/uninstall.py create mode 100644 india_compliance/gst_india/utils/custom_fields.py create mode 100644 india_compliance/income_tax_india/uninstall.py 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 1b9107175..46336c4ad 100644 --- a/india_compliance/gst_india/doctype/gst_settings/gst_settings.py +++ b/india_compliance/gst_india/doctype/gst_settings/gst_settings.py @@ -16,7 +16,8 @@ _disable_api_promo, post_login, ) -from india_compliance.gst_india.utils import can_enable_api, toggle_custom_fields +from india_compliance.gst_india.utils import can_enable_api +from india_compliance.gst_india.utils.custom_fields import toggle_custom_fields class GSTSettings(Document): @@ -56,7 +57,6 @@ def validate_gst_accounts(self): company_wise_account_types = {} for row in self.gst_accounts: - # Validate Duplicate Accounts for fieldname in GST_ACCOUNT_FIELDS: account = row.get(fieldname) diff --git a/india_compliance/gst_india/setup/__init__.py b/india_compliance/gst_india/setup/__init__.py index d1c146d17..4d339d626 100644 --- a/india_compliance/gst_india/setup/__init__.py +++ b/india_compliance/gst_india/setup/__init__.py @@ -13,10 +13,8 @@ SALES_REVERSE_CHARGE_FIELDS, ) from india_compliance.gst_india.setup.property_setters import get_property_setters -from india_compliance.gst_india.utils import get_data_file_path, toggle_custom_fields -from india_compliance.patches.post_install.update_e_invoice_fields_and_logs import ( - delete_custom_fields as _delete_custom_fields, -) +from india_compliance.gst_india.utils import get_data_file_path +from india_compliance.gst_india.utils.custom_fields import toggle_custom_fields ITEM_VARIANT_FIELDNAMES = frozenset(("gst_hsn_code", "is_nil_exempt", "is_non_gst")) @@ -31,25 +29,11 @@ def after_install(): add_fields_to_item_variant_settings() -def before_uninstall(): - delete_custom_fields() - delete_property_setters() - remove_fields_from_item_variant_settings() - - def create_custom_fields(): # Validation ignored for faster creation # Will not fail if a core field with same name already exists (!) # Will update a custom field if it already exists - _create_custom_fields( - _get_custom_fields_map( - CUSTOM_FIELDS, - SALES_REVERSE_CHARGE_FIELDS, - E_INVOICE_FIELDS, - E_WAYBILL_FIELDS, - ), - ignore_validate=True, - ) + _create_custom_fields(get_all_custom_fields(), ignore_validate=True) def create_property_setters(): @@ -57,25 +41,6 @@ def create_property_setters(): frappe.make_property_setter(property_setter) -def delete_custom_fields(): - _delete_custom_fields( - _get_custom_fields_map( - CUSTOM_FIELDS, - SALES_REVERSE_CHARGE_FIELDS, - E_INVOICE_FIELDS, - E_WAYBILL_FIELDS, - ) - ) - - -def delete_property_setters(): - for property_setter in get_property_setters(): - keys_to_update = ["doc_type", "field_name", "property", "value"] - # Update the keys to match with the property setter fields - filters = dict(zip(keys_to_update, list(property_setter.values()))) - frappe.db.delete("Property Setter", filters) - - def create_address_template(): if frappe.db.exists("Address Template", "India"): return @@ -224,16 +189,23 @@ def show_accounts_settings_override_warning(): ) click.secho( - "This is being set as Billing Address, since that's the correct " - "address for determining GST applicablility.", + ( + "This is being set as Billing Address, since that's the correct " + "address for determining GST applicablility." + ), fg="yellow", ) -def _get_custom_fields_map(*custom_fields_list): +def get_all_custom_fields(): result = {} - for custom_fields in custom_fields_list: + for custom_fields in ( + CUSTOM_FIELDS, + SALES_REVERSE_CHARGE_FIELDS, + E_INVOICE_FIELDS, + E_WAYBILL_FIELDS, + ): for doctypes, fields in custom_fields.items(): if isinstance(fields, dict): fields = [fields] @@ -241,11 +213,3 @@ def _get_custom_fields_map(*custom_fields_list): result.setdefault(doctypes, []).extend(fields) return result - - -def remove_fields_from_item_variant_settings(): - settings = frappe.get_doc("Item Variant Settings") - settings.fields = [ - row for row in settings.fields if row.field_name not in ITEM_VARIANT_FIELDNAMES - ] - settings.save() diff --git a/india_compliance/gst_india/uninstall.py b/india_compliance/gst_india/uninstall.py new file mode 100644 index 000000000..f24ab940e --- /dev/null +++ b/india_compliance/gst_india/uninstall.py @@ -0,0 +1,36 @@ +import frappe + +from india_compliance.gst_india.setup import ( + ITEM_VARIANT_FIELDNAMES, + get_all_custom_fields, + get_property_setters, +) +from india_compliance.gst_india.utils.custom_fields import delete_custom_fields + + +def before_uninstall(): + delete_custom_fields(get_all_custom_fields()) + delete_property_setters() + remove_fields_from_item_variant_settings() + + +def delete_property_setters(): + field_map = { + "doctype": "doc_type", + "fieldname": "field_name", + } + + for property_setter in get_property_setters(): + for key, fieldname in field_map.items(): + if key in property_setter: + property_setter[fieldname] = property_setter.pop(key) + + frappe.db.delete("Property Setter", property_setter) + + +def remove_fields_from_item_variant_settings(): + settings = frappe.get_doc("Item Variant Settings") + settings.fields = [ + row for row in settings.fields if row.field_name not in ITEM_VARIANT_FIELDNAMES + ] + settings.save() diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index c75cf8c78..14bb0e1d5 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -318,37 +318,6 @@ def get_all_gst_accounts(company): return accounts_list -def toggle_custom_fields(custom_fields, show): - """ - Show / hide custom fields - - :param custom_fields: a dict like `{'Sales Invoice': [{fieldname: 'test', ...}]}` - :param show: True to show fields, False to hide - """ - - for doctypes, fields in custom_fields.items(): - if isinstance(fields, dict): - # only one field - fields = [fields] - - if isinstance(doctypes, str): - # only one doctype - doctypes = (doctypes,) - - for doctype in doctypes: - frappe.db.set_value( - "Custom Field", - { - "dt": doctype, - "fieldname": ["in", [field["fieldname"] for field in fields]], - }, - "hidden", - int(not show), - ) - - frappe.clear_cache(doctype=doctype) - - def parse_datetime(value, day_first=False): """Convert IST string to offset-naive system time""" @@ -407,22 +376,6 @@ def get_titlecase_version(word, all_caps=False, **kwargs): return word -def delete_old_fields(fields, doctypes): - if isinstance(fields, str): - fields = (fields,) - - if isinstance(doctypes, str): - doctypes = (doctypes,) - - frappe.db.delete( - "Custom Field", - { - "fieldname": ("in", fields), - "dt": ("in", doctypes), - }, - ) - - def is_api_enabled(settings=None): if not settings: settings = frappe.get_cached_value( diff --git a/india_compliance/gst_india/utils/custom_fields.py b/india_compliance/gst_india/utils/custom_fields.py new file mode 100644 index 000000000..26adba3e7 --- /dev/null +++ b/india_compliance/gst_india/utils/custom_fields.py @@ -0,0 +1,74 @@ +import frappe + + +def toggle_custom_fields(custom_fields, show): + """ + Show / hide custom fields + + :param custom_fields: a dict like `{'Sales Invoice': [{fieldname: 'test', ...}]}` + :param show: True to show fields, False to hide + """ + + for doctypes, fields in custom_fields.items(): + if isinstance(fields, dict): + # only one field + fields = [fields] + + if isinstance(doctypes, str): + # only one doctype + doctypes = (doctypes,) + + for doctype in doctypes: + frappe.db.set_value( + "Custom Field", + { + "dt": doctype, + "fieldname": ["in", [field["fieldname"] for field in fields]], + }, + "hidden", + int(not show), + ) + + frappe.clear_cache(doctype=doctype) + + +def delete_old_fields(fieldnames, doctypes): + if isinstance(fieldnames, str): + fields = (fieldnames,) + + if isinstance(doctypes, str): + doctypes = (doctypes,) + + frappe.db.delete( + "Custom Field", + { + "fieldname": ("in", fields), + "dt": ("in", doctypes), + }, + ) + + +def delete_custom_fields(custom_fields): + """ + :param custom_fields: a dict like `{'Sales Invoice': [{fieldname: 'test', ...}]}` + """ + + for doctypes, fields in custom_fields.items(): + if isinstance(fields, dict): + # only one field + fields = [fields] + + if isinstance(doctypes, str): + # only one doctype + doctypes = (doctypes,) + + for doctype in doctypes: + frappe.db.delete( + "Custom Field", + { + "fieldname": ("in", [field["fieldname"] for field in fields]), + "dt": doctype, + }, + ) + + frappe.clear_cache(doctype=doctype) diff --git a/india_compliance/income_tax_india/setup.py b/india_compliance/income_tax_india/setup.py index 66eed0fe4..3ca11115c 100644 --- a/india_compliance/income_tax_india/setup.py +++ b/india_compliance/income_tax_india/setup.py @@ -1,14 +1,7 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from india_compliance.income_tax_india.constants.custom_fields import CUSTOM_FIELDS -from india_compliance.patches.post_install.update_e_invoice_fields_and_logs import ( - delete_custom_fields, -) def after_install(): - create_custom_fields(CUSTOM_FIELDS, update=True) - - -def before_uninstall(): - delete_custom_fields(CUSTOM_FIELDS) + create_custom_fields(CUSTOM_FIELDS, ignore_validate=True) diff --git a/india_compliance/income_tax_india/uninstall.py b/india_compliance/income_tax_india/uninstall.py new file mode 100644 index 000000000..230852591 --- /dev/null +++ b/india_compliance/income_tax_india/uninstall.py @@ -0,0 +1,6 @@ +from india_compliance.gst_india.utils.custom_fields import delete_custom_fields +from india_compliance.income_tax_india.constants.custom_fields import CUSTOM_FIELDS + + +def before_uninstall(): + delete_custom_fields(CUSTOM_FIELDS) diff --git a/india_compliance/patches/post_install/migrate_e_invoice_settings_to_gst_settings.py b/india_compliance/patches/post_install/migrate_e_invoice_settings_to_gst_settings.py index a000b44ca..244fe7f3d 100644 --- a/india_compliance/patches/post_install/migrate_e_invoice_settings_to_gst_settings.py +++ b/india_compliance/patches/post_install/migrate_e_invoice_settings_to_gst_settings.py @@ -5,7 +5,7 @@ from frappe.utils.password import decrypt from india_compliance.gst_india.constants.custom_fields import E_INVOICE_FIELDS -from india_compliance.gst_india.utils import toggle_custom_fields +from india_compliance.gst_india.utils.custom_fields import toggle_custom_fields def execute(): @@ -39,8 +39,10 @@ def execute(): if sbool(old_settings.enable): toggle_custom_fields(E_INVOICE_FIELDS, True) click.secho( - "Your e-Invoice Settings have been migrated to GST Settings." - " Please enable the e-Invoice API in GST Settings manually.\n", + ( + "Your e-Invoice Settings have been migrated to GST Settings." + " Please enable the e-Invoice API in GST Settings manually.\n" + ), fg="yellow", ) diff --git a/india_compliance/patches/post_install/remove_old_fields.py b/india_compliance/patches/post_install/remove_old_fields.py index d3d31af7a..25ff2c237 100644 --- a/india_compliance/patches/post_install/remove_old_fields.py +++ b/india_compliance/patches/post_install/remove_old_fields.py @@ -1,4 +1,4 @@ -from india_compliance.gst_india.utils import delete_old_fields +from india_compliance.gst_india.utils.custom_fields import delete_old_fields def execute(): 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 de094e1f0..6c287aa22 100644 --- a/india_compliance/patches/post_install/set_default_gst_settings.py +++ b/india_compliance/patches/post_install/set_default_gst_settings.py @@ -4,7 +4,7 @@ from india_compliance.gst_india.constants.custom_fields import ( SALES_REVERSE_CHARGE_FIELDS, ) -from india_compliance.gst_india.utils import toggle_custom_fields +from india_compliance.gst_india.utils.custom_fields import toggle_custom_fields # Enable setting only if transaction exists in last 3 years. POSTING_DATE_CONDITION = { diff --git a/india_compliance/patches/post_install/set_gst_category.py b/india_compliance/patches/post_install/set_gst_category.py index f0fb5d45a..50440d7d9 100644 --- a/india_compliance/patches/post_install/set_gst_category.py +++ b/india_compliance/patches/post_install/set_gst_category.py @@ -1,6 +1,6 @@ import frappe -from india_compliance.gst_india.utils import delete_old_fields +from india_compliance.gst_india.utils.custom_fields import delete_old_fields def execute(): diff --git a/india_compliance/patches/post_install/setup_custom_fields_for_gst.py b/india_compliance/patches/post_install/setup_custom_fields_for_gst.py index 9f5884560..51d4db1f7 100644 --- a/india_compliance/patches/post_install/setup_custom_fields_for_gst.py +++ b/india_compliance/patches/post_install/setup_custom_fields_for_gst.py @@ -1,6 +1,6 @@ import frappe -from india_compliance.gst_india.utils import delete_old_fields +from india_compliance.gst_india.utils.custom_fields import delete_old_fields def execute(): diff --git a/india_compliance/patches/post_install/update_e_invoice_fields_and_logs.py b/india_compliance/patches/post_install/update_e_invoice_fields_and_logs.py index 97c9b57cb..cea3efb95 100644 --- a/india_compliance/patches/post_install/update_e_invoice_fields_and_logs.py +++ b/india_compliance/patches/post_install/update_e_invoice_fields_and_logs.py @@ -1,6 +1,7 @@ import frappe from india_compliance.gst_india.utils import parse_datetime +from india_compliance.gst_india.utils.custom_fields import delete_custom_fields user = None @@ -317,32 +318,3 @@ def delete_e_invoice_fields(): ] } delete_custom_fields(FIELDS_TO_DELETE) - - -### Helper Function - - -def delete_custom_fields(custom_fields): - """ - :param custom_fields: a dict like `{'Sales Invoice': [{fieldname: 'test', ...}]}` - """ - - for doctypes, fields in custom_fields.items(): - if isinstance(fields, dict): - # only one field - fields = [fields] - - if isinstance(doctypes, str): - # only one doctype - doctypes = (doctypes,) - - for doctype in doctypes: - frappe.db.delete( - "Custom Field", - { - "fieldname": ("in", [field["fieldname"] for field in fields]), - "dt": doctype, - }, - ) - - frappe.clear_cache(doctype=doctype) diff --git a/india_compliance/patches/post_install/update_reverse_charge_and_export_type.py b/india_compliance/patches/post_install/update_reverse_charge_and_export_type.py index 33764deab..c3174bcb0 100644 --- a/india_compliance/patches/post_install/update_reverse_charge_and_export_type.py +++ b/india_compliance/patches/post_install/update_reverse_charge_and_export_type.py @@ -1,6 +1,6 @@ import frappe -from india_compliance.gst_india.utils import delete_old_fields +from india_compliance.gst_india.utils.custom_fields import delete_old_fields DOCTYPES = ("Purchase Invoice", "Sales Invoice") diff --git a/india_compliance/patches/v14/remove_ecommerce_gstin_from_purchase_invoice.py b/india_compliance/patches/v14/remove_ecommerce_gstin_from_purchase_invoice.py index eb3538ea1..95b7b2b5c 100644 --- a/india_compliance/patches/v14/remove_ecommerce_gstin_from_purchase_invoice.py +++ b/india_compliance/patches/v14/remove_ecommerce_gstin_from_purchase_invoice.py @@ -1,4 +1,4 @@ -from india_compliance.gst_india.utils import delete_old_fields +from india_compliance.gst_india.utils.custom_fields import delete_old_fields def execute(): diff --git a/india_compliance/uninstall.py b/india_compliance/uninstall.py index 44e8ebf52..bf35019db 100644 --- a/india_compliance/uninstall.py +++ b/india_compliance/uninstall.py @@ -1,8 +1,8 @@ import click from india_compliance.gst_india.constants import BUG_REPORT_URL -from india_compliance.gst_india.setup import before_uninstall as remove_gst -from india_compliance.income_tax_india.setup import ( +from india_compliance.gst_india.uninstall import before_uninstall as remove_gst +from india_compliance.income_tax_india.uninstall import ( before_uninstall as remove_income_tax, ) @@ -17,11 +17,11 @@ def before_uninstall(): except Exception as e: click.secho( - "Removing Customizations for India Compliance failed due to an error." - " Please try again or" - f" report the issue on {BUG_REPORT_URL} if not resolved.", + ( + "Removing customizations for India Compliance failed due to an error." + " Please try again or" + f" report the issue on {BUG_REPORT_URL} if not resolved." + ), fg="bright_red", ) raise e - - click.secho("Customizations has been removed Successfully...", fg="green") From 230eb3ea1f72e4eacb99ca76bfca0c3f7e4c1d17 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 4 Mar 2023 18:43:36 +0530 Subject: [PATCH 57/58] fix: rounding and other issues in GSTR-1 related reports (cherry picked from commit 9ffc1072223f47bc899fd5b35a7f3dbfb689271f) --- .../gst_india/report/gstr_1/gstr_1.py | 14 ++-- .../hsn_wise_summary_of_outward_supplies.py | 72 ++++++++++--------- 2 files changed, 47 insertions(+), 39 deletions(-) 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 4459ddc87..46199eb3c 100644 --- a/india_compliance/gst_india/report/gstr_1/gstr_1.py +++ b/india_compliance/gst_india/report/gstr_1/gstr_1.py @@ -1090,20 +1090,20 @@ def get_advances_json(data, gstin): for item in items: itms = { "rt": item["rate"], - "ad_amount": flt(item.get("taxable_value")), - "csamt": flt(item.get("cess_amount")), + "ad_amount": flt(item.get("taxable_value"), 2), + "csamt": flt(item.get("cess_amount"), 2), } if supply_type == "INTRA": itms.update( { - "samt": flt((itms["ad_amount"] * itms["rt"]) / 100), - "camt": flt((itms["ad_amount"] * itms["rt"]) / 100), + "samt": flt((itms["ad_amount"] * itms["rt"]) / 100, 2), + "camt": flt((itms["ad_amount"] * itms["rt"]) / 100, 2), "rt": itms["rt"] * 2, } ) else: - itms.update({"iamt": flt((itms["ad_amount"] * itms["rt"]) / 100)}) + itms["iamt"] = flt((itms["ad_amount"] * itms["rt"]) / 100, 2) row["itms"].append(itms) out.append(row) @@ -1193,7 +1193,7 @@ def get_cdnr_reg_json(res, gstin): inv_item = { "nt_num": invoice[0]["invoice_number"], "nt_dt": getdate(invoice[0]["posting_date"]).strftime("%d-%m-%Y"), - "val": abs(flt(invoice[0]["invoice_value"])), + "val": abs(flt(invoice[0]["invoice_value"], 2)), "ntty": invoice[0]["document_type"], "pos": "%02d" % int(invoice[0]["place_of_supply"].split("-")[0]), "rchrg": invoice[0]["is_reverse_charge"], @@ -1221,7 +1221,7 @@ def get_cdnr_unreg_json(res, gstin): inv_item = { "nt_num": items[0]["invoice_number"], "nt_dt": getdate(items[0]["posting_date"]).strftime("%d-%m-%Y"), - "val": abs(flt(items[0]["invoice_value"])), + "val": abs(flt(items[0]["invoice_value"], 2)), "ntty": items[0]["document_type"], "pos": "%02d" % int(items[0]["place_of_supply"].split("-")[0]), "typ": get_invoice_type(items[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 63a7186e6..20028bac9 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 @@ -16,12 +16,9 @@ def execute(filters=None): - return _execute(filters) - - -def _execute(filters=None): if not filters: filters = {} + columns = get_columns() output_gst_accounts = [ @@ -40,25 +37,39 @@ def _execute(filters=None): data = [] added_item = [] for d in item_list: - if (d.parent, d.gst_hsn_code, d.item_code) not in added_item: - row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty] - total_tax = 0 - tax_rate = 0 - for tax in tax_columns: - item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {}) - tax_rate += flt(item_tax.get("tax_rate", 0)) - total_tax += flt(item_tax.get("tax_amount", 0)) - - row += [tax_rate, d.taxable_value + total_tax, d.taxable_value] - - for tax in tax_columns: - item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {}) - row += [item_tax.get("tax_amount", 0)] - - data.append(row) - added_item.append((d.parent, d.gst_hsn_code, d.item_code)) + if (d.parent, d.gst_hsn_code, d.item_code) in added_item: + continue + + if d.gst_hsn_code.startswith("99"): + # service item doesnt have qty / uom + d.stock_qty = 0 + d.uqc = "NA" + + else: + d.uqc = d.get("uqc", "").upper() + if d.uqc not in UOMS: + d.uqc = "OTH" + + row = [d.gst_hsn_code, d.description, d.uqc, d.stock_qty] + total_tax = 0 + tax_rate = 0 + for tax in tax_columns: + item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {}) + tax_rate += flt(item_tax.get("tax_rate", 0)) + total_tax += flt(item_tax.get("tax_amount", 0)) + + row += [tax_rate, d.taxable_value + total_tax, d.taxable_value] + + for tax in tax_columns: + item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {}) + row += [item_tax.get("tax_amount", 0)] + + data.append(row) + added_item.append((d.parent, d.gst_hsn_code, d.item_code)) + if data: data = get_merged_data(columns, data) # merge same hsn code data + return columns, data @@ -78,8 +89,8 @@ def get_columns(): "width": 300, }, { - "fieldname": "stock_uom", - "label": _("Stock UOM"), + "fieldname": "uqc", + "label": _("UQC"), "fieldtype": "Data", "width": 100, }, @@ -138,7 +149,7 @@ def get_items(filters): f""" SELECT `tabSales Invoice Item`.gst_hsn_code, - `tabSales Invoice Item`.stock_uom, + `tabSales Invoice Item`.stock_uom as uqc, sum(`tabSales Invoice Item`.stock_qty) AS stock_qty, sum(`tabSales Invoice Item`.taxable_value) AS taxable_value, sum(`tabSales Invoice Item`.base_price_list_rate) AS base_price_list_rate, @@ -308,31 +319,28 @@ def download_json_file(): def get_hsn_wise_json_data(filters, report_data): - filters = frappe._dict(filters) gst_accounts = get_gst_accounts_by_type(filters.company, "Output") data = [] count = 1 for hsn in report_data: - uom = hsn.get("stock_uom", "").upper() - if uom not in UOMS: - uom = "OTH" - row = { "num": count, "hsn_sc": hsn.get("gst_hsn_code"), - "desc": hsn.get("description")[:30], - "uqc": uom, + "uqc": hsn.get("uqc"), "qty": hsn.get("stock_qty"), "rt": flt(hsn.get("tax_rate"), 2), - "txval": flt(hsn.get("taxable_amount", 2)), + "txval": flt(hsn.get("taxable_amount"), 2), "iamt": 0.0, "camt": 0.0, "samt": 0.0, "csamt": 0.0, } + if hsn_description := hsn.get("description"): + row["desc"] = hsn_description[:30] + row["iamt"] += flt( hsn.get(frappe.scrub(cstr(gst_accounts.get("igst_account"))), 0.0), 2 ) From 4ee6574257333b716cd2d55499f08f5ab860ced1 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 6 Mar 2023 01:41:21 +0530 Subject: [PATCH 58/58] fix: improved definition and logic of `sanitize_value` (cherry picked from commit 88d8693f142412ea9ed271f44f6090ac04bfcf16) --- india_compliance/gst_india/utils/e_waybill.py | 2 +- .../gst_india/utils/transaction_data.py | 89 +++++++++++++------ 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index dd595e369..c48b22423 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -529,7 +529,7 @@ def get_update_vehicle_data(self, values): "fromPlace": dispatch_address.city, "fromState": dispatch_address.state_number, "reasonCode": UPDATE_VEHICLE_REASON_CODES[values.reason], - "reasonRem": self.sanitize_value(values.remark, 3), + "reasonRem": self.sanitize_value(values.remark, regex=3), "transDocNo": self.transaction_details.lr_no, "transDocDate": self.transaction_details.lr_date, "transMode": self.transaction_details.mode_of_transport, diff --git a/india_compliance/gst_india/utils/transaction_data.py b/india_compliance/gst_india/utils/transaction_data.py index 01facb159..ed6de7a1c 100644 --- a/india_compliance/gst_india/utils/transaction_data.py +++ b/india_compliance/gst_india/utils/transaction_data.py @@ -2,7 +2,7 @@ import frappe from frappe import _ -from frappe.utils import format_date, get_url_to_form, getdate, rounded +from frappe.utils import format_date, get_link_to_form, getdate, rounded from india_compliance.gst_india.constants import GST_TAX_TYPES, PINCODE_FORMAT from india_compliance.gst_india.constants.e_waybill import ( @@ -148,8 +148,10 @@ def set_transporter_details(self): self.doc.mode_of_transport ), "vehicle_type": VEHICLE_TYPES.get(self.doc.gst_vehicle_type) or "R", - "vehicle_no": self.sanitize_value(self.doc.vehicle_no, 1), - "lr_no": self.sanitize_value(self.doc.lr_no, 2, max_length=15), + "vehicle_no": self.sanitize_value(self.doc.vehicle_no, regex=1), + "lr_no": self.sanitize_value( + self.doc.lr_no, regex=2, max_length=15 + ), "lr_date": ( format_date(self.doc.lr_date, self.DATE_FORMAT) if self.doc.lr_no @@ -157,7 +159,9 @@ def set_transporter_details(self): ), "gst_transporter_id": self.doc.gst_transporter_id or "", "transporter_name": ( - self.sanitize_value(self.doc.transporter_name, 3, max_length=25) + self.sanitize_value( + self.doc.transporter_name, regex=3, max_length=25 + ) if self.doc.transporter_name else "" ), @@ -213,7 +217,9 @@ def get_all_item_details(self): "qty": abs(self.rounded(row.qty, 3)), "taxable_value": abs(self.rounded(row.taxable_value)), "hsn_code": row.gst_hsn_code, - "item_name": self.sanitize_value(row.item_name, 3, max_length=300), + "item_name": self.sanitize_value( + row.item_name, regex=3, max_length=300 + ), "uom": uom if uom in UOMS else "OTH", } ) @@ -312,8 +318,8 @@ def get_address_details(self, address_name, validate_gstin=False): self.check_missing_address_fields(address, validate_gstin) error_context = { - "doctype": "Address", - "docname": address.name, + "reference_doctype": "Address", + "reference_name": address.name, } return frappe._dict( @@ -323,23 +329,23 @@ def get_address_details(self, address_name, validate_gstin=False): "address_title": self.sanitize_value( address.address_title, regex=2, - throw=True, - error_context={**error_context, "fieldname": "Address Title"}, + fieldname="address_title", + **error_context, ), "address_line1": self.sanitize_value( address.address_line1, regex=3, min_length=1, - throw=True, - error_context={**error_context, "fieldname": "Address Line 1"}, + fieldname="address_line1", + **error_context, ), "address_line2": self.sanitize_value(address.address_line2, regex=3), "city": self.sanitize_value( address.city, regex=3, max_length=50, - throw=True, - error_context={**error_context, "fieldname": "City"}, + fieldname="city", + **error_context, ), "pincode": int(address.pincode), } @@ -417,12 +423,17 @@ def sanitize_value( min_length=3, max_length=100, truncate=True, - throw=False, - error_context=None, + *, + fieldname=None, + reference_doctype=None, + reference_name=None, ): """ Sanitize value to make it suitable for GST JSON sent for e-Waybill and e-Invoice. + If fieldname, reference doctype and reference name are present, + error will be thrown for invalid values instead of sanitizing them. + Parameters: ---------- @param value: Value to be sanitized @@ -430,38 +441,58 @@ def sanitize_value( @param min_length (default: 3): Minimum length of the value that is acceptable @param max_length (default: 100): Maximum length of the value that is acceptable @param truncate (default: True): Truncate the value if it exceeds max_length - @param throw (default: False): Throw an exception if the value is not acceptable. Used for mandatory fields. - @param error_context: Context to be used in the error message to help the user identify the field - example: error_context = {"fieldname": "Address Line 1", "doctype": "Address" , "docname": "Office Address"} + @param fieldname: Fieldname for which the value is being sanitized + @param reference_doctype: Doctype of the document that contains the field + @param reference_name: Name of the document that contains the field Returns: ---------- @return: Sanitized value """ - throw = throw and error_context - def _throw(message): - if not throw: + def _throw(message, **format_args): + if not (fieldname and reference_doctype and reference_name): return - url = get_url_to_form(error_context["doctype"], error_context["docname"]) + message = message.format( + field=_(frappe.get_meta(reference_doctype).get_label(fieldname)), + **format_args, + ) + frappe.throw( - _( - "{fieldname} {message} for {doctype} - {docname}" - ).format(**error_context, message=message, url=url), - title=_("Invalid Data"), + _("{reference_doctype} {reference_link}: {message}").format( + reference_doctype=_(reference_doctype), + reference_link=frappe.bold( + get_link_to_form(reference_doctype, reference_name) + ), + message=message, + ), + title=_("Invalid Data for GST Upload"), ) if not value or len(value) < min_length: - return _throw(f"must be at least {min_length} characters long") + return _throw( + _("{field} must be at least {min_length} characters long"), + min_length=min_length, + ) - if not value.isascii(): - return _throw("must be ASCII characters only") + original_value = value if regex: value = re.sub(REGEX_MAP[regex], "", value) + if len(value) < min_length: + if not original_value.isascii(): + return _throw(_("{field} must only consist of ASCII characters")) + + return _throw( + _("{field} consists of invalid characters: {invalid_chars}"), + invalid_chars=frappe.bold( + "".join(set(original_value).difference(value)) + ), + ) + if not truncate and len(value) > max_length: return
{{ AMOUNT_FIELDS_MAP[field] }}{{ label }}