From 42b028a3ba834b53d63bc5cd40b51260f89fef69 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 8 Dec 2020 14:59:02 +0530 Subject: [PATCH] feat: log e-invocing requests --- .../doctype/e_invoice_request_log/__init__.py | 0 .../e_invoice_request_log.js | 8 ++ .../e_invoice_request_log.json | 85 ++++++++++++++++++ .../e_invoice_request_log.py | 10 +++ .../test_e_invoice_request_log.py | 10 +++ erpnext/regional/india/e_invoice/utils.py | 87 +++++++++++++------ 6 files changed, 173 insertions(+), 27 deletions(-) create mode 100644 erpnext/regional/doctype/e_invoice_request_log/__init__.py create mode 100644 erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js create mode 100644 erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json create mode 100644 erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py create mode 100644 erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py diff --git a/erpnext/regional/doctype/e_invoice_request_log/__init__.py b/erpnext/regional/doctype/e_invoice_request_log/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js new file mode 100644 index 00000000000..7b7ba964e5e --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('E Invoice Request Log', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json new file mode 100644 index 00000000000..088d5cd7627 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json @@ -0,0 +1,85 @@ +{ + "actions": [], + "autoname": "EINV-REQ-.#####", + "creation": "2020-12-08 12:54:08.175992", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user", + "url", + "headers", + "response", + "column_break_7", + "timestamp", + "reference_invoice", + "data" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User" + }, + { + "fieldname": "reference_invoice", + "fieldtype": "Link", + "label": "Reference Invoice", + "options": "Sales Invoice" + }, + { + "fieldname": "headers", + "fieldtype": "Code", + "label": "Headers", + "options": "JSON" + }, + { + "fieldname": "data", + "fieldtype": "Code", + "label": "Data", + "options": "JSON" + }, + { + "default": "Now", + "fieldname": "timestamp", + "fieldtype": "Datetime", + "label": "Timestamp" + }, + { + "fieldname": "response", + "fieldtype": "Code", + "label": "Response", + "options": "JSON" + }, + { + "fieldname": "url", + "fieldtype": "Data", + "label": "URL" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-12-08 14:50:33.820452", + "modified_by": "Administrator", + "module": "Regional", + "name": "E Invoice Request Log", + "owner": "Administrator", + "permissions": [ + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py new file mode 100644 index 00000000000..9150bdd9260 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class EInvoiceRequestLog(Document): + pass diff --git a/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py new file mode 100644 index 00000000000..c84e9a249bd --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestEInvoiceRequestLog(unittest.TestCase): + pass diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 772154f9a65..c49e3f7e7e8 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import os import re import jwt +import sys import json import base64 import frappe @@ -361,11 +362,12 @@ def validate_einvoice(validations, einvoice, errors=[]): return errors -class ResponseFailure(Exception): pass +class RequestFailed(Exception): pass class GSPConnector(): - def __init__(self, doctype, docname): + def __init__(self, doctype=None, docname=None): self.credentials = frappe.get_cached_doc('E Invoice Settings') + self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None self.base_url = 'https://gsp.adaequare.com/' self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token' @@ -375,14 +377,31 @@ class GSPConnector(): self.cancel_irn_url = self.base_url + 'test/enriched/ei/api/invoice/cancel' self.cancel_ewaybill_url = self.base_url + '/test/enriched/ei/api/ewayapi' self.generate_ewaybill_url = self.base_url + 'test/enriched/ei/api/ewaybill' - - self.invoice = frappe.get_cached_doc(doctype, docname) def get_auth_token(self): if time_diff_in_seconds(self.credentials.token_expiry, now_datetime()) < 150.0: self.fetch_auth_token() return self.credentials.auth_token + + def make_request(self, request_type, url, headers=None, data=None): + function = make_post_request if request_type == 'post' else make_get_request + res = function(url, headers=headers, data=data) + self.log_request(url, headers, data, res) + return res + + def log_request(self, url, headers, data, res): + headers.update({ 'password': self.credentials.password }) + request_log = frappe.get_doc({ + "doctype": "E Invoice Request Log", + "user": frappe.session.user, + "reference_invoice": self.invoice.name if self.invoice else None, + "url": url, + "headers": json.dumps(headers, indent=4) if headers else None, + "data": json.dumps(data, indent=4) if data else None, + "response": json.dumps(res, indent=4) if res else None + }) + request_log.insert(ignore_permissions=True) def fetch_auth_token(self): headers = { @@ -391,7 +410,7 @@ class GSPConnector(): } res = {} try: - res = make_post_request(self.authenticate_url, headers=headers) + res = self.make_request('post', self.authenticate_url, headers) self.credentials.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token')) self.credentials.token_expiry = add_to_date(None, seconds=res.get('expires_in')) self.credentials.save() @@ -409,20 +428,20 @@ class GSPConnector(): 'authorization': self.get_auth_token(), 'requestid': str(base64.b64encode(os.urandom(18))), } - + def fetch_gstin_details(self, gstin): headers = self.get_headers() try: params = '?gstin={gstin}'.format(gstin=gstin) - res = make_get_request(self.gstin_details_url + params, headers=headers) + res = self.make_request('get', self.gstin_details_url + params, headers) if res.get('success'): return res.get('result') else: self.log_error(res) - raise ResponseFailure + raise RequestFailed - except ResponseFailure: + except RequestFailed: self.raise_error() except Exception: @@ -432,6 +451,8 @@ class GSPConnector(): @staticmethod def get_gstin_details(gstin): '''fetch and cache GSTIN details''' + if not hasattr(frappe.local, 'gstin_cache'): + frappe.local.gstin_cache = {} key = gstin gsp_connector = GSPConnector() @@ -447,7 +468,7 @@ class GSPConnector(): data = json.dumps(einvoice) try: - res = make_post_request(self.generate_irn_url, headers=headers, data=data) + res = self.make_request('post', self.generate_irn_url, headers, data) if res.get('success'): self.set_einvoice_data(res.get('result')) @@ -457,12 +478,15 @@ class GSPConnector(): irn_details = self.get_irn_details(irn) if irn_details: self.set_einvoice_data(irn_details) + else: + raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \ + Contact ERPNext support to resolve the issue.') else: self.log_error(res) - raise ResponseFailure + raise RequestFailed - except ResponseFailure: + except RequestFailed: self.raise_error() except Exception: @@ -474,14 +498,14 @@ class GSPConnector(): try: params = '?irn={irn}'.format(irn=irn) - res = make_get_request(self.irn_details_url + params, headers=headers) + res = self.make_request('get', self.irn_details_url + params, headers) if res.get('success'): return res.get('result') else: self.log_error(res) - raise ResponseFailure + raise RequestFailed - except ResponseFailure: + except RequestFailed: self.raise_error() except Exception: @@ -497,7 +521,7 @@ class GSPConnector(): }) try: - res = make_post_request(self.cancel_irn_url, headers=headers, data=data) + res = self.make_request('post', self.cancel_irn_url, headers, data) if res.get('success'): self.invoice.irn_cancelled = 1 self.invoice.flags.updater_reference = { @@ -509,9 +533,9 @@ class GSPConnector(): else: self.log_error(res) - raise ResponseFailure + raise RequestFailed - except ResponseFailure: + except RequestFailed: self.raise_error() except Exception: @@ -536,7 +560,7 @@ class GSPConnector(): }) try: - res = make_post_request(self.generate_ewaybill_url, headers=headers, data=data) + res = self.make_request('post', self.generate_ewaybill_url, headers, data) if res.get('success'): self.invoice.ewaybill = res.get('result').get('EwbNo') self.invoice.eway_bill_cancelled = 0 @@ -550,9 +574,9 @@ class GSPConnector(): else: self.log_error(res) - raise ResponseFailure + raise RequestFailed - except ResponseFailure: + except RequestFailed: self.raise_error() except Exception: @@ -569,7 +593,7 @@ class GSPConnector(): }) try: - res = make_post_request(self.cancel_ewaybill_url, headers=headers, data=data) + res = self.make_request('post', self.cancel_ewaybill_url, headers, data) if res.get('success'): self.invoice.ewaybill = '' self.invoice.eway_bill_cancelled = 1 @@ -582,9 +606,9 @@ class GSPConnector(): else: self.log_error(res) - raise ResponseFailure + raise RequestFailed - except ResponseFailure: + except RequestFailed: self.raise_error() except Exception: @@ -595,13 +619,22 @@ class GSPConnector(): if not isinstance(data, dict): data = json.loads(data) - message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()]) + seperator = "--" * 50 + err_tb = traceback.format_exc() + err_msg = str(sys.exc_info()[1]) + data = json.dumps(data, default=str, indent=4) + + message = "\n".join([ + "Error", err_msg, seperator, + "Data:", data, seperator, + "Exception:", err_tb + ]) frappe.log_error(title="E Invoicing Error", message=message) def raise_error(self, raise_exception=False): - link_to_error_list = '{1}'.format('Error Log', 'Error Log') + link_to_error_list = 'Error Log' frappe.msgprint( - _('An error occurred while making API request. Please check {} for more information.').format(link_to_error_list), + _('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list), title=_('E Invoice Request Failed'), raise_exception=raise_exception, indicator='red'