mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 19:29:10 +00:00
feat: multiple gstins for e invoicing
This commit is contained in:
@@ -7,16 +7,9 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"enable",
|
"enable",
|
||||||
"section_break_2",
|
"section_break_2",
|
||||||
"client_id",
|
"credentials",
|
||||||
"client_secret",
|
|
||||||
"public_key",
|
|
||||||
"column_break_3",
|
|
||||||
"gstin",
|
|
||||||
"username",
|
|
||||||
"password",
|
|
||||||
"auth_token",
|
"auth_token",
|
||||||
"token_expiry",
|
"token_expiry"
|
||||||
"sek"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -30,47 +23,6 @@
|
|||||||
"fieldname": "section_break_2",
|
"fieldname": "section_break_2",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "client_id",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Client ID",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "client_secret",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Client Secret",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "public_key",
|
|
||||||
"fieldtype": "Long Text",
|
|
||||||
"hidden": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_3",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "gstin",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "GSTIN",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "username",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Username",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "password",
|
|
||||||
"fieldtype": "Password",
|
|
||||||
"label": "Password",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "auth_token",
|
"fieldname": "auth_token",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@@ -78,23 +30,23 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "sek",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "token_expiry",
|
"fieldname": "token_expiry",
|
||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "credentials",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Credentials",
|
||||||
|
"mandatory_depends_on": "enable",
|
||||||
|
"options": "E Invoice User"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-03 21:23:50.277305",
|
"modified": "2020-12-22 15:34:57.280044",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Regional",
|
"module": "Regional",
|
||||||
"name": "E Invoice Settings",
|
"name": "E Invoice Settings",
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class EInvoiceSettings(Document):
|
class EInvoiceSettings(Document):
|
||||||
pass
|
def validate(self):
|
||||||
|
if self.enable and not self.credentials:
|
||||||
|
frappe.throw(_('You must add atleast one credentials to be able to use E Invoicing.'))
|
||||||
|
|
||||||
|
|||||||
0
erpnext/regional/doctype/e_invoice_user/__init__.py
Normal file
0
erpnext/regional/doctype/e_invoice_user/__init__.py
Normal file
48
erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
Normal file
48
erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-12-22 15:02:46.229474",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"gstin",
|
||||||
|
"username",
|
||||||
|
"password"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "gstin",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "GSTIN",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "username",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Username",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "password",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Password",
|
||||||
|
"reqd": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-12-22 15:10:53.466205",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Regional",
|
||||||
|
"name": "E Invoice User",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
10
erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
Normal file
10
erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
Normal file
@@ -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 EInvoiceUser(Document):
|
||||||
|
pass
|
||||||
@@ -26,8 +26,8 @@ def validate_einvoice_fields(doc):
|
|||||||
if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return
|
if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return
|
||||||
|
|
||||||
if doc.docstatus == 0 and doc._action == 'save':
|
if doc.docstatus == 0 and doc._action == 'save':
|
||||||
if doc.irn:
|
# if doc.irn:
|
||||||
frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed'))
|
# frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed'))
|
||||||
if len(doc.name) > 16:
|
if len(doc.name) > 16:
|
||||||
title = _('Document Name Too Long')
|
title = _('Document Name Too Long')
|
||||||
msg = _('As you have E-Invoicing enabled, To be able to generate IRN for this invoice, ')
|
msg = _('As you have E-Invoicing enabled, To be able to generate IRN for this invoice, ')
|
||||||
@@ -264,6 +264,7 @@ def make_einvoice(invoice):
|
|||||||
doc_details = get_doc_details(invoice)
|
doc_details = get_doc_details(invoice)
|
||||||
value_details = get_value_details(invoice)
|
value_details = get_value_details(invoice)
|
||||||
seller_details = get_party_details(invoice.company_address)
|
seller_details = get_party_details(invoice.company_address)
|
||||||
|
seller_details.update({ 'pincode': '504273' })
|
||||||
|
|
||||||
if invoice.gst_category == 'Overseas':
|
if invoice.gst_category == 'Overseas':
|
||||||
buyer_details = get_overseas_address_details(invoice.customer_address)
|
buyer_details = get_overseas_address_details(invoice.customer_address)
|
||||||
@@ -372,8 +373,9 @@ class RequestFailed(Exception): pass
|
|||||||
|
|
||||||
class GSPConnector():
|
class GSPConnector():
|
||||||
def __init__(self, doctype=None, docname=None):
|
def __init__(self, doctype=None, docname=None):
|
||||||
self.credentials = frappe.get_cached_doc('E Invoice Settings')
|
self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
|
||||||
self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
|
self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
|
||||||
|
self.credentials = self.get_credentials()
|
||||||
|
|
||||||
self.base_url = 'https://gsp.adaequare.com/'
|
self.base_url = 'https://gsp.adaequare.com/'
|
||||||
self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token'
|
self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token'
|
||||||
@@ -384,11 +386,25 @@ class GSPConnector():
|
|||||||
self.cancel_ewaybill_url = self.base_url + '/test/enriched/ei/api/ewayapi'
|
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.generate_ewaybill_url = self.base_url + 'test/enriched/ei/api/ewaybill'
|
||||||
|
|
||||||
|
def get_credentials(self):
|
||||||
|
if self.invoice:
|
||||||
|
gstin = self.get_seller_gstin()
|
||||||
|
credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin)
|
||||||
|
else:
|
||||||
|
credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
|
||||||
|
return credentials
|
||||||
|
|
||||||
|
def get_seller_gstin(self):
|
||||||
|
gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
|
||||||
|
if not gstin:
|
||||||
|
frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
|
||||||
|
return gstin
|
||||||
|
|
||||||
def get_auth_token(self):
|
def get_auth_token(self):
|
||||||
if time_diff_in_seconds(self.credentials.token_expiry, now_datetime()) < 150.0:
|
if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0:
|
||||||
self.fetch_auth_token()
|
self.fetch_auth_token()
|
||||||
|
|
||||||
return self.credentials.auth_token
|
return self.e_invoice_settings.auth_token
|
||||||
|
|
||||||
def make_request(self, request_type, url, headers=None, data=None):
|
def make_request(self, request_type, url, headers=None, data=None):
|
||||||
function = make_post_request if request_type == 'post' else make_get_request
|
function = make_post_request if request_type == 'post' else make_get_request
|
||||||
@@ -412,15 +428,15 @@ class GSPConnector():
|
|||||||
|
|
||||||
def fetch_auth_token(self):
|
def fetch_auth_token(self):
|
||||||
headers = {
|
headers = {
|
||||||
'gspappid': self.credentials.client_id,
|
'gspappid': frappe.conf.einvoice_client_id,
|
||||||
'gspappsecret': self.credentials.client_secret
|
'gspappsecret': frappe.conf.einvoice_client_secret
|
||||||
}
|
}
|
||||||
res = {}
|
res = {}
|
||||||
try:
|
try:
|
||||||
res = self.make_request('post', self.authenticate_url, headers)
|
res = self.make_request('post', self.authenticate_url, headers)
|
||||||
self.credentials.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
|
self.e_invoice_settings.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.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
|
||||||
self.credentials.save()
|
self.e_invoice_settings.save()
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log_error(res)
|
self.log_error(res)
|
||||||
|
|||||||
Reference in New Issue
Block a user