Skip to content

Commit

Permalink
Merge pull request #2478 from resilient-tech/mergify/bp/version-15-ho…
Browse files Browse the repository at this point in the history
…tfix/pr-2452

fix: Handle error 2283 while generating e-Invoice (backport #2452)
  • Loading branch information
mergify[bot] authored Jul 26, 2024
2 parents 3f5f599 + e171ff1 commit cf2165a
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 34 deletions.
19 changes: 19 additions & 0 deletions india_compliance/gst_india/api_classes/taxpayer_e_invoice.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import frappe
from frappe import _

from india_compliance.gst_india.api_classes.taxpayer_base import TaxpayerBaseAPI


Expand All @@ -12,6 +15,22 @@ class EInvoiceAPI(TaxpayerBaseAPI):
"EINV30109": "queued",
}

def setup(self, doc=None, *, company_gstin=None):
if doc:
company_gstin = doc.company_gstin
self.default_log_values.update(
reference_doctype=doc.doctype,
reference_name=doc.name,
)

if self.sandbox_mode:
frappe.throw(_("Sandbox mode is not supported for Taxpayer e-Invoice API"))

if not company_gstin:
frappe.throw(_("Company GSTIN is required to use the e-Invoice API"))

super().setup(company_gstin=company_gstin)

def get_irn_list(
self,
return_period,
Expand Down
11 changes: 9 additions & 2 deletions india_compliance/gst_india/client_scripts/e_invoice_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,15 @@ frappe.ui.form.on("Sales Invoice", {
frappe.call({
method: "india_compliance.gst_india.utils.e_invoice.generate_e_invoice",
args: { docname: frm.doc.name, force: true },
callback: () => {
return frm.refresh();
callback: async (r) => {
if (r.message?.error_type == "otp_requested") {
await india_compliance.authenticate_otp(frm.doc.company_gstin);
await frappe.call({
method: "india_compliance.gst_india.utils.e_invoice.handle_duplicate_irn_error",
args: r.message
});
}
frm.refresh();
},
});
},
Expand Down
19 changes: 14 additions & 5 deletions india_compliance/gst_india/doctype/gst_settings/gst_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@
"enable_e_invoice",
"auto_generate_e_invoice",
"generate_e_waybill_with_e_invoice",
"apply_e_invoice_only_for_selected_companies",
"fetch_e_invoice_details_from_gst_portal",
"column_break_17",
"apply_e_invoice_only_for_selected_companies",
"e_invoice_applicable_from",
"e_invoice_applicable_companies",
"gstr_1_section_break",
Expand Down Expand Up @@ -314,7 +315,7 @@
{
"default": "0",
"depends_on": "eval:doc.enable_e_invoice",
"description": "e-Invoice will be generated for only selected companies.",
"description": "e-Invoice will be generated for only selected companies",
"fieldname": "apply_e_invoice_only_for_selected_companies",
"fieldtype": "Check",
"label": "Apply e-Invoice for Selected Companies"
Expand Down Expand Up @@ -368,14 +369,14 @@
},
{
"default": "0",
"description": "Reverse Charge will automatically be applied if the Supplier is Unregistered and Reverse Charge value threshold is met. ",
"description": "Reverse Charge will automatically be applied if the Supplier is Unregistered and Reverse Charge value threshold is met",
"fieldname": "enable_rcm_for_unregistered_supplier",
"fieldtype": "Check",
"label": "Enable Reverse Charge for Purchase from Unregistered Supplier"
},
{
"default": "1",
"description": "If checked, Vendor Reference No will be mandatory for transactions (except from unregistered suppliers) to facilitate purchase reconciliation and e-Waybill generation.",
"description": "If checked, Vendor Reference No will be mandatory for transactions (except from unregistered suppliers) to facilitate purchase reconciliation and e-Waybill generation",
"fieldname": "require_supplier_invoice_no",
"fieldtype": "Check",
"label": "Require Vendor Document Reference for GST Purchases or Receipts"
Expand Down Expand Up @@ -619,12 +620,20 @@
"fieldname": "enable_e_waybill_for_sc",
"fieldtype": "Check",
"label": "Enable e-Waybill Generation for Subcontracting"
},
{
"default": "1",
"depends_on": "eval:doc.enable_e_invoice",
"description": "The system will attempt to get e-Invoice details from the GST Portal if it can't find them on the e-Invoice Portal",
"fieldname": "fetch_e_invoice_details_from_gst_portal",
"fieldtype": "Check",
"label": "Fetch e-Invoice details from GST Portal"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-07-12 10:25:49.243652",
"modified": "2024-07-25 19:16:25.036677",
"modified_by": "Administrator",
"module": "GST India",
"name": "GST Settings",
Expand Down
1 change: 1 addition & 0 deletions india_compliance/gst_india/setup/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ def set_default_gst_settings():
"auto_generate_e_invoice": 1,
"generate_e_waybill_with_e_invoice": 1,
"e_invoice_applicable_from": nowdate(),
"fetch_e_invoice_details_from_gst_portal": 1,
"autofill_party_info": 1,
"archive_party_info_days": 7,
"validate_gstin_status": 1,
Expand Down
143 changes: 117 additions & 26 deletions india_compliance/gst_india/utils/e_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from frappe.utils import (
add_to_date,
cstr,
flt,
format_date,
get_datetime,
getdate,
Expand All @@ -15,6 +16,9 @@

from india_compliance.exceptions import GSPServerError
from india_compliance.gst_india.api_classes.e_invoice import EInvoiceAPI
from india_compliance.gst_india.api_classes.taxpayer_e_invoice import (
EInvoiceAPI as TaxpayerEInvoiceAPI,
)
from india_compliance.gst_india.constants import (
CURRENCY_CODES,
EXPORT_TYPES,
Expand Down Expand Up @@ -132,30 +136,15 @@ def generate_e_invoice(docname, throw=True, force=False):

# Handle Duplicate IRN
if result.InfCd == "DUPIRN":
response = api.get_e_invoice_by_irn(result.Desc.Irn)

if signed_data := response.SignedInvoice:
invoice_data = json.loads(
jwt.decode(signed_data, options={"verify_signature": False})["data"]
)

previous_invoice_amount = invoice_data.get("ValDtls").get("TotInvVal")
current_invoice_amount = data.get("ValDtls").get("TotInvVal")

if previous_invoice_amount != current_invoice_amount:
frappe.throw(
_(
"e-Invoice is already available against Invoice {0} with a Grand Total of Rs.{1}"
" Duplicate IRN requests are not considered by e-Invoice Portal."
).format(
frappe.bold(invoice_data.get("DocDtls").get("No")),
frappe.bold(previous_invoice_amount),
)
)

# 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
current_gstin = data.get("BuyerDtls").get("Gstin")
current_invoice_amount = data.get("ValDtls").get("TotInvVal")

return handle_duplicate_irn_error(
irn_data=result.Desc,
current_gstin=current_gstin,
current_invoice_amount=current_invoice_amount,
doc=doc,
)

# Handle Invalid GSTIN Error
if result.error_code in ("3028", "3029"):
Expand Down Expand Up @@ -193,6 +182,108 @@ def generate_e_invoice(docname, throw=True, force=False):
doc.db_set({"einvoice_status": "Failed"})
raise e

return log_and_process_e_invoice_generation(doc, result, api.sandbox_mode)


@frappe.whitelist()
def handle_duplicate_irn_error(
irn_data,
current_gstin,
current_invoice_amount,
doc=None,
docname=None,
):
"""
Handle Duplicate IRN errors by fetching the IRN details and comparing with the current invoice.
Steps:
1. Fetch IRN details using the IRN number using e-Invoice API.
2. If the IRN details cannot be fetched, fetch the IRN details from the GST Portal.
3. Compare the buyer GSTIN and invoice amount with the current invoice and throw an error if they don't match.
"""

if isinstance(irn_data, str):
irn_data = json.loads(irn_data, object_hook=frappe._dict)
current_invoice_amount = flt(current_invoice_amount)

doc = doc or load_doc("Sales Invoice", docname, "submit")
api = EInvoiceAPI(doc)
response = api.get_e_invoice_by_irn(irn_data.Irn)

# Handle error 2283:
# IRN details cannot be provided as it is generated more than 2 days ago
if (
response.error_code == "2283"
and api.settings.fetch_e_invoice_details_from_gst_portal
):
response = TaxpayerEInvoiceAPI(doc).get_irn_details(irn_data.Irn)

if response.error_type == "otp_requested":
response.update(
{
"irn_data": irn_data,
"current_gstin": current_gstin,
"current_invoice_amount": current_invoice_amount,
"docname": doc.name,
}
)

return response

response = frappe._dict(response.data or response.error)

if signed_data := response.SignedInvoice:
verify_e_invoice_details(current_gstin, current_invoice_amount, signed_data)

if response.error_code:
response = irn_data

return log_and_process_e_invoice_generation(doc, response, api.sandbox_mode)


def verify_e_invoice_details(current_gstin, current_invoice_amount, signed_data):
invoice_data = json.loads(
jwt.decode(signed_data, options={"verify_signature": False})["data"]
)

previous_gstin = invoice_data.get("BuyerDtls").get("Gstin")
previous_invoice_amount = invoice_data.get("ValDtls").get("TotInvVal")

error_message = ""
if previous_gstin != current_gstin:
error_message += _("<li>Customer GSTIN (Previous: {0}).</li>").format(
frappe.bold(previous_gstin)
)

if previous_invoice_amount != current_invoice_amount:
previous_invoice_amount_formatted = frappe.format_value(
previous_invoice_amount, currency=frappe.db.get_default("currency")
)

error_message += _("<li>Invoice amount (Previous: {0}).</li>").format(
frappe.bold(previous_invoice_amount_formatted)
)

if error_message:
frappe.throw(
_(
"An e-Invoice already exists for Invoice No {0}, but with different details compared to the current Invoice:<br>{1}"
"Hence, the IRN number is not updated against current Invoice."
"<br><br>Corrective Steps:<br><br>"
"<li>Generate a new Invoice for the same transaction.</li>"
"<li>Try cancelling e-Invoice from e-Invoice portal if possible. Alternatively, clear/update e-Invoice as posted automatically in GSTR-1.</li>"
).format(
frappe.bold(invoice_data.get("DocDtls").get("No")),
error_message,
),
)


def log_and_process_e_invoice_generation(doc, result, sandbox_mode=False):
"""
Load and process the e-Invoice generation result.
"""

doc.db_set(
{
"irn": result.Irn,
Expand All @@ -213,13 +304,13 @@ def generate_e_invoice(docname, throw=True, force=False):
doc,
{
"irn": doc.irn,
"sales_invoice": docname,
"sales_invoice": doc.name,
"acknowledgement_number": result.AckNo,
"acknowledged_on": parse_datetime(result.AckDt),
"signed_invoice": result.SignedInvoice,
"signed_qr_code": result.SignedQRCode,
"invoice_data": invoice_data,
"is_generated_in_sandbox_mode": api.sandbox_mode,
"is_generated_in_sandbox_mode": sandbox_mode,
},
)

Expand Down
2 changes: 1 addition & 1 deletion india_compliance/gst_india/utils/test_e_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ def test_e_invoice_for_duplicate_irn(self):
# Assert if Invoice amount has changed
self.assertRaisesRegex(
frappe.ValidationError,
re.compile(r"^(e-Invoice is already available against Invoice.*)$"),
re.compile(r"^(An e-Invoice already exists for Invoice.*)$"),
generate_e_invoice,
si.name,
)
Expand Down

0 comments on commit cf2165a

Please sign in to comment.