From 1c213a3dbb1ba24ba66d23a66dcef44cd4a09539 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 21:19:59 +0530 Subject: [PATCH 001/137] feat: init e-invoice settings --- .../doctype/e_invoice_settings/__init__.py | 0 .../e_invoice_settings/e_invoice_settings.js | 8 ++ .../e_invoice_settings.json | 82 +++++++++++++++++++ .../e_invoice_settings/e_invoice_settings.py | 10 +++ .../test_e_invoice_settings.py | 10 +++ 5 files changed, 110 insertions(+) create mode 100644 erpnext/erpnext_integrations/doctype/e_invoice_settings/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js create mode 100644 erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json create mode 100644 erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py create mode 100644 erpnext/erpnext_integrations/doctype/e_invoice_settings/test_e_invoice_settings.py diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/__init__.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js new file mode 100644 index 00000000000..9c67e619504 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.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 Settings', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json new file mode 100644 index 00000000000..44d1bd63b55 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json @@ -0,0 +1,82 @@ +{ + "actions": [], + "creation": "2020-09-24 16:23:16.235722", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "client_id", + "client_secret", + "public_key_file", + "column_break_3", + "gstin", + "username", + "password" + ], + "fields": [ + { + "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_file", + "fieldtype": "Attach", + "label": "Public Key", + "reqd": 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 + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2020-09-25 21:19:17.159664", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "E Invoice Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py new file mode 100644 index 00000000000..24ab76a5d84 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.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 EInvoiceSettings(Document): + pass diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/test_e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/test_e_invoice_settings.py new file mode 100644 index 00000000000..a11ce63ee6c --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/test_e_invoice_settings.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 TestEInvoiceSettings(unittest.TestCase): + pass From 39dabe7381b62a3b8df379772f98e7043ba6fd94 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 21:27:50 +0530 Subject: [PATCH 002/137] feat: read public key file --- .../e_invoice_settings/e_invoice_settings.json | 9 ++++++++- .../e_invoice_settings/e_invoice_settings.py | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json index 44d1bd63b55..b91ef817520 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json @@ -8,6 +8,7 @@ "client_id", "client_secret", "public_key_file", + "public_key", "column_break_3", "gstin", "username", @@ -54,12 +55,18 @@ "fieldtype": "Password", "label": "Password", "reqd": 1 + }, + { + "fieldname": "public_key", + "fieldtype": "Long Text", + "hidden": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-09-25 21:19:17.159664", + "modified": "2020-09-25 21:24:00.404460", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "E Invoice Settings", diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index 24ab76a5d84..62e945af87a 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -3,8 +3,19 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe +from frappe.utils import cstr from frappe.model.document import Document class EInvoiceSettings(Document): - pass + def validate(self): + pass + + def before_save(self): + if not self.public_key or self.has_value_changed('public_key_file'): + self.public_key = self.read_key_file() + + def read_key_file(self): + key_file = frappe.get_doc('File', dict(attached_to_name=self.doctype, attached_to_field='public_key_file')) + with open(key_file.get_full_path(), 'rb') as f: + return cstr(f.read()) From b52a9283bb3fd5aeb7c3f05b96ccba0a28b051eb Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 21:52:00 +0530 Subject: [PATCH 003/137] feat: rsa encryption with public key --- .../e_invoice_settings/e_invoice_settings.js | 16 +++++++-- .../e_invoice_settings.json | 12 +++++-- .../e_invoice_settings/e_invoice_settings.py | 35 +++++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js index 9c67e619504..1f9054461f4 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js @@ -2,7 +2,19 @@ // For license information, please see license.txt frappe.ui.form.on('E Invoice Settings', { - // refresh: function(frm) { + refresh: function(frm) { + frm.trigger("show_fetch_token_btn"); + }, - // } + show_fetch_token_btn(frm) { + frm.add_custom_button(__("Fetch Token"), + () => { + frm.call({ + doc: frm.doc, + method: 'make_authentication_request', + freeze: true + }); + } + ); + } }); diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json index b91ef817520..5278c6eebd2 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json @@ -12,7 +12,8 @@ "column_break_3", "gstin", "username", - "password" + "password", + "auto_refresh_token" ], "fields": [ { @@ -61,12 +62,19 @@ "fieldtype": "Long Text", "hidden": 1, "read_only": 1 + }, + { + "default": "0", + "description": "Token will be automatically refreshed 10 mins before expiry", + "fieldname": "auto_refresh_token", + "fieldtype": "Check", + "label": "Auto Refresh Token" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-09-25 21:24:00.404460", + "modified": "2020-09-25 21:32:36.553678", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "E Invoice Settings", diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index 62e945af87a..129dcd35b6e 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -3,9 +3,15 @@ # For license information, please see license.txt from __future__ import unicode_literals +import os +import json +import base64 import frappe from frappe.utils import cstr +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5 from frappe.model.document import Document +from frappe.integrations.utils import make_post_request class EInvoiceSettings(Document): def validate(self): @@ -19,3 +25,32 @@ class EInvoiceSettings(Document): key_file = frappe.get_doc('File', dict(attached_to_name=self.doctype, attached_to_field='public_key_file')) with open(key_file.get_full_path(), 'rb') as f: return cstr(f.read()) + + def rsa_encrypt(self, msg, key): + if not (isinstance(msg, bytes) or isinstance(msg, bytearray)): + msg = str.encode(msg) + + rsa_pub_key = RSA.import_key(key) + cipher = PKCS1_v1_5.new(rsa_pub_key) + enc_msg = cipher.encrypt(msg) + b64_enc_msg = base64.b64encode(enc_msg) + return b64_enc_msg.decode() + + def make_authentication_request(self): + endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' + headers = { 'content-type': 'application/json' } + headers.update(dict(client_id=self.client_id, client_secret=self.client_secret)) + payload = dict(UserName=self.username, ForceRefreshAccessToken=bool(self.auto_refresh_token)) + + appkey = bytearray(os.urandom(32)) + enc_appkey = self.rsa_encrypt(appkey, self.public_key) + + password = self.get_password(fieldname='password') + enc_password = self.rsa_encrypt(password, self.public_key) + + payload.update(dict(Password=enc_password, AppKey=enc_appkey)) + + res = make_post_request(endpoint, headers=headers, data=json.dumps({ 'data': payload })) + + print(res) + From cca0eaaa29c685837978c26bcc2ab6742155ca86 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 21:59:38 +0530 Subject: [PATCH 004/137] feat: save token and sek from auth request --- .../e_invoice_settings/e_invoice_settings.js | 23 ++++++++++------- .../e_invoice_settings.json | 25 +++++++++++++++++-- .../e_invoice_settings/e_invoice_settings.py | 18 ++++++++++++- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js index 1f9054461f4..9fc67ce1b98 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js @@ -7,14 +7,19 @@ frappe.ui.form.on('E Invoice Settings', { }, show_fetch_token_btn(frm) { - frm.add_custom_button(__("Fetch Token"), - () => { - frm.call({ - doc: frm.doc, - method: 'make_authentication_request', - freeze: true - }); - } - ); + const { token_expiry } = frm.doc; + const now = frappe.datetime.now_datetime(); + const expiry_in_mins = moment(token_expiry).diff(now, "minute"); + if (expiry_in_mins <= 1) { + frm.add_custom_button(__("Fetch Token"), + () => { + frm.call({ + doc: frm.doc, + method: 'make_authentication_request', + freeze: true + }); + } + ); + } } }); diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json index 5278c6eebd2..888406b534c 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json @@ -13,7 +13,10 @@ "gstin", "username", "password", - "auto_refresh_token" + "auto_refresh_token", + "auth_token", + "token_expiry", + "sek" ], "fields": [ { @@ -69,12 +72,30 @@ "fieldname": "auto_refresh_token", "fieldtype": "Check", "label": "Auto Refresh Token" + }, + { + "fieldname": "auth_token", + "fieldtype": "Data", + "hidden": 1, + "read_only": 1 + }, + { + "fieldname": "token_expiry", + "fieldtype": "Datetime", + "hidden": 1, + "read_only": 1 + }, + { + "fieldname": "sek", + "fieldtype": "Data", + "hidden": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-09-25 21:32:36.553678", + "modified": "2020-09-25 21:57:58.187647", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "E Invoice Settings", diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index 129dcd35b6e..f157e4ca0fa 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -10,6 +10,7 @@ import frappe from frappe.utils import cstr from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 +from frappe.utils.data import get_datetime from frappe.model.document import Document from frappe.integrations.utils import make_post_request @@ -35,6 +36,9 @@ class EInvoiceSettings(Document): enc_msg = cipher.encrypt(msg) b64_enc_msg = base64.b64encode(enc_msg) return b64_enc_msg.decode() + + def decrypt_sek(self, enc_sek, key): + return enc_sek def make_authentication_request(self): endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' @@ -52,5 +56,17 @@ class EInvoiceSettings(Document): res = make_post_request(endpoint, headers=headers, data=json.dumps({ 'data': payload })) - print(res) + self.extract_token_and_sek(res, appkey) + + def extract_token_and_sek(self, response, appkey): + data = response.get('Data') + auth_token = data.get('AuthToken') + token_expiry = data.get('TokenExpiry') + enc_sek = data.get('Sek') + sek = self.decrypt_sek(enc_sek, appkey) + + self.auth_token = auth_token + self.token_expiry = get_datetime(token_expiry) + self.sek = sek + self.save() From 017543d59d06579f0fec187be997b4fe756c6b6f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 22:02:45 +0530 Subject: [PATCH 005/137] chore: handle error response --- .../doctype/e_invoice_settings/e_invoice_settings.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index f157e4ca0fa..92ec80ef246 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -37,8 +37,8 @@ class EInvoiceSettings(Document): b64_enc_msg = base64.b64encode(enc_msg) return b64_enc_msg.decode() - def decrypt_sek(self, enc_sek, key): - return enc_sek + def aes_decrypt(self, msg, key): + return msg def make_authentication_request(self): endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' @@ -55,6 +55,7 @@ class EInvoiceSettings(Document): payload.update(dict(Password=enc_password, AppKey=enc_appkey)) res = make_post_request(endpoint, headers=headers, data=json.dumps({ 'data': payload })) + self.handle_err_response(res) self.extract_token_and_sek(res, appkey) @@ -63,10 +64,14 @@ class EInvoiceSettings(Document): auth_token = data.get('AuthToken') token_expiry = data.get('TokenExpiry') enc_sek = data.get('Sek') - sek = self.decrypt_sek(enc_sek, appkey) + sek = self.aes_decrypt(enc_sek, appkey) self.auth_token = auth_token self.token_expiry = get_datetime(token_expiry) self.sek = sek self.save() + def handle_err_response(self, response): + if response.get('Status') == 0: + err_msg = response.get('ErrorDetails')[0].get('ErrorMessage') + frappe.throw(_(err_msg), title=_("API Request Failed")) \ No newline at end of file From 31596228ffb4a5bbcc6116ef45e3a9b7cce84556 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 22:17:47 +0530 Subject: [PATCH 006/137] feat: AES decryption of SEK with appkey --- .../e_invoice_settings/e_invoice_settings.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index 92ec80ef246..866d75ac8c3 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -7,9 +7,11 @@ import os import json import base64 import frappe +from frappe import _ from frappe.utils import cstr from Crypto.PublicKey import RSA -from Crypto.Cipher import PKCS1_v1_5 +from Crypto.Cipher import PKCS1_v1_5, AES +from Crypto.Util.Padding import pad, unpad from frappe.utils.data import get_datetime from frappe.model.document import Document from frappe.integrations.utils import make_post_request @@ -37,8 +39,16 @@ class EInvoiceSettings(Document): b64_enc_msg = base64.b64encode(enc_msg) return b64_enc_msg.decode() - def aes_decrypt(self, msg, key): - return msg + def aes_decrypt(self, enc_msg, key): + if not (isinstance(key, bytes) or isinstance(key, bytearray)): + key = base64.b64decode(key) + + cipher = AES.new(key, AES.MODE_ECB) + b64_enc_msg = base64.b64decode(enc_msg) + msg_bytes = cipher.decrypt(b64_enc_msg) + msg_bytes = unpad(msg_bytes, AES.block_size) # due to ECB/PKCS5Padding + b64_msg_bytes = base64.b64encode(msg_bytes) + return b64_msg_bytes.decode() def make_authentication_request(self): endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' From 48449e6d0a716efbf20b05f49e6c0b1311ae788e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 23:29:45 +0530 Subject: [PATCH 007/137] feat: decrypt json data with SEK --- .../e_invoice_settings/e_invoice_settings.js | 15 +++++++++++- .../e_invoice_settings/e_invoice_settings.py | 24 ++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js index 9fc67ce1b98..9c3d0807355 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js @@ -4,6 +4,18 @@ frappe.ui.form.on('E Invoice Settings', { refresh: function(frm) { frm.trigger("show_fetch_token_btn"); + frm.add_custom_button(__("Get GSTIN Details"), + () => { + frm.call({ + doc: frm.doc, + method: 'get_gstin_details', + args: { + 'gstin': '27AAACW8099E1ZX' + }, + freeze: true, + callback: (res) => console.log(res) + }); + }); }, show_fetch_token_btn(frm) { @@ -16,7 +28,8 @@ frappe.ui.form.on('E Invoice Settings', { frm.call({ doc: frm.doc, method: 'make_authentication_request', - freeze: true + freeze: true, + callback: () => frm.refresh() }); } ); diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index 866d75ac8c3..9a9a1b22ff6 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -14,7 +14,7 @@ from Crypto.Cipher import PKCS1_v1_5, AES from Crypto.Util.Padding import pad, unpad from frappe.utils.data import get_datetime from frappe.model.document import Document -from frappe.integrations.utils import make_post_request +from frappe.integrations.utils import make_post_request, make_get_request class EInvoiceSettings(Document): def validate(self): @@ -40,15 +40,18 @@ class EInvoiceSettings(Document): return b64_enc_msg.decode() def aes_decrypt(self, enc_msg, key): + encode_as_b64 = True if not (isinstance(key, bytes) or isinstance(key, bytearray)): key = base64.b64decode(key) + encode_as_b64 = False cipher = AES.new(key, AES.MODE_ECB) b64_enc_msg = base64.b64decode(enc_msg) msg_bytes = cipher.decrypt(b64_enc_msg) msg_bytes = unpad(msg_bytes, AES.block_size) # due to ECB/PKCS5Padding - b64_msg_bytes = base64.b64encode(msg_bytes) - return b64_msg_bytes.decode() + if encode_as_b64: + msg_bytes = base64.b64encode(msg_bytes) + return msg_bytes.decode() def make_authentication_request(self): endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' @@ -80,6 +83,21 @@ class EInvoiceSettings(Document): self.token_expiry = get_datetime(token_expiry) self.sek = sek self.save() + + def get_gstin_details(self, gstin): + endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/Master/gstin/{gstin}'.format(gstin=gstin) + headers = { 'content-type': 'application/json' } + headers.update(dict(client_id=self.client_id, client_secret=self.client_secret, user_name=self.username)) + headers.update(dict(Gstin=self.gstin, AuthToken=self.auth_token)) + + res = make_get_request(endpoint, headers=headers) + self.handle_err_response(res) + + enc_json = res.get('Data') + json_str = self.aes_decrypt(enc_json, self.sek) + data = json.loads(json_str) + + return data def handle_err_response(self, response): if response.get('Status') == 0: From 6f5a9c8049f211ada9be2ff78abd6d956d449b76 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 28 Sep 2020 12:11:35 +0530 Subject: [PATCH 008/137] feat: make e invoice from erpnext sales invoice --- .../e_invoice_settings/e_inv_item_schema.json | 25 ++++ .../e_invoice_settings/e_inv_schema.json | 52 +++++++ .../e_invoice_settings/e_invoice_settings.js | 2 +- .../e_invoice_settings/e_invoice_settings.py | 141 +++++++++++++++++- 4 files changed, 216 insertions(+), 4 deletions(-) create mode 100644 erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_item_schema.json create mode 100644 erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_schema.json diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_item_schema.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_item_schema.json new file mode 100644 index 00000000000..f98e4e5c94e --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_item_schema.json @@ -0,0 +1,25 @@ +{{ + "SlNo": "{item.sr_no}", + "PrdDesc": "{item.description}", + "IsServc": "{item.is_service_item}", + "HsnCd": "{item.gst_hsn_code}", + "Barcde": "{item.barcode}", + "Unit": "{item.uom}", + "Qty": "{item.qty}", + "UnitPrice": "{item.base_rate}", + "TotAmt": "{item.base_amount}", + "Discount": "{item.discount_amount}", + "AssAmt": "{item.base_amount}", + "PrdSlNo": "{item.serial_no}", + "GstRt": "{item.tax_rate}", + "IgstAmt": "{item.igst_amount}", + "CgstAmt": "{item.cgst_amount}", + "SgstAmt": "{item.sgst_amount}", + "CesRt": "{item.cess_rate}", + "CesAmt": "{item.cess_amount}", + "TotItemVal": "{item.total_value}", + "BchDtls": {{ + "Nm": "{item.batch_no}", + "ExpDt": "{item.batch_expiry_date}" + }} +}} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_schema.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_schema.json new file mode 100644 index 00000000000..d194b45bf9e --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_schema.json @@ -0,0 +1,52 @@ +{{ + "Version": "1.01", + "TranDtls": {{ + "TaxSch": "{trans_details.tax_scheme}", + "SupTyp": "{trans_details.supply_type}", + "RegRev": "{trans_details.reverse_charge}" + }}, + "DocDtls": {{ + "Typ": "{doc_details.invoice_type}", + "No": "{doc_details.invoice_name}", + "Dt": "{doc_details.invoice_date}" + }}, + "SellerDtls": {{ + "Gstin": "{seller_details.gstin}", + "LglNm": "{seller_details.legal_name}", + "TrdNm": "{seller_details.trade_name}", + "Loc": "{seller_details.location}", + "Pin": "{seller_details.pincode}", + "Stcd": "{seller_details.state_code}", + "Addr1": "{seller_details.address_line1}", + "Addr2": "{seller_details.address_line2}", + "Ph": "{seller_details.phone}", + "Em": "{seller_details.email}" + }}, + "BuyerDtls": {{ + "Gstin": "{buyer_details.gstin}", + "LglNm": "{buyer_details.legal_name}", + "TrdNm": "{buyer_details.trade_name}", + "Addr1": "{buyer_details.address_line1}", + "Addr2": "{buyer_details.address_line2}", + "Loc": "{buyer_details.location}", + "Pin": "{buyer_details.pincode}", + "Stcd": "{buyer_details.state_code}", + "Ph": "{buyer_details.phone}", + "Em": "{buyer_details.email}", + "Pos": "{buyer_details.place_of_supply}" + }}, + "ItemList": [ + {item_list} + ], + "ValDtls": {{ + "AssVal": "{value_details.base_net_total}", + "CgstVal": "{value_details.total_cgst_amt}", + "SgstVal": "{value_details.total_sgst_amt}", + "IgstVal": "{value_details.total_igst_amt}", + "CesVal": "{value_details.total_cess_amt}", + "Discount": "{value_details.invoice_discount_amt}", + "RndOffAmt": "{value_details.round_off}", + "TotInvVal": "{value_details.base_grand_total}", + "TotInvValFc": "{value_details.grand_total}" + }} +}} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js index 9c3d0807355..6d7eccceece 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js @@ -10,7 +10,7 @@ frappe.ui.form.on('E Invoice Settings', { doc: frm.doc, method: 'get_gstin_details', args: { - 'gstin': '27AAACW8099E1ZX' + 'gstin': '36AAECF1151A1ZC' }, freeze: true, callback: (res) => console.log(res) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index 9a9a1b22ff6..50725091bf0 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -7,13 +7,14 @@ import os import json import base64 import frappe -from frappe import _ from frappe.utils import cstr from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5, AES from Crypto.Util.Padding import pad, unpad -from frappe.utils.data import get_datetime from frappe.model.document import Document +from frappe import _, get_module_path, scrub +from frappe.utils.data import get_datetime, cstr +from erpnext.regional.india.utils import get_gst_accounts from frappe.integrations.utils import make_post_request, make_get_request class EInvoiceSettings(Document): @@ -102,4 +103,138 @@ class EInvoiceSettings(Document): def handle_err_response(self, response): if response.get('Status') == 0: err_msg = response.get('ErrorDetails')[0].get('ErrorMessage') - frappe.throw(_(err_msg), title=_("API Request Failed")) \ No newline at end of file + frappe.throw(_(err_msg), title=_('API Request Failed')) + + def get_schema(self, name): + schema_path = os.path.join(os.path.dirname(__file__), "{name}_schema.json".format(name=name)) + with open(schema_path, 'r') as f: + return cstr(f.read()) + + def get_trans_details(self, invoice): + supply_type = '' + if invoice.gst_category == 'Registered Regular': supply_type = 'B2B' + elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP' + elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP' + elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP' + + if not supply_type: + return _('Invalid invoice transaction category.') + + return frappe._dict(dict( + tax_scheme='GST', supply_type=supply_type, reverse_charge=invoice.reverse_charge + )) + + def get_doc_details(self, invoice): + invoice_type = 'CRN' if invoice.is_return else 'INV' + invoice_name = invoice.name + invoice_date = invoice.posting_date + + return frappe._dict(dict( + invoice_type=invoice_type, invoice_name=invoice_name, invoice_date=invoice_date + )) + + def get_party_gstin_details(self, party_address): + gstin, address_line1, address_line2, phone, email_id = frappe.db.get_value( + "Address", party_address, ["gstin", "address_line1", "address_line2", "phone", "email_id"] + ) + gstin_details = self.get_gstin_details(gstin) + legal_name = gstin_details.get('LegalName') + trade_name = gstin_details.get('TradeName') + location = gstin_details.get('AddrLoc') + state_code = gstin_details.get('StateCode') + pincode = gstin_details.get('AddrPncd') + + return frappe._dict(dict( + gstin=gstin, legal_name=legal_name, trade_name=trade_name, location=location, + pincode=pincode, state_code=state_code, address_line1=address_line1, + address_line2=address_line2, email=email_id, phone=phone + )) + + def get_item_list(self, invoice): + item_list = [] + gst_accounts = get_gst_accounts(invoice.company) + gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] + + for d in invoice.items: + item_schema = self.get_schema("e_inv_item") + item = frappe._dict(dict()) + item.update(d.as_dict()) + item.sr_no = d.idx + item.description = d.item_name + item.is_service_item = "N" if frappe.db.get_value("Item", d.item_code, "is_stock_item") else "Y" + item.batch_expiry_date = frappe.db.get_value("Batch", d.batch_no, "expiry_date") if d.batch_no else None + item.tax_rate = 0 + item.igst_amount = 0 + item.cgst_amount = 0 + item.sgst_amount = 0 + item.cess_rate = 0 + item.cess_amount = 0 + for t in invoice.taxes: + if t.account_head in gst_accounts_list: + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) + if t.account_head in gst_accounts.cess_account: + item.cess_rate += item_tax_detail[0] + item.cess_amount += item_tax_detail[1] + elif t.account_head in gst_accounts.igst_account: + item.tax_rate += item_tax_detail[0] + item.igst_amount += item_tax_detail[1] + elif t.account_head in gst_accounts.sgst_account: + item.tax_rate += item_tax_detail[0] + item.sgst_amount += item_tax_detail[1] + elif t.account_head in gst_accounts.cgst_account: + item.tax_rate += item_tax_detail[0] + item.cgst_amount += item_tax_detail[1] + + item.total_value = item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount + e_inv_item = item_schema.format(item=item) + item_list.append(e_inv_item) + + return ", ".join(item_list) + + def get_value_details(self, invoice): + gst_accounts = get_gst_accounts(invoice.company) + gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] + + value_details = frappe._dict(dict()) + value_details.base_net_total = invoice.base_net_total + value_details.invoice_discount_amt = invoice.discount_amount + value_details.round_off = invoice.base_rounding_adjustment + value_details.base_grand_total = invoice.base_rounded_total + value_details.grand_total = invoice.rounded_total + value_details.total_cgst_amt = 0 + value_details.total_sgst_amt = 0 + value_details.total_igst_amt = 0 + value_details.total_cess_amt = 0 + for t in invoice.taxes: + if t.account_head in gst_accounts_list: + if t.account_head in gst_accounts.cess_account: + value_details.total_cess_amt += t.base_tax_amount + elif t.account_head in gst_accounts.igst_account: + value_details.total_igst_amt += t.base_tax_amount + elif t.account_head in gst_accounts.sgst_account: + value_details.total_sgst_amt += t.base_tax_amount + elif t.account_head in gst_accounts.cgst_account: + value_details.total_cgst_amt += t.base_tax_amount + + return value_details + + def make_e_invoice(self, invoice): + schema = self.get_schema("e_inv") + + trans_details = self.get_trans_details(invoice) + doc_details = self.get_doc_details(invoice) + seller_details = self.get_party_gstin_details(invoice.company_address) + buyer_details = self.get_party_gstin_details(invoice.customer_address) + place_of_supply = invoice.place_of_supply.split('-')[0] + buyer_details.update(dict(place_of_supply=place_of_supply)) + + item_list = self.get_item_list(invoice) + value_details = self.get_value_details(invoice) + + e_invoice = schema.format( + trans_details=trans_details, doc_details=doc_details, + seller_details=seller_details, buyer_details=buyer_details, + item_list=item_list, value_details=value_details + ) + + return json.loads(e_invoice) \ No newline at end of file From a4bd8ba6f9556ccdaae13f38db261fa2794e779b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 11:20:18 +0530 Subject: [PATCH 009/137] feat: generate IRN --- .../e_invoice_settings/e_inv_validation.json | 838 ++++++++++++++++++ .../e_invoice_settings/e_invoice_settings.js | 13 + .../e_invoice_settings/e_invoice_settings.py | 120 ++- 3 files changed, 960 insertions(+), 11 deletions(-) create mode 100644 erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_validation.json diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_validation.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_validation.json new file mode 100644 index 00000000000..51da5943637 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_validation.json @@ -0,0 +1,838 @@ +{ + "Version": { + "type": "string", + "minLength": 1, + "maxLength": 6 + }, + "Irn": { + "type": "string", + "minLength": 64, + "maxLength": 64 + }, + "TranDtls": { + "type": "object", + "properties": { + "TaxSch": { + "type": "string", + "minLength": 3, + "maxLength": 10, + "enum": ["GST"] + }, + "SupTyp": { + "type": "string", + "minLength": 3, + "maxLength": 10, + "enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"] + }, + "RegRev": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"] + }, + "EcmGstin": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "pattern": "([0-9]{2}[0-9A-Z]{13})" + }, + "IgstOnIntra": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"] + } + }, + "required": ["TaxSch", "SupTyp"] + }, + "DocDtls": { + "type": "object", + "properties": { + "Typ": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "enum": ["INV", "CRN", "DBN"] + }, + "No": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$" + }, + "Dt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + } + }, + "required": ["Typ", "No", "Dt"] + }, + "SellerDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "pattern": "([0-9]{2}[0-9A-Z]{13})" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Addr1": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 50 + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999 + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2 + }, + "Ph": { + "type": "string", + "minLength": 6, + "maxLength": 12 + }, + "Em": { + "type": "string", + "minLength": 6, + "maxLength": 100 + } + }, + "required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "BuyerDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "minLength": 3, + "maxLength": 15, + "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Pos": { + "type": "string", + "minLength": 1, + "maxLength": 2 + }, + "Addr1": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999 + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2 + }, + "Ph": { + "type": "string", + "minLength": 6, + "maxLength": 12 + }, + "Em": { + "type": "string", + "minLength": 6, + "maxLength": 100 + } + }, + "required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"] + }, + "DispDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Addr1": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999 + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2 + } + }, + "required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "ShipDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "maxLength": 15, + "minLength": 3, + "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Addr1": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999 + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2 + } + }, + "required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "ItemList": [ + { + "type": "object", + "properties": { + "SlNo": { + "type": "string", + "minLength": 1, + "maxLength": 6 + }, + "PrdDesc": { + "type": "string", + "minLength": 3, + "maxLength": 300 + }, + "IsServc": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"] + }, + "HsnCd": { + "type": "string", + "minLength": 4, + "maxLength": 8 + }, + "Barcde": { + "type": "string", + "minLength": 3, + "maxLength": 30 + }, + "Qty": { + "type": "number", + "minimum": 0, + "maximum": 9999999999.999 + }, + "FreeQty": { + "type": "number", + "minimum": 0, + "maximum": 9999999999.999 + }, + "Unit": { + "type": "string", + "minLength": 3, + "maxLength": 8 + }, + "UnitPrice": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.999 + }, + "TotAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "Discount": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "PreTaxVal": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "AssAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "GstRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999 + }, + "IgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "CgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "SgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "CesRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999 + }, + "CesAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "CesNonAdvlAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "StateCesRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999 + }, + "StateCesAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "StateCesNonAdvlAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "OthChrg": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "TotItemVal": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "OrdLineRef": { + "type": "string", + "minLength": 1, + "maxLength": 50 + }, + "OrgCntry": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "PrdSlNo": { + "type": "string", + "minLength": 1, + "maxLength": 20 + }, + "BchDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 3, + "maxLength": 20 + }, + "ExpDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + }, + "WrDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + } + }, + "required": ["Nm"] + }, + "AttribDtls": { + "type": "Array", + "Attribute": [ + { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "Val": { + "type": "string", + "minLength": 1, + "maxLength": 100 + } + } + } + ] + } + }, + "required": [ + "SlNo", + "IsServc", + "HsnCd", + "UnitPrice", + "TotAmt", + "AssAmt", + "GstRt", + "TotItemVal" + ] + } + ], + "ValDtls": { + "type": "object", + "properties": { + "AssVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + }, + "CgstVal": { + "type": "number", + "maximum": 99999999999999.99, + "minimum": 0 + }, + "SgstVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + }, + "IgstVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + }, + "CesVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + }, + "StCesVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + }, + "Discount": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + }, + "OthChrg": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + }, + "RndOffAmt": { + "type": "number", + "minimum": 0, + "maximum": 99.99 + }, + "TotInvVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + }, + "TotInvValFc": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + } + }, + "required": ["AssVal", "TotInvVal"] + }, + "PayDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "AccDet": { + "type": "string", + "minLength": 1, + "maxLength": 18 + }, + "Mode": { + "type": "string", + "minLength": 1, + "maxLength": 18 + }, + "FinInsBr": { + "type": "string", + "minLength": 1, + "maxLength": 11 + }, + "PayTerm": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "PayInstr": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "CrTrn": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "DirDr": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "CrDay": { + "type": "number", + "minimum": 0, + "maximum": 9999 + }, + "PaidAmt": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + }, + "PaymtDue": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99 + } + } + }, + "RefDtls": { + "type": "object", + "properties": { + "InvRm": { + "type": "string", + "maxLength": 100, + "minLength": 3, + "pattern": "^[0-9A-Za-z/-]{3,100}$" + }, + "DocPerdDtls": { + "type": "object", + "properties": { + "InvStDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + }, + "InvEndDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + } + }, + "required": ["InvStDt", "InvEndDt"] + }, + "PrecDocDtls": { + "type": "Array", + "PrecDocument": [ + { + "type": "object", + "properties": { + "InvNo": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$" + }, + "InvDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + }, + "OthRefNo": { + "type": "string", + "minLength": 1, + "maxLength": 20 + } + } + } + ], + "required": ["InvNo", "InvDt"] + }, + "ContrDtls": { + "type": "Array", + "Contract": [ + { + "type": "object", + "properties": { + "RecAdvRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$" + }, + "RecAdvDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + }, + "TendRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$" + }, + "ContrRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$" + }, + "ExtRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$" + }, + "ProjRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$" + }, + "PORefr": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^([0-9A-Za-z/-]){1,16}$" + }, + "PORefDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + } + } + } + ] + } + }, + "required": ["InvStDt", "InvEndDt"] + }, + "AddlDocDtls": { + "type": "Array", + "AddlDocument": [ + { + "type": "object", + "properties": { + "Url": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Docs": { + "type": "string", + "minLength": 3, + "maxLength": 1000 + }, + "Info": { + "type": "string", + "minLength": 3, + "maxLength": 1000 + } + } + } + ] + }, + "ExpDtls": { + "type": "object", + "properties": { + "ShipBNo": { + "type": "string", + "minLength": 1, + "maxLength": 20 + }, + "ShipBDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + }, + "Port": { + "type": "string", + "minLength": 2, + "maxLength": 10, + "pattern": "^[0-9A-Za-z]{2,10}$" + }, + "RefClm": { + "type": "string", + "minLength": 1, + "maxLength": 1 + }, + "ForCur": { + "type": "string", + "minLength": 3, + "maxLength": 16 + }, + "CntCode": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "ExpDuty": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + } + } + }, + "EwbDtls": { + "type": "object", + "properties": { + "TransId": { + "type": "string", + "minLength": 15, + "maxLength": 15 + }, + "TransName": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "TransMode": { + "type": "string", + "maxLength": 1, + "minLength": 1, + "enum": ["1", 2, 3, 4] + }, + "Distance": { + "type": "number", + "minimum": 1, + "maximum": 9999 + }, + "TransDocNo": { + "type": "string", + "minLength": 1, + "maxLength": 15, + "pattern": "^([0-9A-Z/-]){1,15}$" + }, + "TransDocDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + }, + "VehNo": { + "type": "string", + "minLength": 4, + "maxLength": 20 + }, + "VehType": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["O", "R"] + } + }, + "required": ["Distance"] + }, + "required": [ + "Version", + "TranDtls", + "DocDtls", + "SellerDtls", + "BuyerDtls", + "ItemList", + "ValDtls" + ] +} diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js index 6d7eccceece..4dc7988ec5e 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js @@ -16,6 +16,19 @@ frappe.ui.form.on('E Invoice Settings', { callback: (res) => console.log(res) }); }); + + frm.add_custom_button(__("Generate IRN"), + () => { + frm.call({ + doc: frm.doc, + method: 'generate_irn', + args: { + 'invoice': 'SINV-20-21-0044' + }, + freeze: true, + callback: (res) => console.log(res) + }); + }); }, show_fetch_token_btn(frm) { diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index 50725091bf0..87d06f45bce 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import os +import re import json import base64 import frappe @@ -13,8 +14,8 @@ from Crypto.Cipher import PKCS1_v1_5, AES from Crypto.Util.Padding import pad, unpad from frappe.model.document import Document from frappe import _, get_module_path, scrub -from frappe.utils.data import get_datetime, cstr from erpnext.regional.india.utils import get_gst_accounts +from frappe.utils.data import get_datetime, cstr, cint, format_date from frappe.integrations.utils import make_post_request, make_get_request class EInvoiceSettings(Document): @@ -53,6 +54,17 @@ class EInvoiceSettings(Document): if encode_as_b64: msg_bytes = base64.b64encode(msg_bytes) return msg_bytes.decode() + + def aes_encrypt(self, msg, key): + if not (isinstance(key, bytes) or isinstance(key, bytearray)): + key = base64.b64decode(key) + + cipher = AES.new(key, AES.MODE_ECB) + bytes_msg = str.encode(msg) + padded_bytes_msg = pad(bytes_msg, AES.block_size) + enc_msg = cipher.encrypt(padded_bytes_msg) + b64_enc_msg = base64.b64encode(enc_msg) + return b64_enc_msg.decode() def make_authentication_request(self): endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' @@ -85,12 +97,44 @@ class EInvoiceSettings(Document): self.sek = sek self.save() - def get_gstin_details(self, gstin): - endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/Master/gstin/{gstin}'.format(gstin=gstin) + def get_header(self): headers = { 'content-type': 'application/json' } headers.update(dict(client_id=self.client_id, client_secret=self.client_secret, user_name=self.username)) headers.update(dict(Gstin=self.gstin, AuthToken=self.auth_token)) + return headers + + def get_gstin_details(self, gstin): + endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/Master/gstin/{gstin}'.format(gstin=gstin) + headers = self.get_header() + + res = make_get_request(endpoint, headers=headers) + self.handle_err_response(res) + + enc_json = res.get('Data') + json_str = self.aes_decrypt(enc_json, self.sek) + data = json.loads(json_str) + + return data + + def generate_irn(self, invoice): + endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice' + headers = self.get_header() + + invoice = frappe.get_doc("Sales Invoice", invoice) + e_invoice = self.make_e_invoice(invoice) + enc_e_invoice_json = self.aes_encrypt(e_invoice, self.sek) + payload = dict(Data=enc_e_invoice_json) + + res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) + self.handle_err_response(res) + + return res + + def get_irn_detials(self, irn): + endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/irn/{irn}'.format(irn=irn) + headers = self.get_header() + res = make_get_request(endpoint, headers=headers) self.handle_err_response(res) @@ -102,12 +146,13 @@ class EInvoiceSettings(Document): def handle_err_response(self, response): if response.get('Status') == 0: + print(response) err_msg = response.get('ErrorDetails')[0].get('ErrorMessage') frappe.throw(_(err_msg), title=_('API Request Failed')) - def get_schema(self, name): - schema_path = os.path.join(os.path.dirname(__file__), "{name}_schema.json".format(name=name)) - with open(schema_path, 'r') as f: + def read_json(self, name): + file_path = os.path.join(os.path.dirname(__file__), "{name}.json".format(name=name)) + with open(file_path, 'r') as f: return cstr(f.read()) def get_trans_details(self, invoice): @@ -127,7 +172,7 @@ class EInvoiceSettings(Document): def get_doc_details(self, invoice): invoice_type = 'CRN' if invoice.is_return else 'INV' invoice_name = invoice.name - invoice_date = invoice.posting_date + invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') return frappe._dict(dict( invoice_type=invoice_type, invoice_name=invoice_name, invoice_date=invoice_date @@ -142,7 +187,7 @@ class EInvoiceSettings(Document): trade_name = gstin_details.get('TradeName') location = gstin_details.get('AddrLoc') state_code = gstin_details.get('StateCode') - pincode = gstin_details.get('AddrPncd') + pincode = cint(gstin_details.get('AddrPncd')) return frappe._dict(dict( gstin=gstin, legal_name=legal_name, trade_name=trade_name, location=location, @@ -156,13 +201,14 @@ class EInvoiceSettings(Document): gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] for d in invoice.items: - item_schema = self.get_schema("e_inv_item") + item_schema = self.read_json("e_inv_item_schema") item = frappe._dict(dict()) item.update(d.as_dict()) item.sr_no = d.idx item.description = d.item_name item.is_service_item = "N" if frappe.db.get_value("Item", d.item_code, "is_stock_item") else "Y" item.batch_expiry_date = frappe.db.get_value("Batch", d.batch_no, "expiry_date") if d.batch_no else None + item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None item.tax_rate = 0 item.igst_amount = 0 item.cgst_amount = 0 @@ -219,7 +265,9 @@ class EInvoiceSettings(Document): return value_details def make_e_invoice(self, invoice): - schema = self.get_schema("e_inv") + schema = self.read_json("e_inv_schema") + validations = self.read_json("e_inv_validation") + validations = json.loads(validations) trans_details = self.get_trans_details(invoice) doc_details = self.get_doc_details(invoice) @@ -236,5 +284,55 @@ class EInvoiceSettings(Document): seller_details=seller_details, buyer_details=buyer_details, item_list=item_list, value_details=value_details ) + e_invoice = json.loads(e_invoice) - return json.loads(e_invoice) \ No newline at end of file + self.run_e_invoice_validations(validations, e_invoice) + + return json.dumps(e_invoice) + + def run_e_invoice_validations(self, validations, e_invoice): + type_map = { + "string": cstr, + "number": cint, + "object": dict, + "array": list + } + # validate root mandatory keys + mandatory_fields = validations.get('required') + if mandatory_fields and not set(mandatory_fields).issubset(set(e_invoice.keys())): + print("Mandatory condition failed") + + for field, value in validations.items(): + if isinstance(value, list): value = value[0] + + invoice_value = e_invoice.get(field) + if not invoice_value: + print(field, "Value undefined") + continue + + should_be_of_type = type_map[value.get('type').lower()] + if should_be_of_type == dict: + properties = value.get('properties') + + if isinstance(invoice_value, list): + for d in invoice_value: + self.run_e_invoice_validations(properties, d) + else: + self.run_e_invoice_validations(properties, invoice_value) + continue + + e_invoice[field] = None if invoice_value == "None" else invoice_value + e_invoice[field] = should_be_of_type(invoice_value) if e_invoice[field] else e_invoice[field] + + should_be_of_len = value.get('maxLength') + should_be_greater_than = value.get('minimum') + should_be_less_than = value.get('maximum') + pattern_str = value.get('pattern') + pattern = re.compile(pattern_str or "") + + if should_be_of_type == 'string' and not len(invoice_value) <= should_be_of_len: + print("Max Length Exceeded", field, invoice_value) + if should_be_of_type == 'number' and not (should_be_greater_than <= invoice_value <= should_be_of_len): + print("Value too large", field, invoice_value) + if pattern_str and not pattern.match(invoice_value): + print("Pattern Mismatch", field, invoice_value) From d9e4ebc00cddfaa068e5e8f5964a93eb02a1e683 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 12:47:37 +0530 Subject: [PATCH 010/137] feat: decode signed json and QR code --- .../e_invoice_settings/e_invoice_settings.js | 13 ++++++++++++ .../e_invoice_settings/e_invoice_settings.py | 21 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js index 4dc7988ec5e..23415bb60a6 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js @@ -29,6 +29,19 @@ frappe.ui.form.on('E Invoice Settings', { callback: (res) => console.log(res) }); }); + + frm.add_custom_button(__("Fetch IRN Details"), + () => { + frm.call({ + doc: frm.doc, + method: 'get_irn_details', + args: { + 'irn': 'c63d9e180dfdaa9242e29e2e1e0a8d76f20e116ed3de179a2e9120f384e1b432' + }, + freeze: true, + callback: (res) => console.log(res) + }); + }); }, show_fetch_token_btn(frm) { diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index 87d06f45bce..b141971f76f 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import os import re +import jwt import json import base64 import frappe @@ -65,6 +66,9 @@ class EInvoiceSettings(Document): enc_msg = cipher.encrypt(padded_bytes_msg) b64_enc_msg = base64.b64encode(enc_msg) return b64_enc_msg.decode() + + def jwt_decrypt(self, token): + return jwt.decode(token, verify=False) def make_authentication_request(self): endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' @@ -129,9 +133,12 @@ class EInvoiceSettings(Document): res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) self.handle_err_response(res) - return res + data = json.loads(res) + self.handle_irn_response(data) + + return data - def get_irn_detials(self, irn): + def get_irn_details(self, irn): endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/irn/{irn}'.format(irn=irn) headers = self.get_header() @@ -140,9 +147,19 @@ class EInvoiceSettings(Document): enc_json = res.get('Data') json_str = self.aes_decrypt(enc_json, self.sek) + data = json.loads(json_str) + self.handle_irn_response(data) return data + + def handle_irn_response(self, data): + enc_signed_invoice = data['SignedInvoice'] + enc_signed_qr_code = data['SignedQRCode'] + signed_invoice = self.jwt_decrypt(enc_signed_invoice)['data'] + signed_qr_code = self.jwt_decrypt(enc_signed_qr_code)['data'] + data['DecryptedSignedInvoice'] = json.loads(signed_invoice) + data['DecryptedSignedQRCode'] = json.loads(signed_qr_code) def handle_err_response(self, response): if response.get('Status') == 0: From d72d2367abbbb3a12c926a8d3f7c78a969c8343d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 14:17:50 +0530 Subject: [PATCH 011/137] chore: validations --- .../e_invoice_settings/e_invoice_settings.js | 2 ++ .../e_invoice_settings/e_invoice_settings.json | 15 ++++++++++++++- .../e_invoice_settings/e_invoice_settings.py | 6 ++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js index 23415bb60a6..01b835fd5c9 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js @@ -3,6 +3,8 @@ frappe.ui.form.on('E Invoice Settings', { refresh: function(frm) { + if (!frm.doc.enable) return; + frm.trigger("show_fetch_token_btn"); frm.add_custom_button(__("Get GSTIN Details"), () => { diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json index 888406b534c..c94336cbbad 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json @@ -5,6 +5,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "enable", + "section_break_2", "client_id", "client_secret", "public_key_file", @@ -90,12 +92,23 @@ "fieldtype": "Data", "hidden": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "depends_on": "enable", + "fieldname": "section_break_2", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-09-25 21:57:58.187647", + "modified": "2020-09-29 14:06:36.385386", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "E Invoice Settings", diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index b141971f76f..02a6e98957f 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -9,7 +9,6 @@ import jwt import json import base64 import frappe -from frappe.utils import cstr from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5, AES from Crypto.Util.Padding import pad, unpad @@ -21,7 +20,10 @@ from frappe.integrations.utils import make_post_request, make_get_request class EInvoiceSettings(Document): def validate(self): - pass + mandatory_fields = ['client_id', 'client_secret', 'gstin', 'username', 'password', 'public_key'] + for d in mandatory_fields: + if not self.get(d): + frappe.throw(_("{} is required").format(frappe.unscrub(d)), title=_("Missing Values")) def before_save(self): if not self.public_key or self.has_value_changed('public_key_file'): From c9b6db27cfd3ebe492e976a7ec10f9613b5703fe Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 14:23:48 +0530 Subject: [PATCH 012/137] feat: cancel IRN --- .../e_invoice_settings/e_invoice_settings.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index 02a6e98957f..270e62e0022 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -154,6 +154,19 @@ class EInvoiceSettings(Document): self.handle_irn_response(data) return data + + def cancel_irn(self, irn, reason, remark=''): + endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/Cancel' + headers = self.get_header() + + cancel_e_inv = json.dumps(dict(Irn=irn, CnlRsn=reason, CnlRem=remark)) + enc_json = self.aes_encrypt(cancel_e_inv, self.sek) + payload = dict(Data=enc_json) + + res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) + self.handle_err_response(res) + + return res def handle_irn_response(self, data): enc_signed_invoice = data['SignedInvoice'] From 959942087898454d44fba0997c68d74216012b92 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 16:25:08 +0530 Subject: [PATCH 013/137] feat: complete e-invoice schema --- .../e_invoice_settings/e_inv_item_schema.json | 1 + .../e_invoice_settings/e_inv_schema.json | 59 ++++++- .../e_invoice_settings/e_inv_validation.json | 148 ++++++++---------- .../e_invoice_settings/e_invoice_settings.js | 4 +- .../e_invoice_settings/e_invoice_settings.py | 85 ++++++++-- 5 files changed, 205 insertions(+), 92 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_item_schema.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_item_schema.json index f98e4e5c94e..b4f54448326 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_item_schema.json +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_item_schema.json @@ -6,6 +6,7 @@ "Barcde": "{item.barcode}", "Unit": "{item.uom}", "Qty": "{item.qty}", + "FreeQty": "{item.free_qty}", "UnitPrice": "{item.base_rate}", "TotAmt": "{item.base_amount}", "Discount": "{item.discount_amount}", diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_schema.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_schema.json index d194b45bf9e..15230fe22f9 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_schema.json +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_schema.json @@ -3,7 +3,9 @@ "TranDtls": {{ "TaxSch": "{trans_details.tax_scheme}", "SupTyp": "{trans_details.supply_type}", - "RegRev": "{trans_details.reverse_charge}" + "RegRev": "{trans_details.reverse_charge}", + "EcmGstin": "{trans_details.ecom_gstin}", + "IgstOnIntra": "{trans_details.igst_on_intra}" }}, "DocDtls": {{ "Typ": "{doc_details.invoice_type}", @@ -35,6 +37,24 @@ "Em": "{buyer_details.email}", "Pos": "{buyer_details.place_of_supply}" }}, + "DispDtls": {{ + "Nm": "{dispatch_details.company_name}", + "Addr1": "{dispatch_details.address_line1}", + "Addr2": "{dispatch_details.address_line2}", + "Loc": "{dispatch_details.location}", + "Pin": "{dispatch_details.pincode}", + "Stcd": "{dispatch_details.state_code}" + }}, + "ShipDtls": {{ + "Gstin": "{shipping_details.gstin}", + "LglNm": "{shipping_details.legal_name}", + "TrdNm": "{shipping_details.trader_name}", + "Addr1": "{shipping_details.address_line1}", + "Addr2": "{shipping_details.address_line2}", + "Loc": "{shipping_details.location}", + "Pin": "{shipping_details.pincode}", + "Stcd": "{shipping_details.state_code}" + }}, "ItemList": [ {item_list} ], @@ -48,5 +68,42 @@ "RndOffAmt": "{value_details.round_off}", "TotInvVal": "{value_details.base_grand_total}", "TotInvValFc": "{value_details.grand_total}" + }}, + "PayDtls": {{ + "Nm": "{payment_details.payee_name}", + "AccDet": "{payment_details.account_no}", + "Mode": "{payment_details.mode_of_payment}", + "FinInsBr": "{payment_details.ifsc_code}", + "PayTerm": "{payment_details.terms}", + "PaidAmt": "{payment_details.paid_amount}", + "PaymtDue": "{payment_details.outstanding_amount}" + }}, + "RefDtls": {{ + "DocPerdDtls": {{ + "InvStDt": "{period_details.start_date}", + "InvEndDt": "{period_details.end_date}" + }}, + "PrecDocDtls": {{ + "InvNo": "{prev_doc_details.invoice_name}", + "InvDt": "{prev_doc_details.invoice_date}" + }} + }}, + "ExpDtls": {{ + "ShipBNo": "{export_details.bill_no}", + "ShipBDt": "{export_details.bill_date}", + "Port": "{export_details.port}", + "ForCur": "{export_details.foreign_curr_code}", + "CntCode": "{export_details.country_code}", + "ExpDuty": "{export_details.export_duty}" + }}, + "EwbDtls": {{ + "TransId": "{eway_bill_details.gstin}", + "TransName": "{eway_bill_details.name}", + "TransMode": "{eway_bill_details.mode_of_transport}", + "Distance": "{eway_bill_details.distance}", + "TransDocNo": "{eway_bill_details.document_name}", + "TransDocDt": "{eway_bill_details.document_date}", + "VehNo": "{eway_bill_details.vehicle_no}", + "VehType": "{eway_bill_details.vehicle_type}" }} }} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_validation.json b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_validation.json index 51da5943637..da224d9633f 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_validation.json +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_validation.json @@ -621,90 +621,80 @@ "required": ["InvStDt", "InvEndDt"] }, "PrecDocDtls": { - "type": "Array", - "PrecDocument": [ - { - "type": "object", - "properties": { - "InvNo": { - "type": "string", - "minLength": 1, - "maxLength": 16, - "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$" - }, - "InvDt": { - "type": "string", - "maxLength": 10, - "minLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" - }, - "OthRefNo": { - "type": "string", - "minLength": 1, - "maxLength": 20 - } - } + "type": "object", + "properties": { + "InvNo": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$" + }, + "InvDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + }, + "OthRefNo": { + "type": "string", + "minLength": 1, + "maxLength": 20 } - ], + }, "required": ["InvNo", "InvDt"] }, "ContrDtls": { - "type": "Array", - "Contract": [ - { - "type": "object", - "properties": { - "RecAdvRefr": { - "type": "string", - "minLength": 1, - "maxLength": 20, - "pattern": "^([0-9A-Za-z/-]){1,20}$" - }, - "RecAdvDt": { - "type": "string", - "minLength": 10, - "maxLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" - }, - "TendRefr": { - "type": "string", - "minLength": 1, - "maxLength": 20, - "pattern": "^([0-9A-Za-z/-]){1,20}$" - }, - "ContrRefr": { - "type": "string", - "minLength": 1, - "maxLength": 20, - "pattern": "^([0-9A-Za-z/-]){1,20}$" - }, - "ExtRefr": { - "type": "string", - "minLength": 1, - "maxLength": 20, - "pattern": "^([0-9A-Za-z/-]){1,20}$" - }, - "ProjRefr": { - "type": "string", - "minLength": 1, - "maxLength": 20, - "pattern": "^([0-9A-Za-z/-]){1,20}$" - }, - "PORefr": { - "type": "string", - "minLength": 1, - "maxLength": 16, - "pattern": "^([0-9A-Za-z/-]){1,16}$" - }, - "PORefDt": { - "type": "string", - "minLength": 10, - "maxLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" - } - } + "type": "object", + "properties": { + "RecAdvRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$" + }, + "RecAdvDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + }, + "TendRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$" + }, + "ContrRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$" + }, + "ExtRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$" + }, + "ProjRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$" + }, + "PORefr": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^([0-9A-Za-z/-]){1,16}$" + }, + "PORefDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" } - ] + } } }, "required": ["InvStDt", "InvEndDt"] diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js index 01b835fd5c9..c97ebed013f 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js @@ -4,7 +4,7 @@ frappe.ui.form.on('E Invoice Settings', { refresh: function(frm) { if (!frm.doc.enable) return; - + frm.trigger("show_fetch_token_btn"); frm.add_custom_button(__("Get GSTIN Details"), () => { @@ -25,7 +25,7 @@ frappe.ui.form.on('E Invoice Settings', { doc: frm.doc, method: 'generate_irn', args: { - 'invoice': 'SINV-20-21-0044' + 'invoice': 'SINV-20-21-0051' }, freeze: true, callback: (res) => console.log(res) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py index 270e62e0022..b795296a6a6 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py @@ -129,13 +129,17 @@ class EInvoiceSettings(Document): invoice = frappe.get_doc("Sales Invoice", invoice) e_invoice = self.make_e_invoice(invoice) + enc_e_invoice_json = self.aes_encrypt(e_invoice, self.sek) payload = dict(Data=enc_e_invoice_json) res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) self.handle_err_response(res) - data = json.loads(res) + enc_json = res.get('Data') + json_str = self.aes_decrypt(enc_json, self.sek) + + data = json.loads(json_str) self.handle_irn_response(data) return data @@ -178,8 +182,12 @@ class EInvoiceSettings(Document): def handle_err_response(self, response): if response.get('Status') == 0: + err_details = response.get('ErrorDetails') print(response) - err_msg = response.get('ErrorDetails')[0].get('ErrorMessage') + err_msg = "" + for d in err_details: + err_msg += d.get('ErrorMessage') + err_msg += "
" frappe.throw(_(err_msg), title=_('API Request Failed')) def read_json(self, name): @@ -220,6 +228,8 @@ class EInvoiceSettings(Document): location = gstin_details.get('AddrLoc') state_code = gstin_details.get('StateCode') pincode = cint(gstin_details.get('AddrPncd')) + if state_code == 97: + pincode = 999999 return frappe._dict(dict( gstin=gstin, legal_name=legal_name, trade_name=trade_name, location=location, @@ -227,6 +237,17 @@ class EInvoiceSettings(Document): address_line2=address_line2, email=email_id, phone=phone )) + def get_overseas_address_details(self, party_address): + address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value( + "Address", party_address, ["address_title", "address_line1", "address_line2", "city", "phone", "email_id"] + ) + + return frappe._dict(dict( + gstin='URP', legal_name=address_title, address_line1=address_line1, + address_line2=address_line2, email=email_id, phone=phone, + pincode=999999, state_code=96, place_of_supply=96, location=city + )) + def get_item_list(self, invoice): item_list = [] gst_accounts = get_gst_accounts(invoice.company) @@ -295,6 +316,23 @@ class EInvoiceSettings(Document): value_details.total_cgst_amt += t.base_tax_amount return value_details + + def get_payment_details(self, invoice): + payee_name = invoice.company + mode_of_payment = ", ".join([d.mode_of_payment for d in invoice.payments]) + paid_amount = invoice.base_paid_amount + outstanding_amount = invoice.outstanding_amount + + return frappe._dict(dict( + payee_name=payee_name, mode_of_payment=mode_of_payment, + paid_amount=paid_amount, outstanding_amount=outstanding_amount + )) + + def get_return_doc_reference(self, invoice): + invoice_date = frappe.db.get_value("Sales Invoice", invoice.return_against, "posting_date") + return frappe._dict(dict( + invoice_name=invoice.return_against, invoice_date=invoice_date + )) def make_e_invoice(self, invoice): schema = self.read_json("e_inv_schema") @@ -304,17 +342,39 @@ class EInvoiceSettings(Document): trans_details = self.get_trans_details(invoice) doc_details = self.get_doc_details(invoice) seller_details = self.get_party_gstin_details(invoice.company_address) - buyer_details = self.get_party_gstin_details(invoice.customer_address) - place_of_supply = invoice.place_of_supply.split('-')[0] - buyer_details.update(dict(place_of_supply=place_of_supply)) + + if invoice.gst_category == 'Overseas': + buyer_details = self.get_overseas_address_details(invoice.customer_address) + else: + buyer_details = self.get_party_gstin_details(invoice.customer_address) + place_of_supply = invoice.place_of_supply.split('-')[0] + buyer_details.update(dict(place_of_supply=place_of_supply)) item_list = self.get_item_list(invoice) value_details = self.get_value_details(invoice) + dispatch_details = frappe._dict({}) + period_details = frappe._dict({}) + shipping_details = frappe._dict({}) + export_details = frappe._dict({}) + eway_bill_details = frappe._dict({}) + if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: + shipping_details = self.get_party_gstin_details(invoice.shipping_address_name) + + payment_details = frappe._dict({}) + if invoice.is_pos and invoice.base_paid_amount: + payment_details = self.get_payment_details(invoice) + + prev_doc_details = frappe._dict({}) + if invoice.is_return and invoice.return_against: + prev_doc_details = self.get_return_doc_reference(invoice) + e_invoice = schema.format( - trans_details=trans_details, doc_details=doc_details, - seller_details=seller_details, buyer_details=buyer_details, - item_list=item_list, value_details=value_details + trans_details=trans_details, doc_details=doc_details, dispatch_details=dispatch_details, + seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details, + item_list=item_list, value_details=value_details, payment_details=payment_details, + period_details=period_details, prev_doc_details=prev_doc_details, + export_details=export_details, eway_bill_details=eway_bill_details ) e_invoice = json.loads(e_invoice) @@ -339,7 +399,7 @@ class EInvoiceSettings(Document): invoice_value = e_invoice.get(field) if not invoice_value: - print(field, "Value undefined") + print(field, "value undefined") continue should_be_of_type = type_map[value.get('type').lower()] @@ -351,9 +411,14 @@ class EInvoiceSettings(Document): self.run_e_invoice_validations(properties, d) else: self.run_e_invoice_validations(properties, invoice_value) + if not invoice_value: + e_invoice.pop(field, None) continue - e_invoice[field] = None if invoice_value == "None" else invoice_value + if invoice_value == "None": + e_invoice.pop(field, None) + continue + e_invoice[field] = should_be_of_type(invoice_value) if e_invoice[field] else e_invoice[field] should_be_of_len = value.get('maxLength') From a367cc5c9911eebd6f2db18be0dabddc17cb5348 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 17:10:22 +0530 Subject: [PATCH 014/137] chore: move e-invoice settings to regional --- .../doctype/e_invoice_settings/__init__.py | 0 .../e_invoice_settings/e_inv_item_schema.json | 0 .../e_invoice_settings/e_inv_schema.json | 0 .../e_invoice_settings/e_inv_validation.json | 0 .../e_invoice_settings/e_invoice_settings.js | 0 .../e_invoice_settings.json | 38 +++++++++---------- .../e_invoice_settings/e_invoice_settings.py | 0 .../test_e_invoice_settings.py | 0 8 files changed, 19 insertions(+), 19 deletions(-) rename erpnext/{erpnext_integrations => regional}/doctype/e_invoice_settings/__init__.py (100%) rename erpnext/{erpnext_integrations => regional}/doctype/e_invoice_settings/e_inv_item_schema.json (100%) rename erpnext/{erpnext_integrations => regional}/doctype/e_invoice_settings/e_inv_schema.json (100%) rename erpnext/{erpnext_integrations => regional}/doctype/e_invoice_settings/e_inv_validation.json (100%) rename erpnext/{erpnext_integrations => regional}/doctype/e_invoice_settings/e_invoice_settings.js (100%) rename erpnext/{erpnext_integrations => regional}/doctype/e_invoice_settings/e_invoice_settings.json (96%) rename erpnext/{erpnext_integrations => regional}/doctype/e_invoice_settings/e_invoice_settings.py (100%) rename erpnext/{erpnext_integrations => regional}/doctype/e_invoice_settings/test_e_invoice_settings.py (100%) diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/__init__.py b/erpnext/regional/doctype/e_invoice_settings/__init__.py similarity index 100% rename from erpnext/erpnext_integrations/doctype/e_invoice_settings/__init__.py rename to erpnext/regional/doctype/e_invoice_settings/__init__.py diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_item_schema.json b/erpnext/regional/doctype/e_invoice_settings/e_inv_item_schema.json similarity index 100% rename from erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_item_schema.json rename to erpnext/regional/doctype/e_invoice_settings/e_inv_item_schema.json diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_schema.json b/erpnext/regional/doctype/e_invoice_settings/e_inv_schema.json similarity index 100% rename from erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_schema.json rename to erpnext/regional/doctype/e_invoice_settings/e_inv_schema.json diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_validation.json b/erpnext/regional/doctype/e_invoice_settings/e_inv_validation.json similarity index 100% rename from erpnext/erpnext_integrations/doctype/e_invoice_settings/e_inv_validation.json rename to erpnext/regional/doctype/e_invoice_settings/e_inv_validation.json diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js similarity index 100% rename from erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.js rename to erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json similarity index 96% rename from erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json rename to erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json index c94336cbbad..3df9243925c 100644 --- a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -21,6 +21,17 @@ "sek" ], "fields": [ + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "depends_on": "enable", + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, { "fieldname": "client_id", "fieldtype": "Data", @@ -40,6 +51,12 @@ "label": "Public Key", "reqd": 1 }, + { + "fieldname": "public_key", + "fieldtype": "Long Text", + "hidden": 1, + "read_only": 1 + }, { "fieldname": "column_break_3", "fieldtype": "Column Break" @@ -62,12 +79,6 @@ "label": "Password", "reqd": 1 }, - { - "fieldname": "public_key", - "fieldtype": "Long Text", - "hidden": 1, - "read_only": 1 - }, { "default": "0", "description": "Token will be automatically refreshed 10 mins before expiry", @@ -92,25 +103,14 @@ "fieldtype": "Data", "hidden": 1, "read_only": 1 - }, - { - "default": "0", - "fieldname": "enable", - "fieldtype": "Check", - "label": "Enable" - }, - { - "depends_on": "enable", - "fieldname": "section_break_2", - "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-09-29 14:06:36.385386", + "modified": "2020-09-29 17:05:33.512227", "modified_by": "Administrator", - "module": "ERPNext Integrations", + "module": "Regional", "name": "E Invoice Settings", "owner": "Administrator", "permissions": [ diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py similarity index 100% rename from erpnext/erpnext_integrations/doctype/e_invoice_settings/e_invoice_settings.py rename to erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py diff --git a/erpnext/erpnext_integrations/doctype/e_invoice_settings/test_e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py similarity index 100% rename from erpnext/erpnext_integrations/doctype/e_invoice_settings/test_e_invoice_settings.py rename to erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py From 4d155067913c38a337c95b165babb04ff0af540f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 17:36:55 +0530 Subject: [PATCH 015/137] chore: split einvoice settings and operations --- .../e_invoice_settings/e_invoice_settings.py | 416 +---------------- erpnext/regional/india/e_invoice_utils.py | 431 ++++++++++++++++++ .../einv_item_schema.json} | 0 .../einv_schema.json} | 0 .../einv_validation.json} | 0 5 files changed, 434 insertions(+), 413 deletions(-) create mode 100644 erpnext/regional/india/e_invoice_utils.py rename erpnext/regional/{doctype/e_invoice_settings/e_inv_item_schema.json => india/einv_item_schema.json} (100%) rename erpnext/regional/{doctype/e_invoice_settings/e_inv_schema.json => india/einv_schema.json} (100%) rename erpnext/regional/{doctype/e_invoice_settings/e_inv_validation.json => india/einv_validation.json} (100%) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py index b795296a6a6..1d86208347d 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -3,20 +3,10 @@ # For license information, please see license.txt from __future__ import unicode_literals -import os -import re -import jwt -import json -import base64 import frappe -from Crypto.PublicKey import RSA -from Crypto.Cipher import PKCS1_v1_5, AES -from Crypto.Util.Padding import pad, unpad +from frappe import _ +from frappe.utils.data import cstr from frappe.model.document import Document -from frappe import _, get_module_path, scrub -from erpnext.regional.india.utils import get_gst_accounts -from frappe.utils.data import get_datetime, cstr, cint, format_date -from frappe.integrations.utils import make_post_request, make_get_request class EInvoiceSettings(Document): def validate(self): @@ -32,404 +22,4 @@ class EInvoiceSettings(Document): def read_key_file(self): key_file = frappe.get_doc('File', dict(attached_to_name=self.doctype, attached_to_field='public_key_file')) with open(key_file.get_full_path(), 'rb') as f: - return cstr(f.read()) - - def rsa_encrypt(self, msg, key): - if not (isinstance(msg, bytes) or isinstance(msg, bytearray)): - msg = str.encode(msg) - - rsa_pub_key = RSA.import_key(key) - cipher = PKCS1_v1_5.new(rsa_pub_key) - enc_msg = cipher.encrypt(msg) - b64_enc_msg = base64.b64encode(enc_msg) - return b64_enc_msg.decode() - - def aes_decrypt(self, enc_msg, key): - encode_as_b64 = True - if not (isinstance(key, bytes) or isinstance(key, bytearray)): - key = base64.b64decode(key) - encode_as_b64 = False - - cipher = AES.new(key, AES.MODE_ECB) - b64_enc_msg = base64.b64decode(enc_msg) - msg_bytes = cipher.decrypt(b64_enc_msg) - msg_bytes = unpad(msg_bytes, AES.block_size) # due to ECB/PKCS5Padding - if encode_as_b64: - msg_bytes = base64.b64encode(msg_bytes) - return msg_bytes.decode() - - def aes_encrypt(self, msg, key): - if not (isinstance(key, bytes) or isinstance(key, bytearray)): - key = base64.b64decode(key) - - cipher = AES.new(key, AES.MODE_ECB) - bytes_msg = str.encode(msg) - padded_bytes_msg = pad(bytes_msg, AES.block_size) - enc_msg = cipher.encrypt(padded_bytes_msg) - b64_enc_msg = base64.b64encode(enc_msg) - return b64_enc_msg.decode() - - def jwt_decrypt(self, token): - return jwt.decode(token, verify=False) - - def make_authentication_request(self): - endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' - headers = { 'content-type': 'application/json' } - headers.update(dict(client_id=self.client_id, client_secret=self.client_secret)) - payload = dict(UserName=self.username, ForceRefreshAccessToken=bool(self.auto_refresh_token)) - - appkey = bytearray(os.urandom(32)) - enc_appkey = self.rsa_encrypt(appkey, self.public_key) - - password = self.get_password(fieldname='password') - enc_password = self.rsa_encrypt(password, self.public_key) - - payload.update(dict(Password=enc_password, AppKey=enc_appkey)) - - res = make_post_request(endpoint, headers=headers, data=json.dumps({ 'data': payload })) - self.handle_err_response(res) - - self.extract_token_and_sek(res, appkey) - - def extract_token_and_sek(self, response, appkey): - data = response.get('Data') - auth_token = data.get('AuthToken') - token_expiry = data.get('TokenExpiry') - enc_sek = data.get('Sek') - sek = self.aes_decrypt(enc_sek, appkey) - - self.auth_token = auth_token - self.token_expiry = get_datetime(token_expiry) - self.sek = sek - self.save() - - def get_header(self): - headers = { 'content-type': 'application/json' } - headers.update(dict(client_id=self.client_id, client_secret=self.client_secret, user_name=self.username)) - headers.update(dict(Gstin=self.gstin, AuthToken=self.auth_token)) - - return headers - - def get_gstin_details(self, gstin): - endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/Master/gstin/{gstin}'.format(gstin=gstin) - headers = self.get_header() - - res = make_get_request(endpoint, headers=headers) - self.handle_err_response(res) - - enc_json = res.get('Data') - json_str = self.aes_decrypt(enc_json, self.sek) - data = json.loads(json_str) - - return data - - def generate_irn(self, invoice): - endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice' - headers = self.get_header() - - invoice = frappe.get_doc("Sales Invoice", invoice) - e_invoice = self.make_e_invoice(invoice) - - enc_e_invoice_json = self.aes_encrypt(e_invoice, self.sek) - payload = dict(Data=enc_e_invoice_json) - - res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) - self.handle_err_response(res) - - enc_json = res.get('Data') - json_str = self.aes_decrypt(enc_json, self.sek) - - data = json.loads(json_str) - self.handle_irn_response(data) - - return data - - def get_irn_details(self, irn): - endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/irn/{irn}'.format(irn=irn) - headers = self.get_header() - - res = make_get_request(endpoint, headers=headers) - self.handle_err_response(res) - - enc_json = res.get('Data') - json_str = self.aes_decrypt(enc_json, self.sek) - - data = json.loads(json_str) - self.handle_irn_response(data) - - return data - - def cancel_irn(self, irn, reason, remark=''): - endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/Cancel' - headers = self.get_header() - - cancel_e_inv = json.dumps(dict(Irn=irn, CnlRsn=reason, CnlRem=remark)) - enc_json = self.aes_encrypt(cancel_e_inv, self.sek) - payload = dict(Data=enc_json) - - res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) - self.handle_err_response(res) - - return res - - def handle_irn_response(self, data): - enc_signed_invoice = data['SignedInvoice'] - enc_signed_qr_code = data['SignedQRCode'] - signed_invoice = self.jwt_decrypt(enc_signed_invoice)['data'] - signed_qr_code = self.jwt_decrypt(enc_signed_qr_code)['data'] - data['DecryptedSignedInvoice'] = json.loads(signed_invoice) - data['DecryptedSignedQRCode'] = json.loads(signed_qr_code) - - def handle_err_response(self, response): - if response.get('Status') == 0: - err_details = response.get('ErrorDetails') - print(response) - err_msg = "" - for d in err_details: - err_msg += d.get('ErrorMessage') - err_msg += "
" - frappe.throw(_(err_msg), title=_('API Request Failed')) - - def read_json(self, name): - file_path = os.path.join(os.path.dirname(__file__), "{name}.json".format(name=name)) - with open(file_path, 'r') as f: - return cstr(f.read()) - - def get_trans_details(self, invoice): - supply_type = '' - if invoice.gst_category == 'Registered Regular': supply_type = 'B2B' - elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP' - elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP' - elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP' - - if not supply_type: - return _('Invalid invoice transaction category.') - - return frappe._dict(dict( - tax_scheme='GST', supply_type=supply_type, reverse_charge=invoice.reverse_charge - )) - - def get_doc_details(self, invoice): - invoice_type = 'CRN' if invoice.is_return else 'INV' - invoice_name = invoice.name - invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') - - return frappe._dict(dict( - invoice_type=invoice_type, invoice_name=invoice_name, invoice_date=invoice_date - )) - - def get_party_gstin_details(self, party_address): - gstin, address_line1, address_line2, phone, email_id = frappe.db.get_value( - "Address", party_address, ["gstin", "address_line1", "address_line2", "phone", "email_id"] - ) - gstin_details = self.get_gstin_details(gstin) - legal_name = gstin_details.get('LegalName') - trade_name = gstin_details.get('TradeName') - location = gstin_details.get('AddrLoc') - state_code = gstin_details.get('StateCode') - pincode = cint(gstin_details.get('AddrPncd')) - if state_code == 97: - pincode = 999999 - - return frappe._dict(dict( - gstin=gstin, legal_name=legal_name, trade_name=trade_name, location=location, - pincode=pincode, state_code=state_code, address_line1=address_line1, - address_line2=address_line2, email=email_id, phone=phone - )) - - def get_overseas_address_details(self, party_address): - address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value( - "Address", party_address, ["address_title", "address_line1", "address_line2", "city", "phone", "email_id"] - ) - - return frappe._dict(dict( - gstin='URP', legal_name=address_title, address_line1=address_line1, - address_line2=address_line2, email=email_id, phone=phone, - pincode=999999, state_code=96, place_of_supply=96, location=city - )) - - def get_item_list(self, invoice): - item_list = [] - gst_accounts = get_gst_accounts(invoice.company) - gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] - - for d in invoice.items: - item_schema = self.read_json("e_inv_item_schema") - item = frappe._dict(dict()) - item.update(d.as_dict()) - item.sr_no = d.idx - item.description = d.item_name - item.is_service_item = "N" if frappe.db.get_value("Item", d.item_code, "is_stock_item") else "Y" - item.batch_expiry_date = frappe.db.get_value("Batch", d.batch_no, "expiry_date") if d.batch_no else None - item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None - item.tax_rate = 0 - item.igst_amount = 0 - item.cgst_amount = 0 - item.sgst_amount = 0 - item.cess_rate = 0 - item.cess_amount = 0 - for t in invoice.taxes: - if t.account_head in gst_accounts_list: - item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) - if t.account_head in gst_accounts.cess_account: - item.cess_rate += item_tax_detail[0] - item.cess_amount += item_tax_detail[1] - elif t.account_head in gst_accounts.igst_account: - item.tax_rate += item_tax_detail[0] - item.igst_amount += item_tax_detail[1] - elif t.account_head in gst_accounts.sgst_account: - item.tax_rate += item_tax_detail[0] - item.sgst_amount += item_tax_detail[1] - elif t.account_head in gst_accounts.cgst_account: - item.tax_rate += item_tax_detail[0] - item.cgst_amount += item_tax_detail[1] - - item.total_value = item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount - e_inv_item = item_schema.format(item=item) - item_list.append(e_inv_item) - - return ", ".join(item_list) - - def get_value_details(self, invoice): - gst_accounts = get_gst_accounts(invoice.company) - gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] - - value_details = frappe._dict(dict()) - value_details.base_net_total = invoice.base_net_total - value_details.invoice_discount_amt = invoice.discount_amount - value_details.round_off = invoice.base_rounding_adjustment - value_details.base_grand_total = invoice.base_rounded_total - value_details.grand_total = invoice.rounded_total - value_details.total_cgst_amt = 0 - value_details.total_sgst_amt = 0 - value_details.total_igst_amt = 0 - value_details.total_cess_amt = 0 - for t in invoice.taxes: - if t.account_head in gst_accounts_list: - if t.account_head in gst_accounts.cess_account: - value_details.total_cess_amt += t.base_tax_amount - elif t.account_head in gst_accounts.igst_account: - value_details.total_igst_amt += t.base_tax_amount - elif t.account_head in gst_accounts.sgst_account: - value_details.total_sgst_amt += t.base_tax_amount - elif t.account_head in gst_accounts.cgst_account: - value_details.total_cgst_amt += t.base_tax_amount - - return value_details - - def get_payment_details(self, invoice): - payee_name = invoice.company - mode_of_payment = ", ".join([d.mode_of_payment for d in invoice.payments]) - paid_amount = invoice.base_paid_amount - outstanding_amount = invoice.outstanding_amount - - return frappe._dict(dict( - payee_name=payee_name, mode_of_payment=mode_of_payment, - paid_amount=paid_amount, outstanding_amount=outstanding_amount - )) - - def get_return_doc_reference(self, invoice): - invoice_date = frappe.db.get_value("Sales Invoice", invoice.return_against, "posting_date") - return frappe._dict(dict( - invoice_name=invoice.return_against, invoice_date=invoice_date - )) - - def make_e_invoice(self, invoice): - schema = self.read_json("e_inv_schema") - validations = self.read_json("e_inv_validation") - validations = json.loads(validations) - - trans_details = self.get_trans_details(invoice) - doc_details = self.get_doc_details(invoice) - seller_details = self.get_party_gstin_details(invoice.company_address) - - if invoice.gst_category == 'Overseas': - buyer_details = self.get_overseas_address_details(invoice.customer_address) - else: - buyer_details = self.get_party_gstin_details(invoice.customer_address) - place_of_supply = invoice.place_of_supply.split('-')[0] - buyer_details.update(dict(place_of_supply=place_of_supply)) - - item_list = self.get_item_list(invoice) - value_details = self.get_value_details(invoice) - - dispatch_details = frappe._dict({}) - period_details = frappe._dict({}) - shipping_details = frappe._dict({}) - export_details = frappe._dict({}) - eway_bill_details = frappe._dict({}) - if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: - shipping_details = self.get_party_gstin_details(invoice.shipping_address_name) - - payment_details = frappe._dict({}) - if invoice.is_pos and invoice.base_paid_amount: - payment_details = self.get_payment_details(invoice) - - prev_doc_details = frappe._dict({}) - if invoice.is_return and invoice.return_against: - prev_doc_details = self.get_return_doc_reference(invoice) - - e_invoice = schema.format( - trans_details=trans_details, doc_details=doc_details, dispatch_details=dispatch_details, - seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details, - item_list=item_list, value_details=value_details, payment_details=payment_details, - period_details=period_details, prev_doc_details=prev_doc_details, - export_details=export_details, eway_bill_details=eway_bill_details - ) - e_invoice = json.loads(e_invoice) - - self.run_e_invoice_validations(validations, e_invoice) - - return json.dumps(e_invoice) - - def run_e_invoice_validations(self, validations, e_invoice): - type_map = { - "string": cstr, - "number": cint, - "object": dict, - "array": list - } - # validate root mandatory keys - mandatory_fields = validations.get('required') - if mandatory_fields and not set(mandatory_fields).issubset(set(e_invoice.keys())): - print("Mandatory condition failed") - - for field, value in validations.items(): - if isinstance(value, list): value = value[0] - - invoice_value = e_invoice.get(field) - if not invoice_value: - print(field, "value undefined") - continue - - should_be_of_type = type_map[value.get('type').lower()] - if should_be_of_type == dict: - properties = value.get('properties') - - if isinstance(invoice_value, list): - for d in invoice_value: - self.run_e_invoice_validations(properties, d) - else: - self.run_e_invoice_validations(properties, invoice_value) - if not invoice_value: - e_invoice.pop(field, None) - continue - - if invoice_value == "None": - e_invoice.pop(field, None) - continue - - e_invoice[field] = should_be_of_type(invoice_value) if e_invoice[field] else e_invoice[field] - - should_be_of_len = value.get('maxLength') - should_be_greater_than = value.get('minimum') - should_be_less_than = value.get('maximum') - pattern_str = value.get('pattern') - pattern = re.compile(pattern_str or "") - - if should_be_of_type == 'string' and not len(invoice_value) <= should_be_of_len: - print("Max Length Exceeded", field, invoice_value) - if should_be_of_type == 'number' and not (should_be_greater_than <= invoice_value <= should_be_of_len): - print("Value too large", field, invoice_value) - if pattern_str and not pattern.match(invoice_value): - print("Pattern Mismatch", field, invoice_value) + return cstr(f.read()) \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py new file mode 100644 index 00000000000..3abcf16a72a --- /dev/null +++ b/erpnext/regional/india/e_invoice_utils.py @@ -0,0 +1,431 @@ +# -*- 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 os +import re +import jwt +import json +import base64 +import frappe +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5, AES +from Crypto.Util.Padding import pad, unpad +from frappe.model.document import Document +from frappe import _, get_module_path, scrub +from erpnext.regional.india.utils import get_gst_accounts +from frappe.utils.data import get_datetime, cstr, cint, format_date +from frappe.integrations.utils import make_post_request, make_get_request + +def get_einv_credentials(): + return frappe.get_doc("E Invoice Settings") + +def rsa_encrypt(msg, key): + if not (isinstance(msg, bytes) or isinstance(msg, bytearray)): + msg = str.encode(msg) + + rsa_pub_key = RSA.import_key(key) + cipher = PKCS1_v1_5.new(rsa_pub_key) + enc_msg = cipher.encrypt(msg) + b64_enc_msg = base64.b64encode(enc_msg) + return b64_enc_msg.decode() + +def aes_decrypt(enc_msg, key): + encode_as_b64 = True + if not (isinstance(key, bytes) or isinstance(key, bytearray)): + key = base64.b64decode(key) + encode_as_b64 = False + + cipher = AES.new(key, AES.MODE_ECB) + b64_enc_msg = base64.b64decode(enc_msg) + msg_bytes = cipher.decrypt(b64_enc_msg) + msg_bytes = unpad(msg_bytes, AES.block_size) # due to ECB/PKCS5Padding + if encode_as_b64: + msg_bytes = base64.b64encode(msg_bytes) + return msg_bytes.decode() + +def aes_encrypt(msg, key): + if not (isinstance(key, bytes) or isinstance(key, bytearray)): + key = base64.b64decode(key) + + cipher = AES.new(key, AES.MODE_ECB) + bytes_msg = str.encode(msg) + padded_bytes_msg = pad(bytes_msg, AES.block_size) + enc_msg = cipher.encrypt(padded_bytes_msg) + b64_enc_msg = base64.b64encode(enc_msg) + return b64_enc_msg.decode() + +def jwt_decrypt(token): + return jwt.decode(token, verify=False) + +def get_header(creds): + headers = { 'content-type': 'application/json' } + headers.update(dict(client_id=creds.client_id, client_secret=creds.client_secret, user_name=creds.username)) + headers.update(dict(Gstin=creds.gstin, AuthToken=creds.auth_token)) + return headers + +def fetch_token(self): + einv_creds = get_einv_credentials() + + endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' + headers = { 'content-type': 'application/json' } + headers.update(dict(client_id=einv_creds.client_id, client_secret=einv_creds.client_secret)) + payload = dict(UserName=einv_creds.username, ForceRefreshAccessToken=bool(einv_creds.auto_refresh_token)) + + appkey = bytearray(os.urandom(32)) + enc_appkey = rsa_encrypt(appkey, einv_creds.public_key) + + password = einv_creds.get_password(fieldname='password') + enc_password = rsa_encrypt(password, einv_creds.public_key) + + payload.update(dict(Password=enc_password, AppKey=enc_appkey)) + + res = make_post_request(endpoint, headers=headers, data=json.dumps({ 'data': payload })) + handle_err_response(res) + + auth_token, token_expiry, sek = extract_token_and_sek(res, appkey) + + einv_creds.auth_token = auth_token + einv_creds.token_expiry = get_datetime(token_expiry) + einv_creds.sek = sek + einv_creds.save() + +def extract_token_and_sek(response, appkey): + data = response.get('Data') + auth_token = data.get('AuthToken') + token_expiry = data.get('TokenExpiry') + enc_sek = data.get('Sek') + sek = aes_decrypt(enc_sek, appkey) + return auth_token, token_expiry, sek + +def get_gstin_details(gstin): + einv_creds = get_einv_credentials() + + endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/Master/gstin/{gstin}'.format(gstin=gstin) + headers = get_header(einv_creds) + + res = make_get_request(endpoint, headers=headers) + handle_err_response(res) + + enc_json = res.get('Data') + json_str = aes_decrypt(enc_json, einv_creds.sek) + data = json.loads(json_str) + + return data + +def generate_irn(invoice): + einv_creds = get_einv_credentials() + + endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice' + headers = get_header(einv_creds) + + e_invoice = make_e_invoice(invoice) + + enc_e_invoice_json = aes_encrypt(e_invoice, einv_creds.sek) + payload = dict(Data=enc_e_invoice_json) + + res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) + handle_err_response(res) + + enc_json = res.get('Data') + json_str = aes_decrypt(enc_json, einv_creds.sek) + + data = json.loads(json_str) + handle_irn_response(data) + + return data + +def get_irn_details(irn): + einv_creds = get_einv_credentials() + + endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/irn/{irn}'.format(irn=irn) + headers = get_header(einv_creds) + + res = make_get_request(endpoint, headers=headers) + handle_err_response(res) + + enc_json = res.get('Data') + json_str = aes_decrypt(enc_json, einv_creds.sek) + + data = json.loads(json_str) + handle_irn_response(data) + + return data + +def cancel_irn(irn, reason, remark=''): + einv_creds = get_einv_credentials() + + endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/Cancel' + headers = get_header() + + cancel_e_inv = json.dumps(dict(Irn=irn, CnlRsn=reason, CnlRem=remark)) + enc_json = aes_encrypt(cancel_e_inv, einv_creds.sek) + payload = dict(Data=enc_json) + + res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) + handle_err_response(res) + + return res + +def handle_irn_response(data): + enc_signed_invoice = data['SignedInvoice'] + enc_signed_qr_code = data['SignedQRCode'] + signed_invoice = jwt_decrypt(enc_signed_invoice)['data'] + signed_qr_code = jwt_decrypt(enc_signed_qr_code)['data'] + data['DecryptedSignedInvoice'] = json.loads(signed_invoice) + data['DecryptedSignedQRCode'] = json.loads(signed_qr_code) + +def handle_err_response(response): + if response.get('Status') == 0: + err_details = response.get('ErrorDetails') + print(response) + err_msg = "" + for d in err_details: + err_msg += d.get('ErrorMessage') + err_msg += "
" + frappe.throw(_(err_msg), title=_('API Request Failed')) + +def read_json(name): + file_path = os.path.join(os.path.dirname(__file__), "{name}.json".format(name=name)) + with open(file_path, 'r') as f: + return cstr(f.read()) + +def get_trans_details(invoice): + supply_type = '' + if invoice.gst_category == 'Registered Regular': supply_type = 'B2B' + elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP' + elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP' + elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP' + + if not supply_type: + return _('Invalid invoice transaction category.') + + return frappe._dict(dict( + tax_scheme='GST', supply_type=supply_type, reverse_charge=invoice.reverse_charge + )) + +def get_doc_details(invoice): + invoice_type = 'CRN' if invoice.is_return else 'INV' + invoice_name = invoice.name + invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') + + return frappe._dict(dict( + invoice_type=invoice_type, invoice_name=invoice_name, invoice_date=invoice_date + )) + +def get_party_gstin_details(party_address): + gstin, address_line1, address_line2, phone, email_id = frappe.db.get_value( + "Address", party_address, ["gstin", "address_line1", "address_line2", "phone", "email_id"] + ) + gstin_details = self.get_gstin_details(gstin) + legal_name = gstin_details.get('LegalName') + trade_name = gstin_details.get('TradeName') + location = gstin_details.get('AddrLoc') + state_code = gstin_details.get('StateCode') + pincode = cint(gstin_details.get('AddrPncd')) + if state_code == 97: + pincode = 999999 + + return frappe._dict(dict( + gstin=gstin, legal_name=legal_name, trade_name=trade_name, location=location, + pincode=pincode, state_code=state_code, address_line1=address_line1, + address_line2=address_line2, email=email_id, phone=phone + )) + +def get_overseas_address_details(party_address): + address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value( + "Address", party_address, ["address_title", "address_line1", "address_line2", "city", "phone", "email_id"] + ) + + return frappe._dict(dict( + gstin='URP', legal_name=address_title, address_line1=address_line1, + address_line2=address_line2, email=email_id, phone=phone, + pincode=999999, state_code=96, place_of_supply=96, location=city + )) + +def get_item_list(invoice): + item_list = [] + gst_accounts = get_gst_accounts(invoice.company) + gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] + + for d in invoice.items: + item_schema = read_json("einv_item_schema") + item = frappe._dict(dict()) + item.update(d.as_dict()) + item.sr_no = d.idx + item.description = d.item_name + item.is_service_item = "N" if frappe.db.get_value("Item", d.item_code, "is_stock_item") else "Y" + item.batch_expiry_date = frappe.db.get_value("Batch", d.batch_no, "expiry_date") if d.batch_no else None + item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None + item.tax_rate = 0 + item.igst_amount = 0 + item.cgst_amount = 0 + item.sgst_amount = 0 + item.cess_rate = 0 + item.cess_amount = 0 + for t in invoice.taxes: + if t.account_head in gst_accounts_list: + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) + if t.account_head in gst_accounts.cess_account: + item.cess_rate += item_tax_detail[0] + item.cess_amount += item_tax_detail[1] + elif t.account_head in gst_accounts.igst_account: + item.tax_rate += item_tax_detail[0] + item.igst_amount += item_tax_detail[1] + elif t.account_head in gst_accounts.sgst_account: + item.tax_rate += item_tax_detail[0] + item.sgst_amount += item_tax_detail[1] + elif t.account_head in gst_accounts.cgst_account: + item.tax_rate += item_tax_detail[0] + item.cgst_amount += item_tax_detail[1] + + item.total_value = item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount + e_inv_item = item_schema.format(item=item) + item_list.append(e_inv_item) + + return ", ".join(item_list) + +def get_value_details(invoice): + gst_accounts = get_gst_accounts(invoice.company) + gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] + + value_details = frappe._dict(dict()) + value_details.base_net_total = invoice.base_net_total + value_details.invoice_discount_amt = invoice.discount_amount + value_details.round_off = invoice.base_rounding_adjustment + value_details.base_grand_total = invoice.base_rounded_total + value_details.grand_total = invoice.rounded_total + value_details.total_cgst_amt = 0 + value_details.total_sgst_amt = 0 + value_details.total_igst_amt = 0 + value_details.total_cess_amt = 0 + for t in invoice.taxes: + if t.account_head in gst_accounts_list: + if t.account_head in gst_accounts.cess_account: + value_details.total_cess_amt += t.base_tax_amount + elif t.account_head in gst_accounts.igst_account: + value_details.total_igst_amt += t.base_tax_amount + elif t.account_head in gst_accounts.sgst_account: + value_details.total_sgst_amt += t.base_tax_amount + elif t.account_head in gst_accounts.cgst_account: + value_details.total_cgst_amt += t.base_tax_amount + + return value_details + +def get_payment_details(invoice): + payee_name = invoice.company + mode_of_payment = ", ".join([d.mode_of_payment for d in invoice.payments]) + paid_amount = invoice.base_paid_amount + outstanding_amount = invoice.outstanding_amount + + return frappe._dict(dict( + payee_name=payee_name, mode_of_payment=mode_of_payment, + paid_amount=paid_amount, outstanding_amount=outstanding_amount + )) + +def get_return_doc_reference(invoice): + invoice_date = frappe.db.get_value("Sales Invoice", invoice.return_against, "posting_date") + return frappe._dict(dict( + invoice_name=invoice.return_against, invoice_date=invoice_date + )) + +def make_e_invoice(invoice): + schema = read_json("einv_schema") + validations = read_json("einv_validation") + validations = json.loads(validations) + + trans_details = get_trans_details(invoice) + doc_details = get_doc_details(invoice) + seller_details = get_party_gstin_details(invoice.company_address) + + if invoice.gst_category == 'Overseas': + buyer_details = get_overseas_address_details(invoice.customer_address) + else: + buyer_details = get_party_gstin_details(invoice.customer_address) + place_of_supply = invoice.place_of_supply.split('-')[0] + buyer_details.update(dict(place_of_supply=place_of_supply)) + + item_list = get_item_list(invoice) + value_details = get_value_details(invoice) + + dispatch_details = frappe._dict({}) + period_details = frappe._dict({}) + shipping_details = frappe._dict({}) + export_details = frappe._dict({}) + eway_bill_details = frappe._dict({}) + if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: + shipping_details = get_party_gstin_details(invoice.shipping_address_name) + + payment_details = frappe._dict({}) + if invoice.is_pos and invoice.base_paid_amount: + payment_details = get_payment_details(invoice) + + prev_doc_details = frappe._dict({}) + if invoice.is_return and invoice.return_against: + prev_doc_details = get_return_doc_reference(invoice) + + e_invoice = schema.format( + trans_details=trans_details, doc_details=doc_details, dispatch_details=dispatch_details, + seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details, + item_list=item_list, value_details=value_details, payment_details=payment_details, + period_details=period_details, prev_doc_details=prev_doc_details, + export_details=export_details, eway_bill_details=eway_bill_details + ) + e_invoice = json.loads(e_invoice) + + run_e_invoice_validations(validations, e_invoice) + + return json.dumps(e_invoice) + +def run_e_invoice_validations(validations, e_invoice): + type_map = { + "string": cstr, + "number": cint, + "object": dict, + "array": list + } + # validate root mandatory keys + mandatory_fields = validations.get('required') + if mandatory_fields and not set(mandatory_fields).issubset(set(e_invoice.keys())): + print("Mandatory condition failed") + + for field, value in validations.items(): + if isinstance(value, list): value = value[0] + + invoice_value = e_invoice.get(field) + if not invoice_value: + print(field, "value undefined") + continue + + should_be_of_type = type_map[value.get('type').lower()] + if should_be_of_type == dict: + properties = value.get('properties') + + if isinstance(invoice_value, list): + for d in invoice_value: + self.run_e_invoice_validations(properties, d) + else: + self.run_e_invoice_validations(properties, invoice_value) + if not invoice_value: + e_invoice.pop(field, None) + continue + + if invoice_value == "None": + e_invoice.pop(field, None) + continue + + e_invoice[field] = should_be_of_type(invoice_value) if e_invoice[field] else e_invoice[field] + + should_be_of_len = value.get('maxLength') + should_be_greater_than = value.get('minimum') + should_be_less_than = value.get('maximum') + pattern_str = value.get('pattern') + pattern = re.compile(pattern_str or "") + + if should_be_of_type == 'string' and not len(invoice_value) <= should_be_of_len: + print("Max Length Exceeded", field, invoice_value) + if should_be_of_type == 'number' and not (should_be_greater_than <= invoice_value <= should_be_of_len): + print("Value too large", field, invoice_value) + if pattern_str and not pattern.match(invoice_value): + print("Pattern Mismatch", field, invoice_value) \ No newline at end of file diff --git a/erpnext/regional/doctype/e_invoice_settings/e_inv_item_schema.json b/erpnext/regional/india/einv_item_schema.json similarity index 100% rename from erpnext/regional/doctype/e_invoice_settings/e_inv_item_schema.json rename to erpnext/regional/india/einv_item_schema.json diff --git a/erpnext/regional/doctype/e_invoice_settings/e_inv_schema.json b/erpnext/regional/india/einv_schema.json similarity index 100% rename from erpnext/regional/doctype/e_invoice_settings/e_inv_schema.json rename to erpnext/regional/india/einv_schema.json diff --git a/erpnext/regional/doctype/e_invoice_settings/e_inv_validation.json b/erpnext/regional/india/einv_validation.json similarity index 100% rename from erpnext/regional/doctype/e_invoice_settings/e_inv_validation.json rename to erpnext/regional/india/einv_validation.json From c3e0ff93d7df986c5b3410e4039c2490a4a47070 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 18:39:13 +0530 Subject: [PATCH 016/137] chore: rename schema to template & js cleanup --- .../e_invoice_settings/e_invoice_settings.js | 44 +------------------ erpnext/regional/india/e_invoice_utils.py | 5 ++- ...em_schema.json => einv_item_template.json} | 0 .../{einv_schema.json => einv_template.json} | 0 4 files changed, 5 insertions(+), 44 deletions(-) rename erpnext/regional/india/{einv_item_schema.json => einv_item_template.json} (100%) rename erpnext/regional/india/{einv_schema.json => einv_template.json} (100%) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js index c97ebed013f..1b7efeeeddf 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -4,58 +4,18 @@ frappe.ui.form.on('E Invoice Settings', { refresh: function(frm) { if (!frm.doc.enable) return; - frm.trigger("show_fetch_token_btn"); - frm.add_custom_button(__("Get GSTIN Details"), - () => { - frm.call({ - doc: frm.doc, - method: 'get_gstin_details', - args: { - 'gstin': '36AAECF1151A1ZC' - }, - freeze: true, - callback: (res) => console.log(res) - }); - }); - - frm.add_custom_button(__("Generate IRN"), - () => { - frm.call({ - doc: frm.doc, - method: 'generate_irn', - args: { - 'invoice': 'SINV-20-21-0051' - }, - freeze: true, - callback: (res) => console.log(res) - }); - }); - - frm.add_custom_button(__("Fetch IRN Details"), - () => { - frm.call({ - doc: frm.doc, - method: 'get_irn_details', - args: { - 'irn': 'c63d9e180dfdaa9242e29e2e1e0a8d76f20e116ed3de179a2e9120f384e1b432' - }, - freeze: true, - callback: (res) => console.log(res) - }); - }); }, show_fetch_token_btn(frm) { const { token_expiry } = frm.doc; const now = frappe.datetime.now_datetime(); const expiry_in_mins = moment(token_expiry).diff(now, "minute"); - if (expiry_in_mins <= 1) { + if (expiry_in_mins <= 1 || !token_expiry) { frm.add_custom_button(__("Fetch Token"), () => { frm.call({ - doc: frm.doc, - method: 'make_authentication_request', + method: 'erpnext.regional.india.e_invoice_utils.fetch_token', freeze: true, callback: () => frm.refresh() }); diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index 3abcf16a72a..228430b580a 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -65,6 +65,7 @@ def get_header(creds): headers.update(dict(Gstin=creds.gstin, AuthToken=creds.auth_token)) return headers +@frappe.whitelist() def fetch_token(self): einv_creds = get_einv_credentials() @@ -250,7 +251,7 @@ def get_item_list(invoice): gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] for d in invoice.items: - item_schema = read_json("einv_item_schema") + item_schema = read_json("einv_item_template") item = frappe._dict(dict()) item.update(d.as_dict()) item.sr_no = d.idx @@ -331,7 +332,7 @@ def get_return_doc_reference(invoice): )) def make_e_invoice(invoice): - schema = read_json("einv_schema") + schema = read_json("einv_template") validations = read_json("einv_validation") validations = json.loads(validations) diff --git a/erpnext/regional/india/einv_item_schema.json b/erpnext/regional/india/einv_item_template.json similarity index 100% rename from erpnext/regional/india/einv_item_schema.json rename to erpnext/regional/india/einv_item_template.json diff --git a/erpnext/regional/india/einv_schema.json b/erpnext/regional/india/einv_template.json similarity index 100% rename from erpnext/regional/india/einv_schema.json rename to erpnext/regional/india/einv_template.json From fe054511c8da9f40de5c6ef29d6a80dd68d1979f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 18:39:35 +0530 Subject: [PATCH 017/137] feat: make IRN field on regional setup --- .../doctype/e_invoice_settings/e_invoice_settings.py | 3 +++ erpnext/regional/india/setup.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py index 1d86208347d..cdfe89be307 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -7,6 +7,7 @@ import frappe from frappe import _ from frappe.utils.data import cstr from frappe.model.document import Document +from frappe.custom.doctype.property_setter.property_setter import make_property_setter class EInvoiceSettings(Document): def validate(self): @@ -19,6 +20,8 @@ class EInvoiceSettings(Document): if not self.public_key or self.has_value_changed('public_key_file'): self.public_key = self.read_key_file() + make_property_setter("Sales Invoice", "irn", "reqd", self.enable, "Data") + def read_key_file(self): key_file = frappe.get_doc('File', dict(attached_to_name=self.doctype, attached_to_field='public_key_file')) with open(key_file.get_full_path(), 'rb') as f: diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 77a466fdff7..a66298d270f 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -376,6 +376,10 @@ def make_custom_fields(update=True): } ] + si_einvoice_fields = [ + dict(fieldname='irn', label='IRN', fieldtyp='Data', insert_after='customer') + ] + custom_fields = { 'Address': [ dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data', @@ -388,7 +392,7 @@ def make_custom_fields(update=True): 'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields, 'Purchase Order': purchase_invoice_gst_fields, 'Purchase Receipt': purchase_invoice_gst_fields, - 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields, + 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields, 'Sales Order': sales_invoice_gst_fields, 'Tax Category': inter_state_gst_field, From b12d08a85b601a3d4949bff83cac1c1aaa149b10 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 22:30:02 +0530 Subject: [PATCH 018/137] feat: Generate & Cancel IRN from Sales Invoice --- .../doctype/sales_invoice/regional/india.js | 2 + .../doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/controllers/accounts_controller.py | 10 ++++ erpnext/hooks.py | 3 +- .../e_invoice_settings/e_invoice_settings.py | 2 - erpnext/regional/india/e_invoice_utils.py | 58 +++++++++++++----- erpnext/regional/india/einvoice.js | 59 +++++++++++++++++++ erpnext/regional/india/setup.py | 5 +- 8 files changed, 122 insertions(+), 19 deletions(-) create mode 100644 erpnext/regional/india/einvoice.js diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js index 1ed4b92e7a4..e97671ad6e0 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js @@ -1,6 +1,8 @@ {% include "erpnext/regional/india/taxes.js" %} +{% include "erpnext/regional/india/einvoice.js" %} erpnext.setup_auto_gst_taxation('Sales Invoice'); +erpnext.setup_einvoice_actions('Sales Invoice') frappe.ui.form.on("Sales Invoice", { setup: function(frm) { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 184f98bd2ef..2ff5dd70c0c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -225,9 +225,9 @@ class SalesInvoice(SellingController): frappe.throw(_("At least one mode of payment is required for POS invoice.")) def before_cancel(self): + super(SalesInvoice, self).before_cancel() self.update_time_sheet(None) - def on_cancel(self): super(SalesInvoice, self).on_cancel() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9f582de90e6..3c628345a59 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -108,8 +108,14 @@ class AccountsController(TransactionBase): self.validate_deferred_start_and_end_date() validate_regional(self) + + validate_einvoice_fields(self) + if self.doctype != 'Material Request': apply_pricing_rule_on_transaction(self) + + def before_cancel(self): + validate_einvoice_fields(self) def validate_deferred_start_and_end_date(self): for d in self.items: @@ -1423,3 +1429,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil @erpnext.allow_regional def validate_regional(doc): pass + +@erpnext.allow_regional +def validate_einvoice_fields(doc): + pass diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 1dc6b6b4170..f70533ea38b 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -360,7 +360,8 @@ regional_overrides = { 'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details', 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', - 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries' + 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', + 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice_utils.validate_einvoice_fields', }, 'United Arab Emirates': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data' diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py index cdfe89be307..9f64c081c23 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -20,8 +20,6 @@ class EInvoiceSettings(Document): if not self.public_key or self.has_value_changed('public_key_file'): self.public_key = self.read_key_file() - make_property_setter("Sales Invoice", "irn", "reqd", self.enable, "Data") - def read_key_file(self): key_file = frappe.get_doc('File', dict(attached_to_name=self.doctype, attached_to_field='public_key_file')) with open(key_file.get_full_path(), 'rb') as f: diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index 228430b580a..fdba06183de 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -18,6 +18,14 @@ from erpnext.regional.india.utils import get_gst_accounts from frappe.utils.data import get_datetime, cstr, cint, format_date from frappe.integrations.utils import make_post_request, make_get_request +def validate_einvoice_fields(doc): + if not doc.doctype in ['Sales Invoice', 'Purchase Invoice']: return + + if doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: + frappe.throw(_("You must generate IRN before submitting the document."), title=_("Missing IRN")) + elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: + frappe.throw(_("You must cancel IRN before cancelling the document."), title=_("Not Allowed")) + def get_einv_credentials(): return frappe.get_doc("E Invoice Settings") @@ -66,7 +74,7 @@ def get_header(creds): return headers @frappe.whitelist() -def fetch_token(self): +def fetch_token(): einv_creds = get_einv_credentials() endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' @@ -100,6 +108,16 @@ def extract_token_and_sek(response, appkey): sek = aes_decrypt(enc_sek, appkey) return auth_token, token_expiry, sek +def attach_signed_json(invoice, data): + f = frappe.get_doc({ + "doctype": "File", + "file_name": invoice.name + "e_invoice.json", + "attached_to_doctype": invoice.doctype, + "attached_to_name": invoice.name, + "content": json.dumps(data), + "is_private": True + }).insert() + def get_gstin_details(gstin): einv_creds = get_einv_credentials() @@ -115,19 +133,20 @@ def get_gstin_details(gstin): return data -def generate_irn(invoice): - einv_creds = get_einv_credentials() - +@frappe.whitelist() +def generate_irn(doctype, name): endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice' + einv_creds = get_einv_credentials() headers = get_header(einv_creds) + invoice = frappe.get_doc(doctype, name) e_invoice = make_e_invoice(invoice) enc_e_invoice_json = aes_encrypt(e_invoice, einv_creds.sek) payload = dict(Data=enc_e_invoice_json) res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) - handle_err_response(res) + res = handle_err_response(res) enc_json = res.get('Data') json_str = aes_decrypt(enc_json, einv_creds.sek) @@ -135,6 +154,8 @@ def generate_irn(invoice): data = json.loads(json_str) handle_irn_response(data) + attach_signed_json(invoice, data['DecryptedSignedInvoice']) + return data def get_irn_details(irn): @@ -146,19 +167,20 @@ def get_irn_details(irn): res = make_get_request(endpoint, headers=headers) handle_err_response(res) - enc_json = res.get('Data') - json_str = aes_decrypt(enc_json, einv_creds.sek) + # enc_json = res.get('Data') + # json_str = aes_decrypt(enc_json, einv_creds.sek) - data = json.loads(json_str) - handle_irn_response(data) + # data = json.loads(json_str) + # handle_irn_response(data) - return data + return res +@frappe.whitelist() def cancel_irn(irn, reason, remark=''): einv_creds = get_einv_credentials() endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/Cancel' - headers = get_header() + headers = get_header(einv_creds) cancel_e_inv = json.dumps(dict(Irn=irn, CnlRsn=reason, CnlRem=remark)) enc_json = aes_encrypt(cancel_e_inv, einv_creds.sek) @@ -183,10 +205,18 @@ def handle_err_response(response): print(response) err_msg = "" for d in err_details: + err_code = d.get('ErrorCode') + if err_code == '2150': + irn = [d['Desc']['Irn'] for d in response.get('InfoDtls') if d['InfCd'] == 'DUPIRN'] + response = get_irn_details(irn[0]) + return response + err_msg += d.get('ErrorMessage') err_msg += "
" frappe.throw(_(err_msg), title=_('API Request Failed')) + return response + def read_json(name): file_path = os.path.join(os.path.dirname(__file__), "{name}.json".format(name=name)) with open(file_path, 'r') as f: @@ -219,7 +249,7 @@ def get_party_gstin_details(party_address): gstin, address_line1, address_line2, phone, email_id = frappe.db.get_value( "Address", party_address, ["gstin", "address_line1", "address_line2", "phone", "email_id"] ) - gstin_details = self.get_gstin_details(gstin) + gstin_details = get_gstin_details(gstin) legal_name = gstin_details.get('LegalName') trade_name = gstin_details.get('TradeName') location = gstin_details.get('AddrLoc') @@ -405,9 +435,9 @@ def run_e_invoice_validations(validations, e_invoice): if isinstance(invoice_value, list): for d in invoice_value: - self.run_e_invoice_validations(properties, d) + run_e_invoice_validations(properties, d) else: - self.run_e_invoice_validations(properties, invoice_value) + run_e_invoice_validations(properties, invoice_value) if not invoice_value: e_invoice.pop(field, None) continue diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js new file mode 100644 index 00000000000..76a2bff6e56 --- /dev/null +++ b/erpnext/regional/india/einvoice.js @@ -0,0 +1,59 @@ +erpnext.setup_einvoice_actions = (doctype) => { + frappe.ui.form.on(doctype, { + refresh(frm) { + const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); + if (!einvoicing_enabled) return; + + if (frm.doc.docstatus == 0 && !frm.doc.irn) { + frm.add_custom_button( + "Generate IRN", + () => { + frappe.call({ + method: 'erpnext.regional.india.e_invoice_utils.generate_irn', + args: { doctype: frm.doc.doctype, name: frm.doc.name }, + freeze: true, + callback: (res) => { + console.log(res.message); + frm.set_value('irn', res.message['Irn']); + frm.set_value('signed_einvoice', JSON.stringify(res.message['DecryptedSignedInvoice'])); + frm.set_value('signed_qr_code', JSON.stringify(res.message['DecryptedSignedQRCode'])); + frm.save(); + } + }) + } + ) + } else if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) { + frm.add_custom_button( + "Cancel IRN", + () => { + const d = new frappe.ui.Dialog({ + title: __('Cancel IRN'), + fields: [ + { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data entry mistake", "3-Order Cancelled", "4-Other"] }, + { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 } + ], + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice_utils.cancel_irn', + args: { irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark }, + freeze: true, + callback: (res) => { + if (res.message['Status'] == 1) { + frm.set_value('irn_cancelled', 1); + frm.save_or_update(); + } + d.hide(); + } + }) + }, + primary_action_label: __('Submit') + }); + d.show(); + } + ) + } + } + }) +} \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index a66298d270f..5343f16456b 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -377,7 +377,10 @@ def make_custom_fields(update=True): ] si_einvoice_fields = [ - dict(fieldname='irn', label='IRN', fieldtyp='Data', insert_after='customer') + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer'), + dict(fieldname='irn_cancelled', fieldtype='Check', hidden=1, read_only=1, default=0, allow_on_submit=1), + dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, read_only=1), + dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, read_only=1) ] custom_fields = { From e7a129dde8b638d495ce9c276299881e2436e845 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 12:28:03 +0530 Subject: [PATCH 019/137] chore: minor fixes --- erpnext/regional/india/einvoice.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js index 76a2bff6e56..d03a4c87e03 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/einvoice.js @@ -4,7 +4,7 @@ erpnext.setup_einvoice_actions = (doctype) => { const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); if (!einvoicing_enabled) return; - if (frm.doc.docstatus == 0 && !frm.doc.irn) { + if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) { frm.add_custom_button( "Generate IRN", () => { @@ -30,7 +30,7 @@ erpnext.setup_einvoice_actions = (doctype) => { title: __('Cancel IRN'), fields: [ { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data entry mistake", "3-Order Cancelled", "4-Other"] }, + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 } ], primary_action: function() { From 1679b0adbbe76b7870c8c49034a8faaad2cbe4b9 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 12:59:27 +0530 Subject: [PATCH 020/137] fix: item discount --- erpnext/regional/india/e_invoice_utils.py | 47 +++++++++++-------- .../regional/india/einv_item_template.json | 4 +- erpnext/regional/india/einv_validation.json | 3 +- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index fdba06183de..e45ae37bf3b 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -15,7 +15,7 @@ from Crypto.Util.Padding import pad, unpad from frappe.model.document import Document from frappe import _, get_module_path, scrub from erpnext.regional.india.utils import get_gst_accounts -from frappe.utils.data import get_datetime, cstr, cint, format_date +from frappe.utils.data import get_datetime, cstr, cint, format_date, flt from frappe.integrations.utils import make_post_request, make_get_request def validate_einvoice_fields(doc): @@ -289,6 +289,9 @@ def get_item_list(invoice): item.is_service_item = "N" if frappe.db.get_value("Item", d.item_code, "is_stock_item") else "Y" item.batch_expiry_date = frappe.db.get_value("Batch", d.batch_no, "expiry_date") if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None + item.unit_rate = item.base_price_list_rate if item.discount_amount else item.base_rate + item.total_amount = item.unit_rate * item.qty + item.discount_amount = item.discount_amount * item.qty item.tax_rate = 0 item.igst_amount = 0 item.cgst_amount = 0 @@ -315,6 +318,8 @@ def get_item_list(invoice): e_inv_item = item_schema.format(item=item) item_list.append(e_inv_item) + print(e_inv_item) + return ", ".join(item_list) def get_value_details(invoice): @@ -405,28 +410,25 @@ def make_e_invoice(invoice): ) e_invoice = json.loads(e_invoice) - run_e_invoice_validations(validations, e_invoice) + error_msgs = run_e_invoice_validations(validations, e_invoice, []) + if error_msgs: + frappe.throw(_("{}").format("
".join(error_msgs)), title=_("E Invoice Validation")) return json.dumps(e_invoice) -def run_e_invoice_validations(validations, e_invoice): +def run_e_invoice_validations(validations, e_invoice, error_msgs=[]): type_map = { "string": cstr, "number": cint, "object": dict, "array": list } - # validate root mandatory keys - mandatory_fields = validations.get('required') - if mandatory_fields and not set(mandatory_fields).issubset(set(e_invoice.keys())): - print("Mandatory condition failed") for field, value in validations.items(): if isinstance(value, list): value = value[0] invoice_value = e_invoice.get(field) if not invoice_value: - print(field, "value undefined") continue should_be_of_type = type_map[value.get('type').lower()] @@ -435,28 +437,35 @@ def run_e_invoice_validations(validations, e_invoice): if isinstance(invoice_value, list): for d in invoice_value: - run_e_invoice_validations(properties, d) + run_e_invoice_validations(properties, d, error_msgs) else: - run_e_invoice_validations(properties, invoice_value) + run_e_invoice_validations(properties, invoice_value, error_msgs) + # remove keys with empty dicts if not invoice_value: e_invoice.pop(field, None) continue if invoice_value == "None": + # remove keys with empty values e_invoice.pop(field, None) continue - - e_invoice[field] = should_be_of_type(invoice_value) if e_invoice[field] else e_invoice[field] + + # convert to int or str + e_invoice[field] = should_be_of_type(invoice_value) should_be_of_len = value.get('maxLength') - should_be_greater_than = value.get('minimum') - should_be_less_than = value.get('maximum') + should_be_greater_than = flt(value.get('minimum')) + should_be_less_than = flt(value.get('maximum')) pattern_str = value.get('pattern') pattern = re.compile(pattern_str or "") - if should_be_of_type == 'string' and not len(invoice_value) <= should_be_of_len: - print("Max Length Exceeded", field, invoice_value) - if should_be_of_type == 'number' and not (should_be_greater_than <= invoice_value <= should_be_of_len): - print("Value too large", field, invoice_value) + field_label = value.get("label") or field + + if value.get('type').lower() == 'string' and len(invoice_value) > should_be_of_len: + error_msgs.append("{} should not exceed {} characters".format(field_label, should_be_of_len)) + if value.get('type').lower() == 'number' and not (flt(invoice_value) <= should_be_less_than): + error_msgs.append("{} should be less than {}".format(field_label, should_be_less_than)) if pattern_str and not pattern.match(invoice_value): - print("Pattern Mismatch", field, invoice_value) \ No newline at end of file + error_msgs.append("{} should match {}".format(field_label, pattern_str)) + + return error_msgs \ No newline at end of file diff --git a/erpnext/regional/india/einv_item_template.json b/erpnext/regional/india/einv_item_template.json index b4f54448326..f87b0f15f3c 100644 --- a/erpnext/regional/india/einv_item_template.json +++ b/erpnext/regional/india/einv_item_template.json @@ -7,8 +7,8 @@ "Unit": "{item.uom}", "Qty": "{item.qty}", "FreeQty": "{item.free_qty}", - "UnitPrice": "{item.base_rate}", - "TotAmt": "{item.base_amount}", + "UnitPrice": "{item.unit_rate}", + "TotAmt": "{item.total_amount}", "Discount": "{item.discount_amount}", "AssAmt": "{item.base_amount}", "PrdSlNo": "{item.serial_no}", diff --git a/erpnext/regional/india/einv_validation.json b/erpnext/regional/india/einv_validation.json index da224d9633f..28bfc396e30 100644 --- a/erpnext/regional/india/einv_validation.json +++ b/erpnext/regional/india/einv_validation.json @@ -58,7 +58,8 @@ "type": "string", "minLength": 1, "maxLength": 16, - "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$" + "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$", + "label": "Document Name" }, "Dt": { "type": "string", From 13467e42f318122103ef33c28669010707f0ad70 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 13:07:38 +0530 Subject: [PATCH 021/137] chore: show irn cancelled check after cancellation --- erpnext/regional/india/setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 5343f16456b..c3bfb842a13 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -377,8 +377,10 @@ def make_custom_fields(update=True): ] si_einvoice_fields = [ - dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer'), - dict(fieldname='irn_cancelled', fieldtype='Check', hidden=1, read_only=1, default=0, allow_on_submit=1), + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, + insert_after='customer', depends_on='eval:(doc.irn_cancelled === 0)'), + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', + depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, read_only=1), dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, read_only=1) ] From d4255d77f450164b67375f0b646852861fb6544f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 13:14:07 +0530 Subject: [PATCH 022/137] fix: hide cancel irn dialog on error --- erpnext/regional/india/einvoice.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js index d03a4c87e03..3aed88fb0eb 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/einvoice.js @@ -45,7 +45,8 @@ erpnext.setup_einvoice_actions = (doctype) => { frm.save_or_update(); } d.hide(); - } + }, + error: () => d.hide() }) }, primary_action_label: __('Submit') From 9d805272bee93931321b13cef1b29f5c1aebbf5d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 13:56:52 +0530 Subject: [PATCH 023/137] fix: public key is required on validate --- .../regional/doctype/e_invoice_settings/e_invoice_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py index 9f64c081c23..14ca3726506 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -11,7 +11,7 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_ class EInvoiceSettings(Document): def validate(self): - mandatory_fields = ['client_id', 'client_secret', 'gstin', 'username', 'password', 'public_key'] + mandatory_fields = ['client_id', 'client_secret', 'gstin', 'username', 'password', 'public_key_file'] for d in mandatory_fields: if not self.get(d): frappe.throw(_("{} is required").format(frappe.unscrub(d)), title=_("Missing Values")) From bf2b4bc6f77bde5d2014fe5266432a3a885b3a85 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 14:32:12 +0530 Subject: [PATCH 024/137] fix: cannot find attached key file --- .../regional/doctype/e_invoice_settings/e_invoice_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py index 14ca3726506..e90d07edbd6 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -21,6 +21,6 @@ class EInvoiceSettings(Document): self.public_key = self.read_key_file() def read_key_file(self): - key_file = frappe.get_doc('File', dict(attached_to_name=self.doctype, attached_to_field='public_key_file')) + key_file = frappe.get_doc('File', dict(attached_to_name=self.doctype)) with open(key_file.get_full_path(), 'rb') as f: return cstr(f.read()) \ No newline at end of file From 1b969ff140a72eac8c3fd04fdecced2501cd1073 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 15:02:08 +0530 Subject: [PATCH 025/137] fix: validation if e invoicing is disabled --- erpnext/regional/india/e_invoice_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index e45ae37bf3b..001e9ac358e 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -19,7 +19,8 @@ from frappe.utils.data import get_datetime, cstr, cint, format_date, flt from frappe.integrations.utils import make_post_request, make_get_request def validate_einvoice_fields(doc): - if not doc.doctype in ['Sales Invoice', 'Purchase Invoice']: return + e_invoice_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable") + if not doc.doctype in ['Sales Invoice', 'Purchase Invoice'] or not e_invoice_enabled: return if doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: frappe.throw(_("You must generate IRN before submitting the document."), title=_("Missing IRN")) From b54df979c419660212fca3ccc60117353de07c4b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 15:17:30 +0530 Subject: [PATCH 026/137] fix: do not show generate irn for invalid supply type --- erpnext/regional/india/einvoice.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js index 3aed88fb0eb..6ba306a0563 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/einvoice.js @@ -2,7 +2,11 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.ui.form.on(doctype, { refresh(frm) { const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); - if (!einvoicing_enabled) return; + const supply_type = frm.doc.gst_category; + if (!einvoicing_enabled + || !['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type)) { + return; + } if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) { frm.add_custom_button( From 38f06a1a266166f09b715d563efe44471779f95a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 17:10:18 +0530 Subject: [PATCH 027/137] fix: update irn_cancelled after cancelling irn --- erpnext/regional/india/einvoice.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js index 6ba306a0563..95ccaa5907b 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/einvoice.js @@ -43,12 +43,10 @@ erpnext.setup_einvoice_actions = (doctype) => { method: 'erpnext.regional.india.e_invoice_utils.cancel_irn', args: { irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark }, freeze: true, - callback: (res) => { - if (res.message['Status'] == 1) { - frm.set_value('irn_cancelled', 1); - frm.save_or_update(); - } - d.hide(); + callback: () => { + frm.set_value('irn_cancelled', 1); + frm.save("Update"); + d.hide() }, error: () => d.hide() }) From b6faadab8d78e7256fd3fd5a98179bd0dd6f6812 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 17:14:58 +0530 Subject: [PATCH 028/137] chore: show irn field for proper gst_category --- erpnext/regional/india/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index c3bfb842a13..9adaf8048eb 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -377,8 +377,8 @@ def make_custom_fields(update=True): ] si_einvoice_fields = [ - dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, - insert_after='customer', depends_on='eval:(doc.irn_cancelled === 0)'), + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', + depends_on='eval:in_list(["Register Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, read_only=1), From 4b969a5b9b45c01dde60c386c41ff624a25c64b7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 18:24:24 +0530 Subject: [PATCH 029/137] feat: e-way bill details in e-invoice --- erpnext/regional/india/e_invoice_utils.py | 37 +++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index 001e9ac358e..f597e329621 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -238,7 +238,11 @@ def get_trans_details(invoice): )) def get_doc_details(invoice): - invoice_type = 'CRN' if invoice.is_return else 'INV' + if invoice.doctype == 'Purchase Invoice' and invoice.is_return: + invoice_type = 'DBN' + else: + invoice_type = 'CRN' if invoice.is_return else 'INV' + invoice_name = invoice.name invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') @@ -367,6 +371,24 @@ def get_return_doc_reference(invoice): invoice_name=invoice.return_against, invoice_date=invoice_date )) +def get_eway_bill_details(invoice): + if not invoice.distance: + frappe.throw(_("Distance is mandatory for E-Way Bill generation"), title=_("Missing Values")) + + mode_of_transport = { 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } + vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } + + return frappe._dict(dict( + gstin=invoice.gst_transporter_id, + name=invoice.transporter_name, + mode_of_transport=mode_of_transport[invoice.mode_of_transport], + distance=invoice.distance, + document_name=invoice.lr_no, + document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'), + vehicle_no=invoice.vehicle_no, + vehicle_type=vehicle_type[invoice.gst_vehicle_type] + )) + def make_e_invoice(invoice): schema = read_json("einv_template") validations = read_json("einv_validation") @@ -385,12 +407,8 @@ def make_e_invoice(invoice): item_list = get_item_list(invoice) value_details = get_value_details(invoice) - - dispatch_details = frappe._dict({}) - period_details = frappe._dict({}) + shipping_details = frappe._dict({}) - export_details = frappe._dict({}) - eway_bill_details = frappe._dict({}) if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: shipping_details = get_party_gstin_details(invoice.shipping_address_name) @@ -401,6 +419,13 @@ def make_e_invoice(invoice): prev_doc_details = frappe._dict({}) if invoice.is_return and invoice.return_against: prev_doc_details = get_return_doc_reference(invoice) + + dispatch_details = frappe._dict({}) + period_details = frappe._dict({}) + export_details = frappe._dict({}) + eway_bill_details = frappe._dict({}) + if invoice.transporter: + eway_bill_details = get_eway_bill_details(invoice) e_invoice = schema.format( trans_details=trans_details, doc_details=doc_details, dispatch_details=dispatch_details, From a6666dbf97cb0af5473bdd9d26dcd48c572c78af Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 18:51:39 +0530 Subject: [PATCH 030/137] fix: save e-way bill no on irn generation --- erpnext/regional/india/einvoice.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js index 95ccaa5907b..05cf56e8f51 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/einvoice.js @@ -21,6 +21,8 @@ erpnext.setup_einvoice_actions = (doctype) => { frm.set_value('irn', res.message['Irn']); frm.set_value('signed_einvoice', JSON.stringify(res.message['DecryptedSignedInvoice'])); frm.set_value('signed_qr_code', JSON.stringify(res.message['DecryptedSignedQRCode'])); + + if (res.message['EwbNo']) frm.set_value('ewaybill', res.message['EwbNo']); frm.save(); } }) From 9e5388ac53376debddb1d7278c9df7f97ec8ea47 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 18:52:05 +0530 Subject: [PATCH 031/137] chore: no copy on e invoice custom fields --- erpnext/regional/india/setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 9adaf8048eb..0b032019145 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -377,12 +377,12 @@ def make_custom_fields(update=True): ] si_einvoice_fields = [ - dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, depends_on='eval:in_list(["Register Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), - dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1 depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), - dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, read_only=1), - dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, read_only=1) + dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, read_only=1), + dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, read_only=1) ] custom_fields = { From bdf4bfe98f253948f0f29d5ed63f1d1b851dab15 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 19:34:09 +0530 Subject: [PATCH 032/137] feat: cancel e-way bill before cancelling IRN --- erpnext/regional/india/e_invoice_utils.py | 15 ++++++++++ erpnext/regional/india/einvoice.js | 35 ++++++++++++++++++++++- erpnext/regional/india/setup.py | 8 ++++-- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index f597e329621..dc38b25bed7 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -192,6 +192,21 @@ def cancel_irn(irn, reason, remark=''): return res +@frappe.whitelist() +def cancel_eway_bill(eway_bill, reason, remark=''): + einv_creds = get_einv_credentials() + endpoint = 'https://einv-apisandbox.nic.in/ewaybillapi/v1.03/ewayapi' + headers = get_header(einv_creds) + + cancel_eway_bill_json = json.dumps(dict(ewbNo=eway_bill, cancelRsnCode=reason, cancelRmrk=remark)) + enc_json = aes_encrypt(cancel_eway_bill_json, einv_creds.sek) + payload = dict(action="CANEWB", Data=enc_json) + + res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) + handle_err_response(res) + + return res + def handle_irn_response(data): enc_signed_invoice = data['SignedInvoice'] enc_signed_qr_code = data['SignedQRCode'] diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js index 05cf56e8f51..20461ea317b 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/einvoice.js @@ -28,7 +28,8 @@ erpnext.setup_einvoice_actions = (doctype) => { }) } ) - } else if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) { + } + if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) { frm.add_custom_button( "Cancel IRN", () => { @@ -59,6 +60,38 @@ erpnext.setup_einvoice_actions = (doctype) => { } ) } + if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled && !frm.doc.eway_bill_cancelled) { + frm.add_custom_button( + "Cancel E-Way Bill", + () => { + const d = new frappe.ui.Dialog({ + title: __('Cancel E-Way Bill'), + fields: [ + { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, + { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 } + ], + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice_utils.cancel_eway_bill', + args: { eway_bill: frm.doc.ewaybill, reason: data.reason.split('-')[0], remark: data.remark }, + freeze: true, + callback: () => { + frm.set_value('eway_bill_cancelled', 1); + frm.save("Update"); + d.hide() + }, + error: () => d.hide() + }) + }, + primary_action_label: __('Submit') + }); + d.show(); + } + ) + } } + }) } \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 0b032019145..d5aba17a88f 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -369,7 +369,7 @@ def make_custom_fields(update=True): 'fieldname': 'ewaybill', 'label': 'e-Way Bill No.', 'fieldtype': 'Data', - 'depends_on': 'eval:(doc.docstatus === 1)', + 'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)', 'allow_on_submit': 1, 'insert_after': 'tax_id', 'translatable': 0 @@ -379,10 +379,12 @@ def make_custom_fields(update=True): si_einvoice_fields = [ dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, depends_on='eval:in_list(["Register Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), - dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1 + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, read_only=1), - dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, read_only=1) + dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, read_only=1), + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, + depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), ] custom_fields = { From acc79f739b0a46979d572977a24ece358e5bb762 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 6 Oct 2020 21:27:00 +0530 Subject: [PATCH 033/137] feat: manual download / upload json --- erpnext/regional/india/e_invoice_utils.py | 95 +++++++--- erpnext/regional/india/einv_validation.json | 21 ++- erpnext/regional/india/einvoice.js | 188 ++++++++++++++------ 3 files changed, 214 insertions(+), 90 deletions(-) diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index dc38b25bed7..6109f800f7d 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -22,10 +22,12 @@ def validate_einvoice_fields(doc): e_invoice_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable") if not doc.doctype in ['Sales Invoice', 'Purchase Invoice'] or not e_invoice_enabled: return - if doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: + if doc.docstatus == 0 and doc._action == 'save' and doc.irn: + frappe.throw(_("You cannot edit the invoice after generating IRN"), title=_("Edit Not Allowed")) + elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: frappe.throw(_("You must generate IRN before submitting the document."), title=_("Missing IRN")) elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: - frappe.throw(_("You must cancel IRN before cancelling the document."), title=_("Not Allowed")) + frappe.throw(_("You must cancel IRN before cancelling the document."), title=_("Cancel Not Allowed")) def get_einv_credentials(): return frappe.get_doc("E Invoice Settings") @@ -266,20 +268,24 @@ def get_doc_details(invoice): )) def get_party_gstin_details(party_address): - gstin, address_line1, address_line2, phone, email_id = frappe.db.get_value( - "Address", party_address, ["gstin", "address_line1", "address_line2", "phone", "email_id"] - ) - gstin_details = get_gstin_details(gstin) - legal_name = gstin_details.get('LegalName') - trade_name = gstin_details.get('TradeName') - location = gstin_details.get('AddrLoc') - state_code = gstin_details.get('StateCode') - pincode = cint(gstin_details.get('AddrPncd')) + address = frappe.get_all("Address", filters={"name": party_address}, fields=["*"])[0] + + # gstin_details = get_gstin_details(gstin) + gstin = address.get('gstin') + legal_name = address.get('address_title') + # trade_name = gstin_details.get('TradeName') + location = address.get('city') + state_code = address.get('gst_state_number') + pincode = cint(address.get('pincode')) + address_line1 = address.get('address_line1') + address_line2 = address.get('address_line2') + email_id = address.get('email_id') + phone = address.get('phone') if state_code == 97: pincode = 999999 return frappe._dict(dict( - gstin=gstin, legal_name=legal_name, trade_name=trade_name, location=location, + gstin=gstin, legal_name=legal_name, location=location, pincode=pincode, state_code=state_code, address_line1=address_line1, address_line2=address_line2, email=email_id, phone=phone )) @@ -338,8 +344,6 @@ def get_item_list(invoice): e_inv_item = item_schema.format(item=item) item_list.append(e_inv_item) - print(e_inv_item) - return ", ".join(item_list) def get_value_details(invoice): @@ -404,10 +408,11 @@ def get_eway_bill_details(invoice): vehicle_type=vehicle_type[invoice.gst_vehicle_type] )) -def make_e_invoice(invoice): +@frappe.whitelist() +def make_e_invoice(doctype, name): + invoice = frappe.get_doc(doctype, name) schema = read_json("einv_template") - validations = read_json("einv_validation") - validations = json.loads(validations) + validations = json.loads(read_json("einv_validation")) trans_details = get_trans_details(invoice) doc_details = get_doc_details(invoice) @@ -451,13 +456,17 @@ def make_e_invoice(invoice): ) e_invoice = json.loads(e_invoice) - error_msgs = run_e_invoice_validations(validations, e_invoice, []) + error_msgs = validate_einvoice(validations, e_invoice, []) if error_msgs: - frappe.throw(_("{}").format("
".join(error_msgs)), title=_("E Invoice Validation")) + if len(error_msgs) > 1: + li = ["
  • "+ d +"
  • " for d in error_msgs] + frappe.throw(_("""
      {}
    """).format("".join(li)), title=_("E Invoice Validation Failed")) + else: + frappe.throw(_("{}").format(error_msgs[0]), title=_("E Invoice Validation Failed")) - return json.dumps(e_invoice) + return {'einvoice': json.dumps(e_invoice)} -def run_e_invoice_validations(validations, e_invoice, error_msgs=[]): +def validate_einvoice(validations, e_invoice, error_msgs=[]): type_map = { "string": cstr, "number": cint, @@ -478,9 +487,9 @@ def run_e_invoice_validations(validations, e_invoice, error_msgs=[]): if isinstance(invoice_value, list): for d in invoice_value: - run_e_invoice_validations(properties, d, error_msgs) + validate_einvoice(properties, d, error_msgs) else: - run_e_invoice_validations(properties, invoice_value, error_msgs) + validate_einvoice(properties, invoice_value, error_msgs) # remove keys with empty dicts if not invoice_value: e_invoice.pop(field, None) @@ -507,6 +516,42 @@ def run_e_invoice_validations(validations, e_invoice, error_msgs=[]): if value.get('type').lower() == 'number' and not (flt(invoice_value) <= should_be_less_than): error_msgs.append("{} should be less than {}".format(field_label, should_be_less_than)) if pattern_str and not pattern.match(invoice_value): - error_msgs.append("{} should match {}".format(field_label, pattern_str)) + error_msgs.append(value.get('validationMsg')) - return error_msgs \ No newline at end of file + return error_msgs + +@frappe.whitelist() +def download_einvoice(): + data = frappe._dict(frappe.local.form_dict) + einvoice = data['einvoice'] + name = data['name'] + + frappe.response['filename'] = "E-Invoice-" + name + ".json" + frappe.response['filecontent'] = einvoice + frappe.response['content_type'] = 'application/json' + frappe.response['type'] = 'download' + +@frappe.whitelist() +def upload_einvoice(): + signed_einvoice = json.loads(frappe.local.uploaded_file) + data = frappe._dict(frappe.local.form_dict) + doctype = data['doctype'] + name = data['docname'] + + frappe.db.set_value(doctype, name, "irn", signed_einvoice.get("Irn")) + frappe.db.set_value(doctype, name, "ewaybill", signed_einvoice.get("EwbNo")) + +@frappe.whitelist() +def download_cancel_einvoice(): + data = frappe._dict(frappe.local.form_dict) + name = data['name'] + irn = data['irn'] + reason = data['reason'] + remark = data['remark'] + + cancel_einvoice = json.dumps(dict(Irn=irn, CnlRsn=reason, CnlRem=remark)) + + frappe.response['filename'] = "Cancel E-Invoice " + name + ".json" + frappe.response['filecontent'] = cancel_einvoice + frappe.response['content_type'] = 'application/json' + frappe.response['type'] = 'download' \ No newline at end of file diff --git a/erpnext/regional/india/einv_validation.json b/erpnext/regional/india/einv_validation.json index 28bfc396e30..9f0ef5006c4 100644 --- a/erpnext/regional/india/einv_validation.json +++ b/erpnext/regional/india/einv_validation.json @@ -59,13 +59,15 @@ "minLength": 1, "maxLength": 16, "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$", - "label": "Document Name" + "label": "Document Name", + "validationMsg": "Document name should not be starting with 0, / and -" }, "Dt": { "type": "string", "minLength": 10, "maxLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "validationMsg": "Document Date is invalid" } }, "required": ["Typ", "No", "Dt"] @@ -77,7 +79,8 @@ "type": "string", "minLength": 15, "maxLength": 15, - "pattern": "([0-9]{2}[0-9A-Z]{13})" + "pattern": "([0-9]{2}[0-9A-Z]{13})", + "validationMsg": "Seller GSTIN is invalid" }, "LglNm": { "type": "string", @@ -134,7 +137,8 @@ "type": "string", "minLength": 3, "maxLength": 15, - "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$" + "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$", + "validationMsg": "Buyer GSTIN is invalid" }, "LglNm": { "type": "string", @@ -232,7 +236,8 @@ "type": "string", "maxLength": 15, "minLength": 3, - "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$" + "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$", + "validationMsg": "Shipping Address GSTIN is invalid" }, "LglNm": { "type": "string", @@ -429,13 +434,15 @@ "type": "string", "maxLength": 10, "minLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "validationMsg": "Expiry Date is invalid" }, "WrDt": { "type": "string", "maxLength": 10, "minLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "validationMsg": "Warranty Date is invalid" } }, "required": ["Nm"] diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js index 20461ea317b..4f98ffc88ab 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/einvoice.js @@ -7,27 +7,131 @@ erpnext.setup_einvoice_actions = (doctype) => { || !['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type)) { return; } + // if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) { + // frm.add_custom_button( + // "Generate IRN", + // () => { + // frappe.call({ + // method: 'erpnext.regional.india.e_invoice_utils.generate_irn', + // args: { doctype: frm.doc.doctype, name: frm.doc.name }, + // freeze: true, + // callback: (res) => { + // console.log(res.message); + // frm.set_value('irn', res.message['Irn']); + // frm.set_value('signed_einvoice', JSON.stringify(res.message['DecryptedSignedInvoice'])); + // frm.set_value('signed_qr_code', JSON.stringify(res.message['DecryptedSignedQRCode'])); + + // if (res.message['EwbNo']) frm.set_value('ewaybill', res.message['EwbNo']); + // frm.save(); + // } + // }) + // } + // ) + // } + + // if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) { + // frm.add_custom_button( + // "Cancel IRN", + // () => { + // const d = new frappe.ui.Dialog({ + // title: __('Cancel IRN'), + // fields: [ + // { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", + // "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, + // { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 } + // ], + // primary_action: function() { + // const data = d.get_values(); + // frappe.call({ + // method: 'erpnext.regional.india.e_invoice_utils.cancel_irn', + // args: { irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark }, + // freeze: true, + // callback: () => { + // frm.set_value('irn_cancelled', 1); + // frm.save("Update"); + // d.hide() + // }, + // error: () => d.hide() + // }) + // }, + // primary_action_label: __('Submit') + // }); + // d.show(); + // } + // ) + // } + + // if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled && !frm.doc.eway_bill_cancelled) { + // frm.add_custom_button( + // "Cancel E-Way Bill", + // () => { + // const d = new frappe.ui.Dialog({ + // title: __('Cancel E-Way Bill'), + // fields: [ + // { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", + // "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, + // { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 } + // ], + // primary_action: function() { + // const data = d.get_values(); + // frappe.call({ + // method: 'erpnext.regional.india.e_invoice_utils.cancel_eway_bill', + // args: { eway_bill: frm.doc.ewaybill, reason: data.reason.split('-')[0], remark: data.remark }, + // freeze: true, + // callback: () => { + // frm.set_value('eway_bill_cancelled', 1); + // frm.save("Update"); + // d.hide() + // }, + // error: () => d.hide() + // }) + // }, + // primary_action_label: __('Submit') + // }); + // d.show(); + // } + // ) + // } if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) { frm.add_custom_button( - "Generate IRN", + "Download E-Invoice", () => { frappe.call({ - method: 'erpnext.regional.india.e_invoice_utils.generate_irn', + method: 'erpnext.regional.india.e_invoice_utils.make_e_invoice', args: { doctype: frm.doc.doctype, name: frm.doc.name }, freeze: true, callback: (res) => { - console.log(res.message); - frm.set_value('irn', res.message['Irn']); - frm.set_value('signed_einvoice', JSON.stringify(res.message['DecryptedSignedInvoice'])); - frm.set_value('signed_qr_code', JSON.stringify(res.message['DecryptedSignedQRCode'])); - - if (res.message['EwbNo']) frm.set_value('ewaybill', res.message['EwbNo']); - frm.save(); + if (!res.exc) { + const args = { + cmd: 'erpnext.regional.india.e_invoice_utils.download_einvoice', + einvoice: res.message.einvoice, + name: frm.doc.name + }; + open_url_post(frappe.request.url, args); + } } }) } - ) + ); + } + if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) { + frm.add_custom_button( + "Upload Signed E-Invoice", + () => { + new frappe.ui.FileUploader({ + method: 'erpnext.regional.india.e_invoice_utils.upload_einvoice', + allow_multiple: 0, + doctype: frm.doc.doctype, + docname: frm.doc.name, + on_success: (attachment, r) => { + if (!r.exc) { + frm.reload_doc(); + } + } + }); + } + ); } if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) { frm.add_custom_button( @@ -36,62 +140,30 @@ erpnext.setup_einvoice_actions = (doctype) => { const d = new frappe.ui.Dialog({ title: __('Cancel IRN'), fields: [ - { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, - { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 } + { + "label" : "Reason", "fieldname": "reason", + "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 + } ], primary_action: function() { const data = d.get_values(); - frappe.call({ - method: 'erpnext.regional.india.e_invoice_utils.cancel_irn', - args: { irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark }, - freeze: true, - callback: () => { - frm.set_value('irn_cancelled', 1); - frm.save("Update"); - d.hide() - }, - error: () => d.hide() - }) + const args = { + cmd: 'erpnext.regional.india.e_invoice_utils.download_cancel_einvoice', + irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark, name: frm.doc.name + }; + open_url_post(frappe.request.url, args); + d.hide(); }, - primary_action_label: __('Submit') + primary_action_label: __('Download JSON') }); d.show(); } - ) - } - if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled && !frm.doc.eway_bill_cancelled) { - frm.add_custom_button( - "Cancel E-Way Bill", - () => { - const d = new frappe.ui.Dialog({ - title: __('Cancel E-Way Bill'), - fields: [ - { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, - { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 } - ], - primary_action: function() { - const data = d.get_values(); - frappe.call({ - method: 'erpnext.regional.india.e_invoice_utils.cancel_eway_bill', - args: { eway_bill: frm.doc.ewaybill, reason: data.reason.split('-')[0], remark: data.remark }, - freeze: true, - callback: () => { - frm.set_value('eway_bill_cancelled', 1); - frm.save("Update"); - d.hide() - }, - error: () => d.hide() - }) - }, - primary_action_label: __('Submit') - }); - d.show(); - } - ) + ); } } - }) } \ No newline at end of file From 94db0bba4477ecedbc1ce5d805d6ec181e27c386 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 7 Oct 2020 11:07:11 +0530 Subject: [PATCH 034/137] chore: group e-invoicing actions --- erpnext/regional/india/e_invoice_utils.py | 15 ++++++++++--- erpnext/regional/india/einvoice.js | 26 ++++++++++++++++------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index 6109f800f7d..b33c15507c0 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -464,7 +464,7 @@ def make_e_invoice(doctype, name): else: frappe.throw(_("{}").format(error_msgs[0]), title=_("E Invoice Validation Failed")) - return {'einvoice': json.dumps(e_invoice)} + return {'einvoice': json.dumps([e_invoice])} def validate_einvoice(validations, e_invoice, error_msgs=[]): type_map = { @@ -549,9 +549,18 @@ def download_cancel_einvoice(): reason = data['reason'] remark = data['remark'] - cancel_einvoice = json.dumps(dict(Irn=irn, CnlRsn=reason, CnlRem=remark)) + cancel_einvoice = json.dumps([dict(Irn=irn, CnlRsn=reason, CnlRem=remark)]) frappe.response['filename'] = "Cancel E-Invoice " + name + ".json" frappe.response['filecontent'] = cancel_einvoice frappe.response['content_type'] = 'application/json' - frappe.response['type'] = 'download' \ No newline at end of file + frappe.response['type'] = 'download' + +@frappe.whitelist() +def download_cancel_ack(): + cancel_ack = json.loads(frappe.local.uploaded_file) + data = frappe._dict(frappe.local.form_dict) + doctype = data['doctype'] + name = data['docname'] + + frappe.db.set_value(doctype, name, "irn_cancelled", 1) \ No newline at end of file diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js index 4f98ffc88ab..2255358b8da 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/einvoice.js @@ -112,10 +112,7 @@ erpnext.setup_einvoice_actions = (doctype) => { } } }) - } - ); - } - if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) { + }, "E-Invoicing"); frm.add_custom_button( "Upload Signed E-Invoice", () => { @@ -130,8 +127,7 @@ erpnext.setup_einvoice_actions = (doctype) => { } } }); - } - ); + }, "E-Invoicing"); } if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) { frm.add_custom_button( @@ -161,8 +157,22 @@ erpnext.setup_einvoice_actions = (doctype) => { primary_action_label: __('Download JSON') }); d.show(); - } - ); + }, "E-Invoicing"); + frm.add_custom_button( + "Upload Cancel JSON", + () => { + new frappe.ui.FileUploader({ + method: 'erpnext.regional.india.e_invoice_utils.download_cancel_ack', + allow_multiple: 0, + doctype: frm.doc.doctype, + docname: frm.doc.name, + on_success: (attachment, r) => { + if (!r.exc) { + frm.reload_doc(); + } + } + }); + }, "E-Invoicing"); } } }) From 4f3f935a7a9a0227db8d236a89d955b9883895df Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 7 Oct 2020 11:08:11 +0530 Subject: [PATCH 035/137] fix: fn name --- erpnext/regional/india/e_invoice_utils.py | 2 +- erpnext/regional/india/einvoice.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index b33c15507c0..f7ae758a601 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -557,7 +557,7 @@ def download_cancel_einvoice(): frappe.response['type'] = 'download' @frappe.whitelist() -def download_cancel_ack(): +def upload_cancel_ack(): cancel_ack = json.loads(frappe.local.uploaded_file) data = frappe._dict(frappe.local.form_dict) doctype = data['doctype'] diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js index 2255358b8da..f1494b81716 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/einvoice.js @@ -162,7 +162,7 @@ erpnext.setup_einvoice_actions = (doctype) => { "Upload Cancel JSON", () => { new frappe.ui.FileUploader({ - method: 'erpnext.regional.india.e_invoice_utils.download_cancel_ack', + method: 'erpnext.regional.india.e_invoice_utils.upload_cancel_ack', allow_multiple: 0, doctype: frm.doc.doctype, docname: frm.doc.name, From 718e51714495adf97f91a28c6eddd1e5214d21cf Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 7 Oct 2020 11:21:47 +0530 Subject: [PATCH 036/137] chore: save signed invoice and qrcode after uplaoding irn --- erpnext/regional/india/e_invoice_utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index f7ae758a601..8d54f7e90ad 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -538,8 +538,13 @@ def upload_einvoice(): doctype = data['doctype'] name = data['docname'] - frappe.db.set_value(doctype, name, "irn", signed_einvoice.get("Irn")) - frappe.db.set_value(doctype, name, "ewaybill", signed_einvoice.get("EwbNo")) + enc_signed_invoice = signed_einvoice.get('SignedInvoice') + decrypted_signed_invoice = jwt_decrypt(enc_signed_invoice)['data'] + + frappe.db.set_value(doctype, name, 'irn', signed_einvoice.get('Irn')) + frappe.db.set_value(doctype, name, 'ewaybill', signed_einvoice.get('EwbNo')) + frappe.db.set_value(doctype, name, 'signed_qr_code', signed_einvoice.get('SignedQRCode')) + frappe.db.set_value(doctype, name, 'signed_einvoice', decrypted_signed_invoice) @frappe.whitelist() def download_cancel_einvoice(): From 233cad9e025515451be5edb6fb787f0d09a43f94 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 7 Oct 2020 12:26:45 +0530 Subject: [PATCH 037/137] fix: fetch token if not valid --- erpnext/regional/india/e_invoice_utils.py | 23 +++++++++++++++-------- erpnext/regional/india/einvoice.js | 3 ++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice_utils.py index 8d54f7e90ad..86f65285244 100644 --- a/erpnext/regional/india/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice_utils.py @@ -15,8 +15,8 @@ from Crypto.Util.Padding import pad, unpad from frappe.model.document import Document from frappe import _, get_module_path, scrub from erpnext.regional.india.utils import get_gst_accounts -from frappe.utils.data import get_datetime, cstr, cint, format_date, flt from frappe.integrations.utils import make_post_request, make_get_request +from frappe.utils.data import get_datetime, cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime def validate_einvoice_fields(doc): e_invoice_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable") @@ -29,8 +29,13 @@ def validate_einvoice_fields(doc): elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: frappe.throw(_("You must cancel IRN before cancelling the document."), title=_("Cancel Not Allowed")) -def get_einv_credentials(): - return frappe.get_doc("E Invoice Settings") +def get_einv_credentials(for_token=False): + creds = frappe.get_doc("E Invoice Settings") + if not for_token and (not creds.token_expiry or time_diff_in_seconds(now_datetime(), creds.token_expiry) > 5.0): + fetch_token() + creds.load_from_db() + + return creds def rsa_encrypt(msg, key): if not (isinstance(msg, bytes) or isinstance(msg, bytearray)): @@ -78,7 +83,7 @@ def get_header(creds): @frappe.whitelist() def fetch_token(): - einv_creds = get_einv_credentials() + einv_creds = get_einv_credentials(for_token=True) endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' headers = { 'content-type': 'application/json' } @@ -270,11 +275,13 @@ def get_doc_details(invoice): def get_party_gstin_details(party_address): address = frappe.get_all("Address", filters={"name": party_address}, fields=["*"])[0] - # gstin_details = get_gstin_details(gstin) gstin = address.get('gstin') - legal_name = address.get('address_title') - # trade_name = gstin_details.get('TradeName') - location = address.get('city') + gstin_details = get_gstin_details(gstin) + # legal_name = address.get('address_title') + legal_name = gstin_details.get('LegalName') + trade_name = gstin_details.get('TradeName') + # location = address.get('city') + location = gstin_details.get('Loc') state_code = address.get('gst_state_number') pincode = cint(address.get('pincode')) address_line1 = address.get('address_line1') diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/einvoice.js index f1494b81716..f30e81a500c 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/einvoice.js @@ -157,7 +157,8 @@ erpnext.setup_einvoice_actions = (doctype) => { primary_action_label: __('Download JSON') }); d.show(); - }, "E-Invoicing"); + }, "E-Invoicing"); + frm.add_custom_button( "Upload Cancel JSON", () => { From e0ea4265a8ffc1c6c3f3a417100ce910deb3a040 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 12:35:50 +0530 Subject: [PATCH 038/137] chore: move einvoicing stuff to seperate folder --- .../doctype/sales_invoice/regional/india.js | 2 +- erpnext/hooks.py | 2 +- .../e_invoice_settings/e_invoice_settings.js | 2 +- .../india/{ => e_invoice}/e_invoice_utils.py | 0 .../{ => e_invoice}/einv_item_template.json | 0 .../india/{ => e_invoice}/einv_template.json | 0 .../india/{ => e_invoice}/einv_validation.json | 0 .../regional/india/{ => e_invoice}/einvoice.js | 16 ++++++++-------- 8 files changed, 11 insertions(+), 11 deletions(-) rename erpnext/regional/india/{ => e_invoice}/e_invoice_utils.py (100%) rename erpnext/regional/india/{ => e_invoice}/einv_item_template.json (100%) rename erpnext/regional/india/{ => e_invoice}/einv_template.json (100%) rename erpnext/regional/india/{ => e_invoice}/einv_validation.json (100%) rename erpnext/regional/india/{ => e_invoice}/einvoice.js (88%) diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js index e97671ad6e0..ba672c9368d 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js @@ -1,5 +1,5 @@ {% include "erpnext/regional/india/taxes.js" %} -{% include "erpnext/regional/india/einvoice.js" %} +{% include "erpnext/regional/india/e_invoice/einvoice.js" %} erpnext.setup_auto_gst_taxation('Sales Invoice'); erpnext.setup_einvoice_actions('Sales Invoice') diff --git a/erpnext/hooks.py b/erpnext/hooks.py index f70533ea38b..6b1935f2397 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -361,7 +361,7 @@ regional_overrides = { 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', - 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice_utils.validate_einvoice_fields', + 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.e_invoice_utils.validate_einvoice_fields', }, 'United Arab Emirates': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data' diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js index 1b7efeeeddf..e9fb622b6b6 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -15,7 +15,7 @@ frappe.ui.form.on('E Invoice Settings', { frm.add_custom_button(__("Fetch Token"), () => { frm.call({ - method: 'erpnext.regional.india.e_invoice_utils.fetch_token', + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.fetch_token', freeze: true, callback: () => frm.refresh() }); diff --git a/erpnext/regional/india/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py similarity index 100% rename from erpnext/regional/india/e_invoice_utils.py rename to erpnext/regional/india/e_invoice/e_invoice_utils.py diff --git a/erpnext/regional/india/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json similarity index 100% rename from erpnext/regional/india/einv_item_template.json rename to erpnext/regional/india/e_invoice/einv_item_template.json diff --git a/erpnext/regional/india/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json similarity index 100% rename from erpnext/regional/india/einv_template.json rename to erpnext/regional/india/e_invoice/einv_template.json diff --git a/erpnext/regional/india/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json similarity index 100% rename from erpnext/regional/india/einv_validation.json rename to erpnext/regional/india/e_invoice/einv_validation.json diff --git a/erpnext/regional/india/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js similarity index 88% rename from erpnext/regional/india/einvoice.js rename to erpnext/regional/india/e_invoice/einvoice.js index f30e81a500c..8191bea82fc 100644 --- a/erpnext/regional/india/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -12,7 +12,7 @@ erpnext.setup_einvoice_actions = (doctype) => { // "Generate IRN", // () => { // frappe.call({ - // method: 'erpnext.regional.india.e_invoice_utils.generate_irn', + // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.generate_irn', // args: { doctype: frm.doc.doctype, name: frm.doc.name }, // freeze: true, // callback: (res) => { @@ -43,7 +43,7 @@ erpnext.setup_einvoice_actions = (doctype) => { // primary_action: function() { // const data = d.get_values(); // frappe.call({ - // method: 'erpnext.regional.india.e_invoice_utils.cancel_irn', + // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_irn', // args: { irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark }, // freeze: true, // callback: () => { @@ -75,7 +75,7 @@ erpnext.setup_einvoice_actions = (doctype) => { // primary_action: function() { // const data = d.get_values(); // frappe.call({ - // method: 'erpnext.regional.india.e_invoice_utils.cancel_eway_bill', + // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_eway_bill', // args: { eway_bill: frm.doc.ewaybill, reason: data.reason.split('-')[0], remark: data.remark }, // freeze: true, // callback: () => { @@ -98,13 +98,13 @@ erpnext.setup_einvoice_actions = (doctype) => { "Download E-Invoice", () => { frappe.call({ - method: 'erpnext.regional.india.e_invoice_utils.make_e_invoice', + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.make_e_invoice', args: { doctype: frm.doc.doctype, name: frm.doc.name }, freeze: true, callback: (res) => { if (!res.exc) { const args = { - cmd: 'erpnext.regional.india.e_invoice_utils.download_einvoice', + cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_einvoice', einvoice: res.message.einvoice, name: frm.doc.name }; @@ -117,7 +117,7 @@ erpnext.setup_einvoice_actions = (doctype) => { "Upload Signed E-Invoice", () => { new frappe.ui.FileUploader({ - method: 'erpnext.regional.india.e_invoice_utils.upload_einvoice', + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_einvoice', allow_multiple: 0, doctype: frm.doc.doctype, docname: frm.doc.name, @@ -148,7 +148,7 @@ erpnext.setup_einvoice_actions = (doctype) => { primary_action: function() { const data = d.get_values(); const args = { - cmd: 'erpnext.regional.india.e_invoice_utils.download_cancel_einvoice', + cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_cancel_einvoice', irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark, name: frm.doc.name }; open_url_post(frappe.request.url, args); @@ -163,7 +163,7 @@ erpnext.setup_einvoice_actions = (doctype) => { "Upload Cancel JSON", () => { new frappe.ui.FileUploader({ - method: 'erpnext.regional.india.e_invoice_utils.upload_cancel_ack', + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_cancel_ack', allow_multiple: 0, doctype: frm.doc.doctype, docname: frm.doc.name, From 4e12466bef988262e6781b42546d0bedeef226d3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 19:43:25 +0530 Subject: [PATCH 039/137] feat: QRCode Image and E-Invoice Print Format --- .../print_format/gst_e_invoice/__init__.py | 0 .../gst_e_invoice/gst_e_invoice.html | 147 ++++++++++++++++++ .../gst_e_invoice/gst_e_invoice.json | 24 +++ .../india/e_invoice/e_invoice_utils.py | 85 ++++++---- .../india/e_invoice/einv_template.json | 2 +- erpnext/regional/india/e_invoice/einvoice.js | 24 ++- erpnext/regional/india/setup.py | 18 ++- 7 files changed, 246 insertions(+), 54 deletions(-) create mode 100644 erpnext/accounts/print_format/gst_e_invoice/__init__.py create mode 100644 erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html create mode 100644 erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json diff --git a/erpnext/accounts/print_format/gst_e_invoice/__init__.py b/erpnext/accounts/print_format/gst_e_invoice/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html new file mode 100644 index 00000000000..15ea1c21744 --- /dev/null +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -0,0 +1,147 @@ +{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%} +{%- set einvoice = json.loads(doc.signed_einvoice) -%} + +
    +
    + +
    +
    +
    1. Transaction Details
    +
    +
    +
    +
    {{ einvoice.Irn }}
    +
    +
    +
    +
    {{ einvoice.AckNo }}
    +
    +
    +
    +
    {{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}
    +
    +
    +
    +
    {{ einvoice.TranDtls.SupTyp }}
    +
    +
    +
    +
    {{ einvoice.DocDtls.Typ }}
    +
    +
    +
    +
    {{ einvoice.DocDtls.No }}
    +
    +
    +
    + +
    +
    +
    +
    2. Party Details
    + {%- set seller = einvoice.SellerDtls -%} +
    +
    Seller
    +

    {{ seller.Gstin }}

    +

    {{ seller.LglNm }}

    +

    {{ seller.Addr1 }}

    + {%- if seller.Addr2 -%}

    {{ seller.Addr2 }}

    {% endif %} +

    {{ seller.Loc }}

    +

    {{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}

    + + {%- if einvoice.ShipDtls -%} + {%- set shipping = einvoice.ShipDtls -%} +
    Shipping
    +

    {{ shipping.Gstin }}

    +

    {{ shipping.LglNm }}

    +

    {{ shipping.Addr1 }}

    + {%- if shipping.Addr2 -%}

    {{ shipping.Addr2 }}

    {% endif %} +

    {{ shipping.Loc }}

    +

    {{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}

    + {% endif %} +
    + {%- set buyer = einvoice.BuyerDtls -%} +
    +
    Buyer
    +

    {{ buyer.Gstin }}

    +

    {{ buyer.LglNm }}

    +

    {{ buyer.Addr1 }}

    + {%- if buyer.Addr2 -%}

    {{ buyer.Addr2 }}

    {% endif %} +

    {{ buyer.Loc }}

    +

    {{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}

    +
    +
    +
    +
    3. Item Details
    + + + + + + + + + + + + + + + + + + + {% for item in einvoice.ItemList %} + + + + + + + + + + + + {% endfor %} + + +
    Sr. No.ItemHSN CodeQtyUOMRateDiscountTaxable AmountTax RateOther ChargesTotal
    {{ item.SlNo }}{{ item.PrdDesc }}{{ item.HsnCd }}{{ item.Qty }}{{ item.Unit }}{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}{{ item.GstRt + item.CesRt }} %{{ frappe.utils.fmt_money(0, None, "INR") }}{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}
    +
    +
    +
    4. Value Details
    + + + + + + + + + + + + + + + + + {%- set value_details = einvoice.ValDtls -%} + + + + + + + + + + + + + +
    Taxable AmountCGSTSGSTIGSTCESSState CESSDiscountOther ChargesRound OffTotal Value
    {{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}{{ frappe.utils.fmt_money(0, None, "INR") }}{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}{{ frappe.utils.fmt_money(0, None, "INR") }}{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}
    +
    +
    \ No newline at end of file diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json new file mode 100644 index 00000000000..0a5c8a27882 --- /dev/null +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json @@ -0,0 +1,24 @@ +{ + "align_labels_right": 1, + "creation": "2020-10-10 18:01:21.032914", + "custom_format": 0, + "default_print_language": "en-US", + "disabled": 0, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "", + "idx": 0, + "line_breaks": 1, + "modified": "2020-10-10 18:01:21.032914", + "modified_by": "Administrator", + "module": "Accounts", + "name": "GST E-Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 1, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index 86f65285244..71ee07da68d 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -10,6 +10,7 @@ import json import base64 import frappe from Crypto.PublicKey import RSA +from pyqrcode import create as qrcreate from Crypto.Cipher import PKCS1_v1_5, AES from Crypto.Util.Padding import pad, unpad from frappe.model.document import Document @@ -116,12 +117,12 @@ def extract_token_and_sek(response, appkey): sek = aes_decrypt(enc_sek, appkey) return auth_token, token_expiry, sek -def attach_signed_json(invoice, data): +def attach_signed_invoice(doctype, name, data): f = frappe.get_doc({ "doctype": "File", - "file_name": invoice.name + "e_invoice.json", - "attached_to_doctype": invoice.doctype, - "attached_to_name": invoice.name, + "file_name": name + "e_invoice.json", + "attached_to_doctype": doctype, + "attached_to_name": name, "content": json.dumps(data), "is_private": True }).insert() @@ -147,8 +148,7 @@ def generate_irn(doctype, name): einv_creds = get_einv_credentials() headers = get_header(einv_creds) - invoice = frappe.get_doc(doctype, name) - e_invoice = make_e_invoice(invoice) + e_invoice = make_e_invoice(doctype, name) enc_e_invoice_json = aes_encrypt(e_invoice, einv_creds.sek) payload = dict(Data=enc_e_invoice_json) @@ -159,12 +159,15 @@ def generate_irn(doctype, name): enc_json = res.get('Data') json_str = aes_decrypt(enc_json, einv_creds.sek) - data = json.loads(json_str) - handle_irn_response(data) + signed_einvoice = json.loads(json_str) + handle_irn_response(signed_einvoice) - attach_signed_json(invoice, data['DecryptedSignedInvoice']) + update_einvoice_fields(doctype, name, signed_einvoice) - return data + attach_qrcode_image(doctype, name) + attach_signed_invoice(doctype, name, signed_einvoice['DecryptedSignedInvoice']) + + return signed_einvoice def get_irn_details(irn): einv_creds = get_einv_credentials() @@ -175,16 +178,10 @@ def get_irn_details(irn): res = make_get_request(endpoint, headers=headers) handle_err_response(res) - # enc_json = res.get('Data') - # json_str = aes_decrypt(enc_json, einv_creds.sek) - - # data = json.loads(json_str) - # handle_irn_response(data) - return res @frappe.whitelist() -def cancel_irn(irn, reason, remark=''): +def cancel_irn(doctype, name, irn, reason, remark=''): einv_creds = get_einv_credentials() endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/Cancel' @@ -197,6 +194,8 @@ def cancel_irn(irn, reason, remark=''): res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) handle_err_response(res) + frappe.db.set_value(doctype, name, 'irn_cancelled', 1) + return res @frappe.whitelist() @@ -277,15 +276,13 @@ def get_party_gstin_details(party_address): gstin = address.get('gstin') gstin_details = get_gstin_details(gstin) - # legal_name = address.get('address_title') legal_name = gstin_details.get('LegalName') trade_name = gstin_details.get('TradeName') - # location = address.get('city') - location = gstin_details.get('Loc') - state_code = address.get('gst_state_number') - pincode = cint(address.get('pincode')) - address_line1 = address.get('address_line1') - address_line2 = address.get('address_line2') + location = gstin_details.get('AddrLoc') + state_code = gstin_details.get('StateCode') + pincode = cint(gstin_details.get('AddrPncd')) + address_line1 = "{} {}".format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno')) + address_line2 = "{} {}".format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt')) email_id = address.get('email_id') phone = address.get('phone') if state_code == 97: @@ -527,6 +524,15 @@ def validate_einvoice(validations, e_invoice, error_msgs=[]): return error_msgs +def update_einvoice_fields(doctype, name, signed_einvoice): + enc_signed_invoice = signed_einvoice.get('SignedInvoice') + decrypted_signed_invoice = jwt_decrypt(enc_signed_invoice)['data'] + + frappe.db.set_value(doctype, name, 'irn', signed_einvoice.get('Irn')) + frappe.db.set_value(doctype, name, 'ewaybill', signed_einvoice.get('EwbNo')) + frappe.db.set_value(doctype, name, 'signed_qr_code', signed_einvoice.get('SignedQRCode').split('.')[1]) + frappe.db.set_value(doctype, name, 'signed_einvoice', decrypted_signed_invoice) + @frappe.whitelist() def download_einvoice(): data = frappe._dict(frappe.local.form_dict) @@ -545,13 +551,7 @@ def upload_einvoice(): doctype = data['doctype'] name = data['docname'] - enc_signed_invoice = signed_einvoice.get('SignedInvoice') - decrypted_signed_invoice = jwt_decrypt(enc_signed_invoice)['data'] - - frappe.db.set_value(doctype, name, 'irn', signed_einvoice.get('Irn')) - frappe.db.set_value(doctype, name, 'ewaybill', signed_einvoice.get('EwbNo')) - frappe.db.set_value(doctype, name, 'signed_qr_code', signed_einvoice.get('SignedQRCode')) - frappe.db.set_value(doctype, name, 'signed_einvoice', decrypted_signed_invoice) + update_einvoice_fields(doctype, name, signed_einvoice) @frappe.whitelist() def download_cancel_einvoice(): @@ -575,4 +575,25 @@ def upload_cancel_ack(): doctype = data['doctype'] name = data['docname'] - frappe.db.set_value(doctype, name, "irn_cancelled", 1) \ No newline at end of file + frappe.db.set_value(doctype, name, "irn_cancelled", 1) + +def attach_qrcode_image(doctype, name): + qrcode = frappe.db.get_value(doctype, name, 'signed_qr_code') + + if not qrcode: return + + _file = frappe.get_doc({ + "doctype": "File", + "file_name": "Signed_QR_{name}.png".format(name=name), + "attached_to_doctype": doctype, + "attached_to_name": name, + "attached_to_field": "qrcode_image", + "content": "qrcode" + }) + _file.save() + frappe.db.commit() + url = qrcreate(qrcode) + abs_file_path = os.path.abspath(_file.get_full_path()) + url.png(abs_file_path, scale=2) + + frappe.db.set_value(doctype, name, 'qrcode_image', _file.file_url) \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json index 15230fe22f9..0acc01cd386 100644 --- a/erpnext/regional/india/e_invoice/einv_template.json +++ b/erpnext/regional/india/e_invoice/einv_template.json @@ -1,5 +1,5 @@ {{ - "Version": "1.01", + "Version": "1.1", "TranDtls": {{ "TaxSch": "{trans_details.tax_scheme}", "SupTyp": "{trans_details.supply_type}", diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 8191bea82fc..076f13475d8 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -15,15 +15,7 @@ erpnext.setup_einvoice_actions = (doctype) => { // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.generate_irn', // args: { doctype: frm.doc.doctype, name: frm.doc.name }, // freeze: true, - // callback: (res) => { - // console.log(res.message); - // frm.set_value('irn', res.message['Irn']); - // frm.set_value('signed_einvoice', JSON.stringify(res.message['DecryptedSignedInvoice'])); - // frm.set_value('signed_qr_code', JSON.stringify(res.message['DecryptedSignedQRCode'])); - - // if (res.message['EwbNo']) frm.set_value('ewaybill', res.message['EwbNo']); - // frm.save(); - // } + // callback: () => frm.reload_doc() // }) // } // ) @@ -44,13 +36,15 @@ erpnext.setup_einvoice_actions = (doctype) => { // const data = d.get_values(); // frappe.call({ // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_irn', - // args: { irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark }, - // freeze: true, - // callback: () => { - // frm.set_value('irn_cancelled', 1); - // frm.save("Update"); - // d.hide() + // args: { + // doctype: frm.doc.doctype, + // name: frm.doc.name, + // irn: frm.doc.irn, + // reason: data.reason.split('-')[0], + // remark: data.remark // }, + // freeze: true, + // callback: () => frm.reload_doc() || d.hide(), // error: () => d.hide() // }) // }, diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index d5aba17a88f..fc8004b4752 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -377,14 +377,20 @@ def make_custom_fields(update=True): ] si_einvoice_fields = [ - dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, - depends_on='eval:in_list(["Register Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), - dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, + depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), + + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), - dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, read_only=1), - dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, read_only=1), - dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, + + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1) ] custom_fields = { From 2e16dcc8fa39df969e0ac6ef75992d96cae7f5c5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 19:47:29 +0530 Subject: [PATCH 040/137] fix: bug --- erpnext/regional/india/e_invoice/e_invoice_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index 71ee07da68d..b499bb53353 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -17,7 +17,7 @@ from frappe.model.document import Document from frappe import _, get_module_path, scrub from erpnext.regional.india.utils import get_gst_accounts from frappe.integrations.utils import make_post_request, make_get_request -from frappe.utils.data import get_datetime, cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime +from frappe.utils.data import get_datetime, cstr, cint,format_date, flt, time_diff_in_seconds, now_datetime def validate_einvoice_fields(doc): e_invoice_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable") @@ -120,7 +120,7 @@ def extract_token_and_sek(response, appkey): def attach_signed_invoice(doctype, name, data): f = frappe.get_doc({ "doctype": "File", - "file_name": name + "e_invoice.json", + "file_name": "E-INV--{}.json".format{name), "attached_to_doctype": doctype, "attached_to_name": name, "content": json.dumps(data), From 52b983eb8f70749990ebe7a84ac9783eef0dca88 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 19:48:38 +0530 Subject: [PATCH 041/137] fix: invalid syntax --- erpnext/regional/india/e_invoice/e_invoice_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index b499bb53353..f5f4098c7cb 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -17,7 +17,7 @@ from frappe.model.document import Document from frappe import _, get_module_path, scrub from erpnext.regional.india.utils import get_gst_accounts from frappe.integrations.utils import make_post_request, make_get_request -from frappe.utils.data import get_datetime, cstr, cint,format_date, flt, time_diff_in_seconds, now_datetime +from frappe.utils.data import get_datetime, cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime def validate_einvoice_fields(doc): e_invoice_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable") @@ -120,7 +120,7 @@ def extract_token_and_sek(response, appkey): def attach_signed_invoice(doctype, name, data): f = frappe.get_doc({ "doctype": "File", - "file_name": "E-INV--{}.json".format{name), + "file_name": "E-INV--{}.json".format(name), "attached_to_doctype": doctype, "attached_to_name": name, "content": json.dumps(data), From 2e38f73732c0f1aa1c632fa5c63c40c92d469988 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 21:51:53 +0530 Subject: [PATCH 042/137] chore: code cleanup --- .../india/e_invoice/e_invoice_utils.py | 272 ++++++----- .../india/e_invoice/einv_validation.json | 422 +++++++++--------- erpnext/regional/india/e_invoice/einvoice.js | 2 +- 3 files changed, 344 insertions(+), 352 deletions(-) diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index f5f4098c7cb..044c2c88da2 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -9,6 +9,7 @@ import jwt import json import base64 import frappe +from six import string_types from Crypto.PublicKey import RSA from pyqrcode import create as qrcreate from Crypto.Cipher import PKCS1_v1_5, AES @@ -20,23 +21,23 @@ from frappe.integrations.utils import make_post_request, make_get_request from frappe.utils.data import get_datetime, cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime def validate_einvoice_fields(doc): - e_invoice_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable") - if not doc.doctype in ['Sales Invoice', 'Purchase Invoice'] or not e_invoice_enabled: return + einvoicing_enabled = frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable') + if not doc.doctype in ['Sales Invoice', 'Purchase Invoice'] or not einvoicing_enabled: return if doc.docstatus == 0 and doc._action == 'save' and 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')) elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: - frappe.throw(_("You must generate IRN before submitting the document."), title=_("Missing IRN")) + frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: - frappe.throw(_("You must cancel IRN before cancelling the document."), title=_("Cancel Not Allowed")) + frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed')) -def get_einv_credentials(for_token=False): - creds = frappe.get_doc("E Invoice Settings") - if not for_token and (not creds.token_expiry or time_diff_in_seconds(now_datetime(), creds.token_expiry) > 5.0): - fetch_token() - creds.load_from_db() - - return creds +def get_credentials(): + doc = frappe.get_doc('E Invoice Settings') + if not doc.token_expiry or time_diff_in_seconds(now_datetime(), doc.token_expiry) > 5.0: + fetch_token(doc) + doc.load_from_db() + + return doc def rsa_encrypt(msg, key): if not (isinstance(msg, bytes) or isinstance(msg, bytearray)): @@ -83,19 +84,20 @@ def get_header(creds): return headers @frappe.whitelist() -def fetch_token(): - einv_creds = get_einv_credentials(for_token=True) +def fetch_token(credentials=None): + if not credentials: + credentials = frappe.get_doc('E Invoice Settings') endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' headers = { 'content-type': 'application/json' } - headers.update(dict(client_id=einv_creds.client_id, client_secret=einv_creds.client_secret)) - payload = dict(UserName=einv_creds.username, ForceRefreshAccessToken=bool(einv_creds.auto_refresh_token)) + headers.update(dict(client_id=credentials.client_id, client_secret=credentials.client_secret)) + payload = dict(UserName=credentials.username, ForceRefreshAccessToken=bool(credentials.auto_refresh_token)) appkey = bytearray(os.urandom(32)) - enc_appkey = rsa_encrypt(appkey, einv_creds.public_key) + enc_appkey = rsa_encrypt(appkey, credentials.public_key) - password = einv_creds.get_password(fieldname='password') - enc_password = rsa_encrypt(password, einv_creds.public_key) + password = credentials.get_password(fieldname='password') + enc_password = rsa_encrypt(password, credentials.public_key) payload.update(dict(Password=enc_password, AppKey=enc_appkey)) @@ -104,10 +106,10 @@ def fetch_token(): auth_token, token_expiry, sek = extract_token_and_sek(res, appkey) - einv_creds.auth_token = auth_token - einv_creds.token_expiry = get_datetime(token_expiry) - einv_creds.sek = sek - einv_creds.save() + credentials.auth_token = auth_token + credentials.token_expiry = get_datetime(token_expiry) + credentials.sek = sek + credentials.save() def extract_token_and_sek(response, appkey): data = response.get('Data') @@ -119,45 +121,45 @@ def extract_token_and_sek(response, appkey): def attach_signed_invoice(doctype, name, data): f = frappe.get_doc({ - "doctype": "File", - "file_name": "E-INV--{}.json".format(name), - "attached_to_doctype": doctype, - "attached_to_name": name, - "content": json.dumps(data), - "is_private": True + 'doctype': 'File', + 'file_name': 'E-INV--{}.json'.format(name), + 'attached_to_doctype': doctype, + 'attached_to_name': name, + 'content': json.dumps(data), + 'is_private': True }).insert() def get_gstin_details(gstin): - einv_creds = get_einv_credentials() + credentials = get_credentials() endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/Master/gstin/{gstin}'.format(gstin=gstin) - headers = get_header(einv_creds) + headers = get_header(credentials) res = make_get_request(endpoint, headers=headers) handle_err_response(res) - enc_json = res.get('Data') - json_str = aes_decrypt(enc_json, einv_creds.sek) - data = json.loads(json_str) + enc_details = res.get('Data') + json_str = aes_decrypt(enc_details, credentials.sek) + details = json.loads(json_str) - return data + return details @frappe.whitelist() def generate_irn(doctype, name): endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice' - einv_creds = get_einv_credentials() - headers = get_header(einv_creds) + credentials = get_credentials() + headers = get_header(credentials) - e_invoice = make_e_invoice(doctype, name) + einvoice = make_einvoice(doctype, name) - enc_e_invoice_json = aes_encrypt(e_invoice, einv_creds.sek) - payload = dict(Data=enc_e_invoice_json) + enc_einvoice_json = aes_encrypt(einvoice, credentials.sek) + payload = dict(Data=enc_einvoice_json) res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) res = handle_err_response(res) enc_json = res.get('Data') - json_str = aes_decrypt(enc_json, einv_creds.sek) + json_str = aes_decrypt(enc_json, credentials.sek) signed_einvoice = json.loads(json_str) handle_irn_response(signed_einvoice) @@ -170,10 +172,10 @@ def generate_irn(doctype, name): return signed_einvoice def get_irn_details(irn): - einv_creds = get_einv_credentials() + credentials = get_credentials() endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/irn/{irn}'.format(irn=irn) - headers = get_header(einv_creds) + headers = get_header(credentials) res = make_get_request(endpoint, headers=headers) handle_err_response(res) @@ -182,13 +184,13 @@ def get_irn_details(irn): @frappe.whitelist() def cancel_irn(doctype, name, irn, reason, remark=''): - einv_creds = get_einv_credentials() + credentials = get_credentials() endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/Cancel' - headers = get_header(einv_creds) + headers = get_header(credentials) - cancel_e_inv = json.dumps(dict(Irn=irn, CnlRsn=reason, CnlRem=remark)) - enc_json = aes_encrypt(cancel_e_inv, einv_creds.sek) + cancel_einv = json.dumps(dict(Irn=irn, CnlRsn=reason, CnlRem=remark)) + enc_json = aes_encrypt(cancel_einv, credentials.sek) payload = dict(Data=enc_json) res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) @@ -199,18 +201,21 @@ def cancel_irn(doctype, name, irn, reason, remark=''): return res @frappe.whitelist() -def cancel_eway_bill(eway_bill, reason, remark=''): - einv_creds = get_einv_credentials() +def cancel_eway_bill(doctype, name, eway_bill, reason, remark=''): + credentials = get_credentials() endpoint = 'https://einv-apisandbox.nic.in/ewaybillapi/v1.03/ewayapi' - headers = get_header(einv_creds) + headers = get_header(credentials) cancel_eway_bill_json = json.dumps(dict(ewbNo=eway_bill, cancelRsnCode=reason, cancelRmrk=remark)) - enc_json = aes_encrypt(cancel_eway_bill_json, einv_creds.sek) - payload = dict(action="CANEWB", Data=enc_json) + enc_json = aes_encrypt(cancel_eway_bill_json, credentials.sek) + payload = dict(action='CANEWB', Data=enc_json) res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) handle_err_response(res) + frappe.db.set_value(doctype, name, 'ewaybill', '') + frappe.db.set_value(doctype, name, 'eway_bill_cancelled', 1) + return res def handle_irn_response(data): @@ -225,22 +230,28 @@ def handle_err_response(response): if response.get('Status') == 0: err_details = response.get('ErrorDetails') print(response) - err_msg = "" + error_msgs = [] for d in err_details: err_code = d.get('ErrorCode') + if err_code == '2150': irn = [d['Desc']['Irn'] for d in response.get('InfoDtls') if d['InfCd'] == 'DUPIRN'] response = get_irn_details(irn[0]) return response - err_msg += d.get('ErrorMessage') - err_msg += "
    " - frappe.throw(_(err_msg), title=_('API Request Failed')) + error_msgs.append(d.get('ErrorMessage')) + + if error_msgs: + if len(error_msgs) > 1: + li = ['
  • '+ d +'
  • ' for d in error_msgs] + frappe.throw(_("""
      {}
    """).format(''.join(li)), title=_('API Request Failed')) + else: + frappe.throw(_('{}').format(error_msgs[0]), title=_('API Request Failed')) return response def read_json(name): - file_path = os.path.join(os.path.dirname(__file__), "{name}.json".format(name=name)) + file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name)) with open(file_path, 'r') as f: return cstr(f.read()) @@ -267,12 +278,10 @@ def get_doc_details(invoice): invoice_name = invoice.name invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') - return frappe._dict(dict( - invoice_type=invoice_type, invoice_name=invoice_name, invoice_date=invoice_date - )) + return frappe._dict(dict(invoice_type=invoice_type, invoice_name=invoice_name, invoice_date=invoice_date)) -def get_party_gstin_details(party_address): - address = frappe.get_all("Address", filters={"name": party_address}, fields=["*"])[0] +def get_party_gstin_details(address_name): + address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] gstin = address.get('gstin') gstin_details = get_gstin_details(gstin) @@ -281,8 +290,8 @@ def get_party_gstin_details(party_address): location = gstin_details.get('AddrLoc') state_code = gstin_details.get('StateCode') pincode = cint(gstin_details.get('AddrPncd')) - address_line1 = "{} {}".format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno')) - address_line2 = "{} {}".format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt')) + address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno')) + address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt')) email_id = address.get('email_id') phone = address.get('phone') if state_code == 97: @@ -294,9 +303,9 @@ def get_party_gstin_details(party_address): address_line2=address_line2, email=email_id, phone=phone )) -def get_overseas_address_details(party_address): +def get_overseas_address_details(address_name): address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value( - "Address", party_address, ["address_title", "address_line1", "address_line2", "city", "phone", "email_id"] + 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id'] ) return frappe._dict(dict( @@ -311,13 +320,13 @@ def get_item_list(invoice): gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] for d in invoice.items: - item_schema = read_json("einv_item_template") + item_schema = read_json('einv_item_template') item = frappe._dict(dict()) item.update(d.as_dict()) item.sr_no = d.idx item.description = d.item_name - item.is_service_item = "N" if frappe.db.get_value("Item", d.item_code, "is_stock_item") else "Y" - item.batch_expiry_date = frappe.db.get_value("Batch", d.batch_no, "expiry_date") if d.batch_no else None + item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' + item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None item.unit_rate = item.base_price_list_rate if item.discount_amount else item.base_rate item.total_amount = item.unit_rate * item.qty @@ -345,10 +354,10 @@ def get_item_list(invoice): item.cgst_amount += item_tax_detail[1] item.total_value = item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount - e_inv_item = item_schema.format(item=item) - item_list.append(e_inv_item) + einv_item = item_schema.format(item=item) + item_list.append(einv_item) - return ", ".join(item_list) + return ', '.join(item_list) def get_value_details(invoice): gst_accounts = get_gst_accounts(invoice.company) @@ -379,7 +388,7 @@ def get_value_details(invoice): def get_payment_details(invoice): payee_name = invoice.company - mode_of_payment = ", ".join([d.mode_of_payment for d in invoice.payments]) + mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments]) paid_amount = invoice.base_paid_amount outstanding_amount = invoice.outstanding_amount @@ -389,14 +398,14 @@ def get_payment_details(invoice): )) def get_return_doc_reference(invoice): - invoice_date = frappe.db.get_value("Sales Invoice", invoice.return_against, "posting_date") + invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') return frappe._dict(dict( invoice_name=invoice.return_against, invoice_date=invoice_date )) def get_eway_bill_details(invoice): if not invoice.distance: - frappe.throw(_("Distance is mandatory for E-Way Bill generation"), title=_("Missing Values")) + frappe.throw(_('Distance is mandatory for E-Way Bill generation'), title=_('E Invoice Validation Failed')) mode_of_transport = { 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } @@ -413,13 +422,15 @@ def get_eway_bill_details(invoice): )) @frappe.whitelist() -def make_e_invoice(doctype, name): +def make_einvoice(doctype, name): invoice = frappe.get_doc(doctype, name) - schema = read_json("einv_template") - validations = json.loads(read_json("einv_validation")) + schema = read_json('einv_template') + validations = json.loads(read_json('einv_validation')) - trans_details = get_trans_details(invoice) + item_list = get_item_list(invoice) doc_details = get_doc_details(invoice) + value_details = get_value_details(invoice) + trans_details = get_trans_details(invoice) seller_details = get_party_gstin_details(invoice.company_address) if invoice.gst_category == 'Overseas': @@ -428,99 +439,87 @@ def make_e_invoice(doctype, name): buyer_details = get_party_gstin_details(invoice.customer_address) place_of_supply = invoice.place_of_supply.split('-')[0] buyer_details.update(dict(place_of_supply=place_of_supply)) - - item_list = get_item_list(invoice) - value_details = get_value_details(invoice) - shipping_details = frappe._dict({}) + shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: shipping_details = get_party_gstin_details(invoice.shipping_address_name) - payment_details = frappe._dict({}) if invoice.is_pos and invoice.base_paid_amount: payment_details = get_payment_details(invoice) - prev_doc_details = frappe._dict({}) if invoice.is_return and invoice.return_against: prev_doc_details = get_return_doc_reference(invoice) - dispatch_details = frappe._dict({}) - period_details = frappe._dict({}) - export_details = frappe._dict({}) - eway_bill_details = frappe._dict({}) if invoice.transporter: eway_bill_details = get_eway_bill_details(invoice) + + # not yet implemented + dispatch_details = period_details = export_details = frappe._dict({}) - e_invoice = schema.format( + einvoice = schema.format( trans_details=trans_details, doc_details=doc_details, dispatch_details=dispatch_details, seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details, item_list=item_list, value_details=value_details, payment_details=payment_details, period_details=period_details, prev_doc_details=prev_doc_details, export_details=export_details, eway_bill_details=eway_bill_details ) - e_invoice = json.loads(e_invoice) + einvoice = json.loads(einvoice) - error_msgs = validate_einvoice(validations, e_invoice, []) + error_msgs = validate_einvoice(validations, einvoice, []) if error_msgs: if len(error_msgs) > 1: - li = ["
  • "+ d +"
  • " for d in error_msgs] - frappe.throw(_("""
      {}
    """).format("".join(li)), title=_("E Invoice Validation Failed")) + li = ['
  • '+ d +'
  • ' for d in error_msgs] + frappe.throw("
      {}
    ".format(''.join(li)), title=_('E Invoice Validation Failed')) else: - frappe.throw(_("{}").format(error_msgs[0]), title=_("E Invoice Validation Failed")) + frappe.throw(error_msgs[0], title=_('E Invoice Validation Failed')) - return {'einvoice': json.dumps([e_invoice])} + return {'einvoice': json.dumps([einvoice])} -def validate_einvoice(validations, e_invoice, error_msgs=[]): - type_map = { - "string": cstr, - "number": cint, - "object": dict, - "array": list - } +def validate_einvoice(validations, einvoice, error_msgs=[]): + type_map = { 'string': 'str', 'number': 'int', 'object': 'dict', 'array': 'list' } - for field, value in validations.items(): - if isinstance(value, list): value = value[0] - - invoice_value = e_invoice.get(field) + for field, validation in validations.items(): + invoice_value = einvoice.get(field) if not invoice_value: continue - should_be_of_type = type_map[value.get('type').lower()] - if should_be_of_type == dict: - properties = value.get('properties') + should_be_of_type = type_map[validation.get('type').lower()] + if should_be_of_type in ['dict', 'list']: + child_validations = validation.get('properties') if isinstance(invoice_value, list): for d in invoice_value: - validate_einvoice(properties, d, error_msgs) + validate_einvoice(child_validations, d, error_msgs) else: - validate_einvoice(properties, invoice_value, error_msgs) + validate_einvoice(child_validations, invoice_value, error_msgs) # remove keys with empty dicts if not invoice_value: - e_invoice.pop(field, None) + einvoice.pop(field, None) continue - if invoice_value == "None": + if invoice_value == 'None': # remove keys with empty values - e_invoice.pop(field, None) + einvoice.pop(field, None) continue # convert to int or str - e_invoice[field] = should_be_of_type(invoice_value) - - should_be_of_len = value.get('maxLength') - should_be_greater_than = flt(value.get('minimum')) - should_be_less_than = flt(value.get('maximum')) - pattern_str = value.get('pattern') - pattern = re.compile(pattern_str or "") + function = eval('c' + should_be_of_type) + einvoice[field] = function(invoice_value) - field_label = value.get("label") or field + max_length = validation.get('maxLength') + minimum = flt(validation.get('minimum')) + maximum = flt(validation.get('maximum')) + pattern_str = validation.get('pattern') + pattern = re.compile(pattern_str or '') - if value.get('type').lower() == 'string' and len(invoice_value) > should_be_of_len: - error_msgs.append("{} should not exceed {} characters".format(field_label, should_be_of_len)) - if value.get('type').lower() == 'number' and not (flt(invoice_value) <= should_be_less_than): - error_msgs.append("{} should be less than {}".format(field_label, should_be_less_than)) + field_label = validation.get('label') or field + + if should_be_of_type == 'str' and len(invoice_value) > max_length: + error_msgs.append(_('{} should not exceed {} characters').format(field_label, max_length)) + if should_be_of_type == 'int' and not (flt(invoice_value) <= maximum): + error_msgs.append(_('{} should be less than {}').format(field_label, maximum)) if pattern_str and not pattern.match(invoice_value): - error_msgs.append(value.get('validationMsg')) + error_msgs.append(validation.get('validationMsg')) return error_msgs @@ -539,7 +538,7 @@ def download_einvoice(): einvoice = data['einvoice'] name = data['name'] - frappe.response['filename'] = "E-Invoice-" + name + ".json" + frappe.response['filename'] = 'E-Invoice-' + name + '.json' frappe.response['filecontent'] = einvoice frappe.response['content_type'] = 'application/json' frappe.response['type'] = 'download' @@ -563,7 +562,7 @@ def download_cancel_einvoice(): cancel_einvoice = json.dumps([dict(Irn=irn, CnlRsn=reason, CnlRem=remark)]) - frappe.response['filename'] = "Cancel E-Invoice " + name + ".json" + frappe.response['filename'] = 'Cancel E-Invoice ' + name + '.json' frappe.response['filecontent'] = cancel_einvoice frappe.response['content_type'] = 'application/json' frappe.response['type'] = 'download' @@ -575,7 +574,7 @@ def upload_cancel_ack(): doctype = data['doctype'] name = data['docname'] - frappe.db.set_value(doctype, name, "irn_cancelled", 1) + frappe.db.set_value(doctype, name, 'irn_cancelled', 1) def attach_qrcode_image(doctype, name): qrcode = frappe.db.get_value(doctype, name, 'signed_qr_code') @@ -583,12 +582,11 @@ def attach_qrcode_image(doctype, name): if not qrcode: return _file = frappe.get_doc({ - "doctype": "File", - "file_name": "Signed_QR_{name}.png".format(name=name), - "attached_to_doctype": doctype, - "attached_to_name": name, - "attached_to_field": "qrcode_image", - "content": "qrcode" + 'doctype': 'File', + 'file_name': 'Signed_QR_{name}.png'.format(name=name), + 'attached_to_doctype': doctype, + 'attached_to_name': name, + 'content': 'qrcode' }) _file.save() frappe.db.commit() diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json index 9f0ef5006c4..3f0767b8be8 100644 --- a/erpnext/regional/india/e_invoice/einv_validation.json +++ b/erpnext/regional/india/e_invoice/einv_validation.json @@ -277,209 +277,205 @@ }, "required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"] }, - "ItemList": [ - { - "type": "object", - "properties": { - "SlNo": { - "type": "string", - "minLength": 1, - "maxLength": 6 + "ItemList": { + "type": "Array", + "properties": { + "SlNo": { + "type": "string", + "minLength": 1, + "maxLength": 6 + }, + "PrdDesc": { + "type": "string", + "minLength": 3, + "maxLength": 300 + }, + "IsServc": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"] + }, + "HsnCd": { + "type": "string", + "minLength": 4, + "maxLength": 8 + }, + "Barcde": { + "type": "string", + "minLength": 3, + "maxLength": 30 + }, + "Qty": { + "type": "number", + "minimum": 0, + "maximum": 9999999999.999 + }, + "FreeQty": { + "type": "number", + "minimum": 0, + "maximum": 9999999999.999 + }, + "Unit": { + "type": "string", + "minLength": 3, + "maxLength": 8 + }, + "UnitPrice": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.999 + }, + "TotAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "Discount": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "PreTaxVal": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "AssAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "GstRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999 + }, + "IgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "CgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "SgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "CesRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999 + }, + "CesAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "CesNonAdvlAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "StateCesRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999 + }, + "StateCesAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "StateCesNonAdvlAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "OthChrg": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "TotItemVal": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99 + }, + "OrdLineRef": { + "type": "string", + "minLength": 1, + "maxLength": 50 + }, + "OrgCntry": { + "type": "string", + "minLength": 2, + "maxLength": 2 + }, + "PrdSlNo": { + "type": "string", + "minLength": 1, + "maxLength": 20 + }, + "BchDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 3, + "maxLength": 20 + }, + "ExpDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "validationMsg": "Expiry Date is invalid" + }, + "WrDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "validationMsg": "Warranty Date is invalid" + } }, - "PrdDesc": { - "type": "string", - "minLength": 3, - "maxLength": 300 - }, - "IsServc": { - "type": "string", - "minLength": 1, - "maxLength": 1, - "enum": ["Y", "N"] - }, - "HsnCd": { - "type": "string", - "minLength": 4, - "maxLength": 8 - }, - "Barcde": { - "type": "string", - "minLength": 3, - "maxLength": 30 - }, - "Qty": { - "type": "number", - "minimum": 0, - "maximum": 9999999999.999 - }, - "FreeQty": { - "type": "number", - "minimum": 0, - "maximum": 9999999999.999 - }, - "Unit": { - "type": "string", - "minLength": 3, - "maxLength": 8 - }, - "UnitPrice": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.999 - }, - "TotAmt": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "Discount": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "PreTaxVal": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "AssAmt": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "GstRt": { - "type": "number", - "minimum": 0, - "maximum": 999.999 - }, - "IgstAmt": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "CgstAmt": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "SgstAmt": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "CesRt": { - "type": "number", - "minimum": 0, - "maximum": 999.999 - }, - "CesAmt": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "CesNonAdvlAmt": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "StateCesRt": { - "type": "number", - "minimum": 0, - "maximum": 999.999 - }, - "StateCesAmt": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "StateCesNonAdvlAmt": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "OthChrg": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "TotItemVal": { - "type": "number", - "minimum": 0, - "maximum": 999999999999.99 - }, - "OrdLineRef": { - "type": "string", - "minLength": 1, - "maxLength": 50 - }, - "OrgCntry": { - "type": "string", - "minLength": 2, - "maxLength": 2 - }, - "PrdSlNo": { - "type": "string", - "minLength": 1, - "maxLength": 20 - }, - "BchDtls": { + "required": ["Nm"] + }, + "AttribDtls": { + "type": "Array", + "Attribute": { "type": "object", "properties": { "Nm": { "type": "string", - "minLength": 3, - "maxLength": 20 + "minLength": 1, + "maxLength": 100 }, - "ExpDt": { + "Val": { "type": "string", - "maxLength": 10, - "minLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", - "validationMsg": "Expiry Date is invalid" - }, - "WrDt": { - "type": "string", - "maxLength": 10, - "minLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", - "validationMsg": "Warranty Date is invalid" + "minLength": 1, + "maxLength": 100 } - }, - "required": ["Nm"] - }, - "AttribDtls": { - "type": "Array", - "Attribute": [ - { - "type": "object", - "properties": { - "Nm": { - "type": "string", - "minLength": 1, - "maxLength": 100 - }, - "Val": { - "type": "string", - "minLength": 1, - "maxLength": 100 - } - } - } - ] + } } - }, - "required": [ - "SlNo", - "IsServc", - "HsnCd", - "UnitPrice", - "TotAmt", - "AssAmt", - "GstRt", - "TotItemVal" - ] - } - ], + } + }, + "required": [ + "SlNo", + "IsServc", + "HsnCd", + "UnitPrice", + "TotAmt", + "AssAmt", + "GstRt", + "TotItemVal" + ] + }, "ValDtls": { "type": "object", "properties": { @@ -709,28 +705,26 @@ }, "AddlDocDtls": { "type": "Array", - "AddlDocument": [ - { - "type": "object", - "properties": { - "Url": { - "type": "string", - "minLength": 3, - "maxLength": 100 - }, - "Docs": { - "type": "string", - "minLength": 3, - "maxLength": 1000 - }, - "Info": { - "type": "string", - "minLength": 3, - "maxLength": 1000 - } + "AddlDocument": { + "type": "object", + "properties": { + "Url": { + "type": "string", + "minLength": 3, + "maxLength": 100 + }, + "Docs": { + "type": "string", + "minLength": 3, + "maxLength": 1000 + }, + "Info": { + "type": "string", + "minLength": 3, + "maxLength": 1000 } } - ] + } }, "ExpDtls": { "type": "object", diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 076f13475d8..87e2031f883 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -92,7 +92,7 @@ erpnext.setup_einvoice_actions = (doctype) => { "Download E-Invoice", () => { frappe.call({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.make_e_invoice', + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.make_einvoice', args: { doctype: frm.doc.doctype, name: frm.doc.name }, freeze: true, callback: (res) => { From 65289334e325bacd4c3ece3ea21f78e819932da9 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 13 Oct 2020 15:54:25 +0530 Subject: [PATCH 043/137] chore: clean up e invoice actions --- .../india/e_invoice/e_invoice_utils.py | 6 +- erpnext/regional/india/e_invoice/einvoice.js | 317 ++++++++++-------- 2 files changed, 177 insertions(+), 146 deletions(-) diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index 044c2c88da2..fe48bbe0af4 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -162,7 +162,7 @@ def generate_irn(doctype, name): json_str = aes_decrypt(enc_json, credentials.sek) signed_einvoice = json.loads(json_str) - handle_irn_response(signed_einvoice) + decrypt_irn_response(signed_einvoice) update_einvoice_fields(doctype, name, signed_einvoice) @@ -218,7 +218,7 @@ def cancel_eway_bill(doctype, name, eway_bill, reason, remark=''): return res -def handle_irn_response(data): +def decrypt_irn_response(data): enc_signed_invoice = data['SignedInvoice'] enc_signed_qr_code = data['SignedQRCode'] signed_invoice = jwt_decrypt(enc_signed_invoice)['data'] @@ -473,7 +473,7 @@ def make_einvoice(doctype, name): else: frappe.throw(error_msgs[0], title=_('E Invoice Validation Failed')) - return {'einvoice': json.dumps([einvoice])} + return json.dumps(einvoice) def validate_einvoice(validations, einvoice, error_msgs=[]): type_map = { 'string': 'str', 'number': 'int', 'object': 'dict', 'array': 'list' } diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 87e2031f883..bcdd6ef02ff 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -3,24 +3,157 @@ erpnext.setup_einvoice_actions = (doctype) => { refresh(frm) { const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); const supply_type = frm.doc.gst_category; - if (!einvoicing_enabled - || !['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type)) { - return; + const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type) + + if (!einvoicing_enabled || !valid_supply_type) return; + + const { docstatus, irn, irn_cancelled, ewaybill, eway_bill_cancelled, doctype, name, __unsaved } = frm.doc; + + if (docstatus == 0 && !irn && !__unsaved) { + frm.add_custom_button( + _("Generate IRN"), + () => { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.generate_irn', + args: { doctype: doctype, name: name }, + freeze: true, + callback: () => frm.reload_doc() + }) + }, + __("E Invoicing") + ); } + + if (docstatus == 1 && irn && !irn_cancelled) { + frm.add_custom_button( + __("Cancel IRN"), + () => { + const fields = [ + { + "label" : "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + const d = new frappe.ui.Dialog({ + title: __("Cancel IRN"), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_irn', + args: { + doctype: doctype, + name: name, + irn: irn, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }); + }, + primary_action_label: __('Submit') + }); + d.show(); + }, + __("E Invoicing") + ) + } + + if (docstatus == 1 && irn && !irn_cancelled && !eway_bill_cancelled) { + frm.add_custom_button( + __("Cancel E-Way Bill"), + () => { + const fields = [ + { + "label" : "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ] + const d = new frappe.ui.Dialog({ + title: __('Cancel E-Way Bill'), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_eway_bill', + args: { + doctype: doctype, + name: name, + eway_bill: ewaybill, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }) + }, + primary_action_label: __('Submit') + }); + d.show(); + }, + __("E Invoicing") + ); + } + // if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) { // frm.add_custom_button( - // "Generate IRN", + // "Download E-Invoice", // () => { // frappe.call({ - // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.generate_irn', + // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.make_einvoice', // args: { doctype: frm.doc.doctype, name: frm.doc.name }, // freeze: true, - // callback: () => frm.reload_doc() + // callback: (res) => { + // if (!res.exc) { + // const args = { + // cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_einvoice', + // einvoice: res.message.einvoice, + // name: frm.doc.name + // }; + // open_url_post(frappe.request.url, args); + // } + // } // }) - // } - // ) + // }, "E-Invoicing"); + // frm.add_custom_button( + // "Upload Signed E-Invoice", + // () => { + // new frappe.ui.FileUploader({ + // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_einvoice', + // allow_multiple: 0, + // doctype: frm.doc.doctype, + // docname: frm.doc.name, + // on_success: (attachment, r) => { + // if (!r.exc) { + // frm.reload_doc(); + // } + // } + // }); + // }, "E-Invoicing"); // } - // if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) { // frm.add_custom_button( // "Cancel IRN", @@ -28,147 +161,45 @@ erpnext.setup_einvoice_actions = (doctype) => { // const d = new frappe.ui.Dialog({ // title: __('Cancel IRN'), // fields: [ - // { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", - // "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, - // { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 } + // { + // "label" : "Reason", "fieldname": "reason", + // "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", + // "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + // }, + // { + // "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 + // } // ], // primary_action: function() { // const data = d.get_values(); - // frappe.call({ - // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_irn', - // args: { - // doctype: frm.doc.doctype, - // name: frm.doc.name, - // irn: frm.doc.irn, - // reason: data.reason.split('-')[0], - // remark: data.remark - // }, - // freeze: true, - // callback: () => frm.reload_doc() || d.hide(), - // error: () => d.hide() - // }) + // const args = { + // cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_cancel_einvoice', + // irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark, name: frm.doc.name + // }; + // open_url_post(frappe.request.url, args); + // d.hide(); // }, - // primary_action_label: __('Submit') + // primary_action_label: __('Download JSON') // }); // d.show(); - // } - // ) - // } - - // if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled && !frm.doc.eway_bill_cancelled) { - // frm.add_custom_button( - // "Cancel E-Way Bill", - // () => { - // const d = new frappe.ui.Dialog({ - // title: __('Cancel E-Way Bill'), - // fields: [ - // { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", - // "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, - // { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 } - // ], - // primary_action: function() { - // const data = d.get_values(); - // frappe.call({ - // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_eway_bill', - // args: { eway_bill: frm.doc.ewaybill, reason: data.reason.split('-')[0], remark: data.remark }, - // freeze: true, - // callback: () => { - // frm.set_value('eway_bill_cancelled', 1); - // frm.save("Update"); - // d.hide() - // }, - // error: () => d.hide() - // }) - // }, - // primary_action_label: __('Submit') - // }); - // d.show(); - // } - // ) - // } - - if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) { - frm.add_custom_button( - "Download E-Invoice", - () => { - frappe.call({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.make_einvoice', - args: { doctype: frm.doc.doctype, name: frm.doc.name }, - freeze: true, - callback: (res) => { - if (!res.exc) { - const args = { - cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_einvoice', - einvoice: res.message.einvoice, - name: frm.doc.name - }; - open_url_post(frappe.request.url, args); - } - } - }) - }, "E-Invoicing"); - frm.add_custom_button( - "Upload Signed E-Invoice", - () => { - new frappe.ui.FileUploader({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_einvoice', - allow_multiple: 0, - doctype: frm.doc.doctype, - docname: frm.doc.name, - on_success: (attachment, r) => { - if (!r.exc) { - frm.reload_doc(); - } - } - }); - }, "E-Invoicing"); - } - if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) { - frm.add_custom_button( - "Cancel IRN", - () => { - const d = new frappe.ui.Dialog({ - title: __('Cancel IRN'), - fields: [ - { - "label" : "Reason", "fieldname": "reason", - "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - }, - { - "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 - } - ], - primary_action: function() { - const data = d.get_values(); - const args = { - cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_cancel_einvoice', - irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark, name: frm.doc.name - }; - open_url_post(frappe.request.url, args); - d.hide(); - }, - primary_action_label: __('Download JSON') - }); - d.show(); - }, "E-Invoicing"); + // }, "E-Invoicing"); - frm.add_custom_button( - "Upload Cancel JSON", - () => { - new frappe.ui.FileUploader({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_cancel_ack', - allow_multiple: 0, - doctype: frm.doc.doctype, - docname: frm.doc.name, - on_success: (attachment, r) => { - if (!r.exc) { - frm.reload_doc(); - } - } - }); - }, "E-Invoicing"); - } + // frm.add_custom_button( + // "Upload Cancel JSON", + // () => { + // new frappe.ui.FileUploader({ + // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_cancel_ack', + // allow_multiple: 0, + // doctype: frm.doc.doctype, + // docname: frm.doc.name, + // on_success: (attachment, r) => { + // if (!r.exc) { + // frm.reload_doc(); + // } + // } + // }); + // }, "E-Invoicing"); + // } } }) } \ No newline at end of file From e984632221339b15791fbabcf98e01a2ed22d7c0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 21 Oct 2020 19:09:27 +0530 Subject: [PATCH 044/137] fix: download & upload e-invoice --- .../gst_e_invoice/gst_e_invoice.html | 8 +- .../india/e_invoice/e_invoice_utils.py | 15 +- erpnext/regional/india/e_invoice/einvoice.js | 352 +++++++++--------- 3 files changed, 192 insertions(+), 183 deletions(-) diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html index 15ea1c21744..a900711af1b 100644 --- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -92,8 +92,8 @@ - - {% for item in einvoice.ItemList %} + {% for item in einvoice.ItemList %} + {{ item.SlNo }} {{ item.PrdDesc }} {{ item.HsnCd }} @@ -105,8 +105,8 @@ {{ item.GstRt + item.CesRt }} % {{ frappe.utils.fmt_money(0, None, "INR") }} {{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }} - {% endfor %} - + + {% endfor %} diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index fe48bbe0af4..649f5eb9f3e 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -473,7 +473,7 @@ def make_einvoice(doctype, name): else: frappe.throw(error_msgs[0], title=_('E Invoice Validation Failed')) - return json.dumps(einvoice) + return {'einvoice': json.dumps([einvoice])} def validate_einvoice(validations, einvoice, error_msgs=[]): type_map = { 'string': 'str', 'number': 'int', 'object': 'dict', 'array': 'list' } @@ -503,8 +503,10 @@ def validate_einvoice(validations, einvoice, error_msgs=[]): continue # convert to int or str - function = eval('c' + should_be_of_type) - einvoice[field] = function(invoice_value) + if should_be_of_type == 'str': + einvoice[field] = str(invoice_value) + elif should_be_of_type == 'int': + einvoice[field] = flt(invoice_value, 3) max_length = validation.get('maxLength') minimum = flt(validation.get('minimum')) @@ -527,6 +529,12 @@ def update_einvoice_fields(doctype, name, signed_einvoice): enc_signed_invoice = signed_einvoice.get('SignedInvoice') decrypted_signed_invoice = jwt_decrypt(enc_signed_invoice)['data'] + if json.loads(decrypted_signed_invoice)['DocDtls']['No'] != name: + frappe.throw( + _("Document number of uploaded Signed E-Invoice doesn't matches with Sales Invoice"), + title=_("Inappropriate E-Invoice") + ) + frappe.db.set_value(doctype, name, 'irn', signed_einvoice.get('Irn')) frappe.db.set_value(doctype, name, 'ewaybill', signed_einvoice.get('EwbNo')) frappe.db.set_value(doctype, name, 'signed_qr_code', signed_einvoice.get('SignedQRCode').split('.')[1]) @@ -551,6 +559,7 @@ def upload_einvoice(): name = data['docname'] update_einvoice_fields(doctype, name, signed_einvoice) + attach_qrcode_image(doctype, name) @frappe.whitelist() def download_cancel_einvoice(): diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index bcdd6ef02ff..7373e831b0c 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -9,197 +9,197 @@ erpnext.setup_einvoice_actions = (doctype) => { const { docstatus, irn, irn_cancelled, ewaybill, eway_bill_cancelled, doctype, name, __unsaved } = frm.doc; - if (docstatus == 0 && !irn && !__unsaved) { - frm.add_custom_button( - _("Generate IRN"), - () => { - frappe.call({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.generate_irn', - args: { doctype: doctype, name: name }, - freeze: true, - callback: () => frm.reload_doc() - }) - }, - __("E Invoicing") - ); - } - - if (docstatus == 1 && irn && !irn_cancelled) { - frm.add_custom_button( - __("Cancel IRN"), - () => { - const fields = [ - { - "label" : "Reason", - "fieldname": "reason", - "fieldtype": "Select", - "reqd": 1, - "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - }, - { - "label": "Remark", - "fieldname": "remark", - "fieldtype": "Data", - "reqd": 1 - } - ]; - const d = new frappe.ui.Dialog({ - title: __("Cancel IRN"), - fields: fields, - primary_action: function() { - const data = d.get_values(); - frappe.call({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_irn', - args: { - doctype: doctype, - name: name, - irn: irn, - reason: data.reason.split('-')[0], - remark: data.remark - }, - freeze: true, - callback: () => frm.reload_doc() || d.hide(), - error: () => d.hide() - }); - }, - primary_action_label: __('Submit') - }); - d.show(); - }, - __("E Invoicing") - ) - } - - if (docstatus == 1 && irn && !irn_cancelled && !eway_bill_cancelled) { - frm.add_custom_button( - __("Cancel E-Way Bill"), - () => { - const fields = [ - { - "label" : "Reason", - "fieldname": "reason", - "fieldtype": "Select", - "reqd": 1, - "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - }, - { - "label": "Remark", - "fieldname": "remark", - "fieldtype": "Data", - "reqd": 1 - } - ] - const d = new frappe.ui.Dialog({ - title: __('Cancel E-Way Bill'), - fields: fields, - primary_action: function() { - const data = d.get_values(); - frappe.call({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_eway_bill', - args: { - doctype: doctype, - name: name, - eway_bill: ewaybill, - reason: data.reason.split('-')[0], - remark: data.remark - }, - freeze: true, - callback: () => frm.reload_doc() || d.hide(), - error: () => d.hide() - }) - }, - primary_action_label: __('Submit') - }); - d.show(); - }, - __("E Invoicing") - ); - } - - // if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) { + // if (docstatus == 0 && !irn && !__unsaved) { // frm.add_custom_button( - // "Download E-Invoice", + // _("Generate IRN"), // () => { // frappe.call({ - // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.make_einvoice', - // args: { doctype: frm.doc.doctype, name: frm.doc.name }, + // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.generate_irn', + // args: { doctype: doctype, name: name }, // freeze: true, - // callback: (res) => { - // if (!res.exc) { - // const args = { - // cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_einvoice', - // einvoice: res.message.einvoice, - // name: frm.doc.name - // }; - // open_url_post(frappe.request.url, args); - // } - // } + // callback: () => frm.reload_doc() // }) - // }, "E-Invoicing"); - // frm.add_custom_button( - // "Upload Signed E-Invoice", - // () => { - // new frappe.ui.FileUploader({ - // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_einvoice', - // allow_multiple: 0, - // doctype: frm.doc.doctype, - // docname: frm.doc.name, - // on_success: (attachment, r) => { - // if (!r.exc) { - // frm.reload_doc(); - // } - // } - // }); - // }, "E-Invoicing"); + // }, + // __("E Invoicing") + // ); // } - // if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) { + + // if (docstatus == 1 && irn && !irn_cancelled) { // frm.add_custom_button( - // "Cancel IRN", + // __("Cancel IRN"), // () => { + // const fields = [ + // { + // "label" : "Reason", + // "fieldname": "reason", + // "fieldtype": "Select", + // "reqd": 1, + // "default": "1-Duplicate", + // "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + // }, + // { + // "label": "Remark", + // "fieldname": "remark", + // "fieldtype": "Data", + // "reqd": 1 + // } + // ]; // const d = new frappe.ui.Dialog({ - // title: __('Cancel IRN'), - // fields: [ - // { - // "label" : "Reason", "fieldname": "reason", - // "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", - // "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - // }, - // { - // "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 - // } - // ], + // title: __("Cancel IRN"), + // fields: fields, // primary_action: function() { // const data = d.get_values(); - // const args = { - // cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_cancel_einvoice', - // irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark, name: frm.doc.name - // }; - // open_url_post(frappe.request.url, args); - // d.hide(); + // frappe.call({ + // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_irn', + // args: { + // doctype: doctype, + // name: name, + // irn: irn, + // reason: data.reason.split('-')[0], + // remark: data.remark + // }, + // freeze: true, + // callback: () => frm.reload_doc() || d.hide(), + // error: () => d.hide() + // }); // }, - // primary_action_label: __('Download JSON') + // primary_action_label: __('Submit') // }); // d.show(); - // }, "E-Invoicing"); - - // frm.add_custom_button( - // "Upload Cancel JSON", - // () => { - // new frappe.ui.FileUploader({ - // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_cancel_ack', - // allow_multiple: 0, - // doctype: frm.doc.doctype, - // docname: frm.doc.name, - // on_success: (attachment, r) => { - // if (!r.exc) { - // frm.reload_doc(); - // } - // } - // }); - // }, "E-Invoicing"); + // }, + // __("E Invoicing") + // ) // } + + // if (docstatus == 1 && irn && !irn_cancelled && !eway_bill_cancelled) { + // frm.add_custom_button( + // __("Cancel E-Way Bill"), + // () => { + // const fields = [ + // { + // "label" : "Reason", + // "fieldname": "reason", + // "fieldtype": "Select", + // "reqd": 1, + // "default": "1-Duplicate", + // "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + // }, + // { + // "label": "Remark", + // "fieldname": "remark", + // "fieldtype": "Data", + // "reqd": 1 + // } + // ] + // const d = new frappe.ui.Dialog({ + // title: __('Cancel E-Way Bill'), + // fields: fields, + // primary_action: function() { + // const data = d.get_values(); + // frappe.call({ + // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_eway_bill', + // args: { + // doctype: doctype, + // name: name, + // eway_bill: ewaybill, + // reason: data.reason.split('-')[0], + // remark: data.remark + // }, + // freeze: true, + // callback: () => frm.reload_doc() || d.hide(), + // error: () => d.hide() + // }) + // }, + // primary_action_label: __('Submit') + // }); + // d.show(); + // }, + // __("E Invoicing") + // ); + // } + + if (docstatus == 0 && !irn && !__unsaved) { + frm.add_custom_button( + "Download E-Invoice", + () => { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.make_einvoice', + args: { doctype, name }, + freeze: true, + callback: (res) => { + if (!res.exc) { + const args = { + cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_einvoice', + einvoice: res.message.einvoice, + name: name + }; + open_url_post(frappe.request.url, args); + } + } + }) + }, "E-Invoicing"); + frm.add_custom_button( + "Upload Signed E-Invoice", + () => { + new frappe.ui.FileUploader({ + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_einvoice', + allow_multiple: 0, + doctype: doctype, + docname: name, + on_success: (attachment, r) => { + if (!r.exc) { + frm.reload_doc(); + } + } + }); + }, "E-Invoicing"); + } + if (docstatus == 1 && irn && !irn_cancelled) { + frm.add_custom_button( + "Cancel IRN", + () => { + const d = new frappe.ui.Dialog({ + title: __('Cancel IRN'), + fields: [ + { + "label" : "Reason", "fieldname": "reason", + "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 + } + ], + primary_action: function() { + const data = d.get_values(); + const args = { + cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_cancel_einvoice', + irn: irn, reason: data.reason.split('-')[0], remark: data.remark, name: name + }; + open_url_post(frappe.request.url, args); + d.hide(); + }, + primary_action_label: __('Download JSON') + }); + d.show(); + }, "E-Invoicing"); + + frm.add_custom_button( + "Upload Cancel JSON", + () => { + new frappe.ui.FileUploader({ + method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_cancel_ack', + allow_multiple: 0, + doctype: doctype, + docname: name, + on_success: (attachment, r) => { + if (!r.exc) { + frm.reload_doc(); + } + } + }); + }, "E-Invoicing"); + } } }) } \ No newline at end of file From 03cd4135d2d93bfa570c050a703661a62716266e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 21 Oct 2020 19:18:17 +0530 Subject: [PATCH 045/137] fix: print format --- erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html index a900711af1b..b4255038d39 100644 --- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -3,7 +3,7 @@
    - From 4db3ff1673f76986a2e04cdaa51145e0cbaa369a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 22 Oct 2020 18:21:53 +0530 Subject: [PATCH 046/137] fix: validations --- .../india/e_invoice/e_invoice_utils.py | 104 +++++++++--------- erpnext/regional/india/e_invoice/einvoice.js | 2 +- 2 files changed, 51 insertions(+), 55 deletions(-) diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index 649f5eb9f3e..46242e4a671 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -151,6 +151,7 @@ def generate_irn(doctype, name): headers = get_header(credentials) einvoice = make_einvoice(doctype, name) + einvoice = json.dumps(einvoice) enc_einvoice_json = aes_encrypt(einvoice, credentials.sek) payload = dict(Data=enc_einvoice_json) @@ -230,7 +231,7 @@ def handle_err_response(response): if response.get('Status') == 0: err_details = response.get('ErrorDetails') print(response) - error_msgs = [] + errors = [] for d in err_details: err_code = d.get('ErrorCode') @@ -239,14 +240,14 @@ def handle_err_response(response): response = get_irn_details(irn[0]) return response - error_msgs.append(d.get('ErrorMessage')) + errors.append(d.get('ErrorMessage')) - if error_msgs: - if len(error_msgs) > 1: - li = ['
  • '+ d +'
  • ' for d in error_msgs] + if errors: + if len(errors) > 1: + li = ['
  • '+ d +'
  • ' for d in errors] frappe.throw(_("""
      {}
    """).format(''.join(li)), title=_('API Request Failed')) else: - frappe.throw(_('{}').format(error_msgs[0]), title=_('API Request Failed')) + frappe.throw(_('{}').format(errors[0]), title=_('API Request Failed')) return response @@ -425,7 +426,6 @@ def get_eway_bill_details(invoice): def make_einvoice(doctype, name): invoice = frappe.get_doc(doctype, name) schema = read_json('einv_template') - validations = json.loads(read_json('einv_validation')) item_list = get_item_list(invoice) doc_details = get_doc_details(invoice) @@ -464,66 +464,62 @@ def make_einvoice(doctype, name): export_details=export_details, eway_bill_details=eway_bill_details ) einvoice = json.loads(einvoice) - - error_msgs = validate_einvoice(validations, einvoice, []) - if error_msgs: - if len(error_msgs) > 1: - li = ['
  • '+ d +'
  • ' for d in error_msgs] + + validations = json.loads(read_json('einv_validation')) + errors = validate_einvoice(validations, einvoice, []) + if errors: + if len(errors) > 1: + li = ['
  • '+ d +'
  • ' for d in errors] frappe.throw("
      {}
    ".format(''.join(li)), title=_('E Invoice Validation Failed')) else: - frappe.throw(error_msgs[0], title=_('E Invoice Validation Failed')) + frappe.throw(errors[0], title=_('E Invoice Validation Failed')) - return {'einvoice': json.dumps([einvoice])} + return einvoice -def validate_einvoice(validations, einvoice, error_msgs=[]): - type_map = { 'string': 'str', 'number': 'int', 'object': 'dict', 'array': 'list' } - - for field, validation in validations.items(): - invoice_value = einvoice.get(field) - if not invoice_value: - continue - - should_be_of_type = type_map[validation.get('type').lower()] - if should_be_of_type in ['dict', 'list']: - child_validations = validation.get('properties') - - if isinstance(invoice_value, list): - for d in invoice_value: - validate_einvoice(child_validations, d, error_msgs) - else: - validate_einvoice(child_validations, invoice_value, error_msgs) - # remove keys with empty dicts - if not invoice_value: - einvoice.pop(field, None) - continue - - if invoice_value == 'None': +def validate_einvoice(validations, einvoice, errors=[]): + for fieldname, field_validation in validations.items(): + value = einvoice.get(fieldname, None) + if value in (None, "None", {}, []): # remove keys with empty values - einvoice.pop(field, None) + einvoice.pop(fieldname, None) + continue + + value_type = field_validation.get("type").lower() + if value_type in ['object', 'array']: + child_validations = field_validation.get('properties') + + if isinstance(value, list): + for d in value: + validate_einvoice(child_validations, d, errors) + else: + validate_einvoice(child_validations, value, errors) + if not value: + # remove empty dicts + einvoice.pop(fieldname, None) continue # convert to int or str - if should_be_of_type == 'str': - einvoice[field] = str(invoice_value) - elif should_be_of_type == 'int': - einvoice[field] = flt(invoice_value, 3) + if value_type == 'string': + einvoice[fieldname] = str(value) + elif value_type == 'number': + einvoice[fieldname] = flt(value, 2) - max_length = validation.get('maxLength') - minimum = flt(validation.get('minimum')) - maximum = flt(validation.get('maximum')) - pattern_str = validation.get('pattern') + max_length = field_validation.get('maxLength') + minimum = flt(field_validation.get('minimum')) + maximum = flt(field_validation.get('maximum')) + pattern_str = field_validation.get('pattern') pattern = re.compile(pattern_str or '') - field_label = validation.get('label') or field + label = field_validation.get('label') or fieldname - if should_be_of_type == 'str' and len(invoice_value) > max_length: - error_msgs.append(_('{} should not exceed {} characters').format(field_label, max_length)) - if should_be_of_type == 'int' and not (flt(invoice_value) <= maximum): - error_msgs.append(_('{} should be less than {}').format(field_label, maximum)) - if pattern_str and not pattern.match(invoice_value): - error_msgs.append(validation.get('validationMsg')) + if value_type == 'string' and len(value) > max_length: + errors.append(_('{} should not exceed {} characters').format(fieldname_label, max_length)) + if value_type == 'number' and not (flt(value) <= maximum): + errors.append(_('{} should be less than {}').format(label, maximum)) + if pattern_str and not pattern.match(value): + errors.append(field_validation.get('validationMsg')) - return error_msgs + return errors def update_einvoice_fields(doctype, name, signed_einvoice): enc_signed_invoice = signed_einvoice.get('SignedInvoice') diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 7373e831b0c..b071919570d 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -130,7 +130,7 @@ erpnext.setup_einvoice_actions = (doctype) => { if (!res.exc) { const args = { cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_einvoice', - einvoice: res.message.einvoice, + einvoice: JSON.stringify([res.message]), name: name }; open_url_post(frappe.request.url, args); From e14bb5e978f1dafc03304599649e6dae1022920e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Oct 2020 20:30:08 +0530 Subject: [PATCH 047/137] fix: add permissions on regional setup --- .../print_format/gst_e_invoice/gst_e_invoice.json | 4 ++-- .../e_invoice_settings/e_invoice_settings.json | 15 ++------------- erpnext/regional/india/setup.py | 5 +++-- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json index 0a5c8a27882..1001199a092 100644 --- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json @@ -3,7 +3,7 @@ "creation": "2020-10-10 18:01:21.032914", "custom_format": 0, "default_print_language": "en-US", - "disabled": 0, + "disabled": 1, "doc_type": "Sales Invoice", "docstatus": 0, "doctype": "Print Format", @@ -11,7 +11,7 @@ "html": "", "idx": 0, "line_breaks": 1, - "modified": "2020-10-10 18:01:21.032914", + "modified": "2020-10-23 19:54:40.634936", "modified_by": "Administrator", "module": "Accounts", "name": "GST E-Invoice", diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json index 3df9243925c..d9a1c4976a9 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -108,23 +108,12 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-09-29 17:05:33.512227", + "modified": "2020-10-23 19:55:11.417161", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Settings", "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], + "permissions": [], "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index fc8004b4752..4f1ca5012d1 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -77,7 +77,7 @@ def add_custom_roles_for_reports(): )).insert() def add_permissions(): - for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'): + for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate', 'E Invoice Settings'): add_permission(doctype, 'All', 0) for role in ('Accounts Manager', 'Accounts User', 'System Manager'): add_permission(doctype, role, 0) @@ -93,9 +93,10 @@ def add_permissions(): def add_print_formats(): frappe.reload_doc("regional", "print_format", "gst_tax_invoice") frappe.reload_doc("accounts", "print_format", "gst_pos_invoice") + frappe.reload_doc("accounts", "print_format", "GST E-Invoice") frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where - name in('GST POS Invoice', 'GST Tax Invoice') """) + name in('GST POS Invoice', 'GST Tax Invoice', 'GST E-Invoice') """) def make_custom_fields(update=True): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', From 53b05c2850abca2de0a3b4664378992617c1ea02 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Oct 2020 21:19:55 +0530 Subject: [PATCH 048/137] feat: add patch --- erpnext/patches.txt | 3 +- .../patches/v12_0/setup_einvoice_fields.py | 31 +++++++++++++++++++ .../india/e_invoice/e_invoice_utils.py | 10 ++++-- 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 erpnext/patches/v12_0/setup_einvoice_fields.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b5f31bafa7e..67dc8e38e95 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -677,4 +677,5 @@ erpnext.patches.v12_0.set_multi_uom_in_rfq erpnext.patches.v12_0.update_state_code_for_daman_and_diu erpnext.patches.v12_0.rename_lost_reason_detail erpnext.patches.v12_0.update_leave_application_status -erpnext.patches.v12_0.update_payment_entry_status \ No newline at end of file +erpnext.patches.v12_0.update_payment_entry_status +erpnext.patches.v12_0.setup_einvoice_fields diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py new file mode 100644 index 00000000000..e230eb0bf75 --- /dev/null +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -0,0 +1,31 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields +from erpnext.regional.india.setup import add_permissions, add_print_formats + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, + depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), + + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1) + ] + } + create_custom_fields(custom_fields, update=True) + add_permissions() + add_print_formats() \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index 46242e4a671..6155a04da29 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -16,8 +16,8 @@ from Crypto.Cipher import PKCS1_v1_5, AES from Crypto.Util.Padding import pad, unpad from frappe.model.document import Document from frappe import _, get_module_path, scrub -from erpnext.regional.india.utils import get_gst_accounts from frappe.integrations.utils import make_post_request, make_get_request +from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply from frappe.utils.data import get_datetime, cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime def validate_einvoice_fields(doc): @@ -33,6 +33,9 @@ def validate_einvoice_fields(doc): def get_credentials(): doc = frappe.get_doc('E Invoice Settings') + if not doc.enable: + frappe.throw(_("To setup E Invoicing you need to enable E Invoice Settings first."), title=_("E Invoicing Disabled")) + if not doc.token_expiry or time_diff_in_seconds(now_datetime(), doc.token_expiry) > 5.0: fetch_token(doc) doc.load_from_db() @@ -437,7 +440,8 @@ def make_einvoice(doctype, name): buyer_details = get_overseas_address_details(invoice.customer_address) else: buyer_details = get_party_gstin_details(invoice.customer_address) - place_of_supply = invoice.place_of_supply.split('-')[0] + place_of_supply = get_place_of_supply(invoice, doctype) or invoice.billing_address_gstin + place_of_supply = place_of_supply[:2] buyer_details.update(dict(place_of_supply=place_of_supply)) shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) @@ -513,7 +517,7 @@ def validate_einvoice(validations, einvoice, errors=[]): label = field_validation.get('label') or fieldname if value_type == 'string' and len(value) > max_length: - errors.append(_('{} should not exceed {} characters').format(fieldname_label, max_length)) + errors.append(_('{} should not exceed {} characters').format(label, max_length)) if value_type == 'number' and not (flt(value) <= maximum): errors.append(_('{} should be less than {}').format(label, maximum)) if pattern_str and not pattern.match(value): From 7015609f7ffe5262a33893a21fafb4a476165ff7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 12:13:00 +0530 Subject: [PATCH 049/137] fix: validate document name --- .../india/e_invoice/e_invoice_utils.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index 6155a04da29..c4fff274c47 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -15,19 +15,33 @@ from pyqrcode import create as qrcreate from Crypto.Cipher import PKCS1_v1_5, AES from Crypto.Util.Padding import pad, unpad from frappe.model.document import Document -from frappe import _, get_module_path, scrub +from frappe import _, get_module_path, scrub, bold from frappe.integrations.utils import make_post_request, make_get_request from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply from frappe.utils.data import get_datetime, cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime def validate_einvoice_fields(doc): einvoicing_enabled = frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable') - if not doc.doctype in ['Sales Invoice', 'Purchase Invoice'] or not einvoicing_enabled: return + invalid_doctype = doc.doctype not in ['Sales Invoice', 'Purchase Invoice'] + invalid_supply_type = doc.gst_category not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] + + if invalid_doctype or invalid_supply_type or not einvoicing_enabled: return + + if doc.docstatus == 0 and doc._action == 'save': + if doc.irn: + frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) + if len(doc.name) > 16: + title = _('Document Name Too Long') + msg = (_('As you have E-Invoicing enabled, To be able to generate IRN for this invoice, document name {} exceed 16 letters. ') + .format(bold(_('should not')))) + msg += '

    ' + msg += (_('You {} modify your {} in order to have document name of {} length of 16. ') + .format(bold(_('must')), bold(_('naming series')), bold(_('maximum')))) + frappe.throw(msg, title=title) - if doc.docstatus == 0 and doc._action == 'save' and doc.irn: - frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) + elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed')) From e7ea94ae6fd3d8905218514727d7c6389126ce48 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 12:41:13 +0530 Subject: [PATCH 050/137] fix: return date --- erpnext/regional/india/e_invoice/e_invoice_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index c4fff274c47..e1d380596b6 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -418,7 +418,7 @@ def get_payment_details(invoice): def get_return_doc_reference(invoice): invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') return frappe._dict(dict( - invoice_name=invoice.return_against, invoice_date=invoice_date + invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') )) def get_eway_bill_details(invoice): From a8745871567b41253671a247904ba0011e1f7962 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 12:57:46 +0530 Subject: [PATCH 051/137] fix: credit note einvoice --- .../india/e_invoice/e_invoice_utils.py | 40 ++++++++++--------- .../india/e_invoice/einv_template.json | 4 +- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index e1d380596b6..9c7b4d5c46f 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -339,16 +339,18 @@ def get_item_list(invoice): for d in invoice.items: item_schema = read_json('einv_item_template') - item = frappe._dict(dict()) + item = frappe._dict({}) item.update(d.as_dict()) item.sr_no = d.idx item.description = d.item_name item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None - item.unit_rate = item.base_price_list_rate if item.discount_amount else item.base_rate - item.total_amount = item.unit_rate * item.qty - item.discount_amount = item.discount_amount * item.qty + item.qty = abs(item.qty) + item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_rate) + item.total_amount = abs(item.unit_rate * item.qty) + item.discount_amount = abs(item.discount_amount * item.qty) + item.base_amount = abs(item.base_amount) item.tax_rate = 0 item.igst_amount = 0 item.cgst_amount = 0 @@ -360,18 +362,18 @@ def get_item_list(invoice): item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) if t.account_head in gst_accounts.cess_account: item.cess_rate += item_tax_detail[0] - item.cess_amount += item_tax_detail[1] + item.cess_amount += abs(item_tax_detail[1]) elif t.account_head in gst_accounts.igst_account: item.tax_rate += item_tax_detail[0] - item.igst_amount += item_tax_detail[1] + item.igst_amount += abs(item_tax_detail[1]) elif t.account_head in gst_accounts.sgst_account: item.tax_rate += item_tax_detail[0] - item.sgst_amount += item_tax_detail[1] + item.sgst_amount += abs(item_tax_detail[1]) elif t.account_head in gst_accounts.cgst_account: item.tax_rate += item_tax_detail[0] - item.cgst_amount += item_tax_detail[1] + item.cgst_amount += abs(item_tax_detail[1]) - item.total_value = item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount + item.total_value = abs(item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount) einv_item = item_schema.format(item=item) item_list.append(einv_item) @@ -382,11 +384,11 @@ def get_value_details(invoice): gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] value_details = frappe._dict(dict()) - value_details.base_net_total = invoice.base_net_total - value_details.invoice_discount_amt = invoice.discount_amount - value_details.round_off = invoice.base_rounding_adjustment - value_details.base_grand_total = invoice.base_rounded_total - value_details.grand_total = invoice.rounded_total + value_details.base_net_total = abs(invoice.base_net_total) + value_details.invoice_discount_amt = abs(invoice.discount_amount) + value_details.round_off = abs(invoice.base_rounding_adjustment) + value_details.base_grand_total = abs(invoice.base_rounded_total) + value_details.grand_total = abs(invoice.rounded_total) value_details.total_cgst_amt = 0 value_details.total_sgst_amt = 0 value_details.total_igst_amt = 0 @@ -394,13 +396,13 @@ def get_value_details(invoice): for t in invoice.taxes: if t.account_head in gst_accounts_list: if t.account_head in gst_accounts.cess_account: - value_details.total_cess_amt += t.base_tax_amount + value_details.total_cess_amt += abs(t.base_tax_amount) elif t.account_head in gst_accounts.igst_account: - value_details.total_igst_amt += t.base_tax_amount + value_details.total_igst_amt += abs(t.base_tax_amount) elif t.account_head in gst_accounts.sgst_account: - value_details.total_sgst_amt += t.base_tax_amount + value_details.total_sgst_amt += abs(t.base_tax_amount) elif t.account_head in gst_accounts.cgst_account: - value_details.total_cgst_amt += t.base_tax_amount + value_details.total_cgst_amt += abs(t.base_tax_amount) return value_details @@ -520,7 +522,7 @@ def validate_einvoice(validations, einvoice, errors=[]): if value_type == 'string': einvoice[fieldname] = str(value) elif value_type == 'number': - einvoice[fieldname] = flt(value, 2) + einvoice[fieldname] = flt(value, 2) if fieldname != 'Pin' else int(value) max_length = field_validation.get('maxLength') minimum = flt(field_validation.get('minimum')) diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json index 0acc01cd386..46741ca0337 100644 --- a/erpnext/regional/india/e_invoice/einv_template.json +++ b/erpnext/regional/india/e_invoice/einv_template.json @@ -83,10 +83,10 @@ "InvStDt": "{period_details.start_date}", "InvEndDt": "{period_details.end_date}" }}, - "PrecDocDtls": {{ + "PrecDocDtls": [{{ "InvNo": "{prev_doc_details.invoice_name}", "InvDt": "{prev_doc_details.invoice_date}" - }} + }}] }}, "ExpDtls": {{ "ShipBNo": "{export_details.bill_no}", From 1866965508f9737255fb4336923d10d87707064a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 13:18:33 +0530 Subject: [PATCH 052/137] fix: validations --- erpnext/regional/india/e_invoice/e_invoice_utils.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index 9c7b4d5c46f..82ac20cd76d 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -396,13 +396,13 @@ def get_value_details(invoice): for t in invoice.taxes: if t.account_head in gst_accounts_list: if t.account_head in gst_accounts.cess_account: - value_details.total_cess_amt += abs(t.base_tax_amount) + value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) elif t.account_head in gst_accounts.igst_account: - value_details.total_igst_amt += abs(t.base_tax_amount) + value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount) elif t.account_head in gst_accounts.sgst_account: - value_details.total_sgst_amt += abs(t.base_tax_amount) + value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount) elif t.account_head in gst_accounts.cgst_account: - value_details.total_cgst_amt += abs(t.base_tax_amount) + value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount) return value_details @@ -499,7 +499,7 @@ def make_einvoice(doctype, name): def validate_einvoice(validations, einvoice, errors=[]): for fieldname, field_validation in validations.items(): value = einvoice.get(fieldname, None) - if value in (None, "None", {}, []): + if not value or value == "None": # remove keys with empty values einvoice.pop(fieldname, None) continue @@ -511,6 +511,9 @@ def validate_einvoice(validations, einvoice, errors=[]): if isinstance(value, list): for d in value: validate_einvoice(child_validations, d, errors) + if not d: + # remove empty dicts + einvoice.pop(fieldname, None) else: validate_einvoice(child_validations, value, errors) if not value: From 3d7f8338994a9b3f76a1287a62d85174ecf51925 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 14:13:11 +0530 Subject: [PATCH 053/137] fix: error logging --- erpnext/regional/india/e_invoice/e_invoice_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/e_invoice_utils.py index 82ac20cd76d..4913569c395 100644 --- a/erpnext/regional/india/e_invoice/e_invoice_utils.py +++ b/erpnext/regional/india/e_invoice/e_invoice_utils.py @@ -247,7 +247,6 @@ def decrypt_irn_response(data): def handle_err_response(response): if response.get('Status') == 0: err_details = response.get('ErrorDetails') - print(response) errors = [] for d in err_details: err_code = d.get('ErrorCode') @@ -260,11 +259,12 @@ def handle_err_response(response): errors.append(d.get('ErrorMessage')) if errors: + frappe.log_error(title="E Invoice API Request Failed", message=json.dumps(errors, default=str, indent=4)) if len(errors) > 1: li = ['
  • '+ d +'
  • ' for d in errors] frappe.throw(_("""
      {}
    """).format(''.join(li)), title=_('API Request Failed')) else: - frappe.throw(_('{}').format(errors[0]), title=_('API Request Failed')) + frappe.throw(errors[0], title=_('API Request Failed')) return response @@ -488,6 +488,7 @@ def make_einvoice(doctype, name): validations = json.loads(read_json('einv_validation')) errors = validate_einvoice(validations, einvoice, []) if errors: + frappe.log_error(title="E Invoice Validation Failed", message=json.dumps(errors, default=str, indent=4)) if len(errors) > 1: li = ['
  • '+ d +'
  • ' for d in errors] frappe.throw("
      {}
    ".format(''.join(li)), title=_('E Invoice Validation Failed')) From bfb68e4e801df19b7a371c4fbdf21b67ebfbb7e5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 17:19:51 +0530 Subject: [PATCH 054/137] fix: e_invoice module not found --- erpnext/hooks.py | 2 +- erpnext/regional/india/e_invoice/__init__.py | 0 .../regional/india/e_invoice/{e_invoice_utils.py => utils.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 erpnext/regional/india/e_invoice/__init__.py rename erpnext/regional/india/e_invoice/{e_invoice_utils.py => utils.py} (100%) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 6b1935f2397..bf81805833b 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -361,7 +361,7 @@ regional_overrides = { 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries', - 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.e_invoice_utils.validate_einvoice_fields', + 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields' }, 'United Arab Emirates': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data' diff --git a/erpnext/regional/india/e_invoice/__init__.py b/erpnext/regional/india/e_invoice/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/india/e_invoice/e_invoice_utils.py b/erpnext/regional/india/e_invoice/utils.py similarity index 100% rename from erpnext/regional/india/e_invoice/e_invoice_utils.py rename to erpnext/regional/india/e_invoice/utils.py From 965bc9bb800720a4af2c33815b428e512b690430 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 17:59:37 +0530 Subject: [PATCH 055/137] fix: add missing package --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index f807fa6c29d..20e43c44948 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ PyGithub==1.44.1 python-stdnum==1.12 Unidecode==1.1.1 WooCommerce==2.1.1 +pycryptodome==3.9.8 \ No newline at end of file From 160585321dc8b2e369738ac35ffc8e27812d418a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Oct 2020 11:25:16 +0530 Subject: [PATCH 056/137] fix: rename e_invoice_utils.py --- .../doctype/e_invoice_settings/e_invoice_settings.js | 2 +- .../doctype/e_invoice_settings/e_invoice_settings.py | 4 +++- erpnext/regional/india/e_invoice/einvoice.js | 10 +++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js index e9fb622b6b6..bea16ffa7fd 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -15,7 +15,7 @@ frappe.ui.form.on('E Invoice Settings', { frm.add_custom_button(__("Fetch Token"), () => { frm.call({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.fetch_token', + method: 'erpnext.regional.india.e_invoice.utils.fetch_token', freeze: true, callback: () => frm.refresh() }); diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py index e90d07edbd6..27f4a19b246 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -17,7 +17,9 @@ class EInvoiceSettings(Document): frappe.throw(_("{} is required").format(frappe.unscrub(d)), title=_("Missing Values")) def before_save(self): - if not self.public_key or self.has_value_changed('public_key_file'): + previous = self.get_doc_before_save() + public_file_changed = previous.get('public_key_file') != self.get('public_key_file') if previous else True + if not self.public_key or public_file_changed: self.public_key = self.read_key_file() def read_key_file(self): diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index b071919570d..e6fccbd7be4 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -123,13 +123,13 @@ erpnext.setup_einvoice_actions = (doctype) => { "Download E-Invoice", () => { frappe.call({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.make_einvoice', + method: 'erpnext.regional.india.e_invoice.utils.make_einvoice', args: { doctype, name }, freeze: true, callback: (res) => { if (!res.exc) { const args = { - cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_einvoice', + cmd: 'erpnext.regional.india.e_invoice.utils.download_einvoice', einvoice: JSON.stringify([res.message]), name: name }; @@ -142,7 +142,7 @@ erpnext.setup_einvoice_actions = (doctype) => { "Upload Signed E-Invoice", () => { new frappe.ui.FileUploader({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_einvoice', + method: 'erpnext.regional.india.e_invoice.utils.upload_einvoice', allow_multiple: 0, doctype: doctype, docname: name, @@ -173,7 +173,7 @@ erpnext.setup_einvoice_actions = (doctype) => { primary_action: function() { const data = d.get_values(); const args = { - cmd: 'erpnext.regional.india.e_invoice.e_invoice_utils.download_cancel_einvoice', + cmd: 'erpnext.regional.india.e_invoice.utils.download_cancel_einvoice', irn: irn, reason: data.reason.split('-')[0], remark: data.remark, name: name }; open_url_post(frappe.request.url, args); @@ -188,7 +188,7 @@ erpnext.setup_einvoice_actions = (doctype) => { "Upload Cancel JSON", () => { new frappe.ui.FileUploader({ - method: 'erpnext.regional.india.e_invoice.e_invoice_utils.upload_cancel_ack', + method: 'erpnext.regional.india.e_invoice.utils.upload_cancel_ack', allow_multiple: 0, doctype: doctype, docname: name, From 0e00bff182974c0c7b67a31b3c362c59de26e14e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Oct 2020 12:38:58 +0530 Subject: [PATCH 057/137] fix: einvoice field validation --- erpnext/regional/india/e_invoice/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 4913569c395..8bca1f84e96 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -22,8 +22,8 @@ from frappe.utils.data import get_datetime, cstr, cint, format_date, flt, time_d def validate_einvoice_fields(doc): einvoicing_enabled = frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable') - invalid_doctype = doc.doctype not in ['Sales Invoice', 'Purchase Invoice'] - invalid_supply_type = doc.gst_category not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] + invalid_doctype = doc.doctype not in ['Sales Invoice'] + invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] if invalid_doctype or invalid_supply_type or not einvoicing_enabled: return From 1041aef097a5335621cc1202bca1ab78b9b9917d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Oct 2020 18:51:52 +0530 Subject: [PATCH 058/137] fix: patch --- erpnext/patches/v12_0/setup_einvoice_fields.py | 1 + erpnext/regional/india/e_invoice/utils.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index e230eb0bf75..fbb313a9fe1 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -8,6 +8,7 @@ def execute(): if not company: return + frappe.reload_doc("regional", "doctype", "e_invoice_settings") custom_fields = { 'Sales Invoice': [ dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 8bca1f84e96..103245ba12b 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -526,7 +526,7 @@ def validate_einvoice(validations, einvoice, errors=[]): if value_type == 'string': einvoice[fieldname] = str(value) elif value_type == 'number': - einvoice[fieldname] = flt(value, 2) if fieldname != 'Pin' else int(value) + einvoice[fieldname] = flt(value, 2) if fieldname != 'Pin' or fieldname != 'Distance' else int(value) max_length = field_validation.get('maxLength') minimum = flt(field_validation.get('minimum')) From df1686611b465efe413304935a6a40ab44b72188 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 1 Nov 2020 12:48:53 +0530 Subject: [PATCH 059/137] fix: invoice totals calculation --- .../india/e_invoice/einv_item_template.json | 5 +++++ .../regional/india/e_invoice/einv_template.json | 1 + erpnext/regional/india/e_invoice/utils.py | 16 +++++++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json index f87b0f15f3c..23afd013d56 100644 --- a/erpnext/regional/india/e_invoice/einv_item_template.json +++ b/erpnext/regional/india/e_invoice/einv_item_template.json @@ -18,6 +18,11 @@ "SgstAmt": "{item.sgst_amount}", "CesRt": "{item.cess_rate}", "CesAmt": "{item.cess_amount}", + "CesNonAdvlAmt": "{item.cess_nadv_amount}", + "StateCesRt": "{item.state_cess_rate}", + "StateCesAmt": "{item.state_cess_amount}", + "StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}", + "OthChrg": "{item.other_charges}", "TotItemVal": "{item.total_value}", "BchDtls": {{ "Nm": "{item.batch_no}", diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json index 46741ca0337..bf6b855d2b8 100644 --- a/erpnext/regional/india/e_invoice/einv_template.json +++ b/erpnext/regional/india/e_invoice/einv_template.json @@ -66,6 +66,7 @@ "CesVal": "{value_details.total_cess_amt}", "Discount": "{value_details.invoice_discount_amt}", "RndOffAmt": "{value_details.round_off}", + "OthChrg": "{value_details.total_other_charges}", "TotInvVal": "{value_details.base_grand_total}", "TotInvValFc": "{value_details.grand_total}" }}, diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 103245ba12b..9e385d67117 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -347,19 +347,20 @@ def get_item_list(invoice): item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None item.qty = abs(item.qty) - item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_rate) + item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate) item.total_amount = abs(item.unit_rate * item.qty) item.discount_amount = abs(item.discount_amount * item.qty) - item.base_amount = abs(item.base_amount) + item.base_amount = abs(item.base_net_amount) item.tax_rate = 0 item.igst_amount = 0 item.cgst_amount = 0 item.sgst_amount = 0 item.cess_rate = 0 item.cess_amount = 0 + item.other_charges = 0 for t in invoice.taxes: + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) if t.account_head in gst_accounts_list: - item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) if t.account_head in gst_accounts.cess_account: item.cess_rate += item_tax_detail[0] item.cess_amount += abs(item_tax_detail[1]) @@ -372,8 +373,10 @@ def get_item_list(invoice): elif t.account_head in gst_accounts.cgst_account: item.tax_rate += item_tax_detail[0] item.cgst_amount += abs(item_tax_detail[1]) + else: + item.other_charges += abs(item_tax_detail[1]) - item.total_value = abs(item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount) + item.total_value = abs(item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount + item.other_charges) einv_item = item_schema.format(item=item) item_list.append(einv_item) @@ -386,13 +389,14 @@ def get_value_details(invoice): value_details = frappe._dict(dict()) value_details.base_net_total = abs(invoice.base_net_total) value_details.invoice_discount_amt = abs(invoice.discount_amount) - value_details.round_off = abs(invoice.base_rounding_adjustment) + value_details.round_off = invoice.rounding_adjustment if invoice.rounding_adjustment > 0 else 0 value_details.base_grand_total = abs(invoice.base_rounded_total) value_details.grand_total = abs(invoice.rounded_total) value_details.total_cgst_amt = 0 value_details.total_sgst_amt = 0 value_details.total_igst_amt = 0 value_details.total_cess_amt = 0 + value_details.total_other_charges = 0 for t in invoice.taxes: if t.account_head in gst_accounts_list: if t.account_head in gst_accounts.cess_account: @@ -403,6 +407,8 @@ def get_value_details(invoice): value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount) elif t.account_head in gst_accounts.cgst_account: value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount) + else: + value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount) return value_details From 9958639a9ade06dc4c2e53960ad691a55042df12 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 2 Nov 2020 14:10:38 +0530 Subject: [PATCH 060/137] fix: other charges calculation --- erpnext/regional/india/e_invoice/utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 9e385d67117..34139e5db7d 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -373,8 +373,6 @@ def get_item_list(invoice): elif t.account_head in gst_accounts.cgst_account: item.tax_rate += item_tax_detail[0] item.cgst_amount += abs(item_tax_detail[1]) - else: - item.other_charges += abs(item_tax_detail[1]) item.total_value = abs(item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount + item.other_charges) einv_item = item_schema.format(item=item) @@ -388,8 +386,8 @@ def get_value_details(invoice): value_details = frappe._dict(dict()) value_details.base_net_total = abs(invoice.base_net_total) - value_details.invoice_discount_amt = abs(invoice.discount_amount) - value_details.round_off = invoice.rounding_adjustment if invoice.rounding_adjustment > 0 else 0 + value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount > 0 else 0 + value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount < 0 else 0) value_details.base_grand_total = abs(invoice.base_rounded_total) value_details.grand_total = abs(invoice.rounded_total) value_details.total_cgst_amt = 0 From 05589e965186cd697a4ef341c86648b9a59feecc Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 2 Nov 2020 19:52:42 +0530 Subject: [PATCH 061/137] chore: improve document name validation message --- erpnext/regional/india/e_invoice/utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 34139e5db7d..a5d58e62781 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -32,11 +32,13 @@ def validate_einvoice_fields(doc): frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) if len(doc.name) > 16: title = _('Document Name Too Long') - msg = (_('As you have E-Invoicing enabled, To be able to generate IRN for this invoice, document name {} exceed 16 letters. ') - .format(bold(_('should not')))) + msg = _('As you have E-Invoicing enabled, To be able to generate IRN for this invoice, ') + msg += _('document name {} exceed 16 letters. ').format(bold(_('should not'))) msg += '

    ' - msg += (_('You {} modify your {} in order to have document name of {} length of 16. ') - .format(bold(_('must')), bold(_('naming series')), bold(_('maximum')))) + msg += _('You must {} your {} in order to have document name of {} length 16. ').format( + bold(_('modify')), bold(_('naming series')), bold(_('maximum')) + ) + msg += _('Please account for ammended document names too. ') frappe.throw(msg, title=title) elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: From 44877d5a60c439b84fef79b99b57a1ac673b747a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 3 Nov 2020 15:21:16 +0530 Subject: [PATCH 062/137] fix: qr code image string --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index a5d58e62781..fdc63ae570d 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -563,7 +563,7 @@ def update_einvoice_fields(doctype, name, signed_einvoice): frappe.db.set_value(doctype, name, 'irn', signed_einvoice.get('Irn')) frappe.db.set_value(doctype, name, 'ewaybill', signed_einvoice.get('EwbNo')) - frappe.db.set_value(doctype, name, 'signed_qr_code', signed_einvoice.get('SignedQRCode').split('.')[1]) + frappe.db.set_value(doctype, name, 'signed_qr_code', '.'.join(signed_einvoice.get('SignedQRCode').split('.')[1:])) frappe.db.set_value(doctype, name, 'signed_einvoice', decrypted_signed_invoice) @frappe.whitelist() From 4dad6742e14fbc8e762190ee5a7e076fc7d1edbb Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:19:20 +0530 Subject: [PATCH 063/137] feat: initialize GSP connector --- erpnext/regional/india/e_invoice/utils.py | 34 ++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index fdc63ae570d..e6bfe74af58 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -629,4 +629,36 @@ def attach_qrcode_image(doctype, name): abs_file_path = os.path.abspath(_file.get_full_path()) url.png(abs_file_path, scale=2) - frappe.db.set_value(doctype, name, 'qrcode_image', _file.file_url) \ No newline at end of file + frappe.db.set_value(doctype, name, 'qrcode_image', _file.file_url) + +class GSPConnector(): + def __init__(self): + self.credentials = frappe.get_cached_doc('E Invoice Settings') + + self.base_url = 'https://gsp.adaequare.com/' + self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token' + + 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 fetch_auth_token(self): + headers = { + 'gspappid': self.credentials.client_id, + 'gspappsecret': self.credentials.client_secret + } + + try: + res = make_post_request(self.authenticate_url, headers=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() + + except Exception as e: + self.log_error(e) + raise + + def log_error(self, exc): + print(exc) \ No newline at end of file From 68d2a1f02b2e97ff86579351f9d14340d795f3b7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:19:50 +0530 Subject: [PATCH 064/137] chore: remove unwanted fields --- .../e_invoice_settings.json | 30 +++++-------------- .../e_invoice_settings/e_invoice_settings.py | 21 +------------ 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json index d9a1c4976a9..8816cbde3e4 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -9,13 +9,11 @@ "section_break_2", "client_id", "client_secret", - "public_key_file", "public_key", "column_break_3", "gstin", "username", "password", - "auto_refresh_token", "auth_token", "token_expiry", "sek" @@ -45,12 +43,6 @@ "label": "Client Secret", "reqd": 1 }, - { - "fieldname": "public_key_file", - "fieldtype": "Attach", - "label": "Public Key", - "reqd": 1 - }, { "fieldname": "public_key", "fieldtype": "Long Text", @@ -79,36 +71,30 @@ "label": "Password", "reqd": 1 }, - { - "default": "0", - "description": "Token will be automatically refreshed 10 mins before expiry", - "fieldname": "auto_refresh_token", - "fieldtype": "Check", - "label": "Auto Refresh Token" - }, { "fieldname": "auth_token", "fieldtype": "Data", "hidden": 1, "read_only": 1 }, - { - "fieldname": "token_expiry", - "fieldtype": "Datetime", - "hidden": 1, - "read_only": 1 - }, { "fieldname": "sek", "fieldtype": "Data", "hidden": 1, "read_only": 1 + }, + { + "default": "0", + "fieldname": "token_expiry", + "fieldtype": "Datetime", + "hidden": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-10-23 19:55:11.417161", + "modified": "2020-11-03 21:23:50.277305", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Settings", diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py index 27f4a19b246..2a4f00365f3 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -3,26 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.utils.data import cstr from frappe.model.document import Document -from frappe.custom.doctype.property_setter.property_setter import make_property_setter class EInvoiceSettings(Document): - def validate(self): - mandatory_fields = ['client_id', 'client_secret', 'gstin', 'username', 'password', 'public_key_file'] - for d in mandatory_fields: - if not self.get(d): - frappe.throw(_("{} is required").format(frappe.unscrub(d)), title=_("Missing Values")) - - def before_save(self): - previous = self.get_doc_before_save() - public_file_changed = previous.get('public_key_file') != self.get('public_key_file') if previous else True - if not self.public_key or public_file_changed: - self.public_key = self.read_key_file() - - def read_key_file(self): - key_file = frappe.get_doc('File', dict(attached_to_name=self.doctype)) - with open(key_file.get_full_path(), 'rb') as f: - return cstr(f.read()) \ No newline at end of file + pass From 537a236dc93ca9ce39390e4ed83445bb26d769e7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:23:01 +0530 Subject: [PATCH 065/137] fix: qr code generation --- erpnext/regional/india/e_invoice/utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index e6bfe74af58..5f3edbcc407 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -563,7 +563,7 @@ def update_einvoice_fields(doctype, name, signed_einvoice): frappe.db.set_value(doctype, name, 'irn', signed_einvoice.get('Irn')) frappe.db.set_value(doctype, name, 'ewaybill', signed_einvoice.get('EwbNo')) - frappe.db.set_value(doctype, name, 'signed_qr_code', '.'.join(signed_einvoice.get('SignedQRCode').split('.')[1:])) + frappe.db.set_value(doctype, name, 'signed_qr_code', signed_einvoice.get('SignedQRCode')) frappe.db.set_value(doctype, name, 'signed_einvoice', decrypted_signed_invoice) @frappe.whitelist() @@ -616,14 +616,15 @@ def attach_qrcode_image(doctype, name): if not qrcode: return - _file = frappe.get_doc({ - 'doctype': 'File', + _file = frappe.new_doc('File') + _file.update({ 'file_name': 'Signed_QR_{name}.png'.format(name=name), 'attached_to_doctype': doctype, 'attached_to_name': name, - 'content': 'qrcode' + 'content': 'qrcode', + 'is_private': 1 }) - _file.save() + _file.insert() frappe.db.commit() url = qrcreate(qrcode) abs_file_path = os.path.abspath(_file.get_full_path()) From 0938db87300abbc1480492ba8f8fb8b3c6be6175 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:28:31 +0530 Subject: [PATCH 066/137] feat: fetch and cache GSTIN details --- erpnext/regional/india/e_invoice/utils.py | 74 ++++++++++++++++++++--- 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 5f3edbcc407..92736470553 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -300,16 +300,18 @@ def get_doc_details(invoice): return frappe._dict(dict(invoice_type=invoice_type, invoice_name=invoice_name, invoice_date=invoice_date)) -def get_party_gstin_details(address_name): - address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] +def get_party_details(address): + from erpnext.regional.doctype.e_invoice_settings.e_invoice_settings import GSPConnector + address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] gstin = address.get('gstin') - gstin_details = get_gstin_details(gstin) + + gstin_details = GSPConnector.get_gstin_details(gstin) legal_name = gstin_details.get('LegalName') trade_name = gstin_details.get('TradeName') - location = gstin_details.get('AddrLoc') + location = gstin_details.get('AddrLoc') or address.get('city') state_code = gstin_details.get('StateCode') - pincode = cint(gstin_details.get('AddrPncd')) + pincode = gstin_details.get('AddrPncd') address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno')) address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt')) email_id = address.get('email_id') @@ -376,7 +378,10 @@ def get_item_list(invoice): item.tax_rate += item_tax_detail[0] item.cgst_amount += abs(item_tax_detail[1]) - item.total_value = abs(item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount + item.other_charges) + item.total_value = abs( + item.base_amount + item.igst_amount + item.sgst_amount + + item.cgst_amount + item.cess_amount + item.other_charges + ) einv_item = item_schema.format(item=item) item_list.append(einv_item) @@ -456,19 +461,19 @@ def make_einvoice(doctype, name): doc_details = get_doc_details(invoice) value_details = get_value_details(invoice) trans_details = get_trans_details(invoice) - seller_details = get_party_gstin_details(invoice.company_address) + seller_details = get_party_details(invoice.company_address) if invoice.gst_category == 'Overseas': buyer_details = get_overseas_address_details(invoice.customer_address) else: - buyer_details = get_party_gstin_details(invoice.customer_address) + buyer_details = get_party_details(invoice.customer_address) place_of_supply = get_place_of_supply(invoice, doctype) or invoice.billing_address_gstin place_of_supply = place_of_supply[:2] buyer_details.update(dict(place_of_supply=place_of_supply)) shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name: - shipping_details = get_party_gstin_details(invoice.shipping_address_name) + shipping_details = get_party_details(invoice.shipping_address_name) if invoice.is_pos and invoice.base_paid_amount: payment_details = get_payment_details(invoice) @@ -532,7 +537,7 @@ def validate_einvoice(validations, einvoice, errors=[]): if value_type == 'string': einvoice[fieldname] = str(value) elif value_type == 'number': - einvoice[fieldname] = flt(value, 2) if fieldname != 'Pin' or fieldname != 'Distance' else int(value) + einvoice[fieldname] = flt(value, 2) if fieldname not in ['Pin', 'Distance'] else int(value) max_length = field_validation.get('maxLength') minimum = flt(field_validation.get('minimum')) @@ -638,6 +643,7 @@ class GSPConnector(): self.base_url = 'https://gsp.adaequare.com/' self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token' + self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin' def get_auth_token(self): if time_diff_in_seconds(self.credentials.token_expiry, now_datetime()) < 150.0: @@ -660,6 +666,54 @@ class GSPConnector(): except Exception as e: self.log_error(e) raise + + def get_headers(self): + return { + 'content-type': 'application/json', + 'user_name': self.credentials.username, + 'password': self.credentials.get_password(), + 'gstin': self.credentials.gstin, + '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) + if res.get('success'): + return res.get('result') + + except Exception as e: + self.log_error(e) + + return {} + + @staticmethod + def get_gstin_details(gstin): + '''fetch or get cached GSTIN details''' + + if not hasattr(frappe.local, 'gstin_cache'): + frappe.local.gstin_cache = {} + + key = gstin + details = frappe.local.gstin_cache.get(key) + if details: + return details + + details = frappe.cache().hget('gstin_cache', key) + if details: + frappe.local.gstin_cache[key] = details + return details + + gsp_connector = GSPConnector() + details = gsp_connector.fetch_gstin_details(gstin) + + frappe.local.gstin_cache[key] = details + frappe.cache().hset('gstin_cache', key, details) + return details def log_error(self, exc): print(exc) \ No newline at end of file From a15c02ec711776144720f109f6e8da1d136e6f79 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:39:39 +0530 Subject: [PATCH 067/137] feat: generate & cancel IRN --- erpnext/regional/india/e_invoice/utils.py | 58 ++++++++++++++++++----- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 92736470553..153493d6564 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -556,20 +556,17 @@ def validate_einvoice(validations, einvoice, errors=[]): return errors -def update_einvoice_fields(doctype, name, signed_einvoice): - enc_signed_invoice = signed_einvoice.get('SignedInvoice') - decrypted_signed_invoice = jwt_decrypt(enc_signed_invoice)['data'] +def update_invoice(invoice, res): + doctype = invoice.doctype + name = invoice.name - if json.loads(decrypted_signed_invoice)['DocDtls']['No'] != name: - frappe.throw( - _("Document number of uploaded Signed E-Invoice doesn't matches with Sales Invoice"), - title=_("Inappropriate E-Invoice") - ) + enc_signed_invoice = res.get('SignedInvoice') + dec_signed_invoice = jwt.decode(enc_signed_invoice)['data'] - frappe.db.set_value(doctype, name, 'irn', signed_einvoice.get('Irn')) - frappe.db.set_value(doctype, name, 'ewaybill', signed_einvoice.get('EwbNo')) - frappe.db.set_value(doctype, name, 'signed_qr_code', signed_einvoice.get('SignedQRCode')) - frappe.db.set_value(doctype, name, 'signed_einvoice', decrypted_signed_invoice) + frappe.db.set_value(doctype, name, 'irn', res.get('Irn')) + frappe.db.set_value(doctype, name, 'ewaybill', res.get('EwbNo')) + frappe.db.set_value(doctype, name, 'signed_invoice', dec_signed_invoice) + frappe.db.set_value(doctype, name, 'signed_qr_code', res.get('SignedQRCode')) @frappe.whitelist() def download_einvoice(): @@ -644,6 +641,8 @@ class GSPConnector(): self.base_url = 'https://gsp.adaequare.com/' self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token' self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin' + self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice' + self.cancel_irn_url = self.base_url + 'test/enriched/ei/api/invoice/cancel' def get_auth_token(self): if time_diff_in_seconds(self.credentials.token_expiry, now_datetime()) < 150.0: @@ -715,5 +714,40 @@ class GSPConnector(): frappe.cache().hset('gstin_cache', key, details) return details + def generate_irn(self, docname): + headers = self.get_headers() + doctype = 'Sales Invoice' + einvoice = make_einvoice(doctype, docname) + data = json.dumps(einvoice) + + try: + res = make_post_request(self.generate_irn_url, headers=headers, data=data) + if res.get('success'): + update_invoice(invoice, res) + else: + # {'success': False, 'message': '3039 : Seller Details:Pincode-560009 does not belong to the state-1, 2177 : Invalid item unit code(s)-UNIT'} + self.log_error() + + except Exception as e: + self.log_error(e) + + def cancel_irn(self, docname, irn, reason, remark): + headers = self.get_headers() + doctype = 'Sales Invoice' + data = json.dumps({ + 'Irn': irn, + 'Cnlrsn': reason, + 'Cnlrem': remark + }) + + try: + res = make_post_request(self.cancel_irn_url, headers=headers, data=data) + if res.get('success'): + frappe.db.set_value(doctype, docname, 'irn_cancelled', 1) + # frappe.db.set_value(doctype, docname, 'cancelled_on', res.get('CancelDate')) + + except Exception as e: + self.log_error(e) + def log_error(self, exc): print(exc) \ No newline at end of file From 5822cfb45433127f0da092c209e5054545552304 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:40:13 +0530 Subject: [PATCH 068/137] feat: cancel eway bill --- erpnext/regional/india/e_invoice/utils.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 153493d6564..7250c7e75a5 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -643,6 +643,7 @@ class GSPConnector(): self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin' self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice' 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' def get_auth_token(self): if time_diff_in_seconds(self.credentials.token_expiry, now_datetime()) < 150.0: @@ -749,5 +750,23 @@ class GSPConnector(): except Exception as e: self.log_error(e) + def cancel_eway_bill(self, docname, eway_bill, reason, remark): + headers = self.get_headers() + doctype = 'Sales Invoice' + data = json.dumps({ + 'ewbNo': eway_bill, + 'cancelRsnCode': reason, + 'cancelRmrk': remark + }) + + try: + res = make_post_request(self.cancel_ewaybill_url, headers=headers, data=data) + if res.get('success'): + frappe.db.set_value(doctype, docname, 'ewaybill', '') + frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) + + except Exception as e: + self.log_error(e) + def log_error(self, exc): print(exc) \ No newline at end of file From 6dcf334199a91b97f6c27c2ab1c72f01440213fe Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:45:24 +0530 Subject: [PATCH 069/137] chore: remove unwanted fuctions --- erpnext/regional/india/e_invoice/einvoice.js | 257 ++++++------------ erpnext/regional/india/e_invoice/utils.py | 267 ------------------- 2 files changed, 87 insertions(+), 437 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index e6fccbd7be4..e0db6c44e69 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -9,196 +9,113 @@ erpnext.setup_einvoice_actions = (doctype) => { const { docstatus, irn, irn_cancelled, ewaybill, eway_bill_cancelled, doctype, name, __unsaved } = frm.doc; - // if (docstatus == 0 && !irn && !__unsaved) { - // frm.add_custom_button( - // _("Generate IRN"), - // () => { - // frappe.call({ - // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.generate_irn', - // args: { doctype: doctype, name: name }, - // freeze: true, - // callback: () => frm.reload_doc() - // }) - // }, - // __("E Invoicing") - // ); - // } - - // if (docstatus == 1 && irn && !irn_cancelled) { - // frm.add_custom_button( - // __("Cancel IRN"), - // () => { - // const fields = [ - // { - // "label" : "Reason", - // "fieldname": "reason", - // "fieldtype": "Select", - // "reqd": 1, - // "default": "1-Duplicate", - // "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - // }, - // { - // "label": "Remark", - // "fieldname": "remark", - // "fieldtype": "Data", - // "reqd": 1 - // } - // ]; - // const d = new frappe.ui.Dialog({ - // title: __("Cancel IRN"), - // fields: fields, - // primary_action: function() { - // const data = d.get_values(); - // frappe.call({ - // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_irn', - // args: { - // doctype: doctype, - // name: name, - // irn: irn, - // reason: data.reason.split('-')[0], - // remark: data.remark - // }, - // freeze: true, - // callback: () => frm.reload_doc() || d.hide(), - // error: () => d.hide() - // }); - // }, - // primary_action_label: __('Submit') - // }); - // d.show(); - // }, - // __("E Invoicing") - // ) - // } - - // if (docstatus == 1 && irn && !irn_cancelled && !eway_bill_cancelled) { - // frm.add_custom_button( - // __("Cancel E-Way Bill"), - // () => { - // const fields = [ - // { - // "label" : "Reason", - // "fieldname": "reason", - // "fieldtype": "Select", - // "reqd": 1, - // "default": "1-Duplicate", - // "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - // }, - // { - // "label": "Remark", - // "fieldname": "remark", - // "fieldtype": "Data", - // "reqd": 1 - // } - // ] - // const d = new frappe.ui.Dialog({ - // title: __('Cancel E-Way Bill'), - // fields: fields, - // primary_action: function() { - // const data = d.get_values(); - // frappe.call({ - // method: 'erpnext.regional.india.e_invoice.e_invoice_utils.cancel_eway_bill', - // args: { - // doctype: doctype, - // name: name, - // eway_bill: ewaybill, - // reason: data.reason.split('-')[0], - // remark: data.remark - // }, - // freeze: true, - // callback: () => frm.reload_doc() || d.hide(), - // error: () => d.hide() - // }) - // }, - // primary_action_label: __('Submit') - // }); - // d.show(); - // }, - // __("E Invoicing") - // ); - // } - if (docstatus == 0 && !irn && !__unsaved) { frm.add_custom_button( - "Download E-Invoice", + __("Generate IRN"), () => { frappe.call({ - method: 'erpnext.regional.india.e_invoice.utils.make_einvoice', - args: { doctype, name }, + method: 'erpnext.regional.india.e_invoice.utils.generate_irn', + args: { doctype: doctype, name: name }, freeze: true, - callback: (res) => { - if (!res.exc) { - const args = { - cmd: 'erpnext.regional.india.e_invoice.utils.download_einvoice', - einvoice: JSON.stringify([res.message]), - name: name - }; - open_url_post(frappe.request.url, args); - } - } + callback: () => frm.reload_doc() }) - }, "E-Invoicing"); - frm.add_custom_button( - "Upload Signed E-Invoice", - () => { - new frappe.ui.FileUploader({ - method: 'erpnext.regional.india.e_invoice.utils.upload_einvoice', - allow_multiple: 0, - doctype: doctype, - docname: name, - on_success: (attachment, r) => { - if (!r.exc) { - frm.reload_doc(); - } - } - }); - }, "E-Invoicing"); + }, + __("E Invoicing") + ); } + if (docstatus == 1 && irn && !irn_cancelled) { frm.add_custom_button( - "Cancel IRN", + __("Cancel IRN"), () => { + const fields = [ + { + "label" : "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; const d = new frappe.ui.Dialog({ - title: __('Cancel IRN'), - fields: [ - { - "label" : "Reason", "fieldname": "reason", - "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - }, - { - "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 - } - ], + title: __("Cancel IRN"), + fields: fields, primary_action: function() { const data = d.get_values(); - const args = { - cmd: 'erpnext.regional.india.e_invoice.utils.download_cancel_einvoice', - irn: irn, reason: data.reason.split('-')[0], remark: data.remark, name: name - }; - open_url_post(frappe.request.url, args); - d.hide(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_irn', + args: { + doctype: doctype, + name: name, + irn: irn, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }); }, - primary_action_label: __('Download JSON') + primary_action_label: __('Submit') }); d.show(); - }, "E-Invoicing"); - + }, + __("E Invoicing") + ) + } + + if (docstatus == 1 && irn && !irn_cancelled && !eway_bill_cancelled) { frm.add_custom_button( - "Upload Cancel JSON", + __("Cancel E-Way Bill"), () => { - new frappe.ui.FileUploader({ - method: 'erpnext.regional.india.e_invoice.utils.upload_cancel_ack', - allow_multiple: 0, - doctype: doctype, - docname: name, - on_success: (attachment, r) => { - if (!r.exc) { - frm.reload_doc(); - } + const fields = [ + { + "label" : "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 } + ] + const d = new frappe.ui.Dialog({ + title: __('Cancel E-Way Bill'), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', + args: { + doctype: doctype, + name: name, + eway_bill: ewaybill, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }) + }, + primary_action_label: __('Submit') }); - }, "E-Invoicing"); + d.show(); + }, + __("E Invoicing") + ); } } }) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 7250c7e75a5..2a174202968 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -47,229 +47,6 @@ def validate_einvoice_fields(doc): elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed')) -def get_credentials(): - doc = frappe.get_doc('E Invoice Settings') - if not doc.enable: - frappe.throw(_("To setup E Invoicing you need to enable E Invoice Settings first."), title=_("E Invoicing Disabled")) - - if not doc.token_expiry or time_diff_in_seconds(now_datetime(), doc.token_expiry) > 5.0: - fetch_token(doc) - doc.load_from_db() - - return doc - -def rsa_encrypt(msg, key): - if not (isinstance(msg, bytes) or isinstance(msg, bytearray)): - msg = str.encode(msg) - - rsa_pub_key = RSA.import_key(key) - cipher = PKCS1_v1_5.new(rsa_pub_key) - enc_msg = cipher.encrypt(msg) - b64_enc_msg = base64.b64encode(enc_msg) - return b64_enc_msg.decode() - -def aes_decrypt(enc_msg, key): - encode_as_b64 = True - if not (isinstance(key, bytes) or isinstance(key, bytearray)): - key = base64.b64decode(key) - encode_as_b64 = False - - cipher = AES.new(key, AES.MODE_ECB) - b64_enc_msg = base64.b64decode(enc_msg) - msg_bytes = cipher.decrypt(b64_enc_msg) - msg_bytes = unpad(msg_bytes, AES.block_size) # due to ECB/PKCS5Padding - if encode_as_b64: - msg_bytes = base64.b64encode(msg_bytes) - return msg_bytes.decode() - -def aes_encrypt(msg, key): - if not (isinstance(key, bytes) or isinstance(key, bytearray)): - key = base64.b64decode(key) - - cipher = AES.new(key, AES.MODE_ECB) - bytes_msg = str.encode(msg) - padded_bytes_msg = pad(bytes_msg, AES.block_size) - enc_msg = cipher.encrypt(padded_bytes_msg) - b64_enc_msg = base64.b64encode(enc_msg) - return b64_enc_msg.decode() - -def jwt_decrypt(token): - return jwt.decode(token, verify=False) - -def get_header(creds): - headers = { 'content-type': 'application/json' } - headers.update(dict(client_id=creds.client_id, client_secret=creds.client_secret, user_name=creds.username)) - headers.update(dict(Gstin=creds.gstin, AuthToken=creds.auth_token)) - return headers - -@frappe.whitelist() -def fetch_token(credentials=None): - if not credentials: - credentials = frappe.get_doc('E Invoice Settings') - - endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth' - headers = { 'content-type': 'application/json' } - headers.update(dict(client_id=credentials.client_id, client_secret=credentials.client_secret)) - payload = dict(UserName=credentials.username, ForceRefreshAccessToken=bool(credentials.auto_refresh_token)) - - appkey = bytearray(os.urandom(32)) - enc_appkey = rsa_encrypt(appkey, credentials.public_key) - - password = credentials.get_password(fieldname='password') - enc_password = rsa_encrypt(password, credentials.public_key) - - payload.update(dict(Password=enc_password, AppKey=enc_appkey)) - - res = make_post_request(endpoint, headers=headers, data=json.dumps({ 'data': payload })) - handle_err_response(res) - - auth_token, token_expiry, sek = extract_token_and_sek(res, appkey) - - credentials.auth_token = auth_token - credentials.token_expiry = get_datetime(token_expiry) - credentials.sek = sek - credentials.save() - -def extract_token_and_sek(response, appkey): - data = response.get('Data') - auth_token = data.get('AuthToken') - token_expiry = data.get('TokenExpiry') - enc_sek = data.get('Sek') - sek = aes_decrypt(enc_sek, appkey) - return auth_token, token_expiry, sek - -def attach_signed_invoice(doctype, name, data): - f = frappe.get_doc({ - 'doctype': 'File', - 'file_name': 'E-INV--{}.json'.format(name), - 'attached_to_doctype': doctype, - 'attached_to_name': name, - 'content': json.dumps(data), - 'is_private': True - }).insert() - -def get_gstin_details(gstin): - credentials = get_credentials() - - endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/Master/gstin/{gstin}'.format(gstin=gstin) - headers = get_header(credentials) - - res = make_get_request(endpoint, headers=headers) - handle_err_response(res) - - enc_details = res.get('Data') - json_str = aes_decrypt(enc_details, credentials.sek) - details = json.loads(json_str) - - return details - -@frappe.whitelist() -def generate_irn(doctype, name): - endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice' - credentials = get_credentials() - headers = get_header(credentials) - - einvoice = make_einvoice(doctype, name) - einvoice = json.dumps(einvoice) - - enc_einvoice_json = aes_encrypt(einvoice, credentials.sek) - payload = dict(Data=enc_einvoice_json) - - res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) - res = handle_err_response(res) - - enc_json = res.get('Data') - json_str = aes_decrypt(enc_json, credentials.sek) - - signed_einvoice = json.loads(json_str) - decrypt_irn_response(signed_einvoice) - - update_einvoice_fields(doctype, name, signed_einvoice) - - attach_qrcode_image(doctype, name) - attach_signed_invoice(doctype, name, signed_einvoice['DecryptedSignedInvoice']) - - return signed_einvoice - -def get_irn_details(irn): - credentials = get_credentials() - - endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/irn/{irn}'.format(irn=irn) - headers = get_header(credentials) - - res = make_get_request(endpoint, headers=headers) - handle_err_response(res) - - return res - -@frappe.whitelist() -def cancel_irn(doctype, name, irn, reason, remark=''): - credentials = get_credentials() - - endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/Cancel' - headers = get_header(credentials) - - cancel_einv = json.dumps(dict(Irn=irn, CnlRsn=reason, CnlRem=remark)) - enc_json = aes_encrypt(cancel_einv, credentials.sek) - payload = dict(Data=enc_json) - - res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) - handle_err_response(res) - - frappe.db.set_value(doctype, name, 'irn_cancelled', 1) - - return res - -@frappe.whitelist() -def cancel_eway_bill(doctype, name, eway_bill, reason, remark=''): - credentials = get_credentials() - endpoint = 'https://einv-apisandbox.nic.in/ewaybillapi/v1.03/ewayapi' - headers = get_header(credentials) - - cancel_eway_bill_json = json.dumps(dict(ewbNo=eway_bill, cancelRsnCode=reason, cancelRmrk=remark)) - enc_json = aes_encrypt(cancel_eway_bill_json, credentials.sek) - payload = dict(action='CANEWB', Data=enc_json) - - res = make_post_request(endpoint, headers=headers, data=json.dumps(payload)) - handle_err_response(res) - - frappe.db.set_value(doctype, name, 'ewaybill', '') - frappe.db.set_value(doctype, name, 'eway_bill_cancelled', 1) - - return res - -def decrypt_irn_response(data): - enc_signed_invoice = data['SignedInvoice'] - enc_signed_qr_code = data['SignedQRCode'] - signed_invoice = jwt_decrypt(enc_signed_invoice)['data'] - signed_qr_code = jwt_decrypt(enc_signed_qr_code)['data'] - data['DecryptedSignedInvoice'] = json.loads(signed_invoice) - data['DecryptedSignedQRCode'] = json.loads(signed_qr_code) - -def handle_err_response(response): - if response.get('Status') == 0: - err_details = response.get('ErrorDetails') - errors = [] - for d in err_details: - err_code = d.get('ErrorCode') - - if err_code == '2150': - irn = [d['Desc']['Irn'] for d in response.get('InfoDtls') if d['InfCd'] == 'DUPIRN'] - response = get_irn_details(irn[0]) - return response - - errors.append(d.get('ErrorMessage')) - - if errors: - frappe.log_error(title="E Invoice API Request Failed", message=json.dumps(errors, default=str, indent=4)) - if len(errors) > 1: - li = ['
  • '+ d +'
  • ' for d in errors] - frappe.throw(_("""
      {}
    """).format(''.join(li)), title=_('API Request Failed')) - else: - frappe.throw(errors[0], title=_('API Request Failed')) - - return response - def read_json(name): file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name)) with open(file_path, 'r') as f: @@ -568,50 +345,6 @@ def update_invoice(invoice, res): frappe.db.set_value(doctype, name, 'signed_invoice', dec_signed_invoice) frappe.db.set_value(doctype, name, 'signed_qr_code', res.get('SignedQRCode')) -@frappe.whitelist() -def download_einvoice(): - data = frappe._dict(frappe.local.form_dict) - einvoice = data['einvoice'] - name = data['name'] - - frappe.response['filename'] = 'E-Invoice-' + name + '.json' - frappe.response['filecontent'] = einvoice - frappe.response['content_type'] = 'application/json' - frappe.response['type'] = 'download' - -@frappe.whitelist() -def upload_einvoice(): - signed_einvoice = json.loads(frappe.local.uploaded_file) - data = frappe._dict(frappe.local.form_dict) - doctype = data['doctype'] - name = data['docname'] - - update_einvoice_fields(doctype, name, signed_einvoice) - attach_qrcode_image(doctype, name) - -@frappe.whitelist() -def download_cancel_einvoice(): - data = frappe._dict(frappe.local.form_dict) - name = data['name'] - irn = data['irn'] - reason = data['reason'] - remark = data['remark'] - - cancel_einvoice = json.dumps([dict(Irn=irn, CnlRsn=reason, CnlRem=remark)]) - - frappe.response['filename'] = 'Cancel E-Invoice ' + name + '.json' - frappe.response['filecontent'] = cancel_einvoice - frappe.response['content_type'] = 'application/json' - frappe.response['type'] = 'download' - -@frappe.whitelist() -def upload_cancel_ack(): - cancel_ack = json.loads(frappe.local.uploaded_file) - data = frappe._dict(frappe.local.form_dict) - doctype = data['doctype'] - name = data['docname'] - - frappe.db.set_value(doctype, name, 'irn_cancelled', 1) def attach_qrcode_image(doctype, name): qrcode = frappe.db.get_value(doctype, name, 'signed_qr_code') From ac2fc8470bf1c1c66e1724c4a9a29eabb324fcce Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:54:01 +0530 Subject: [PATCH 070/137] chore: clean up einvoice actions --- erpnext/regional/india/e_invoice/einvoice.js | 188 +++++++++---------- 1 file changed, 90 insertions(+), 98 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index e0db6c44e69..65cac992ff4 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -10,112 +10,104 @@ erpnext.setup_einvoice_actions = (doctype) => { const { docstatus, irn, irn_cancelled, ewaybill, eway_bill_cancelled, doctype, name, __unsaved } = frm.doc; if (docstatus == 0 && !irn && !__unsaved) { - frm.add_custom_button( - __("Generate IRN"), - () => { - frappe.call({ - method: 'erpnext.regional.india.e_invoice.utils.generate_irn', - args: { doctype: doctype, name: name }, - freeze: true, - callback: () => frm.reload_doc() - }) - }, - __("E Invoicing") - ); + const action = () => { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_irn', + args: { doctype: doctype, name: name }, + freeze: true, + callback: () => frm.reload_doc() + }) + }; + + frm.add_custom_button(__("Generate IRN"), action, __('E Invoicing')); } if (docstatus == 1 && irn && !irn_cancelled) { - frm.add_custom_button( - __("Cancel IRN"), - () => { - const fields = [ - { - "label" : "Reason", - "fieldname": "reason", - "fieldtype": "Select", - "reqd": 1, - "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - }, - { - "label": "Remark", - "fieldname": "remark", - "fieldtype": "Data", - "reqd": 1 - } - ]; - const d = new frappe.ui.Dialog({ - title: __("Cancel IRN"), - fields: fields, - primary_action: function() { - const data = d.get_values(); - frappe.call({ - method: 'erpnext.regional.india.e_invoice.utils.cancel_irn', - args: { - doctype: doctype, - name: name, - irn: irn, - reason: data.reason.split('-')[0], - remark: data.remark - }, - freeze: true, - callback: () => frm.reload_doc() || d.hide(), - error: () => d.hide() - }); - }, - primary_action_label: __('Submit') - }); - d.show(); + const fields = [ + { + "label" : "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, - __("E Invoicing") - ) + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + const action = () => { + const d = new frappe.ui.Dialog({ + title: __("Cancel IRN"), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_irn', + args: { + doctype: doctype, + name: name, + irn: irn, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + frm.add_custom_button(__("Cancel IRN"), action, __("E Invoicing")); } if (docstatus == 1 && irn && !irn_cancelled && !eway_bill_cancelled) { - frm.add_custom_button( - __("Cancel E-Way Bill"), - () => { - const fields = [ - { - "label" : "Reason", - "fieldname": "reason", - "fieldtype": "Select", - "reqd": 1, - "default": "1-Duplicate", - "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] - }, - { - "label": "Remark", - "fieldname": "remark", - "fieldtype": "Data", - "reqd": 1 - } - ] - const d = new frappe.ui.Dialog({ - title: __('Cancel E-Way Bill'), - fields: fields, - primary_action: function() { - const data = d.get_values(); - frappe.call({ - method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', - args: { - doctype: doctype, - name: name, - eway_bill: ewaybill, - reason: data.reason.split('-')[0], - remark: data.remark - }, - freeze: true, - callback: () => frm.reload_doc() || d.hide(), - error: () => d.hide() - }) - }, - primary_action_label: __('Submit') - }); - d.show(); + const fields = [ + { + "label" : "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] }, - __("E Invoicing") - ); + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + const action = () => { + const d = new frappe.ui.Dialog({ + title: __('Cancel E-Way Bill'), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', + args: { + doctype: doctype, + name: name, + eway_bill: ewaybill, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }) + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + frm.add_custom_button(__("Cancel E-Way Bill"), action, __("E Invoicing")); } } }) From e6cd9ccea0bd0e14555213b5eb5d43603c91483a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:58:34 +0530 Subject: [PATCH 071/137] fix: attach qrcode on irn generation --- erpnext/regional/india/e_invoice/utils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 2a174202968..878bc11fb11 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -343,17 +343,18 @@ def update_invoice(invoice, res): frappe.db.set_value(doctype, name, 'irn', res.get('Irn')) frappe.db.set_value(doctype, name, 'ewaybill', res.get('EwbNo')) frappe.db.set_value(doctype, name, 'signed_invoice', dec_signed_invoice) - frappe.db.set_value(doctype, name, 'signed_qr_code', res.get('SignedQRCode')) + signed_qrcode = res.get('SignedQRCode') + frappe.db.set_value(doctype, name, 'signed_qr_code', signed_qrcode) -def attach_qrcode_image(doctype, name): - qrcode = frappe.db.get_value(doctype, name, 'signed_qr_code') + attach_qrcode_image(doctype, name, signed_qrcode) +def attach_qrcode_image(doctype, name, qrcode): if not qrcode: return _file = frappe.new_doc('File') _file.update({ - 'file_name': 'Signed_QR_{name}.png'.format(name=name), + 'file_name': f'QRCode_{name}.png', 'attached_to_doctype': doctype, 'attached_to_name': name, 'content': 'qrcode', From 628dcb1ff96dcc56f8e0a522436456a8aa3e6913 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 12:35:05 +0530 Subject: [PATCH 072/137] fix: generate & cancel IRN --- erpnext/regional/india/e_invoice/einvoice.js | 5 +-- erpnext/regional/india/e_invoice/utils.py | 47 +++++++++++--------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 65cac992ff4..464b3b2e880 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -13,7 +13,7 @@ erpnext.setup_einvoice_actions = (doctype) => { const action = () => { frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.generate_irn', - args: { doctype: doctype, name: name }, + args: { docname: name }, freeze: true, callback: () => frm.reload_doc() }) @@ -48,8 +48,7 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.cancel_irn', args: { - doctype: doctype, - name: name, + docname: name, irn: irn, reason: data.reason.split('-')[0], remark: data.remark diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 878bc11fb11..8e2b154ffd0 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -77,9 +77,7 @@ def get_doc_details(invoice): return frappe._dict(dict(invoice_type=invoice_type, invoice_name=invoice_name, invoice_date=invoice_date)) -def get_party_details(address): - from erpnext.regional.doctype.e_invoice_settings.e_invoice_settings import GSPConnector - +def get_party_details(address_name): address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] gstin = address.get('gstin') @@ -333,30 +331,27 @@ def validate_einvoice(validations, einvoice, errors=[]): return errors -def update_invoice(invoice, res): - doctype = invoice.doctype - name = invoice.name - +def update_invoice(doctype, docname, res): enc_signed_invoice = res.get('SignedInvoice') - dec_signed_invoice = jwt.decode(enc_signed_invoice)['data'] + dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data'] - frappe.db.set_value(doctype, name, 'irn', res.get('Irn')) - frappe.db.set_value(doctype, name, 'ewaybill', res.get('EwbNo')) - frappe.db.set_value(doctype, name, 'signed_invoice', dec_signed_invoice) + frappe.db.set_value(doctype, docname, 'irn', res.get('Irn')) + frappe.db.set_value(doctype, docname, 'ewaybill', res.get('EwbNo')) + frappe.db.set_value(doctype, docname, 'signed_einvoice', dec_signed_invoice) - signed_qrcode = res.get('SignedQRCode') - frappe.db.set_value(doctype, name, 'signed_qr_code', signed_qrcode) + signed_qr_code = res.get('SignedQRCode') + frappe.db.set_value(doctype, docname, 'signed_qr_code', signed_qr_code) - attach_qrcode_image(doctype, name, signed_qrcode) + attach_qrcode_image(doctype, docname, signed_qr_code) -def attach_qrcode_image(doctype, name, qrcode): +def attach_qrcode_image(doctype, docname, qrcode): if not qrcode: return _file = frappe.new_doc('File') _file.update({ - 'file_name': f'QRCode_{name}.png', + 'file_name': f'QRCode_{docname}.png', 'attached_to_doctype': doctype, - 'attached_to_name': name, + 'attached_to_name': docname, 'content': 'qrcode', 'is_private': 1 }) @@ -366,7 +361,7 @@ def attach_qrcode_image(doctype, name, qrcode): abs_file_path = os.path.abspath(_file.get_full_path()) url.png(abs_file_path, scale=2) - frappe.db.set_value(doctype, name, 'qrcode_image', _file.file_url) + frappe.db.set_value(doctype, docname, 'qrcode_image', _file.file_url) class GSPConnector(): def __init__(self): @@ -458,10 +453,10 @@ class GSPConnector(): try: res = make_post_request(self.generate_irn_url, headers=headers, data=data) if res.get('success'): - update_invoice(invoice, res) + update_invoice(doctype, docname, res.get('result')) else: # {'success': False, 'message': '3039 : Seller Details:Pincode-560009 does not belong to the state-1, 2177 : Invalid item unit code(s)-UNIT'} - self.log_error() + self.log_error(res) except Exception as e: self.log_error(e) @@ -503,4 +498,14 @@ class GSPConnector(): self.log_error(e) def log_error(self, exc): - print(exc) \ No newline at end of file + print(exc) + +@frappe.whitelist() +def generate_irn(docname): + gsp_connector = GSPConnector() + gsp_connector.generate_irn(docname) + +@frappe.whitelist() +def cancel_irn(docname, irn, reason, remark): + gsp_connector = GSPConnector() + gsp_connector.cancel_irn(docname, irn, reason, remark) \ No newline at end of file From 988fd97625b41882d3797613f69717868b673090 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 18:50:15 +0530 Subject: [PATCH 073/137] fix: show/hide eway bill fields --- erpnext/patches.txt | 2 +- erpnext/patches/v12_0/setup_einvoice_fields.py | 10 +++++++++- erpnext/regional/india/setup.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 67dc8e38e95..d08a9283a08 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -678,4 +678,4 @@ erpnext.patches.v12_0.update_state_code_for_daman_and_diu erpnext.patches.v12_0.rename_lost_reason_detail erpnext.patches.v12_0.update_leave_application_status erpnext.patches.v12_0.update_payment_entry_status -erpnext.patches.v12_0.setup_einvoice_fields +erpnext.patches.v12_0.setup_einvoice_fields #2020-11-04 diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index fbb313a9fe1..9d7cfc52eb4 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -29,4 +29,12 @@ def execute(): } create_custom_fields(custom_fields, update=True) add_permissions() - add_print_formats() \ No newline at end of file + add_print_formats() + + frappe.db.set_value('Custom Field', { 'fieldname': 'mode_of_transport' }, 'default', '') + frappe.db.set_value('Custom Field', { 'fieldname': 'distance' }, 'mandatory_depends_on', 'transporter') + frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'mandatory_depends_on', 'eval:doc.mode_of_transport == "Road"') + frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on', 'depends_on': 'eval:doc.mode_of_transport == "Road"') + frappe.db.set_value('Custom Field', { 'fieldname': 'gst_vehicle_type' }, 'mandatory_depends_on', 'eval:doc.mode_of_transport == "Road"') + frappe.db.set_value('Custom Field', { 'fieldname': 'lr_date' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') + frappe.db.set_value('Custom Field', { 'fieldname': 'lr_no' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 4f1ca5012d1..bb0d2ff8013 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -303,6 +303,7 @@ def make_custom_fields(update=True): 'label': 'Vehicle No', 'fieldtype': 'Data', 'insert_after': 'lr_no', + 'depends_on': 'eval:(doc.mode_of_transport === "Road")', 'print_hide': 1, 'translatable': 0 }, @@ -333,7 +334,6 @@ def make_custom_fields(update=True): 'label': 'Mode of Transport', 'fieldtype': 'Select', 'options': '\nRoad\nAir\nRail\nShip', - 'default': 'Road', 'insert_after': 'transporter_name', 'print_hide': 1, 'translatable': 0 From 3b62e05a5c81b0966637af1cb9fd23cc0e598b42 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 19:44:54 +0530 Subject: [PATCH 074/137] fix: valiations --- .../india/e_invoice/einv_item_template.json | 4 +- .../india/e_invoice/einv_validation.json | 448 +++++++++++------- erpnext/regional/india/e_invoice/utils.py | 48 +- 3 files changed, 317 insertions(+), 183 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json index 23afd013d56..78e56518dff 100644 --- a/erpnext/regional/india/e_invoice/einv_item_template.json +++ b/erpnext/regional/india/e_invoice/einv_item_template.json @@ -8,9 +8,9 @@ "Qty": "{item.qty}", "FreeQty": "{item.free_qty}", "UnitPrice": "{item.unit_rate}", - "TotAmt": "{item.total_amount}", + "TotAmt": "{item.gross_amount}", "Discount": "{item.discount_amount}", - "AssAmt": "{item.base_amount}", + "AssAmt": "{item.taxable_value}", "PrdSlNo": "{item.serial_no}", "GstRt": "{item.tax_rate}", "IgstAmt": "{item.igst_amount}", diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json index 3f0767b8be8..86290cfe524 100644 --- a/erpnext/regional/india/e_invoice/einv_validation.json +++ b/erpnext/regional/india/e_invoice/einv_validation.json @@ -2,12 +2,14 @@ "Version": { "type": "string", "minLength": 1, - "maxLength": 6 + "maxLength": 6, + "description": "Version of the schema" }, "Irn": { "type": "string", "minLength": 64, - "maxLength": 64 + "maxLength": 64, + "description": "Invoice Reference Number" }, "TranDtls": { "type": "object", @@ -16,31 +18,37 @@ "type": "string", "minLength": 3, "maxLength": 10, - "enum": ["GST"] + "enum": ["GST"], + "description": "GST- Goods and Services Tax Scheme" }, "SupTyp": { "type": "string", "minLength": 3, "maxLength": 10, - "enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"] + "enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"], + "description": "Type of Supply: B2B-Business to Business, SEZWP - SEZ with payment, SEZWOP - SEZ without payment, EXPWP - Export with Payment, EXPWOP - Export without payment,DEXP - Deemed Export" }, "RegRev": { "type": "string", "minLength": 1, "maxLength": 1, - "enum": ["Y", "N"] + "enum": ["Y", "N"], + "description": "Y- whether the tax liability is payable under reverse charge" }, "EcmGstin": { "type": "string", "minLength": 15, "maxLength": 15, - "pattern": "([0-9]{2}[0-9A-Z]{13})" + "pattern": "([0-9]{2}[0-9A-Z]{13})", + "description": "E-Commerce GSTIN", + "validationMsg": "E-Commerce GSTIN is invalid" }, "IgstOnIntra": { "type": "string", "minLength": 1, "maxLength": 1, - "enum": ["Y", "N"] + "enum": ["Y", "N"], + "description": "Y- indicates the supply is intra state but chargeable to IGST" } }, "required": ["TaxSch", "SupTyp"] @@ -52,22 +60,23 @@ "type": "string", "minLength": 3, "maxLength": 3, - "enum": ["INV", "CRN", "DBN"] + "enum": ["INV", "CRN", "DBN"], + "description": "Document Type" }, "No": { "type": "string", "minLength": 1, "maxLength": 16, "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$", - "label": "Document Name", - "validationMsg": "Document name should not be starting with 0, / and -" + "description": "Document Number", + "validationMsg": "Document Number should not be starting with 0, / and -" }, "Dt": { "type": "string", "minLength": 10, "maxLength": 10, "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", - "validationMsg": "Document Date is invalid" + "description": "Document Date" } }, "required": ["Typ", "No", "Dt"] @@ -80,52 +89,62 @@ "minLength": 15, "maxLength": 15, "pattern": "([0-9]{2}[0-9A-Z]{13})", - "validationMsg": "Seller GSTIN is invalid" + "description": "Supplier GSTIN", + "validationMsg": "Company GSTIN is invalid" }, "LglNm": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Legal Name" }, "TrdNm": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Tradename" }, "Addr1": { "type": "string", - "minLength": 3, - "maxLength": 100 + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" }, "Addr2": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Address Line 2" }, "Loc": { "type": "string", "minLength": 3, - "maxLength": 50 + "maxLength": 50, + "description": "Location" }, "Pin": { "type": "number", "minimum": 100000, - "maximum": 999999 + "maximum": 999999, + "description": "Pincode" }, "Stcd": { "type": "string", "minLength": 1, - "maxLength": 2 + "maxLength": 2, + "description": "Supplier State Code" }, "Ph": { "type": "string", "minLength": 6, - "maxLength": 12 + "maxLength": 12, + "description": "Phone" }, "Em": { "type": "string", "minLength": 6, - "maxLength": 100 + "maxLength": 100, + "description": "Email-Id" } }, "required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"] @@ -138,57 +157,68 @@ "minLength": 3, "maxLength": 15, "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$", - "validationMsg": "Buyer GSTIN is invalid" + "description": "Buyer GSTIN", + "validationMsg": "Customer GSTIN is invalid" }, "LglNm": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Legal Name" }, "TrdNm": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Trade Name" }, "Pos": { "type": "string", "minLength": 1, - "maxLength": 2 + "maxLength": 2, + "description": "Place of Supply State code" }, "Addr1": { "type": "string", - "minLength": 3, - "maxLength": 100 + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" }, "Addr2": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Address Line 2" }, "Loc": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Location" }, "Pin": { "type": "number", "minimum": 100000, - "maximum": 999999 + "maximum": 999999, + "description": "Pincode" }, "Stcd": { "type": "string", "minLength": 1, - "maxLength": 2 + "maxLength": 2, + "description": "Buyer State Code" }, "Ph": { "type": "string", "minLength": 6, - "maxLength": 12 + "maxLength": 12, + "description": "Phone" }, "Em": { "type": "string", "minLength": 6, - "maxLength": 100 + "maxLength": 100, + "description": "Email-Id" } }, "required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"] @@ -199,32 +229,38 @@ "Nm": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Dispatch Address Name" }, "Addr1": { "type": "string", - "minLength": 3, - "maxLength": 100 + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" }, "Addr2": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Address Line 2" }, "Loc": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Location" }, "Pin": { "type": "number", "minimum": 100000, - "maximum": 999999 + "maximum": 999999, + "description": "Pincode" }, "Stcd": { "type": "string", "minLength": 1, - "maxLength": 2 + "maxLength": 2, + "description": "State Code" } }, "required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"] @@ -237,42 +273,50 @@ "maxLength": 15, "minLength": 3, "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$", + "description": "Shipping Address GSTIN", "validationMsg": "Shipping Address GSTIN is invalid" }, "LglNm": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Legal Name" }, "TrdNm": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Trade Name" }, "Addr1": { "type": "string", - "minLength": 3, - "maxLength": 100 + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" }, "Addr2": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Address Line 2" }, "Loc": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Location" }, "Pin": { "type": "number", "minimum": 100000, - "maximum": 999999 + "maximum": 999999, + "description": "Pincode" }, "Stcd": { "type": "string", "minLength": 1, - "maxLength": 2 + "maxLength": 2, + "description": "State Code" } }, "required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"] @@ -283,143 +327,171 @@ "SlNo": { "type": "string", "minLength": 1, - "maxLength": 6 + "maxLength": 6, + "description": "Serial No. of Item" }, "PrdDesc": { "type": "string", "minLength": 3, - "maxLength": 300 + "maxLength": 300, + "description": "Item Name" }, "IsServc": { "type": "string", "minLength": 1, "maxLength": 1, - "enum": ["Y", "N"] + "enum": ["Y", "N"], + "description": "Is Service Item" }, "HsnCd": { "type": "string", "minLength": 4, - "maxLength": 8 + "maxLength": 8, + "description": "HSN Code" }, "Barcde": { "type": "string", "minLength": 3, - "maxLength": 30 + "maxLength": 30, + "description": "Barcode" }, "Qty": { "type": "number", "minimum": 0, - "maximum": 9999999999.999 + "maximum": 9999999999.999, + "description": "Quantity" }, "FreeQty": { "type": "number", "minimum": 0, - "maximum": 9999999999.999 + "maximum": 9999999999.999, + "description": "Free Quantity" }, "Unit": { "type": "string", "minLength": 3, - "maxLength": 8 + "maxLength": 8, + "description": "UOM" }, "UnitPrice": { "type": "number", "minimum": 0, - "maximum": 999999999999.999 + "maximum": 999999999999.999, + "description": "Rate" }, "TotAmt": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "Gross Amount" }, "Discount": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "Discount" }, "PreTaxVal": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "Pre tax value" }, "AssAmt": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "Taxable Value" }, "GstRt": { "type": "number", "minimum": 0, - "maximum": 999.999 + "maximum": 999.999, + "description": "GST Rate" }, "IgstAmt": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "IGST Amount" }, "CgstAmt": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "CGST Amount" }, "SgstAmt": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "SGST Amount" }, "CesRt": { "type": "number", "minimum": 0, - "maximum": 999.999 + "maximum": 999.999, + "description": "Cess Rate" }, "CesAmt": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "Cess Amount (Advalorem)" }, "CesNonAdvlAmt": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "Cess Amount (Non-Advalorem)" }, "StateCesRt": { "type": "number", "minimum": 0, - "maximum": 999.999 + "maximum": 999.999, + "description": "State CESS Rate" }, "StateCesAmt": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "State CESS Amount" }, "StateCesNonAdvlAmt": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "State CESS Amount (Non Advalorem)" }, "OthChrg": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "Other Charges" }, "TotItemVal": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "Total Item Value" }, "OrdLineRef": { "type": "string", "minLength": 1, - "maxLength": 50 + "maxLength": 50, + "description": "Order line reference" }, "OrgCntry": { "type": "string", "minLength": 2, - "maxLength": 2 + "maxLength": 2, + "description": "Origin Country" }, "PrdSlNo": { "type": "string", "minLength": 1, - "maxLength": 20 + "maxLength": 20, + "description": "Serial number" }, "BchDtls": { "type": "object", @@ -427,21 +499,22 @@ "Nm": { "type": "string", "minLength": 3, - "maxLength": 20 + "maxLength": 20, + "description": "Batch number" }, "ExpDt": { "type": "string", "maxLength": 10, "minLength": 10, "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", - "validationMsg": "Expiry Date is invalid" + "description": "Batch Expiry Date" }, "WrDt": { "type": "string", "maxLength": 10, "minLength": 10, "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", - "validationMsg": "Warranty Date is invalid" + "description": "Warranty Date" } }, "required": ["Nm"] @@ -454,12 +527,14 @@ "Nm": { "type": "string", "minLength": 1, - "maxLength": 100 + "maxLength": 100, + "description": "Attribute name of the item" }, "Val": { "type": "string", "minLength": 1, - "maxLength": 100 + "maxLength": 100, + "description": "Attribute value of the item" } } } @@ -482,57 +557,68 @@ "AssVal": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Total Assessable value of all items" }, "CgstVal": { "type": "number", "maximum": 99999999999999.99, - "minimum": 0 + "minimum": 0, + "description": "Total CGST value of all items" }, "SgstVal": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Total SGST value of all items" }, "IgstVal": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Total IGST value of all items" }, "CesVal": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Total CESS value of all items" }, "StCesVal": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Total State CESS value of all items" }, "Discount": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Invoice Discount" }, "OthChrg": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Other Charges" }, "RndOffAmt": { "type": "number", - "minimum": 0, - "maximum": 99.99 + "minimum": -99.99, + "maximum": 99.99, + "description": "Rounded off Amount" }, "TotInvVal": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Final Invoice Value " }, "TotInvValFc": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Final Invoice value in Foreign Currency" } }, "required": ["AssVal", "TotInvVal"] @@ -543,57 +629,68 @@ "Nm": { "type": "string", "minLength": 1, - "maxLength": 100 + "maxLength": 100, + "description": "Payee Name" }, "AccDet": { "type": "string", "minLength": 1, - "maxLength": 18 + "maxLength": 18, + "description": "Bank Account Number of Payee" }, "Mode": { "type": "string", "minLength": 1, - "maxLength": 18 + "maxLength": 18, + "description": "Mode of Payment" }, "FinInsBr": { "type": "string", "minLength": 1, - "maxLength": 11 + "maxLength": 11, + "description": "Branch or IFSC code" }, "PayTerm": { "type": "string", "minLength": 1, - "maxLength": 100 + "maxLength": 100, + "description": "Terms of Payment" }, "PayInstr": { "type": "string", "minLength": 1, - "maxLength": 100 + "maxLength": 100, + "description": "Payment Instruction" }, "CrTrn": { "type": "string", "minLength": 1, - "maxLength": 100 + "maxLength": 100, + "description": "Credit Transfer" }, "DirDr": { "type": "string", "minLength": 1, - "maxLength": 100 + "maxLength": 100, + "description": "Direct Debit" }, "CrDay": { "type": "number", "minimum": 0, - "maximum": 9999 + "maximum": 9999, + "description": "Credit Days" }, "PaidAmt": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Advance Amount" }, "PaymtDue": { "type": "number", "minimum": 0, - "maximum": 99999999999999.99 + "maximum": 99999999999999.99, + "description": "Outstanding Amount" } } }, @@ -604,7 +701,8 @@ "type": "string", "maxLength": 100, "minLength": 3, - "pattern": "^[0-9A-Za-z/-]{3,100}$" + "pattern": "^[0-9A-Za-z/-]{3,100}$", + "description": "Remarks/Note" }, "DocPerdDtls": { "type": "object", @@ -613,16 +711,18 @@ "type": "string", "maxLength": 10, "minLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Invoice Period Start Date" }, "InvEndDt": { "type": "string", "maxLength": 10, "minLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Invoice Period End Date" } }, - "required": ["InvStDt", "InvEndDt"] + "required": ["InvStDt ", "InvEndDt "] }, "PrecDocDtls": { "type": "object", @@ -631,22 +731,25 @@ "type": "string", "minLength": 1, "maxLength": 16, - "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$" + "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$", + "description": "Reference of Original Invoice" }, "InvDt": { "type": "string", "maxLength": 10, "minLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Date of Orginal Invoice" }, "OthRefNo": { "type": "string", "minLength": 1, - "maxLength": 20 + "maxLength": 20, + "description": "Other Reference" } - }, - "required": ["InvNo", "InvDt"] + } }, + "required": ["InvNo", "InvDt"], "ContrDtls": { "type": "object", "properties": { @@ -654,117 +757,132 @@ "type": "string", "minLength": 1, "maxLength": 20, - "pattern": "^([0-9A-Za-z/-]){1,20}$" + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Receipt Advice No." }, "RecAdvDt": { "type": "string", "minLength": 10, "maxLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Date of receipt advice" }, "TendRefr": { "type": "string", "minLength": 1, "maxLength": 20, - "pattern": "^([0-9A-Za-z/-]){1,20}$" + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Lot/Batch Reference No." }, "ContrRefr": { "type": "string", "minLength": 1, "maxLength": 20, - "pattern": "^([0-9A-Za-z/-]){1,20}$" + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Contract Reference Number" }, "ExtRefr": { "type": "string", "minLength": 1, "maxLength": 20, - "pattern": "^([0-9A-Za-z/-]){1,20}$" + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Any other reference" }, "ProjRefr": { "type": "string", "minLength": 1, "maxLength": 20, - "pattern": "^([0-9A-Za-z/-]){1,20}$" + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Project Reference Number" }, "PORefr": { "type": "string", "minLength": 1, "maxLength": 16, - "pattern": "^([0-9A-Za-z/-]){1,16}$" + "pattern": "^([0-9A-Za-z/-]){1,16}$", + "description": "PO Reference Number" }, "PORefDt": { "type": "string", "minLength": 10, "maxLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "PO Reference date" } } } - }, - "required": ["InvStDt", "InvEndDt"] - }, - "AddlDocDtls": { - "type": "Array", - "AddlDocument": { - "type": "object", - "properties": { - "Url": { - "type": "string", - "minLength": 3, - "maxLength": 100 - }, - "Docs": { - "type": "string", - "minLength": 3, - "maxLength": 1000 - }, - "Info": { - "type": "string", - "minLength": 3, - "maxLength": 1000 - } - } } }, + "AddlDocDtls": { + "type": "Array", + "properties": { + "Url": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Supporting document URL" + }, + "Docs": { + "type": "string", + "minLength": 3, + "maxLength": 1000, + "description": "Supporting document in Base64 Format" + }, + "Info": { + "type": "string", + "minLength": 3, + "maxLength": 1000, + "description": "Any additional information" + } + } + }, + "ExpDtls": { "type": "object", "properties": { "ShipBNo": { "type": "string", "minLength": 1, - "maxLength": 20 + "maxLength": 20, + "description": "Shipping Bill No." }, "ShipBDt": { "type": "string", "minLength": 10, "maxLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Shipping Bill Date" }, "Port": { "type": "string", "minLength": 2, "maxLength": 10, - "pattern": "^[0-9A-Za-z]{2,10}$" + "pattern": "^[0-9A-Za-z]{2,10}$", + "description": "Port Code. Refer the master" }, "RefClm": { "type": "string", "minLength": 1, - "maxLength": 1 + "maxLength": 1, + "description": "Claiming Refund. Y/N" }, "ForCur": { "type": "string", "minLength": 3, - "maxLength": 16 + "maxLength": 16, + "description": "Additional Currency Code. Refer the master" }, "CntCode": { "type": "string", "minLength": 2, - "maxLength": 2 + "maxLength": 2, + "description": "Country Code. Refer the master" }, "ExpDuty": { "type": "number", "minimum": 0, - "maximum": 999999999999.99 + "maximum": 999999999999.99, + "description": "Export Duty" } } }, @@ -774,46 +892,54 @@ "TransId": { "type": "string", "minLength": 15, - "maxLength": 15 + "maxLength": 15, + "description": "Transporter GSTIN" }, "TransName": { "type": "string", "minLength": 3, - "maxLength": 100 + "maxLength": 100, + "description": "Transporter Name" }, "TransMode": { "type": "string", "maxLength": 1, "minLength": 1, - "enum": ["1", 2, 3, 4] + "enum": ["1", "2", "3", "4"], + "description": "Mode of Transport" }, "Distance": { "type": "number", "minimum": 1, - "maximum": 9999 + "maximum": 9999, + "description": "Distance" }, "TransDocNo": { "type": "string", "minLength": 1, "maxLength": 15, - "pattern": "^([0-9A-Z/-]){1,15}$" + "pattern": "^([0-9A-Z/-]){1,15}$", + "description": "Tranport Document Number" }, "TransDocDt": { "type": "string", "minLength": 10, "maxLength": 10, - "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]" + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Transport Document Date" }, "VehNo": { "type": "string", "minLength": 4, - "maxLength": 20 + "maxLength": 20, + "description": "Vehicle Number" }, "VehType": { "type": "string", "minLength": 1, "maxLength": 1, - "enum": ["O", "R"] + "enum": ["O", "R"], + "description": "Vehicle Type" } }, "required": ["Distance"] diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 8e2b154ffd0..11284852484 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -60,10 +60,16 @@ def get_trans_details(invoice): elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP' if not supply_type: - return _('Invalid invoice transaction category.') + rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export') + frappe.throw( + _('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export), + title=_('Invalid Supply Type') + ) return frappe._dict(dict( - tax_scheme='GST', supply_type=supply_type, reverse_charge=invoice.reverse_charge + tax_scheme='GST', + supply_type=supply_type, + reverse_charge=invoice.reverse_charge )) def get_doc_details(invoice): @@ -75,7 +81,11 @@ def get_doc_details(invoice): invoice_name = invoice.name invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') - return frappe._dict(dict(invoice_type=invoice_type, invoice_name=invoice_name, invoice_date=invoice_date)) + return frappe._dict(dict( + invoice_type=invoice_type, + invoice_name=invoice_name, + invoice_date=invoice_date + )) def get_party_details(address_name): address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] @@ -127,16 +137,11 @@ def get_item_list(invoice): item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None item.qty = abs(item.qty) item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate) - item.total_amount = abs(item.unit_rate * item.qty) + item.gross_amount = abs(item.unit_rate * item.qty) item.discount_amount = abs(item.discount_amount * item.qty) - item.base_amount = abs(item.base_net_amount) - item.tax_rate = 0 - item.igst_amount = 0 - item.cgst_amount = 0 - item.sgst_amount = 0 - item.cess_rate = 0 - item.cess_amount = 0 - item.other_charges = 0 + item.taxable_value = abs(item.base_net_amount) + item.tax_rate = item.cess_rate = item.other_charges = 0 + item.cgst_amount = item.sgst_amount = item.igst_amount = item.cess_amount = 0 for t in invoice.taxes: item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) if t.account_head in gst_accounts_list: @@ -210,8 +215,8 @@ def get_return_doc_reference(invoice): )) def get_eway_bill_details(invoice): - if not invoice.distance: - frappe.throw(_('Distance is mandatory for E-Way Bill generation'), title=_('E Invoice Validation Failed')) + if invoice.is_return: + frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed')) mode_of_transport = { 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } @@ -220,7 +225,7 @@ def get_eway_bill_details(invoice): gstin=invoice.gst_transporter_id, name=invoice.transporter_name, mode_of_transport=mode_of_transport[invoice.mode_of_transport], - distance=invoice.distance, + distance=invoice.distance or 0, document_name=invoice.lr_no, document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'), vehicle_no=invoice.vehicle_no, @@ -232,6 +237,7 @@ def make_einvoice(doctype, name): invoice = frappe.get_doc(doctype, name) schema = read_json('einv_template') + trans_details = get_trans_details(invoice) item_list = get_item_list(invoice) doc_details = get_doc_details(invoice) value_details = get_value_details(invoice) @@ -272,7 +278,7 @@ def make_einvoice(doctype, name): einvoice = json.loads(einvoice) validations = json.loads(read_json('einv_validation')) - errors = validate_einvoice(validations, einvoice, []) + errors = validate_einvoice(validations, einvoice) if errors: frappe.log_error(title="E Invoice Validation Failed", message=json.dumps(errors, default=str, indent=4)) if len(errors) > 1: @@ -312,7 +318,9 @@ def validate_einvoice(validations, einvoice, errors=[]): if value_type == 'string': einvoice[fieldname] = str(value) elif value_type == 'number': - einvoice[fieldname] = flt(value, 2) if fieldname not in ['Pin', 'Distance'] else int(value) + is_integer = '.' not in str(field_validation.get('maximum')) + einvoice[fieldname] = flt(value, 2) if not is_integer else cint(value) + value = einvoice[fieldname] max_length = field_validation.get('maxLength') minimum = flt(field_validation.get('minimum')) @@ -320,12 +328,12 @@ def validate_einvoice(validations, einvoice, errors=[]): pattern_str = field_validation.get('pattern') pattern = re.compile(pattern_str or '') - label = field_validation.get('label') or fieldname + label = field_validation.get('description') or fieldname if value_type == 'string' and len(value) > max_length: errors.append(_('{} should not exceed {} characters').format(label, max_length)) - if value_type == 'number' and not (flt(value) <= maximum): - errors.append(_('{} should be less than {}').format(label, maximum)) + if value_type == 'number' and (value > maximum or value < minimum): + errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum)) if pattern_str and not pattern.match(value): errors.append(field_validation.get('validationMsg')) From 2e1f56e117c440ca94e907e80deb5ca1ad4d16a3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 22:17:26 +0530 Subject: [PATCH 075/137] feat: generate eway bill from IRN --- .../patches/v12_0/setup_einvoice_fields.py | 3 +- erpnext/regional/india/e_invoice/einvoice.js | 120 +++++++++++++++++- erpnext/regional/india/e_invoice/utils.py | 56 +++++++- 3 files changed, 171 insertions(+), 8 deletions(-) diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index 9d7cfc52eb4..b72248c838d 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -37,4 +37,5 @@ def execute(): frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on', 'depends_on': 'eval:doc.mode_of_transport == "Road"') frappe.db.set_value('Custom Field', { 'fieldname': 'gst_vehicle_type' }, 'mandatory_depends_on', 'eval:doc.mode_of_transport == "Road"') frappe.db.set_value('Custom Field', { 'fieldname': 'lr_date' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') - frappe.db.set_value('Custom Field', { 'fieldname': 'lr_no' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') \ No newline at end of file + frappe.db.set_value('Custom Field', { 'fieldname': 'lr_no' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') + frappe.db.set_value('Custom Field', { 'fieldname': 'ewaybill' }, 'read_only_depends_on', 'eval:doc.irn && doc.ewaybill') \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 464b3b2e880..d70a8ef089e 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -7,7 +7,7 @@ erpnext.setup_einvoice_actions = (doctype) => { if (!einvoicing_enabled || !valid_supply_type) return; - const { docstatus, irn, irn_cancelled, ewaybill, eway_bill_cancelled, doctype, name, __unsaved } = frm.doc; + const { docstatus, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; if (docstatus == 0 && !irn && !__unsaved) { const action = () => { @@ -22,7 +22,7 @@ erpnext.setup_einvoice_actions = (doctype) => { frm.add_custom_button(__("Generate IRN"), action, __('E Invoicing')); } - if (docstatus == 1 && irn && !irn_cancelled) { + if (docstatus == 1 && irn && !irn_cancelled && !ewaybill) { const fields = [ { "label" : "Reason", @@ -65,7 +65,118 @@ erpnext.setup_einvoice_actions = (doctype) => { frm.add_custom_button(__("Cancel IRN"), action, __("E Invoicing")); } - if (docstatus == 1 && irn && !irn_cancelled && !eway_bill_cancelled) { + if (irn && !irn_cancelled && !ewaybill) { + const fields = [ + { + 'fieldname': 'transporter', + 'label': 'Transporter', + 'fieldtype': 'Link', + 'options': 'Supplier', + 'default': frm.doc.transporter + }, + { + 'fieldname': 'gst_transporter_id', + 'label': 'GST Transporter ID', + 'fieldtype': 'Data', + 'fetch_from': 'transporter.gst_transporter_id', + 'default': frm.doc.gst_transporter_id + }, + { + 'fieldname': 'driver', + 'label': 'Driver', + 'fieldtype': 'Link', + 'options': 'Driver', + 'default': frm.doc.driver + }, + { + 'fieldname': 'lr_no', + 'label': 'Transport Receipt No', + 'fieldtype': 'Data', + 'default': frm.doc.lr_no + }, + { + 'fieldname': 'vehicle_no', + 'label': 'Vehicle No', + 'fieldtype': 'Data', + 'depends_on': 'eval:(doc.mode_of_transport === "Road")', + 'default': frm.doc.vehicle_no + }, + { + 'fieldname': 'distance', + 'label': 'Distance (in km)', + 'fieldtype': 'Float', + 'default': frm.doc.distance + }, + { + 'fieldname': 'transporter_col_break', + 'fieldtype': 'Column Break', + }, + { + 'fieldname': 'transporter_name', + 'label': 'Transporter Name', + 'fieldtype': 'Data', + 'fetch_from': 'transporter.name', + 'read_only': 1, + 'default': frm.doc.transporter_name + }, + { + 'fieldname': 'mode_of_transport', + 'label': 'Mode of Transport', + 'fieldtype': 'Select', + 'options': `\nRoad\nAir\nRail\nShip`, + 'default': frm.doc.mode_of_transport + }, + { + 'fieldname': 'driver_name', + 'label': 'Driver Name', + 'fieldtype': 'Data', + 'fetch_from': 'driver.full_name', + 'read_only': 1, + 'default': frm.doc.driver_name + }, + { + 'fieldname': 'lr_date', + 'label': 'Transport Receipt Date', + 'fieldtype': 'Date', + 'default': frm.doc.lr_date + }, + { + 'fieldname': 'gst_vehicle_type', + 'label': 'GST Vehicle Type', + 'fieldtype': 'Select', + 'options': `Regular\nOver Dimensional Cargo (ODC)`, + 'depends_on': 'eval:(doc.mode_of_transport === "Road")', + 'default': frm.doc.gst_vehicle_type + } + ] + + const action = () => { + const d = new frappe.ui.Dialog({ + title: __('Generate E-Way Bill'), + wide: 1, + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_eway_bill', + args: { + docname: name, irn, + ...data + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }) + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + + frm.add_custom_button(__("Generate E-Way Bill"), action, __("E Invoicing")); + } + + if (docstatus == 1 && irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { const fields = [ { "label" : "Reason", @@ -91,8 +202,7 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', args: { - doctype: doctype, - name: name, + docname: name, eway_bill: ewaybill, reason: data.reason.split('-')[0], remark: data.remark diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 11284852484..c4da7b2585f 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -218,7 +218,7 @@ def get_eway_bill_details(invoice): if invoice.is_return: frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed')) - mode_of_transport = { 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } + mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } return frappe._dict(dict( @@ -381,6 +381,7 @@ class GSPConnector(): self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice' 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' def get_auth_token(self): if time_diff_in_seconds(self.credentials.token_expiry, now_datetime()) < 150.0: @@ -422,6 +423,8 @@ class GSPConnector(): res = make_get_request(self.gstin_details_url + params, headers=headers) if res.get('success'): return res.get('result') + else: + self.log_error(res) except Exception as e: self.log_error(e) @@ -483,6 +486,42 @@ class GSPConnector(): if res.get('success'): frappe.db.set_value(doctype, docname, 'irn_cancelled', 1) # frappe.db.set_value(doctype, docname, 'cancelled_on', res.get('CancelDate')) + else: + self.log_error(res) + + except Exception as e: + self.log_error(e) + + def generate_eway_bill(self, **kwargs): + args = frappe._dict(kwargs) + + headers = self.get_headers() + doctype = 'Sales Invoice' + docname = args.docname + eway_bill_details = get_eway_bill_details(args) + data = json.dumps({ + "Irn": args.irn, + "Distance": cint(eway_bill_details.distance), + "TransMode": eway_bill_details.mode_of_transport, + "TransId": eway_bill_details.gstin, + "TransName": eway_bill_details.transporter, + "TrnDocDt": eway_bill_details.document_date, + "TrnDocNo": eway_bill_details.document_name, + "VehNo": eway_bill_details.vehicle_no, + "VehType": eway_bill_details.vehicle_type + }) + + try: + res = make_post_request(self.generate_ewaybill_url, headers=headers, data=data) + if res.get('success'): + frappe.db.set_value(doctype, docname, 'ewaybill', res.get('result').get('EwbNo')) + frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 0) + for d in args: + if d in ['docname', 'cmd']: continue + # update eway bill details in sales invoice + frappe.db.set_value(doctype, docname, d, args[d]) + else: + self.log_error(res) except Exception as e: self.log_error(e) @@ -502,6 +541,9 @@ class GSPConnector(): frappe.db.set_value(doctype, docname, 'ewaybill', '') frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) + else: + self.log_error(res) + except Exception as e: self.log_error(e) @@ -516,4 +558,14 @@ def generate_irn(docname): @frappe.whitelist() def cancel_irn(docname, irn, reason, remark): gsp_connector = GSPConnector() - gsp_connector.cancel_irn(docname, irn, reason, remark) \ No newline at end of file + gsp_connector.cancel_irn(docname, irn, reason, remark) + +@frappe.whitelist() +def generate_eway_bill(**kwargs): + gsp_connector = GSPConnector() + gsp_connector.generate_eway_bill(**kwargs) + +@frappe.whitelist() +def cancel_eway_bill(docname, eway_bill, reason, remark): + gsp_connector = GSPConnector() + gsp_connector.cancel_eway_bill(docname, eway_bill, reason, remark) \ No newline at end of file From fb65644026bddb1b6381c53ec33ceaeb0b84cdd3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 22:22:59 +0530 Subject: [PATCH 076/137] chore: remove unwanted imports --- erpnext/regional/india/e_invoice/utils.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index c4da7b2585f..168ee38587f 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -9,16 +9,12 @@ import jwt import json import base64 import frappe -from six import string_types -from Crypto.PublicKey import RSA +from frappe import _, bold from pyqrcode import create as qrcreate -from Crypto.Cipher import PKCS1_v1_5, AES -from Crypto.Util.Padding import pad, unpad from frappe.model.document import Document -from frappe import _, get_module_path, scrub, bold from frappe.integrations.utils import make_post_request, make_get_request from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply -from frappe.utils.data import get_datetime, cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime +from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime def validate_einvoice_fields(doc): einvoicing_enabled = frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable') From 4965af2321d603a81388f270fd4e2e53b6040a2c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 10:49:21 +0530 Subject: [PATCH 077/137] chore: error logging --- erpnext/regional/india/e_invoice/utils.py | 28 ++++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 168ee38587f..bda0cd8ef5c 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -237,8 +237,8 @@ def make_einvoice(doctype, name): item_list = get_item_list(invoice) doc_details = get_doc_details(invoice) value_details = get_value_details(invoice) - trans_details = get_trans_details(invoice) seller_details = get_party_details(invoice.company_address) + seller_details.update({ 'pincode': 193502 }) if invoice.gst_category == 'Overseas': buyer_details = get_overseas_address_details(invoice.customer_address) @@ -496,15 +496,15 @@ class GSPConnector(): docname = args.docname eway_bill_details = get_eway_bill_details(args) data = json.dumps({ - "Irn": args.irn, - "Distance": cint(eway_bill_details.distance), - "TransMode": eway_bill_details.mode_of_transport, - "TransId": eway_bill_details.gstin, - "TransName": eway_bill_details.transporter, - "TrnDocDt": eway_bill_details.document_date, - "TrnDocNo": eway_bill_details.document_name, - "VehNo": eway_bill_details.vehicle_no, - "VehType": eway_bill_details.vehicle_type + 'Irn': args.irn, + 'Distance': cint(eway_bill_details.distance), + 'TransMode': eway_bill_details.mode_of_transport, + 'TransId': eway_bill_details.gstin, + 'TransName': eway_bill_details.transporter, + 'TrnDocDt': eway_bill_details.document_date, + 'TrnDocNo': eway_bill_details.document_name, + 'VehNo': eway_bill_details.vehicle_no, + 'VehType': eway_bill_details.vehicle_type }) try: @@ -544,7 +544,13 @@ class GSPConnector(): self.log_error(e) def log_error(self, exc): - print(exc) + message = "\n".join(["Data:", json.dumps(exc), "--" * 50, "\nException:", traceback.format_exc()]) + frappe.log_error(title="E Invoicing Error", message=message) + link_to_error_list = '{1}'.format('Error Log', 'Error Log') + frappe.throw( + _('An error occurred while making API request. Please check {} for more information.').format(link_to_error_list), + title=_('E Invoice Request Failed') + ) @frappe.whitelist() def generate_irn(docname): From 88668019325797f13214d41b61638da907de6259 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 11:34:22 +0530 Subject: [PATCH 078/137] feat: header & footer in GST E Invoice --- .../gst_e_invoice/gst_e_invoice.html | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html index b4255038d39..9827e00b71b 100644 --- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -2,12 +2,27 @@ {%- set einvoice = json.loads(doc.signed_einvoice) -%}
    -
    +
    + {% if letter_head and not no_letterhead %} +
    {{ letter_head }}
    + {% endif %}
    -
    + {% if print_settings.repeat_header_footer %} + + {% endif %} +
    1. Transaction Details
    From 16e879bc1a7f96847b62b2d61c1cd6a3b6a818fa Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 11:35:34 +0530 Subject: [PATCH 079/137] chore: remove test pincode --- erpnext/regional/india/e_invoice/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index bda0cd8ef5c..6fbca2d90c0 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -238,7 +238,6 @@ def make_einvoice(doctype, name): doc_details = get_doc_details(invoice) value_details = get_value_details(invoice) seller_details = get_party_details(invoice.company_address) - seller_details.update({ 'pincode': 193502 }) if invoice.gst_category == 'Overseas': buyer_details = get_overseas_address_details(invoice.customer_address) @@ -462,7 +461,6 @@ class GSPConnector(): if res.get('success'): update_invoice(doctype, docname, res.get('result')) else: - # {'success': False, 'message': '3039 : Seller Details:Pincode-560009 does not belong to the state-1, 2177 : Invalid item unit code(s)-UNIT'} self.log_error(res) except Exception as e: @@ -536,7 +534,6 @@ class GSPConnector(): if res.get('success'): frappe.db.set_value(doctype, docname, 'ewaybill', '') frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) - else: self.log_error(res) From 61e6760a6beeee76cb9454e3329227ca7b740591 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 13:47:29 +0530 Subject: [PATCH 080/137] fix: invalid syntax --- erpnext/patches/v12_0/setup_einvoice_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index b72248c838d..908fc8f5852 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -34,7 +34,7 @@ def execute(): frappe.db.set_value('Custom Field', { 'fieldname': 'mode_of_transport' }, 'default', '') frappe.db.set_value('Custom Field', { 'fieldname': 'distance' }, 'mandatory_depends_on', 'transporter') frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'mandatory_depends_on', 'eval:doc.mode_of_transport == "Road"') - frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on', 'depends_on': 'eval:doc.mode_of_transport == "Road"') + frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on': 'eval:doc.mode_of_transport == "Road"') frappe.db.set_value('Custom Field', { 'fieldname': 'gst_vehicle_type' }, 'mandatory_depends_on', 'eval:doc.mode_of_transport == "Road"') frappe.db.set_value('Custom Field', { 'fieldname': 'lr_date' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') frappe.db.set_value('Custom Field', { 'fieldname': 'lr_no' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') From 85cc1d68f0347d27aa15d9d5941ca842e89a4a8b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 13:47:29 +0530 Subject: [PATCH 081/137] fix: invalid syntax --- erpnext/patches/v12_0/setup_einvoice_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index 908fc8f5852..eb889ba0beb 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -34,7 +34,7 @@ def execute(): frappe.db.set_value('Custom Field', { 'fieldname': 'mode_of_transport' }, 'default', '') frappe.db.set_value('Custom Field', { 'fieldname': 'distance' }, 'mandatory_depends_on', 'transporter') frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'mandatory_depends_on', 'eval:doc.mode_of_transport == "Road"') - frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on': 'eval:doc.mode_of_transport == "Road"') + frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on', 'eval:doc.mode_of_transport == "Road"') frappe.db.set_value('Custom Field', { 'fieldname': 'gst_vehicle_type' }, 'mandatory_depends_on', 'eval:doc.mode_of_transport == "Road"') frappe.db.set_value('Custom Field', { 'fieldname': 'lr_date' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') frappe.db.set_value('Custom Field', { 'fieldname': 'lr_no' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') From 27b68be854f948186d7b174e3bda16598318843a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 13:56:04 +0530 Subject: [PATCH 082/137] fix: patch --- erpnext/patches/v12_0/setup_einvoice_fields.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index eb889ba0beb..dacdc86af53 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -32,10 +32,4 @@ def execute(): add_print_formats() frappe.db.set_value('Custom Field', { 'fieldname': 'mode_of_transport' }, 'default', '') - frappe.db.set_value('Custom Field', { 'fieldname': 'distance' }, 'mandatory_depends_on', 'transporter') - frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'mandatory_depends_on', 'eval:doc.mode_of_transport == "Road"') - frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on', 'eval:doc.mode_of_transport == "Road"') - frappe.db.set_value('Custom Field', { 'fieldname': 'gst_vehicle_type' }, 'mandatory_depends_on', 'eval:doc.mode_of_transport == "Road"') - frappe.db.set_value('Custom Field', { 'fieldname': 'lr_date' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') - frappe.db.set_value('Custom Field', { 'fieldname': 'lr_no' }, 'mandatory_depends_on', 'eval:in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)') - frappe.db.set_value('Custom Field', { 'fieldname': 'ewaybill' }, 'read_only_depends_on', 'eval:doc.irn && doc.ewaybill') \ No newline at end of file + frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on', 'eval:doc.mode_of_transport == "Road"') \ No newline at end of file From eabaa16f1358b4c64b0f27e3ffdeef0a1a29bded Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 14:04:39 +0530 Subject: [PATCH 083/137] feat: cess non advolem on einvoice item --- erpnext/regional/india/e_invoice/utils.py | 25 ++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 6fbca2d90c0..1a5bae470d4 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -136,14 +136,23 @@ def get_item_list(invoice): item.gross_amount = abs(item.unit_rate * item.qty) item.discount_amount = abs(item.discount_amount * item.qty) item.taxable_value = abs(item.base_net_amount) - item.tax_rate = item.cess_rate = item.other_charges = 0 - item.cgst_amount = item.sgst_amount = item.igst_amount = item.cess_amount = 0 + + for attr in [ + 'tax_rate', 'cess_rate', 'cess_nadv_amount', + 'cgst_amount', 'sgst_amount', 'igst_amount', + 'cess_amount', 'cess_nadv_amount', 'other_charges' + ]: + item[attr] = 0 + for t in invoice.taxes: item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) if t.account_head in gst_accounts_list: if t.account_head in gst_accounts.cess_account: - item.cess_rate += item_tax_detail[0] - item.cess_amount += abs(item_tax_detail[1]) + if t.charge_type == 'On Item Quantity': + item.cess_nadv_amount += abs(item_tax_detail[1]) + else: + item.cess_rate += item_tax_detail[0] + item.cess_amount += abs(item_tax_detail[1]) elif t.account_head in gst_accounts.igst_account: item.tax_rate += item_tax_detail[0] item.igst_amount += abs(item_tax_detail[1]) @@ -156,7 +165,7 @@ def get_item_list(invoice): item.total_value = abs( item.base_amount + item.igst_amount + item.sgst_amount + - item.cgst_amount + item.cess_amount + item.other_charges + item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges ) einv_item = item_schema.format(item=item) item_list.append(einv_item) @@ -170,9 +179,11 @@ def get_value_details(invoice): value_details = frappe._dict(dict()) value_details.base_net_total = abs(invoice.base_net_total) value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount > 0 else 0 + # discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount < 0 else 0) - value_details.base_grand_total = abs(invoice.base_rounded_total) - value_details.grand_total = abs(invoice.rounded_total) + disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') + value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else doc.base_rounded_total + value_details.grand_total = abs(invoice.grand_total) if disable_rounded else doc.rounded_total value_details.total_cgst_amt = 0 value_details.total_sgst_amt = 0 value_details.total_igst_amt = 0 From 209aa447f759003ae48f8e983570af4099fb28f2 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 14:05:24 +0530 Subject: [PATCH 084/137] chore: remove fetch token from e invocie settings --- .../e_invoice_settings/e_invoice_settings.js | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js index bea16ffa7fd..9ea43e70150 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -1,26 +1,4 @@ // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('E Invoice Settings', { - refresh: function(frm) { - if (!frm.doc.enable) return; - frm.trigger("show_fetch_token_btn"); - }, - - show_fetch_token_btn(frm) { - const { token_expiry } = frm.doc; - const now = frappe.datetime.now_datetime(); - const expiry_in_mins = moment(token_expiry).diff(now, "minute"); - if (expiry_in_mins <= 1 || !token_expiry) { - frm.add_custom_button(__("Fetch Token"), - () => { - frm.call({ - method: 'erpnext.regional.india.e_invoice.utils.fetch_token', - freeze: true, - callback: () => frm.refresh() - }); - } - ); - } - } -}); +frappe.ui.form.on('E Invoice Settings', {}); From 4ea03c23a4ed3eebbef5d8195a558f1a64d56403 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 17:53:22 +0530 Subject: [PATCH 085/137] fix: import format_date --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 1a5bae470d4..73e9edbb8b9 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -14,7 +14,7 @@ from pyqrcode import create as qrcreate from frappe.model.document import Document from frappe.integrations.utils import make_post_request, make_get_request from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply -from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime +from frappe.utils.data import cstr, cint, formatdate as format_date, flt, time_diff_in_seconds, now_datetime def validate_einvoice_fields(doc): einvoicing_enabled = frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable') From 9f0d567ea523e85e4ca5630e905e3ab1c1360e2e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 19:43:47 +0530 Subject: [PATCH 086/137] fix: imports --- erpnext/regional/india/e_invoice/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 73e9edbb8b9..292dab132d2 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -9,12 +9,13 @@ import jwt import json import base64 import frappe +import traceback from frappe import _, bold from pyqrcode import create as qrcreate from frappe.model.document import Document from frappe.integrations.utils import make_post_request, make_get_request from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply -from frappe.utils.data import cstr, cint, formatdate as format_date, flt, time_diff_in_seconds, now_datetime +from frappe.utils.data import cstr, cint, formatdate as format_date, flt, time_diff_in_seconds, now_datetime, add_to_date def validate_einvoice_fields(doc): einvoicing_enabled = frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable') From ac603a895dad5b50db3a63a27223110dc31f3668 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 19:44:37 +0530 Subject: [PATCH 087/137] fix: error handling --- erpnext/regional/india/e_invoice/utils.py | 111 +++++++++++++++++----- 1 file changed, 88 insertions(+), 23 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 292dab132d2..54f18d37f00 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -183,8 +183,8 @@ def get_value_details(invoice): # discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount < 0 else 0) disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') - value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else doc.base_rounded_total - value_details.grand_total = abs(invoice.grand_total) if disable_rounded else doc.rounded_total + value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else invoice.base_rounded_total + value_details.grand_total = abs(invoice.grand_total) if disable_rounded else invoice.rounded_total value_details.total_cgst_amt = 0 value_details.total_sgst_amt = 0 value_details.total_igst_amt = 0 @@ -287,7 +287,12 @@ def make_einvoice(doctype, name): validations = json.loads(read_json('einv_validation')) errors = validate_einvoice(validations, einvoice) if errors: - frappe.log_error(title="E Invoice Validation Failed", message=json.dumps(errors, default=str, indent=4)) + message = "\n".join([ + "E Invoice: ", json.dumps(einvoice, default=str, indent=4), + "-" * 50, + "Errors: ", json.dumps(errors, default=str, indent=4) + ]) + frappe.log_error(title="E Invoice Validation Failed", message=message) if len(errors) > 1: li = ['
  • '+ d +'
  • ' for d in errors] frappe.throw("
      {}
    ".format(''.join(li)), title=_('E Invoice Validation Failed')) @@ -378,6 +383,8 @@ def attach_qrcode_image(doctype, docname, qrcode): frappe.db.set_value(doctype, docname, 'qrcode_image', _file.file_url) +class ResponseFailure(Exception): pass + class GSPConnector(): def __init__(self): self.credentials = frappe.get_cached_doc('E Invoice Settings') @@ -386,6 +393,7 @@ class GSPConnector(): self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token' self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin' self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice' + self.irn_details_url = self.base_url + 'test/enriched/ei/api/invoice/irn' 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' @@ -401,16 +409,16 @@ class GSPConnector(): 'gspappid': self.credentials.client_id, 'gspappsecret': self.credentials.client_secret } - + res = {} try: res = make_post_request(self.authenticate_url, headers=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() - except Exception as e: - self.log_error(e) - raise + except Exception: + self.log_error(res) + self.raise_error(True) def get_headers(self): return { @@ -432,11 +440,14 @@ class GSPConnector(): return res.get('result') else: self.log_error(res) - - except Exception as e: - self.log_error(e) + raise ResponseFailure - return {} + except ResponseFailure: + self.raise_error() + + except Exception: + self.log_error() + self.raise_error(True) @staticmethod def get_gstin_details(gstin): @@ -472,11 +483,43 @@ class GSPConnector(): res = make_post_request(self.generate_irn_url, headers=headers, data=data) if res.get('success'): update_invoice(doctype, docname, res.get('result')) + + elif '2150' in res.get('message'): + # IRN already generated + irn = res.get('result')[0].get('Desc').get('Irn') + irn_details = self.get_irn_details(irn) + if irn_details: + update_invoice(doctype, docname, irn_details) + else: self.log_error(res) + raise ResponseFailure + + except ResponseFailure: + self.raise_error() - except Exception as e: - self.log_error(e) + except Exception: + self.log_error(data) + self.raise_error(True) + + def get_irn_details(self, irn): + headers = self.get_headers() + + try: + params = '?irn={irn}'.format(irn=irn) + res = make_get_request(self.irn_details_url + params, headers=headers) + if res.get('success'): + return res.get('result') + else: + self.log_error(res) + raise ResponseFailure + + except ResponseFailure: + self.raise_error() + + except Exception: + self.log_error() + self.raise_error(True) def cancel_irn(self, docname, irn, reason, remark): headers = self.get_headers() @@ -494,9 +537,14 @@ class GSPConnector(): # frappe.db.set_value(doctype, docname, 'cancelled_on', res.get('CancelDate')) else: self.log_error(res) + raise ResponseFailure + + except ResponseFailure: + self.raise_error() - except Exception as e: - self.log_error(e) + except Exception: + self.log_error(data) + self.raise_error(True) def generate_eway_bill(self, **kwargs): args = frappe._dict(kwargs) @@ -528,9 +576,14 @@ class GSPConnector(): frappe.db.set_value(doctype, docname, d, args[d]) else: self.log_error(res) + raise ResponseFailure - except Exception as e: - self.log_error(e) + except ResponseFailure: + self.raise_error() + + except Exception: + self.log_error(data) + self.raise_error(True) def cancel_eway_bill(self, docname, eway_bill, reason, remark): headers = self.get_headers() @@ -548,17 +601,29 @@ class GSPConnector(): frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) else: self.log_error(res) + raise ResponseFailure - except Exception as e: - self.log_error(e) + except ResponseFailure: + self.raise_error() - def log_error(self, exc): - message = "\n".join(["Data:", json.dumps(exc), "--" * 50, "\nException:", traceback.format_exc()]) + except Exception: + self.log_error(data) + self.raise_error(True) + + def log_error(self, data={}): + 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()]) 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') - frappe.throw( + frappe.msgprint( _('An error occurred while making API request. Please check {} for more information.').format(link_to_error_list), - title=_('E Invoice Request Failed') + title=_('E Invoice Request Failed'), + raise_exception=raise_exception, + indicator='red' ) @frappe.whitelist() From 5d92e2481d2b7b266e64d33f8377fc1b5cfb4a38 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 21:00:38 +0530 Subject: [PATCH 088/137] fix: ewaybill could be modified after ewaybill generation --- erpnext/regional/india/e_invoice/einvoice.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index d70a8ef089e..62a2d2d5202 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -9,6 +9,10 @@ erpnext.setup_einvoice_actions = (doctype) => { const { docstatus, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; + if (ewaybill && irn) { + frm.set_df_property('ewaybill', 'read_only', 1); + } + if (docstatus == 0 && !irn && !__unsaved) { const action = () => { frappe.call({ From 5a56b63eedc73ebb3eddd5b34525768b5a63d00c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 21:15:19 +0530 Subject: [PATCH 089/137] feat: update timeline on einvoice actions --- .../patches/v12_0/setup_einvoice_fields.py | 3 +- erpnext/regional/india/e_invoice/einvoice.js | 196 ++++++++++-------- erpnext/regional/india/e_invoice/utils.py | 161 ++++++++------ 3 files changed, 199 insertions(+), 161 deletions(-) diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index dacdc86af53..cbad581ab2a 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -32,4 +32,5 @@ def execute(): add_print_formats() frappe.db.set_value('Custom Field', { 'fieldname': 'mode_of_transport' }, 'default', '') - frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on', 'eval:doc.mode_of_transport == "Road"') \ No newline at end of file + frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on', 'eval:doc.mode_of_transport == "Road"') + frappe.db.set_value('Custom Field', { 'fieldname': 'ewaybill' }, 'depends_on', 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)') \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 62a2d2d5202..d4c473363d0 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -7,7 +7,13 @@ erpnext.setup_einvoice_actions = (doctype) => { if (!einvoicing_enabled || !valid_supply_type) return; - const { docstatus, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; + const { doctype, docstatus, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; + + const add_custom_button = (label, action) => { + if (!frm.custom_buttons[label]) { + frm.add_custom_button(label, action, __('E Invoicing')); + } + } if (ewaybill && irn) { frm.set_df_property('ewaybill', 'read_only', 1); @@ -17,13 +23,13 @@ erpnext.setup_einvoice_actions = (doctype) => { const action = () => { frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.generate_irn', - args: { docname: name }, + args: { doctype, docname: name }, freeze: true, callback: () => frm.reload_doc() }) }; - frm.add_custom_button(__("Generate IRN"), action, __('E Invoicing')); + add_custom_button(__("Generate IRN"), action); } if (docstatus == 1 && irn && !irn_cancelled && !ewaybill) { @@ -52,6 +58,7 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.cancel_irn', args: { + doctype, docname: name, irn: irn, reason: data.reason.split('-')[0], @@ -66,105 +73,23 @@ erpnext.setup_einvoice_actions = (doctype) => { }); d.show(); }; - frm.add_custom_button(__("Cancel IRN"), action, __("E Invoicing")); + add_custom_button(__("Cancel IRN"), action); } if (irn && !irn_cancelled && !ewaybill) { - const fields = [ - { - 'fieldname': 'transporter', - 'label': 'Transporter', - 'fieldtype': 'Link', - 'options': 'Supplier', - 'default': frm.doc.transporter - }, - { - 'fieldname': 'gst_transporter_id', - 'label': 'GST Transporter ID', - 'fieldtype': 'Data', - 'fetch_from': 'transporter.gst_transporter_id', - 'default': frm.doc.gst_transporter_id - }, - { - 'fieldname': 'driver', - 'label': 'Driver', - 'fieldtype': 'Link', - 'options': 'Driver', - 'default': frm.doc.driver - }, - { - 'fieldname': 'lr_no', - 'label': 'Transport Receipt No', - 'fieldtype': 'Data', - 'default': frm.doc.lr_no - }, - { - 'fieldname': 'vehicle_no', - 'label': 'Vehicle No', - 'fieldtype': 'Data', - 'depends_on': 'eval:(doc.mode_of_transport === "Road")', - 'default': frm.doc.vehicle_no - }, - { - 'fieldname': 'distance', - 'label': 'Distance (in km)', - 'fieldtype': 'Float', - 'default': frm.doc.distance - }, - { - 'fieldname': 'transporter_col_break', - 'fieldtype': 'Column Break', - }, - { - 'fieldname': 'transporter_name', - 'label': 'Transporter Name', - 'fieldtype': 'Data', - 'fetch_from': 'transporter.name', - 'read_only': 1, - 'default': frm.doc.transporter_name - }, - { - 'fieldname': 'mode_of_transport', - 'label': 'Mode of Transport', - 'fieldtype': 'Select', - 'options': `\nRoad\nAir\nRail\nShip`, - 'default': frm.doc.mode_of_transport - }, - { - 'fieldname': 'driver_name', - 'label': 'Driver Name', - 'fieldtype': 'Data', - 'fetch_from': 'driver.full_name', - 'read_only': 1, - 'default': frm.doc.driver_name - }, - { - 'fieldname': 'lr_date', - 'label': 'Transport Receipt Date', - 'fieldtype': 'Date', - 'default': frm.doc.lr_date - }, - { - 'fieldname': 'gst_vehicle_type', - 'label': 'GST Vehicle Type', - 'fieldtype': 'Select', - 'options': `Regular\nOver Dimensional Cargo (ODC)`, - 'depends_on': 'eval:(doc.mode_of_transport === "Road")', - 'default': frm.doc.gst_vehicle_type - } - ] - const action = () => { const d = new frappe.ui.Dialog({ title: __('Generate E-Way Bill'), wide: 1, - fields: fields, + fields: get_ewaybill_fields(frm), primary_action: function() { const data = d.get_values(); frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.generate_eway_bill', args: { - docname: name, irn, + doctype, + docname: name, + irn, ...data }, freeze: true, @@ -177,7 +102,7 @@ erpnext.setup_einvoice_actions = (doctype) => { d.show(); }; - frm.add_custom_button(__("Generate E-Way Bill"), action, __("E Invoicing")); + add_custom_button(__("Generate E-Way Bill"), action); } if (docstatus == 1 && irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { @@ -206,6 +131,7 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', args: { + doctype, docname: name, eway_bill: ewaybill, reason: data.reason.split('-')[0], @@ -220,8 +146,94 @@ erpnext.setup_einvoice_actions = (doctype) => { }); d.show(); }; - frm.add_custom_button(__("Cancel E-Way Bill"), action, __("E Invoicing")); + add_custom_button(__("Cancel E-Way Bill"), action); } } }) +} + +const get_ewaybill_fields = (frm) => { + return [ + { + 'fieldname': 'transporter', + 'label': 'Transporter', + 'fieldtype': 'Link', + 'options': 'Supplier', + 'default': frm.doc.transporter + }, + { + 'fieldname': 'gst_transporter_id', + 'label': 'GST Transporter ID', + 'fieldtype': 'Data', + 'fetch_from': 'transporter.gst_transporter_id', + 'default': frm.doc.gst_transporter_id + }, + { + 'fieldname': 'driver', + 'label': 'Driver', + 'fieldtype': 'Link', + 'options': 'Driver', + 'default': frm.doc.driver + }, + { + 'fieldname': 'lr_no', + 'label': 'Transport Receipt No', + 'fieldtype': 'Data', + 'default': frm.doc.lr_no + }, + { + 'fieldname': 'vehicle_no', + 'label': 'Vehicle No', + 'fieldtype': 'Data', + 'depends_on': 'eval:(doc.mode_of_transport === "Road")', + 'default': frm.doc.vehicle_no + }, + { + 'fieldname': 'distance', + 'label': 'Distance (in km)', + 'fieldtype': 'Float', + 'default': frm.doc.distance + }, + { + 'fieldname': 'transporter_col_break', + 'fieldtype': 'Column Break', + }, + { + 'fieldname': 'transporter_name', + 'label': 'Transporter Name', + 'fieldtype': 'Data', + 'fetch_from': 'transporter.name', + 'read_only': 1, + 'default': frm.doc.transporter_name + }, + { + 'fieldname': 'mode_of_transport', + 'label': 'Mode of Transport', + 'fieldtype': 'Select', + 'options': `\nRoad\nAir\nRail\nShip`, + 'default': frm.doc.mode_of_transport + }, + { + 'fieldname': 'driver_name', + 'label': 'Driver Name', + 'fieldtype': 'Data', + 'fetch_from': 'driver.full_name', + 'read_only': 1, + 'default': frm.doc.driver_name + }, + { + 'fieldname': 'lr_date', + 'label': 'Transport Receipt Date', + 'fieldtype': 'Date', + 'default': frm.doc.lr_date + }, + { + 'fieldname': 'gst_vehicle_type', + 'label': 'GST Vehicle Type', + 'fieldtype': 'Select', + 'options': `Regular\nOver Dimensional Cargo (ODC)`, + 'depends_on': 'eval:(doc.mode_of_transport === "Road")', + 'default': frm.doc.gst_vehicle_type + } + ]; } \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 54f18d37f00..b38c5070cbd 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -240,9 +240,7 @@ def get_eway_bill_details(invoice): vehicle_type=vehicle_type[invoice.gst_vehicle_type] )) -@frappe.whitelist() -def make_einvoice(doctype, name): - invoice = frappe.get_doc(doctype, name) +def make_einvoice(invoice): schema = read_json('einv_template') trans_details = get_trans_details(invoice) @@ -255,7 +253,7 @@ def make_einvoice(doctype, name): buyer_details = get_overseas_address_details(invoice.customer_address) else: buyer_details = get_party_details(invoice.customer_address) - place_of_supply = get_place_of_supply(invoice, doctype) or invoice.billing_address_gstin + place_of_supply = get_place_of_supply(invoice, invoice.doctype) or invoice.billing_address_gstin place_of_supply = place_of_supply[:2] buyer_details.update(dict(place_of_supply=place_of_supply)) @@ -351,42 +349,10 @@ def validate_einvoice(validations, einvoice, errors=[]): return errors -def update_invoice(doctype, docname, res): - enc_signed_invoice = res.get('SignedInvoice') - dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data'] - - frappe.db.set_value(doctype, docname, 'irn', res.get('Irn')) - frappe.db.set_value(doctype, docname, 'ewaybill', res.get('EwbNo')) - frappe.db.set_value(doctype, docname, 'signed_einvoice', dec_signed_invoice) - - signed_qr_code = res.get('SignedQRCode') - frappe.db.set_value(doctype, docname, 'signed_qr_code', signed_qr_code) - - attach_qrcode_image(doctype, docname, signed_qr_code) - -def attach_qrcode_image(doctype, docname, qrcode): - if not qrcode: return - - _file = frappe.new_doc('File') - _file.update({ - 'file_name': f'QRCode_{docname}.png', - 'attached_to_doctype': doctype, - 'attached_to_name': docname, - 'content': 'qrcode', - 'is_private': 1 - }) - _file.insert() - frappe.db.commit() - url = qrcreate(qrcode) - abs_file_path = os.path.abspath(_file.get_full_path()) - url.png(abs_file_path, scale=2) - - frappe.db.set_value(doctype, docname, 'qrcode_image', _file.file_url) - class ResponseFailure(Exception): pass class GSPConnector(): - def __init__(self): + def __init__(self, doctype, docname): self.credentials = frappe.get_cached_doc('E Invoice Settings') self.base_url = 'https://gsp.adaequare.com/' @@ -397,6 +363,8 @@ 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: @@ -473,23 +441,22 @@ class GSPConnector(): frappe.cache().hset('gstin_cache', key, details) return details - def generate_irn(self, docname): + def generate_irn(self): headers = self.get_headers() - doctype = 'Sales Invoice' - einvoice = make_einvoice(doctype, docname) + einvoice = make_einvoice(self.invoice) data = json.dumps(einvoice) try: res = make_post_request(self.generate_irn_url, headers=headers, data=data) if res.get('success'): - update_invoice(doctype, docname, res.get('result')) + self.set_einvoice_data(res.get('result')) elif '2150' in res.get('message'): # IRN already generated irn = res.get('result')[0].get('Desc').get('Irn') irn_details = self.get_irn_details(irn) if irn_details: - update_invoice(doctype, docname, irn_details) + self.set_einvoice_data(irn_details) else: self.log_error(res) @@ -521,9 +488,8 @@ class GSPConnector(): self.log_error() self.raise_error(True) - def cancel_irn(self, docname, irn, reason, remark): + def cancel_irn(self, irn, reason, remark): headers = self.get_headers() - doctype = 'Sales Invoice' data = json.dumps({ 'Irn': irn, 'Cnlrsn': reason, @@ -533,8 +499,14 @@ class GSPConnector(): try: res = make_post_request(self.cancel_irn_url, headers=headers, data=data) if res.get('success'): - frappe.db.set_value(doctype, docname, 'irn_cancelled', 1) - # frappe.db.set_value(doctype, docname, 'cancelled_on', res.get('CancelDate')) + self.invoice.irn_cancelled = 1 + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('IRN Cancelled - {}').format(remark) + } + self.update_invoice() + else: self.log_error(res) raise ResponseFailure @@ -550,8 +522,6 @@ class GSPConnector(): args = frappe._dict(kwargs) headers = self.get_headers() - doctype = 'Sales Invoice' - docname = args.docname eway_bill_details = get_eway_bill_details(args) data = json.dumps({ 'Irn': args.irn, @@ -568,12 +538,16 @@ class GSPConnector(): try: res = make_post_request(self.generate_ewaybill_url, headers=headers, data=data) if res.get('success'): - frappe.db.set_value(doctype, docname, 'ewaybill', res.get('result').get('EwbNo')) - frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 0) - for d in args: - if d in ['docname', 'cmd']: continue - # update eway bill details in sales invoice - frappe.db.set_value(doctype, docname, d, args[d]) + self.invoice.ewaybill = res.get('result').get('EwbNo') + self.invoice.eway_bill_cancelled = 0 + self.invoice.update(args) + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('E-Way Bill Generated') + } + self.update_invoice() + else: self.log_error(res) raise ResponseFailure @@ -585,7 +559,7 @@ class GSPConnector(): self.log_error(data) self.raise_error(True) - def cancel_eway_bill(self, docname, eway_bill, reason, remark): + def cancel_eway_bill(self, eway_bill, reason, remark): headers = self.get_headers() doctype = 'Sales Invoice' data = json.dumps({ @@ -597,8 +571,15 @@ class GSPConnector(): try: res = make_post_request(self.cancel_ewaybill_url, headers=headers, data=data) if res.get('success'): - frappe.db.set_value(doctype, docname, 'ewaybill', '') - frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1) + self.invoice.ewaybill = '' + self.invoice.eway_bill_cancelled = 1 + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('E-Way Bill Cancelled - {}').format(remark) + } + self.update_invoice() + else: self.log_error(res) raise ResponseFailure @@ -625,23 +606,67 @@ class GSPConnector(): raise_exception=raise_exception, indicator='red' ) + + def set_einvoice_data(self, res): + enc_signed_invoice = res.get('SignedInvoice') + dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data'] + + self.invoice.irn = res.get('Irn') + self.invoice.ewaybill = res.get('EwbNo') + self.invoice.signed_einvoice = dec_signed_invoice + self.invoice.signed_qr_code = res.get('SignedQRCode') + + self.attach_qrcode_image() + + self.invoice.flags.updater_reference = { + 'doctype': self.invoice.doctype, + 'docname': self.invoice.name, + 'label': _('IRN Generated') + } + self.update_invoice() + + def attach_qrcode_image(self): + qrcode = self.invoice.signed_qr_code + doctype = self.invoice.doctype + docname = self.invoice.name + + _file = frappe.new_doc('File') + _file.update({ + 'file_name': f'QRCode_{docname}.png', + 'attached_to_doctype': doctype, + 'attached_to_name': docname, + 'content': 'qrcode', + 'is_private': 1 + }) + _file.insert() + frappe.db.commit() + url = qrcreate(qrcode) + abs_file_path = os.path.abspath(_file.get_full_path()) + url.png(abs_file_path, scale=2) + + self.invoice.qrcode_image = _file.file_url + + def update_invoice(self): + self.invoice.flags.ignore_validate_update_after_submit = True + self.invoice.flags.ignore_validate = True + self.invoice.save() @frappe.whitelist() -def generate_irn(docname): - gsp_connector = GSPConnector() - gsp_connector.generate_irn(docname) +def generate_irn(doctype, docname): + gsp_connector = GSPConnector(doctype, docname) + gsp_connector.generate_irn() @frappe.whitelist() -def cancel_irn(docname, irn, reason, remark): - gsp_connector = GSPConnector() - gsp_connector.cancel_irn(docname, irn, reason, remark) +def cancel_irn(doctype, docname, irn, reason, remark): + gsp_connector = GSPConnector(doctype, docname) + gsp_connector.cancel_irn(irn, reason, remark) @frappe.whitelist() -def generate_eway_bill(**kwargs): - gsp_connector = GSPConnector() +def generate_eway_bill(doctype, docname, **kwargs): + gsp_connector = GSPConnector(doctype, docname) gsp_connector.generate_eway_bill(**kwargs) @frappe.whitelist() -def cancel_eway_bill(docname, eway_bill, reason, remark): - gsp_connector = GSPConnector() - gsp_connector.cancel_eway_bill(docname, eway_bill, reason, remark) \ No newline at end of file +def cancel_eway_bill(doctype, docname, eway_bill, reason, remark): + gsp_connector = GSPConnector(doctype, docname) + gsp_connector.cancel_eway_bill(eway_bill, reason, remark) \ No newline at end of file From c6143c45424f80cba6a7212489c5732344be3f64 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 22:44:12 +0530 Subject: [PATCH 090/137] fix: qrcode image size --- erpnext/regional/india/e_invoice/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index b38c5070cbd..fe9afa8d837 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -248,6 +248,7 @@ def make_einvoice(invoice): doc_details = get_doc_details(invoice) value_details = get_value_details(invoice) seller_details = get_party_details(invoice.company_address) + seller_details.update({ 'pincode': 193502 }) if invoice.gst_category == 'Overseas': buyer_details = get_overseas_address_details(invoice.customer_address) @@ -640,9 +641,9 @@ class GSPConnector(): }) _file.insert() frappe.db.commit() - url = qrcreate(qrcode) + url = qrcreate(qrcode, error='L') abs_file_path = os.path.abspath(_file.get_full_path()) - url.png(abs_file_path, scale=2) + url.png(abs_file_path, scale=2, quiet_zone=1) self.invoice.qrcode_image = _file.file_url From f0d591351b6324bbc199a2333651d0d895a60c79 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 23 Nov 2020 16:19:50 +0530 Subject: [PATCH 091/137] fix: enabling track changes for stock settings --- .../stock_settings/stock_settings.json | 895 ++---------------- 1 file changed, 90 insertions(+), 805 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index f43390f19d2..d82d375038b 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -1,955 +1,240 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, + "actions": [], "creation": "2013-06-24 16:37:54", - "custom": 0, "description": "Settings", - "docstatus": 0, "doctype": "DocType", - "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "item_naming_by", + "item_group", + "stock_uom", + "default_warehouse", + "sample_retention_warehouse", + "column_break_4", + "valuation_method", + "over_delivery_receipt_allowance", + "action_if_quality_inspection_is_not_submitted", + "show_barcode_field", + "clean_description_html", + "section_break_7", + "auto_insert_price_list_rate_if_missing", + "allow_negative_stock", + "column_break_10", + "automatically_set_serial_nos_based_on_fifo", + "set_qty_in_transactions_based_on_serial_no_input", + "auto_material_request", + "auto_indent", + "reorder_email_notify", + "inter_warehouse_transfer_settings_section", + "allow_from_dn", + "allow_from_pr", + "freeze_stock_entries", + "stock_frozen_upto", + "stock_frozen_upto_days", + "stock_auth_role", + "batch_id_sb", + "use_naming_series", + "naming_series_prefix" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Item Code", - "fetch_if_empty": 0, "fieldname": "item_naming_by", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Item Naming By", - "length": 0, - "no_copy": 0, - "options": "Item Code\nNaming Series", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Item Code\nNaming Series" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, "fieldname": "item_group", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Default Item Group", - "length": 0, - "no_copy": 0, - "options": "Item Group", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Item Group" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "stock_uom", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Default Stock UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "UOM" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "default_warehouse", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Default Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Warehouse" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "sample_retention_warehouse", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Sample Retention Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Warehouse" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "valuation_method", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Default Valuation Method", - "length": 0, - "no_copy": 0, - "options": "FIFO\nMoving Average", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "FIFO\nMoving Average" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", - "fetch_if_empty": 0, + "description": "The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.", "fieldname": "over_delivery_receipt_allowance", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Over Delivery/Receipt Allowance (%)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Over Delivery/Receipt Allowance (%)" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Stop", - "fetch_if_empty": 0, "fieldname": "action_if_quality_inspection_is_not_submitted", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Action if Quality inspection is not submitted", - "length": 0, - "no_copy": 0, - "options": "Stop\nWarn", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Action If Quality Inspection Is Not Submitted", + "options": "Stop\nWarn" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", - "fetch_if_empty": 0, "fieldname": "show_barcode_field", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Barcode Field", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Show Barcode Field" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", - "fetch_if_empty": 0, "fieldname": "clean_description_html", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Convert Item Description to Clean HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Convert Item Description to Clean HTML" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "auto_insert_price_list_rate_if_missing", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto insert Price List rate if missing", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Auto Insert Price List Rate If Missing" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "allow_negative_stock", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Negative Stock", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Allow Negative Stock" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", - "fetch_if_empty": 0, "fieldname": "automatically_set_serial_nos_based_on_fifo", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Automatically Set Serial Nos based on FIFO", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Automatically Set Serial Nos Based on FIFO" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", - "fetch_if_empty": 0, "fieldname": "set_qty_in_transactions_based_on_serial_no_input", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Set Qty in Transactions based on Serial No Input", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Set Qty in Transactions Based on Serial No Input" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "auto_material_request", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto Material Request", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Auto Material Request" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "auto_indent", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Raise Material Request when stock reaches re-order level", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Raise Material Request When Stock Reaches Re-order Level" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "reorder_email_notify", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notify by Email on creation of automatic Material Request", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Notify by Email on Creation of Automatic Material Request" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "freeze_stock_entries", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Freeze Stock Entries", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Freeze Stock Entries" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "stock_frozen_upto", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Stock Frozen Upto", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Stock Frozen Upto" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "stock_frozen_upto_days", "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Freeze Stocks Older Than [Days]", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Freeze Stocks Older Than (Days)" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "stock_auth_role", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Role Allowed to edit frozen stock", - "length": 0, - "no_copy": 0, - "options": "Role", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Role Allowed to Edit Frozen Stock", + "options": "Role" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "batch_id_sb", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Batch Identification", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Batch Identification" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "fetch_if_empty": 0, "fieldname": "use_naming_series", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Use Naming Series", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Use Naming Series" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "BATCH-", "depends_on": "eval:doc.use_naming_series==1", - "fetch_if_empty": 0, "fieldname": "naming_series_prefix", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Naming Series Prefix", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Naming Series Prefix" + }, + { + "fieldname": "inter_warehouse_transfer_settings_section", + "fieldtype": "Section Break", + "label": "Inter Warehouse Transfer Settings" + }, + { + "default": "0", + "fieldname": "allow_from_dn", + "fieldtype": "Check", + "label": "Allow Material Transfer from Delivery Note to Sales Invoice" + }, + { + "default": "0", + "fieldname": "allow_from_pr", + "fieldtype": "Check", + "label": "Allow Material Transfer from Purchase Receipt to Purchase Invoice" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "icon-cog", "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, + "index_web_pages_for_search": 1, "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2019-07-04 01:19:07.738045", + "links": [], + "modified": "2020-11-23 16:17:40.061466", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, - "report": 0, "role": "Stock Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, + "sort_field": "modified", "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file From 79ac9dc486d2e6f747cc1b9ecfd4e53a1f89ae0a Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 23 Nov 2020 16:34:40 +0530 Subject: [PATCH 092/137] fix: enabling track changes for stock settings --- .../stock_settings/stock_settings.json | 1193 +++++++++++++---- 1 file changed, 954 insertions(+), 239 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index d82d375038b..c116ccba987 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -1,240 +1,955 @@ { - "actions": [], - "creation": "2013-06-24 16:37:54", - "description": "Settings", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "item_naming_by", - "item_group", - "stock_uom", - "default_warehouse", - "sample_retention_warehouse", - "column_break_4", - "valuation_method", - "over_delivery_receipt_allowance", - "action_if_quality_inspection_is_not_submitted", - "show_barcode_field", - "clean_description_html", - "section_break_7", - "auto_insert_price_list_rate_if_missing", - "allow_negative_stock", - "column_break_10", - "automatically_set_serial_nos_based_on_fifo", - "set_qty_in_transactions_based_on_serial_no_input", - "auto_material_request", - "auto_indent", - "reorder_email_notify", - "inter_warehouse_transfer_settings_section", - "allow_from_dn", - "allow_from_pr", - "freeze_stock_entries", - "stock_frozen_upto", - "stock_frozen_upto_days", - "stock_auth_role", - "batch_id_sb", - "use_naming_series", - "naming_series_prefix" - ], - "fields": [ - { - "default": "Item Code", - "fieldname": "item_naming_by", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Item Naming By", - "options": "Item Code\nNaming Series" - }, - { - "fieldname": "item_group", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Default Item Group", - "options": "Item Group" - }, - { - "fieldname": "stock_uom", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Default Stock UOM", - "options": "UOM" - }, - { - "fieldname": "default_warehouse", - "fieldtype": "Link", - "label": "Default Warehouse", - "options": "Warehouse" - }, - { - "fieldname": "sample_retention_warehouse", - "fieldtype": "Link", - "label": "Sample Retention Warehouse", - "options": "Warehouse" - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "valuation_method", - "fieldtype": "Select", - "label": "Default Valuation Method", - "options": "FIFO\nMoving Average" - }, - { - "description": "The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.", - "fieldname": "over_delivery_receipt_allowance", - "fieldtype": "Float", - "label": "Over Delivery/Receipt Allowance (%)" - }, - { - "default": "Stop", - "fieldname": "action_if_quality_inspection_is_not_submitted", - "fieldtype": "Select", - "label": "Action If Quality Inspection Is Not Submitted", - "options": "Stop\nWarn" - }, - { - "default": "1", - "fieldname": "show_barcode_field", - "fieldtype": "Check", - "label": "Show Barcode Field" - }, - { - "default": "1", - "fieldname": "clean_description_html", - "fieldtype": "Check", - "label": "Convert Item Description to Clean HTML" - }, - { - "fieldname": "section_break_7", - "fieldtype": "Section Break" - }, - { - "default": "0", - "fieldname": "auto_insert_price_list_rate_if_missing", - "fieldtype": "Check", - "label": "Auto Insert Price List Rate If Missing" - }, - { - "default": "0", - "fieldname": "allow_negative_stock", - "fieldtype": "Check", - "label": "Allow Negative Stock" - }, - { - "fieldname": "column_break_10", - "fieldtype": "Column Break" - }, - { - "default": "1", - "fieldname": "automatically_set_serial_nos_based_on_fifo", - "fieldtype": "Check", - "label": "Automatically Set Serial Nos Based on FIFO" - }, - { - "default": "1", - "fieldname": "set_qty_in_transactions_based_on_serial_no_input", - "fieldtype": "Check", - "label": "Set Qty in Transactions Based on Serial No Input" - }, - { - "fieldname": "auto_material_request", - "fieldtype": "Section Break", - "label": "Auto Material Request" - }, - { - "default": "0", - "fieldname": "auto_indent", - "fieldtype": "Check", - "label": "Raise Material Request When Stock Reaches Re-order Level" - }, - { - "default": "0", - "fieldname": "reorder_email_notify", - "fieldtype": "Check", - "label": "Notify by Email on Creation of Automatic Material Request" - }, - { - "fieldname": "freeze_stock_entries", - "fieldtype": "Section Break", - "label": "Freeze Stock Entries" - }, - { - "fieldname": "stock_frozen_upto", - "fieldtype": "Date", - "label": "Stock Frozen Upto" - }, - { - "fieldname": "stock_frozen_upto_days", - "fieldtype": "Int", - "label": "Freeze Stocks Older Than (Days)" - }, - { - "fieldname": "stock_auth_role", - "fieldtype": "Link", - "label": "Role Allowed to Edit Frozen Stock", - "options": "Role" - }, - { - "fieldname": "batch_id_sb", - "fieldtype": "Section Break", - "label": "Batch Identification" - }, - { - "default": "0", - "fieldname": "use_naming_series", - "fieldtype": "Check", - "label": "Use Naming Series" - }, - { - "default": "BATCH-", - "depends_on": "eval:doc.use_naming_series==1", - "fieldname": "naming_series_prefix", - "fieldtype": "Data", - "label": "Naming Series Prefix" - }, - { - "fieldname": "inter_warehouse_transfer_settings_section", - "fieldtype": "Section Break", - "label": "Inter Warehouse Transfer Settings" - }, - { - "default": "0", - "fieldname": "allow_from_dn", - "fieldtype": "Check", - "label": "Allow Material Transfer from Delivery Note to Sales Invoice" - }, - { - "default": "0", - "fieldname": "allow_from_pr", - "fieldtype": "Check", - "label": "Allow Material Transfer from Purchase Receipt to Purchase Invoice" - } - ], - "icon": "icon-cog", - "idx": 1, - "index_web_pages_for_search": 1, - "issingle": 1, - "links": [], - "modified": "2020-11-23 16:17:40.061466", - "modified_by": "Administrator", - "module": "Stock", - "name": "Stock Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "Stock Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "ASC", - "track_changes": 1 -} \ No newline at end of file + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2013-06-24 16:37:54", + "custom": 0, + "description": "Settings", + "docstatus": 0, + "doctype": "DocType", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Item Code", + "fetch_if_empty": 0, + "fieldname": "item_naming_by", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Item Naming By", + "length": 0, + "no_copy": 0, + "options": "Item Code\nNaming Series", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fetch_if_empty": 0, + "fieldname": "item_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Default Item Group", + "length": 0, + "no_copy": 0, + "options": "Item Group", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Default Stock UOM", + "length": 0, + "no_copy": 0, + "options": "UOM", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "default_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "sample_retention_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sample Retention Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "valuation_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Valuation Method", + "length": 0, + "no_copy": 0, + "options": "FIFO\nMoving Average", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", + "fetch_if_empty": 0, + "fieldname": "over_delivery_receipt_allowance", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Over Delivery/Receipt Allowance (%)", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Stop", + "fetch_if_empty": 0, + "fieldname": "action_if_quality_inspection_is_not_submitted", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Action if Quality inspection is not submitted", + "length": 0, + "no_copy": 0, + "options": "Stop\nWarn", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "show_barcode_field", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Show Barcode Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "clean_description_html", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Convert Item Description to Clean HTML", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_insert_price_list_rate_if_missing", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto insert Price List rate if missing", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "allow_negative_stock", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allow Negative Stock", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "automatically_set_serial_nos_based_on_fifo", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Automatically Set Serial Nos based on FIFO", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "set_qty_in_transactions_based_on_serial_no_input", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Set Qty in Transactions based on Serial No Input", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_material_request", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto Material Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_indent", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Raise Material Request when stock reaches re-order level", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "reorder_email_notify", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify by Email on creation of automatic Material Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "freeze_stock_entries", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Freeze Stock Entries", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_frozen_upto", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Stock Frozen Upto", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_frozen_upto_days", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Freeze Stocks Older Than [Days]", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_auth_role", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Role Allowed to edit frozen stock", + "length": 0, + "no_copy": 0, + "options": "Role", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "batch_id_sb", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Batch Identification", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fetch_if_empty": 0, + "fieldname": "use_naming_series", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Use Naming Series", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "BATCH-", + "depends_on": "eval:doc.use_naming_series==1", + "fetch_if_empty": 0, + "fieldname": "naming_series_prefix", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Naming Series Prefix", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "icon-cog", + "idx": 1, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2019-07-04 01:19:07.738045", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Settings", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "Stock Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_order": "ASC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 + } \ No newline at end of file From 5b554a5e20726a443e2bd3b432ce5eb4c9af9491 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 23 Nov 2020 16:37:00 +0530 Subject: [PATCH 093/137] fix: enabling track changes for stock settings --- .../stock_settings/stock_settings.json | 1906 ++++++++--------- 1 file changed, 953 insertions(+), 953 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index c116ccba987..d408b4184d2 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -1,955 +1,955 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2013-06-24 16:37:54", - "custom": 0, - "description": "Settings", - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Item Code", - "fetch_if_empty": 0, - "fieldname": "item_naming_by", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Naming By", - "length": 0, - "no_copy": 0, - "options": "Item Code\nNaming Series", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "item_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Item Group", - "length": 0, - "no_copy": 0, - "options": "Item Group", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Stock UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "default_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sample_retention_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sample Retention Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "valuation_method", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Valuation Method", - "length": 0, - "no_copy": 0, - "options": "FIFO\nMoving Average", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", - "fetch_if_empty": 0, - "fieldname": "over_delivery_receipt_allowance", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Over Delivery/Receipt Allowance (%)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Stop", - "fetch_if_empty": 0, - "fieldname": "action_if_quality_inspection_is_not_submitted", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Action if Quality inspection is not submitted", - "length": 0, - "no_copy": 0, - "options": "Stop\nWarn", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "show_barcode_field", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Barcode Field", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "clean_description_html", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Convert Item Description to Clean HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_insert_price_list_rate_if_missing", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto insert Price List rate if missing", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "allow_negative_stock", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Negative Stock", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "automatically_set_serial_nos_based_on_fifo", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Automatically Set Serial Nos based on FIFO", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "set_qty_in_transactions_based_on_serial_no_input", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Set Qty in Transactions based on Serial No Input", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_material_request", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto Material Request", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_indent", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Raise Material Request when stock reaches re-order level", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "reorder_email_notify", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notify by Email on creation of automatic Material Request", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "freeze_stock_entries", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Freeze Stock Entries", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_frozen_upto", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Stock Frozen Upto", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_frozen_upto_days", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Freeze Stocks Older Than [Days]", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_auth_role", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Role Allowed to edit frozen stock", - "length": 0, - "no_copy": 0, - "options": "Role", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "batch_id_sb", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Batch Identification", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "use_naming_series", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Use Naming Series", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "BATCH-", - "depends_on": "eval:doc.use_naming_series==1", - "fetch_if_empty": 0, - "fieldname": "naming_series_prefix", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Naming Series Prefix", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-cog", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2019-07-04 01:19:07.738045", - "modified_by": "Administrator", - "module": "Stock", - "name": "Stock Settings", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, +"allow_copy": 0, +"allow_events_in_timeline": 0, +"allow_guest_to_view": 0, +"allow_import": 0, +"allow_rename": 0, +"autoname": "", +"beta": 0, +"creation": "2013-06-24 16:37:54", +"custom": 0, +"description": "Settings", +"docstatus": 0, +"doctype": "DocType", +"editable_grid": 0, +"fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Item Code", + "fetch_if_empty": 0, + "fieldname": "item_naming_by", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Item Naming By", + "length": 0, + "no_copy": 0, + "options": "Item Code\nNaming Series", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 - } \ No newline at end of file + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fetch_if_empty": 0, + "fieldname": "item_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Default Item Group", + "length": 0, + "no_copy": 0, + "options": "Item Group", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Default Stock UOM", + "length": 0, + "no_copy": 0, + "options": "UOM", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "default_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "sample_retention_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sample Retention Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "valuation_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Valuation Method", + "length": 0, + "no_copy": 0, + "options": "FIFO\nMoving Average", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", + "fetch_if_empty": 0, + "fieldname": "over_delivery_receipt_allowance", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Over Delivery/Receipt Allowance (%)", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Stop", + "fetch_if_empty": 0, + "fieldname": "action_if_quality_inspection_is_not_submitted", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Action if Quality inspection is not submitted", + "length": 0, + "no_copy": 0, + "options": "Stop\nWarn", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "show_barcode_field", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Show Barcode Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "clean_description_html", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Convert Item Description to Clean HTML", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_insert_price_list_rate_if_missing", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto insert Price List rate if missing", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "allow_negative_stock", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allow Negative Stock", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "automatically_set_serial_nos_based_on_fifo", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Automatically Set Serial Nos based on FIFO", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "set_qty_in_transactions_based_on_serial_no_input", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Set Qty in Transactions based on Serial No Input", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_material_request", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto Material Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_indent", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Raise Material Request when stock reaches re-order level", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "reorder_email_notify", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify by Email on creation of automatic Material Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "freeze_stock_entries", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Freeze Stock Entries", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_frozen_upto", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Stock Frozen Upto", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_frozen_upto_days", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Freeze Stocks Older Than [Days]", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_auth_role", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Role Allowed to edit frozen stock", + "length": 0, + "no_copy": 0, + "options": "Role", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "batch_id_sb", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Batch Identification", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fetch_if_empty": 0, + "fieldname": "use_naming_series", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Use Naming Series", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "BATCH-", + "depends_on": "eval:doc.use_naming_series==1", + "fetch_if_empty": 0, + "fieldname": "naming_series_prefix", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Naming Series Prefix", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } +], +"has_web_view": 0, +"hide_heading": 0, +"hide_toolbar": 0, +"icon": "icon-cog", +"idx": 1, +"image_view": 0, +"in_create": 0, +"is_submittable": 0, +"issingle": 1, +"istable": 0, +"max_attachments": 0, +"modified": "2019-07-04 01:19:07.738045", +"modified_by": "Administrator", +"module": "Stock", +"name": "Stock Settings", +"owner": "Administrator", +"permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "Stock Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } +], +"quick_entry": 1, +"read_only": 0, +"read_only_onload": 0, +"show_name_in_global_search": 0, +"sort_order": "ASC", +"track_changes": 0, +"track_seen": 1, +"track_views": 0 +} \ No newline at end of file From f4f05dd5addac4c8317b7b034e732a0326a6cd8f Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 23 Nov 2020 16:41:51 +0530 Subject: [PATCH 094/137] fix: enabling track changes for stock settings --- .../stock_settings/stock_settings.json | 1906 ++++++++--------- 1 file changed, 953 insertions(+), 953 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index d408b4184d2..c116ccba987 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -1,955 +1,955 @@ { -"allow_copy": 0, -"allow_events_in_timeline": 0, -"allow_guest_to_view": 0, -"allow_import": 0, -"allow_rename": 0, -"autoname": "", -"beta": 0, -"creation": "2013-06-24 16:37:54", -"custom": 0, -"description": "Settings", -"docstatus": 0, -"doctype": "DocType", -"editable_grid": 0, -"fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Item Code", - "fetch_if_empty": 0, - "fieldname": "item_naming_by", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Naming By", - "length": 0, - "no_copy": 0, - "options": "Item Code\nNaming Series", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2013-06-24 16:37:54", + "custom": 0, + "description": "Settings", + "docstatus": 0, + "doctype": "DocType", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Item Code", + "fetch_if_empty": 0, + "fieldname": "item_naming_by", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Item Naming By", + "length": 0, + "no_copy": 0, + "options": "Item Code\nNaming Series", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fetch_if_empty": 0, + "fieldname": "item_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Default Item Group", + "length": 0, + "no_copy": 0, + "options": "Item Group", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Default Stock UOM", + "length": 0, + "no_copy": 0, + "options": "UOM", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "default_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "sample_retention_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sample Retention Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "valuation_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Valuation Method", + "length": 0, + "no_copy": 0, + "options": "FIFO\nMoving Average", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", + "fetch_if_empty": 0, + "fieldname": "over_delivery_receipt_allowance", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Over Delivery/Receipt Allowance (%)", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Stop", + "fetch_if_empty": 0, + "fieldname": "action_if_quality_inspection_is_not_submitted", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Action if Quality inspection is not submitted", + "length": 0, + "no_copy": 0, + "options": "Stop\nWarn", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "show_barcode_field", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Show Barcode Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "clean_description_html", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Convert Item Description to Clean HTML", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_insert_price_list_rate_if_missing", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto insert Price List rate if missing", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "allow_negative_stock", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allow Negative Stock", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "automatically_set_serial_nos_based_on_fifo", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Automatically Set Serial Nos based on FIFO", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "set_qty_in_transactions_based_on_serial_no_input", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Set Qty in Transactions based on Serial No Input", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_material_request", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto Material Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_indent", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Raise Material Request when stock reaches re-order level", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "reorder_email_notify", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify by Email on creation of automatic Material Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "freeze_stock_entries", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Freeze Stock Entries", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_frozen_upto", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Stock Frozen Upto", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_frozen_upto_days", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Freeze Stocks Older Than [Days]", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_auth_role", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Role Allowed to edit frozen stock", + "length": 0, + "no_copy": 0, + "options": "Role", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "batch_id_sb", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Batch Identification", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fetch_if_empty": 0, + "fieldname": "use_naming_series", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Use Naming Series", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "BATCH-", + "depends_on": "eval:doc.use_naming_series==1", + "fetch_if_empty": 0, + "fieldname": "naming_series_prefix", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Naming Series Prefix", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "icon-cog", + "idx": 1, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2019-07-04 01:19:07.738045", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Settings", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "Stock Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "item_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Item Group", - "length": 0, - "no_copy": 0, - "options": "Item Group", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Stock UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "default_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sample_retention_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sample Retention Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "valuation_method", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Valuation Method", - "length": 0, - "no_copy": 0, - "options": "FIFO\nMoving Average", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", - "fetch_if_empty": 0, - "fieldname": "over_delivery_receipt_allowance", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Over Delivery/Receipt Allowance (%)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Stop", - "fetch_if_empty": 0, - "fieldname": "action_if_quality_inspection_is_not_submitted", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Action if Quality inspection is not submitted", - "length": 0, - "no_copy": 0, - "options": "Stop\nWarn", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "show_barcode_field", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Barcode Field", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "clean_description_html", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Convert Item Description to Clean HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_insert_price_list_rate_if_missing", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto insert Price List rate if missing", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "allow_negative_stock", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Negative Stock", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "automatically_set_serial_nos_based_on_fifo", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Automatically Set Serial Nos based on FIFO", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "set_qty_in_transactions_based_on_serial_no_input", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Set Qty in Transactions based on Serial No Input", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_material_request", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto Material Request", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_indent", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Raise Material Request when stock reaches re-order level", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "reorder_email_notify", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notify by Email on creation of automatic Material Request", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "freeze_stock_entries", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Freeze Stock Entries", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_frozen_upto", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Stock Frozen Upto", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_frozen_upto_days", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Freeze Stocks Older Than [Days]", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_auth_role", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Role Allowed to edit frozen stock", - "length": 0, - "no_copy": 0, - "options": "Role", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "batch_id_sb", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Batch Identification", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "use_naming_series", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Use Naming Series", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "BATCH-", - "depends_on": "eval:doc.use_naming_series==1", - "fetch_if_empty": 0, - "fieldname": "naming_series_prefix", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Naming Series Prefix", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } -], -"has_web_view": 0, -"hide_heading": 0, -"hide_toolbar": 0, -"icon": "icon-cog", -"idx": 1, -"image_view": 0, -"in_create": 0, -"is_submittable": 0, -"issingle": 1, -"istable": 0, -"max_attachments": 0, -"modified": "2019-07-04 01:19:07.738045", -"modified_by": "Administrator", -"module": "Stock", -"name": "Stock Settings", -"owner": "Administrator", -"permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } -], -"quick_entry": 1, -"read_only": 0, -"read_only_onload": 0, -"show_name_in_global_search": 0, -"sort_order": "ASC", -"track_changes": 0, -"track_seen": 1, -"track_views": 0 -} \ No newline at end of file + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_order": "ASC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 + } \ No newline at end of file From 53bcf704b3ad36118e8cfc68f64902ea96c4c236 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 9 Dec 2020 17:45:00 +0100 Subject: [PATCH 095/137] feat: separate equity tree in CoA SKR04 --- ..._kontenplan_SKR04_with_account_number.json | 206 ++++++++++++------ 1 file changed, 138 insertions(+), 68 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json index 4ee12209caa..bcbdc2ab9a0 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json @@ -910,75 +910,8 @@ }, "is_group": 1 }, - "Passiva": { + "Passiva - Verbindlichkeiten": { "root_type": "Liability", - "A - Eigenkapital": { - "account_type": "Equity", - "is_group": 1, - "I - Gezeichnetes Kapital": { - "account_type": "Equity", - "is_group": 1 - }, - "II - Kapitalr\u00fccklage": { - "account_type": "Equity", - "is_group": 1 - }, - "III - Gewinnr\u00fccklagen": { - "account_type": "Equity", - "1 - gesetzliche R\u00fccklage": { - "account_type": "Equity", - "is_group": 1 - }, - "2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": { - "account_type": "Equity", - "is_group": 1 - }, - "3 - satzungsm\u00e4\u00dfige R\u00fccklagen": { - "account_type": "Equity", - "is_group": 1 - }, - "4 - andere Gewinnr\u00fccklagen": { - "account_type": "Equity", - "is_group": 1, - "Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": { - "is_group": 1, - "Gewinnr\u00fccklagen (BilMoG)": { - "account_number": "2963" - }, - "Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": { - "account_number": "2964" - }, - "Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": { - "account_number": "2965" - }, - "Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": { - "account_number": "2966" - } - }, - "Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": { - "account_number": "2967" - }, - "Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": { - "account_number": "2968" - }, - "Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": { - "account_number": "2969" - } - }, - "is_group": 1 - }, - "IV - Gewinnvortrag/Verlustvortrag": { - "account_type": "Equity", - "is_group": 1 - }, - "V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": { - "account_type": "Equity", - "is_group": 1 - }, - "Einlagen stiller Gesellschafter": { - "account_number": "9295" - } - }, "B - R\u00fcckstellungen": { "is_group": 1, "1 - R\u00fcckstellungen f. Pensionen und \u00e4hnliche Verplicht.": { @@ -1595,6 +1528,143 @@ }, "is_group": 1 }, + "Passiva - Eigenkapital": { + "root_type": "Equity", + "A - Eigenkapital": { + "account_type": "Equity", + "is_group": 1, + "I - Gezeichnetes Kapital": { + "account_type": "Equity", + "is_group": 1, + "Gezeichnetes Kapital": { + "account_number": "2900", + "account_type": "Equity" + }, + "Gesch\u00e4ftsguthaben der verbleibenden Mitglieder": { + "account_number": "2901" + }, + "Gesch\u00e4ftsguthaben der ausscheidenden Mitglieder": { + "account_number": "2902" + }, + "Gesch\u00e4ftsguthaben aus gek\u00fcndigten Gesch\u00e4ftsanteilen": { + "account_number": "2903" + }, + "R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": { + "account_number": "2906" + }, + "Gegenkonto R\u00fcckst\u00e4ndige f\u00e4llige Einzahlungen auf Gesch\u00e4ftsanteile, vermerkt": { + "account_number": "2907" + }, + "Kapitalerh\u00f6hung aus Gesellschaftsmitteln": { + "account_number": "2908" + }, + "Ausstehende Einlagen auf das gezeichnete Kapital, nicht eingefordert": { + "account_number": "2910" + } + }, + "II - Kapitalr\u00fccklage": { + "account_type": "Equity", + "is_group": 1, + "Kapitalr\u00fccklage": { + "account_number": "2920" + }, + "Kapitalr\u00fccklage durch Ausgabe von Anteilen \u00fcber Nennbetrag": { + "account_number": "2925" + }, + "Kapitalr\u00fccklage durch Ausgabe von Schuldverschreibungen": { + "account_number": "2926" + }, + "Kapitalr\u00fccklage durch Zuzahlungen gegen Gew\u00e4hrung eines Vorzugs": { + "account_number": "2927" + }, + "Kapitalr\u00fccklage durch Zuzahlungen in das Eigenkapital": { + "account_number": "2928" + }, + "Nachschusskapital (Gegenkonto 1299)": { + "account_number": "2929" + } + }, + "III - Gewinnr\u00fccklagen": { + "account_type": "Equity", + "1 - gesetzliche R\u00fccklage": { + "account_type": "Equity", + "is_group": 1, + "Gesetzliche R\u00fccklage": { + "account_number": "2930" + } + }, + "2 - R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": { + "account_type": "Equity", + "is_group": 1, + "R\u00fccklage f. Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen": { + "account_number": "2935" + } + }, + "3 - satzungsm\u00e4\u00dfige R\u00fccklagen": { + "account_type": "Equity", + "is_group": 1, + "Satzungsm\u00e4\u00dfige R\u00fccklagen": { + "account_number": "2950" + } + }, + "4 - andere Gewinnr\u00fccklagen": { + "account_type": "Equity", + "is_group": 1, + "Andere Gewinnr\u00fccklagen": { + "account_number": "2960" + }, + "Andere Gewinnr\u00fccklagen aus dem Erwerb eigener Anteile": { + "account_number": "2961" + }, + "Eigenkapitalanteil von Wertaufholungen": { + "account_number": "2962" + }, + "Gewinnr\u00fccklagen aus den \u00dcbergangsvorschriften BilMoG": { + "is_group": 1, + "Gewinnr\u00fccklagen (BilMoG)": { + "account_number": "2963" + }, + "Gewinnr\u00fccklagen aus Zuschreibung Sachanlageverm\u00f6gen (BilMoG)": { + "account_number": "2964" + }, + "Gewinnr\u00fccklagen aus Zuschreibung Finanzanlageverm\u00f6gen (BilMoG)": { + "account_number": "2965" + }, + "Gewinnr\u00fccklagen aus Aufl\u00f6sung der Sonderposten mit R\u00fccklageanteil (BilMoG)": { + "account_number": "2966" + } + }, + "Latente Steuern (Gewinnr\u00fccklage Haben) aus erfolgsneutralen Verrechnungen": { + "account_number": "2967" + }, + "Latente Steuern (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": { + "account_number": "2968" + }, + "Rechnungsabgrenzungsposten (Gewinnr\u00fccklage Soll) aus erfolgsneutralen Verrechnungen": { + "account_number": "2969" + } + }, + "is_group": 1 + }, + "IV - Gewinnvortrag/Verlustvortrag": { + "account_type": "Equity", + "is_group": 1, + "Gewinnvortrag vor Verwendung": { + "account_number": "2970" + }, + "Verlustvortrag vor Verwendung": { + "account_number": "2978" + } + }, + "V - Jahres\u00fcberschu\u00df/Jahresfehlbetrag": { + "account_type": "Equity", + "is_group": 1 + }, + "Einlagen stiller Gesellschafter": { + "account_number": "9295" + } + } + }, "1 - Umsatzerl\u00f6se": { "root_type": "Income", "is_group": 1, From e33179d1308e1ef022fac018668b7bb79571eceb Mon Sep 17 00:00:00 2001 From: Anuja P Date: Thu, 10 Dec 2020 22:05:09 +0530 Subject: [PATCH 096/137] fix: Added TDS account number and an error message --- .../standard_chart_of_accounts_with_account_number.py | 3 +++ erpnext/accounts/utils.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py index 6c83e3bd670..acb11e557a5 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py @@ -245,6 +245,9 @@ def get(): "account_number": "2200" }, _("Duties and Taxes"): { + _("TDS Payable"): { + "account_number": "2310" + }, "account_type": "Tax", "is_group": 1, "account_number": "2300" diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index f27911f7a3a..c8bf3455dd5 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -75,7 +75,10 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb else: return ((fy.name, fy.year_start_date, fy.year_end_date),) - error_msg = _("""{0} {1} not in any active Fiscal Year.""").format(label, formatdate(transaction_date)) + error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date)) + if company: + error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company)) + if verbose==1: frappe.msgprint(error_msg) raise FiscalYearError(error_msg) From 4ee433a51c238ca0c402b34a2afc421114374642 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Fri, 11 Dec 2020 10:25:08 +0530 Subject: [PATCH 097/137] fix: indentation --- .../stock_settings/stock_settings.json | 1826 ++++++++--------- 1 file changed, 913 insertions(+), 913 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index c116ccba987..4b7e6444954 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -13,899 +13,899 @@ "doctype": "DocType", "editable_grid": 0, "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Item Code", - "fetch_if_empty": 0, - "fieldname": "item_naming_by", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Naming By", - "length": 0, - "no_copy": 0, - "options": "Item Code\nNaming Series", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "item_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Item Group", - "length": 0, - "no_copy": 0, - "options": "Item Group", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Stock UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "default_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sample_retention_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sample Retention Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "valuation_method", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Valuation Method", - "length": 0, - "no_copy": 0, - "options": "FIFO\nMoving Average", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", - "fetch_if_empty": 0, - "fieldname": "over_delivery_receipt_allowance", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Over Delivery/Receipt Allowance (%)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Stop", - "fetch_if_empty": 0, - "fieldname": "action_if_quality_inspection_is_not_submitted", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Action if Quality inspection is not submitted", - "length": 0, - "no_copy": 0, - "options": "Stop\nWarn", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "show_barcode_field", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Barcode Field", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "clean_description_html", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Convert Item Description to Clean HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_insert_price_list_rate_if_missing", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto insert Price List rate if missing", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "allow_negative_stock", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Negative Stock", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "automatically_set_serial_nos_based_on_fifo", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Automatically Set Serial Nos based on FIFO", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "set_qty_in_transactions_based_on_serial_no_input", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Set Qty in Transactions based on Serial No Input", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_material_request", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto Material Request", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_indent", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Raise Material Request when stock reaches re-order level", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "reorder_email_notify", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notify by Email on creation of automatic Material Request", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "freeze_stock_entries", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Freeze Stock Entries", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_frozen_upto", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Stock Frozen Upto", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_frozen_upto_days", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Freeze Stocks Older Than [Days]", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_auth_role", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Role Allowed to edit frozen stock", - "length": 0, - "no_copy": 0, - "options": "Role", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "batch_id_sb", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Batch Identification", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "use_naming_series", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Use Naming Series", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "BATCH-", - "depends_on": "eval:doc.use_naming_series==1", - "fetch_if_empty": 0, - "fieldname": "naming_series_prefix", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Naming Series Prefix", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Item Code", + "fetch_if_empty": 0, + "fieldname": "item_naming_by", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Item Naming By", + "length": 0, + "no_copy": 0, + "options": "Item Code\nNaming Series", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fetch_if_empty": 0, + "fieldname": "item_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Default Item Group", + "length": 0, + "no_copy": 0, + "options": "Item Group", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Default Stock UOM", + "length": 0, + "no_copy": 0, + "options": "UOM", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "default_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "sample_retention_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sample Retention Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "valuation_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Valuation Method", + "length": 0, + "no_copy": 0, + "options": "FIFO\nMoving Average", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", + "fetch_if_empty": 0, + "fieldname": "over_delivery_receipt_allowance", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Over Delivery/Receipt Allowance (%)", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Stop", + "fetch_if_empty": 0, + "fieldname": "action_if_quality_inspection_is_not_submitted", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Action if Quality inspection is not submitted", + "length": 0, + "no_copy": 0, + "options": "Stop\nWarn", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "show_barcode_field", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Show Barcode Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "clean_description_html", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Convert Item Description to Clean HTML", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_insert_price_list_rate_if_missing", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto insert Price List rate if missing", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "allow_negative_stock", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allow Negative Stock", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "automatically_set_serial_nos_based_on_fifo", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Automatically Set Serial Nos based on FIFO", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "set_qty_in_transactions_based_on_serial_no_input", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Set Qty in Transactions based on Serial No Input", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_material_request", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto Material Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_indent", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Raise Material Request when stock reaches re-order level", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "reorder_email_notify", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify by Email on creation of automatic Material Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "freeze_stock_entries", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Freeze Stock Entries", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_frozen_upto", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Stock Frozen Upto", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_frozen_upto_days", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Freeze Stocks Older Than [Days]", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_auth_role", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Role Allowed to edit frozen stock", + "length": 0, + "no_copy": 0, + "options": "Role", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "batch_id_sb", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Batch Identification", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fetch_if_empty": 0, + "fieldname": "use_naming_series", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Use Naming Series", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "BATCH-", + "depends_on": "eval:doc.use_naming_series==1", + "fetch_if_empty": 0, + "fieldname": "naming_series_prefix", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Naming Series Prefix", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } ], "has_web_view": 0, "hide_heading": 0, @@ -924,25 +924,25 @@ "name": "Stock Settings", "owner": "Administrator", "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "Stock Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } ], "quick_entry": 1, "read_only": 0, @@ -952,4 +952,4 @@ "track_changes": 1, "track_seen": 0, "track_views": 0 - } \ No newline at end of file +} \ No newline at end of file From 0ce03cc36d1d7032268e258d5ce526646577b02a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 24 Nov 2020 13:52:54 +0530 Subject: [PATCH 098/137] fix: exclude intra company transactions --- erpnext/regional/india/e_invoice/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index fe9afa8d837..9052f1bcf3f 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -21,8 +21,9 @@ def validate_einvoice_fields(doc): einvoicing_enabled = frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable') invalid_doctype = doc.doctype not in ['Sales Invoice'] invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] + company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') - if invalid_doctype or invalid_supply_type or not einvoicing_enabled: return + if invalid_doctype or invalid_supply_type or company_transaction or not einvoicing_enabled: return if doc.docstatus == 0 and doc._action == 'save': if doc.irn: From b9cbdeaa95e14968451facf8a4f1865554baffca Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 25 Nov 2020 13:17:02 +0530 Subject: [PATCH 099/137] fix: eway bill test --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 4a6a771d099..36c6047221a 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1912,6 +1912,7 @@ class TestSalesInvoice(unittest.TestCase): si.customer_address = "_Test Customer-Address for Eway bill-Shipping" si.vehicle_no = "KA12KA1234" si.gst_category = "Registered Regular" + si.mode_of_transport = 'Road' si.append("taxes", { "charge_type": "On Net Total", From 6af53a645a3722cdc294f19e81fa6b5b8e6e3636 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 2 Dec 2020 12:59:20 +0530 Subject: [PATCH 100/137] fix: ewaybill mandatory conditions --- erpnext/patches.txt | 2 +- erpnext/patches/v12_0/setup_einvoice_fields.py | 14 +++++++++++--- erpnext/regional/india/setup.py | 1 - 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d08a9283a08..e6066a9fead 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -678,4 +678,4 @@ erpnext.patches.v12_0.update_state_code_for_daman_and_diu erpnext.patches.v12_0.rename_lost_reason_detail erpnext.patches.v12_0.update_leave_application_status erpnext.patches.v12_0.update_payment_entry_status -erpnext.patches.v12_0.setup_einvoice_fields #2020-11-04 +erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02 diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index cbad581ab2a..6c7b3e8feb6 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -31,6 +31,14 @@ def execute(): add_permissions() add_print_formats() - frappe.db.set_value('Custom Field', { 'fieldname': 'mode_of_transport' }, 'default', '') - frappe.db.set_value('Custom Field', { 'fieldname': 'vehicle_no' }, 'depends_on', 'eval:doc.mode_of_transport == "Road"') - frappe.db.set_value('Custom Field', { 'fieldname': 'ewaybill' }, 'depends_on', 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)') \ No newline at end of file + t = { + 'mode_of_transport': [{'default': None}], + 'ewaybill': [ + {'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'} + ] + } + + for field, conditions in t.items(): + for c in conditions: + [(prop, value)] = c.items() + frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value) \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index bb0d2ff8013..317a0e242b7 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -303,7 +303,6 @@ def make_custom_fields(update=True): 'label': 'Vehicle No', 'fieldtype': 'Data', 'insert_after': 'lr_no', - 'depends_on': 'eval:(doc.mode_of_transport === "Road")', 'print_hide': 1, 'translatable': 0 }, From 8f5425d99bec502465f4c9a70089ae84f09ddfbf Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 2 Dec 2020 17:51:51 +0530 Subject: [PATCH 101/137] chore: add tests --- .../sales_invoice/test_sales_invoice.py | 270 ++++++++++++------ erpnext/regional/india/e_invoice/utils.py | 50 ++-- 2 files changed, 206 insertions(+), 114 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 36c6047221a..d6cdb1f42a9 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1841,94 +1841,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(target_doc.supplier, "_Test Internal Supplier") def test_eway_bill_json(self): - if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): - address = frappe.get_doc({ - "address_line1": "_Test Address Line 1", - "address_title": "_Test Address for Eway bill", - "address_type": "Billing", - "city": "_Test City", - "state": "Test State", - "country": "India", - "doctype": "Address", - "is_primary_address": 1, - "phone": "+91 0000000000", - "gstin": "27AAECE4835E1ZR", - "gst_state": "Maharashtra", - "gst_state_number": "27", - "pincode": "401108" - }).insert() - - address.append("links", { - "link_doctype": "Company", - "link_name": "_Test Company" - }) - - address.save() - - if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'): - address = frappe.get_doc({ - "address_line1": "_Test Address Line 1", - "address_title": "_Test Customer-Address for Eway bill", - "address_type": "Shipping", - "city": "_Test City", - "state": "Test State", - "country": "India", - "doctype": "Address", - "is_primary_address": 1, - "phone": "+91 0000000000", - "gst_state": "Maharashtra", - "gst_state_number": "27", - "pincode": "410038" - }).insert() - - address.append("links", { - "link_doctype": "Customer", - "link_name": "_Test Customer" - }) - - address.save() - - gst_settings = frappe.get_doc("GST Settings") - - gst_account = frappe.get_all( - "GST Account", - fields=["cgst_account", "sgst_account", "igst_account"], - filters = {"company": "_Test Company"}) - - if not gst_account: - gst_settings.append("gst_accounts", { - "company": "_Test Company", - "cgst_account": "CGST - _TC", - "sgst_account": "SGST - _TC", - "igst_account": "IGST - _TC", - }) - - gst_settings.save() - - si = create_sales_invoice(do_not_save =1, rate = '60000') - - si.distance = 2000 - si.company_address = "_Test Address for Eway bill-Billing" - si.customer_address = "_Test Customer-Address for Eway bill-Shipping" - si.vehicle_no = "KA12KA1234" - si.gst_category = "Registered Regular" - si.mode_of_transport = 'Road' - - si.append("taxes", { - "charge_type": "On Net Total", - "account_head": "CGST - _TC", - "cost_center": "Main - _TC", - "description": "CGST @ 9.0", - "rate": 9 - }) - - si.append("taxes", { - "charge_type": "On Net Total", - "account_head": "SGST - _TC", - "cost_center": "Main - _TC", - "description": "SGST @ 9.0", - "rate": 9 - }) + si = make_sales_invoice_for_ewaybill() si.submit() @@ -1944,6 +1857,187 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(data['billLists'][0]['sgstValue'], 5400) self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) + + def test_einvoice_submission_without_irn(self): + # init + frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1) + country = frappe.flags.country + frappe.flags.country = 'India' + + si = make_sales_invoice_for_ewaybill() + self.assertRaises(frappe.ValidationError, si.submit) + + si.irn = 'test_irn' + si.submit() + + # reset + frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0) + frappe.flags.country = country + + def test_einvoice_json(self): + from erpnext.regional.india.e_invoice.utils import make_einvoice + + customer_gstin = '27AACCM7806M1Z3' + customer_gstin_dtls = { + 'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City', + 'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg', + 'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street' + } + company_gstin = '27AAECE4835E1ZR' + company_gstin_dtls = { + 'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City', + 'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg', + 'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street' + } + # set cache gstin details to avoid fetching details which will require connection to GSP servers + frappe.local.gstin_cache = {} + frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls + frappe.local.gstin_cache[company_gstin] = company_gstin_dtls + + si = make_sales_invoice_for_ewaybill() + si.naming_series = 'INV-2020-.#####' + si.items = [] + si.append("items", { + "item_code": "_Test Item", + "uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + "qty": 2, + "rate": 100, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }) + si.append("items", { + "item_code": "_Test Item 2", + "uom": "Nos", + "warehouse": "_Test Warehouse - _TC", + "qty": 4, + "rate": 150, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }) + si.save() + + einvoice = make_einvoice(si) + + total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']]) + total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']]) + total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']]) + total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']]) + total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']]) + + self.assertEqual(einvoice['Version'], '1.1') + self.assertEqual(einvoice['ValDtls']['AssVal'], total_item_ass_value) + self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value) + self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value) + self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value) + self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value) + self.assertTrue(einvoice['EwbDtls']) + +def make_sales_invoice_for_ewaybill(): + if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test Address for Eway bill", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+910000000000", + "gstin": "27AAECE4835E1ZR", + "gst_state": "Maharashtra", + "gst_state_number": "27", + "pincode": "401108" + }).insert() + + address.append("links", { + "link_doctype": "Company", + "link_name": "_Test Company" + }) + + address.save() + + if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test Customer-Address for Eway bill", + "address_type": "Shipping", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+910000000000", + "gstin": "27AACCM7806M1Z3", + "gst_state": "Maharashtra", + "gst_state_number": "27", + "pincode": "410038" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test Customer" + }) + + address.save() + + if not frappe.db.exists('Supplier', '_Test Transporter'): + supplier = frappe.get_doc({ + "doctype": "Supplier", + "supplier_name": "_Test Transporter", + "country": "India", + "supplier_group": "_Test Supplier Group", + "supplier_type": "Company", + "is_transporter": 1 + }).insert() + + gst_settings = frappe.get_doc("GST Settings") + + gst_account = frappe.get_all( + "GST Account", + fields=["cgst_account", "sgst_account", "igst_account"], + filters = {"company": "_Test Company"}) + + if not gst_account: + gst_settings.append("gst_accounts", { + "company": "_Test Company", + "cgst_account": "CGST - _TC", + "sgst_account": "SGST - _TC", + "igst_account": "IGST - _TC", + }) + + gst_settings.save() + + si = create_sales_invoice(do_not_save =1, rate = '60000') + + si.distance = 2000 + si.company_address = "_Test Address for Eway bill-Billing" + si.customer_address = "_Test Customer-Address for Eway bill-Shipping" + si.vehicle_no = "KA12KA1234" + si.gst_category = "Registered Regular" + si.mode_of_transport = 'Road' + si.transporter = '_Test Transporter' + + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "CGST - _TC", + "cost_center": "Main - _TC", + "description": "CGST @ 9.0", + "rate": 9 + }) + + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "SGST - _TC", + "cost_center": "Main - _TC", + "description": "SGST @ 9.0", + "rate": 9 + }) + + return si def test_item_tax_validity(self): item = frappe.get_doc("Item", "_Test Item 2") diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 9052f1bcf3f..087c5956dc0 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -71,10 +71,7 @@ def get_trans_details(invoice): )) def get_doc_details(invoice): - if invoice.doctype == 'Purchase Invoice' and invoice.is_return: - invoice_type = 'DBN' - else: - invoice_type = 'CRN' if invoice.is_return else 'INV' + invoice_type = 'CRN' if invoice.is_return else 'INV' invoice_name = invoice.name invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy') @@ -89,7 +86,7 @@ def get_party_details(address_name): address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] gstin = address.get('gstin') - gstin_details = GSPConnector.get_gstin_details(gstin) + gstin_details = get_gstin_details(gstin) legal_name = gstin_details.get('LegalName') trade_name = gstin_details.get('TradeName') location = gstin_details.get('AddrLoc') or address.get('city') @@ -98,7 +95,7 @@ def get_party_details(address_name): address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno')) address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt')) email_id = address.get('email_id') - phone = address.get('phone') + phone = address.get('phone').replace(" ", "")[-10:] # get last 10 digit if state_code == 97: pincode = 999999 @@ -108,6 +105,23 @@ def get_party_details(address_name): address_line2=address_line2, email=email_id, phone=phone )) +def get_gstin_details(gstin): + if not hasattr(frappe.local, 'gstin_cache'): + frappe.local.gstin_cache = {} + + key = gstin + details = frappe.local.gstin_cache.get(key) + if details: + return details + + details = frappe.cache().hget('gstin_cache', key) + if details: + frappe.local.gstin_cache[key] = details + return details + + if not details: + return GSPConnector.get_gstin_details(gstin) + def get_overseas_address_details(address_name): address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value( 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id'] @@ -180,9 +194,9 @@ def get_value_details(invoice): value_details = frappe._dict(dict()) value_details.base_net_total = abs(invoice.base_net_total) - value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount > 0 else 0 + value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0 # discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off - value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount < 0 else 0) + value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0) disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else invoice.base_rounded_total value_details.grand_total = abs(invoice.grand_total) if disable_rounded else invoice.rounded_total @@ -293,11 +307,7 @@ def make_einvoice(invoice): "Errors: ", json.dumps(errors, default=str, indent=4) ]) frappe.log_error(title="E Invoice Validation Failed", message=message) - if len(errors) > 1: - li = ['
  • '+ d +'
  • ' for d in errors] - frappe.throw("
      {}
    ".format(''.join(li)), title=_('E Invoice Validation Failed')) - else: - frappe.throw(errors[0], title=_('E Invoice Validation Failed')) + frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1) return einvoice @@ -421,21 +431,9 @@ class GSPConnector(): @staticmethod def get_gstin_details(gstin): - '''fetch or get cached GSTIN details''' - - if not hasattr(frappe.local, 'gstin_cache'): - frappe.local.gstin_cache = {} + '''fetch and cache GSTIN details''' key = gstin - details = frappe.local.gstin_cache.get(key) - if details: - return details - - details = frappe.cache().hget('gstin_cache', key) - if details: - frappe.local.gstin_cache[key] = details - return details - gsp_connector = GSPConnector() details = gsp_connector.fetch_gstin_details(gstin) From 68e476c7fed3fc6d38491bf608287f443e34dd4d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 3 Dec 2020 10:59:41 +0530 Subject: [PATCH 102/137] fix: returning condition --- erpnext/regional/india/e_invoice/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 087c5956dc0..772154f9a65 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -18,12 +18,12 @@ from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply from frappe.utils.data import cstr, cint, formatdate as format_date, flt, time_diff_in_seconds, now_datetime, add_to_date def validate_einvoice_fields(doc): - einvoicing_enabled = frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable') + einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) invalid_doctype = doc.doctype not in ['Sales Invoice'] invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin') - if invalid_doctype or invalid_supply_type or company_transaction or not einvoicing_enabled: 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.irn: From 5c68fe4d86d4135e36f5292f1bffe4316a659cf3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 8 Dec 2020 14:59:02 +0530 Subject: [PATCH 103/137] 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' From f454ab4ce2659b2650bcb00c87b44a2e7053cee1 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 9 Dec 2020 12:17:23 +0530 Subject: [PATCH 104/137] chore: add ack date and ack no field for print formats --- erpnext/patches/v12_0/setup_einvoice_fields.py | 4 ++++ erpnext/regional/india/setup.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py index 6c7b3e8feb6..4be4b03d2f8 100644 --- a/erpnext/patches/v12_0/setup_einvoice_fields.py +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -13,6 +13,10 @@ def execute(): 'Sales Invoice': [ dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 317a0e242b7..3cfcaa4d9c8 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -379,6 +379,10 @@ def make_custom_fields(update=True): si_einvoice_fields = [ dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), From b1054a3ddc983198abc6195c95974f4c718e672f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 9 Dec 2020 12:29:25 +0530 Subject: [PATCH 105/137] fix: sider issues --- .../sales_invoice/test_sales_invoice.py | 2 +- erpnext/regional/india/e_invoice/einvoice.js | 20 +++++++++---------- erpnext/regional/india/e_invoice/utils.py | 3 --- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index d6cdb1f42a9..9936a6f83da 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1985,7 +1985,7 @@ def make_sales_invoice_for_ewaybill(): address.save() if not frappe.db.exists('Supplier', '_Test Transporter'): - supplier = frappe.get_doc({ + frappe.get_doc({ "doctype": "Supplier", "supplier_name": "_Test Transporter", "country": "India", diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index d4c473363d0..ff5cef0443e 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -3,7 +3,7 @@ erpnext.setup_einvoice_actions = (doctype) => { refresh(frm) { const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); const supply_type = frm.doc.gst_category; - const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type) + const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type); if (!einvoicing_enabled || !valid_supply_type) return; @@ -13,7 +13,7 @@ erpnext.setup_einvoice_actions = (doctype) => { if (!frm.custom_buttons[label]) { frm.add_custom_button(label, action, __('E Invoicing')); } - } + }; if (ewaybill && irn) { frm.set_df_property('ewaybill', 'read_only', 1); @@ -26,7 +26,7 @@ erpnext.setup_einvoice_actions = (doctype) => { args: { doctype, docname: name }, freeze: true, callback: () => frm.reload_doc() - }) + }); }; add_custom_button(__("Generate IRN"), action); @@ -35,7 +35,7 @@ erpnext.setup_einvoice_actions = (doctype) => { if (docstatus == 1 && irn && !irn_cancelled && !ewaybill) { const fields = [ { - "label" : "Reason", + "label": "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, @@ -95,7 +95,7 @@ erpnext.setup_einvoice_actions = (doctype) => { freeze: true, callback: () => frm.reload_doc() || d.hide(), error: () => d.hide() - }) + }); }, primary_action_label: __('Submit') }); @@ -108,7 +108,7 @@ erpnext.setup_einvoice_actions = (doctype) => { if (docstatus == 1 && irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { const fields = [ { - "label" : "Reason", + "label": "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, @@ -140,7 +140,7 @@ erpnext.setup_einvoice_actions = (doctype) => { freeze: true, callback: () => frm.reload_doc() || d.hide(), error: () => d.hide() - }) + }); }, primary_action_label: __('Submit') }); @@ -149,8 +149,8 @@ erpnext.setup_einvoice_actions = (doctype) => { add_custom_button(__("Cancel E-Way Bill"), action); } } - }) -} + }); +}; const get_ewaybill_fields = (frm) => { return [ @@ -236,4 +236,4 @@ const get_ewaybill_fields = (frm) => { 'default': frm.doc.gst_vehicle_type } ]; -} \ No newline at end of file +}; \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index c49e3f7e7e8..532e0955bc2 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -13,7 +13,6 @@ import frappe import traceback from frappe import _, bold from pyqrcode import create as qrcreate -from frappe.model.document import Document from frappe.integrations.utils import make_post_request, make_get_request from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply from frappe.utils.data import cstr, cint, formatdate as format_date, flt, time_diff_in_seconds, now_datetime, add_to_date @@ -89,7 +88,6 @@ def get_party_details(address_name): gstin_details = get_gstin_details(gstin) legal_name = gstin_details.get('LegalName') - trade_name = gstin_details.get('TradeName') location = gstin_details.get('AddrLoc') or address.get('city') state_code = gstin_details.get('StateCode') pincode = gstin_details.get('AddrPncd') @@ -585,7 +583,6 @@ class GSPConnector(): def cancel_eway_bill(self, eway_bill, reason, remark): headers = self.get_headers() - doctype = 'Sales Invoice' data = json.dumps({ 'ewbNo': eway_bill, 'cancelRsnCode': reason, From b510d208054fa865f4ed7c8d98cf73bf45ed6b63 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 16:40:23 +0530 Subject: [PATCH 106/137] fix: adjust qr code image size --- .../print_format/gst_e_invoice/gst_e_invoice.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html index 9827e00b71b..ec9be9aa64e 100644 --- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -24,7 +24,7 @@ {% endif %}
    1. Transaction Details
    -
    +
    {{ einvoice.Irn }}
    @@ -50,8 +50,12 @@
    {{ einvoice.DocDtls.No }}
    -
    - +
    + QRCode Image
    From d01cae95d96ecbf31315955c107703513a93e119 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 16:51:25 +0530 Subject: [PATCH 107/137] feat: show e-invoice preview before IRN generation --- erpnext/regional/india/e_invoice/einvoice.js | 75 +++++++++++++++++++- erpnext/regional/india/e_invoice/utils.py | 35 +++++---- 2 files changed, 95 insertions(+), 15 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index ff5cef0443e..7466de1ae19 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -22,11 +22,14 @@ erpnext.setup_einvoice_actions = (doctype) => { if (docstatus == 0 && !irn && !__unsaved) { const action = () => { frappe.call({ - method: 'erpnext.regional.india.e_invoice.utils.generate_irn', + method: 'erpnext.regional.india.e_invoice.utils.get_einvoice', args: { doctype, docname: name }, freeze: true, - callback: () => frm.reload_doc() - }); + callback: (res) => { + const einvoice = res.message; + show_einvoice_preview(frm, einvoice); + } + }) }; add_custom_button(__("Generate IRN"), action); @@ -236,4 +239,70 @@ const get_ewaybill_fields = (frm) => { 'default': frm.doc.gst_vehicle_type } ]; +}; + +const request_irn_generation = (frm, dialog) => { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_irn', + args: { doctype: frm.doc.doctype, docname: frm.doc.name }, + freeze: true, + callback: () => frm.reload_doc() || dialog.hide(), + error: () => dialog.hide() + }); +} + +const get_preview_dialog = (frm, action) => { + return new frappe.ui.Dialog({ + title: __("E Invoice Preview"), + wide: 1, + fields: [ + { + "label": "Preview", + "fieldname": "preview_html", + "fieldtype": "HTML" + } + ], + primary_action: () => action(frm), + primary_action_label: __('Generate IRN') + }); +} + +const show_einvoice_preview = (frm, einvoice) => { + const preview_dialog = get_preview_dialog(frm, request_irn_generation); + + // initialize empty e-invoice fields + einvoice.Irn = einvoice.AckNo = einvoice.AckDate = ''; + frm.doc.signed_einvoice = JSON.stringify(einvoice); + + // initialize preview wrapper + const $preview_wrapper = preview_dialog.get_field("preview_html").$wrapper; + $preview_wrapper.html( + `
    + +
    +
    ` + ); + + frappe.call({ + method: "frappe.www.printview.get_html_and_style", + args: { + doc: frm.doc, + print_format: "GST E-Invoice", + no_letterhead: 1 + }, + callback: function (r) { + if (!r.exc) { + $preview_wrapper.find(".print-format").html(r.message.html); + const style = ` + .print-format { font-size: 7.0pt; box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; } + .print-preview { min-height: 0px; } + .modal-dialog { width: 650px; } + ` + frappe.dom.set_style(style, "custom-print-style"); + preview_dialog.show(); + } + } + }); }; \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 532e0955bc2..ed7187846c7 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -94,7 +94,9 @@ def get_party_details(address_name): address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno')) address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt')) email_id = address.get('email_id') - phone = address.get('phone').replace(" ", "")[-10:] # get last 10 digit + phone = address.get('phone') + # get last 10 digit + phone = phone.replace(" ", "")[-10:] if phone else '' if state_code == 97: pincode = 999999 @@ -179,7 +181,7 @@ def get_item_list(invoice): item.cgst_amount += abs(item_tax_detail[1]) item.total_value = abs( - item.base_amount + item.igst_amount + item.sgst_amount + + item.taxable_value + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges ) einv_item = item_schema.format(item=item) @@ -197,8 +199,8 @@ def get_value_details(invoice): # discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0) disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') - value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else invoice.base_rounded_total - value_details.grand_total = abs(invoice.grand_total) if disable_rounded else invoice.rounded_total + value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else abs(invoice.base_rounded_total) + value_details.grand_total = abs(invoice.grand_total) if disable_rounded else abs(invoice.rounded_total) value_details.total_cgst_amt = 0 value_details.total_sgst_amt = 0 value_details.total_igst_amt = 0 @@ -301,12 +303,16 @@ def make_einvoice(invoice): errors = validate_einvoice(validations, einvoice) if errors: message = "\n".join([ - "E Invoice: ", json.dumps(einvoice, default=str, indent=4), + "E Invoice: ", json.dumps(einvoice, indent=4), "-" * 50, - "Errors: ", json.dumps(errors, default=str, indent=4) + "Errors: ", json.dumps(errors, indent=4) ]) frappe.log_error(title="E Invoice Validation Failed", message=message) - frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1) + if len(errors) > 1: + li = ['
  • '+ d +'
  • ' for d in errors] + frappe.throw("
      {}
    ".format(''.join(li)), title=_('E Invoice Validation Failed')) + else: + frappe.throw(errors[0], title=_('E Invoice Validation Failed')) return einvoice @@ -396,7 +402,7 @@ class GSPConnector(): "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, + "data": json.dumps(data, indent=4) if isinstance(data, dict) else data, "response": json.dumps(res, indent=4) if res else None }) request_log.insert(ignore_permissions=True) @@ -463,7 +469,7 @@ class GSPConnector(): def generate_irn(self): headers = self.get_headers() einvoice = make_einvoice(self.invoice) - data = json.dumps(einvoice) + data = json.dumps(einvoice, indent=4) try: res = self.make_request('post', self.generate_irn_url, headers, data) @@ -516,7 +522,7 @@ class GSPConnector(): 'Irn': irn, 'Cnlrsn': reason, 'Cnlrem': remark - }) + }, indent=4) try: res = self.make_request('post', self.cancel_irn_url, headers, data) @@ -555,7 +561,7 @@ class GSPConnector(): 'TrnDocNo': eway_bill_details.document_name, 'VehNo': eway_bill_details.vehicle_no, 'VehType': eway_bill_details.vehicle_type - }) + }, indent=4) try: res = self.make_request('post', self.generate_ewaybill_url, headers, data) @@ -587,7 +593,7 @@ class GSPConnector(): 'ewbNo': eway_bill, 'cancelRsnCode': reason, 'cancelRmrk': remark - }) + }, indent=4) try: res = self.make_request('post', self.cancel_ewaybill_url, headers, data) @@ -681,6 +687,11 @@ class GSPConnector(): self.invoice.flags.ignore_validate = True self.invoice.save() +@frappe.whitelist() +def get_einvoice(doctype, docname): + invoice = frappe.get_doc(doctype, docname) + return make_einvoice(invoice) + @frappe.whitelist() def generate_irn(doctype, docname): gsp_connector = GSPConnector(doctype, docname) From d22279ba3896121e8bd6997f63586a4d25e264ce Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 17:15:36 +0530 Subject: [PATCH 108/137] fix: minor ux issues --- erpnext/regional/india/e_invoice/einvoice.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 7466de1ae19..36ebb0e8baf 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -253,7 +253,7 @@ const request_irn_generation = (frm, dialog) => { const get_preview_dialog = (frm, action) => { return new frappe.ui.Dialog({ - title: __("E Invoice Preview"), + title: __("Preview"), wide: 1, fields: [ { @@ -271,7 +271,7 @@ const show_einvoice_preview = (frm, einvoice) => { const preview_dialog = get_preview_dialog(frm, request_irn_generation); // initialize empty e-invoice fields - einvoice.Irn = einvoice.AckNo = einvoice.AckDate = ''; + einvoice.Irn = einvoice.AckNo = ''; einvoice.AckDate = frappe.datetime.nowdate(); frm.doc.signed_einvoice = JSON.stringify(einvoice); // initialize preview wrapper @@ -296,9 +296,9 @@ const show_einvoice_preview = (frm, einvoice) => { if (!r.exc) { $preview_wrapper.find(".print-format").html(r.message.html); const style = ` - .print-format { font-size: 7.0pt; box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; } + .print-format { box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; } .print-preview { min-height: 0px; } - .modal-dialog { width: 650px; } + .modal-dialog { width: 720px; } ` frappe.dom.set_style(style, "custom-print-style"); preview_dialog.show(); From 08f082089e8108115804898085643e9a36cab41e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 17:31:31 +0530 Subject: [PATCH 109/137] fix: dialog is undefined --- erpnext/regional/india/e_invoice/einvoice.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 36ebb0e8baf..916d436e3f9 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -246,13 +246,12 @@ const request_irn_generation = (frm, dialog) => { method: 'erpnext.regional.india.e_invoice.utils.generate_irn', args: { doctype: frm.doc.doctype, docname: frm.doc.name }, freeze: true, - callback: () => frm.reload_doc() || dialog.hide(), - error: () => dialog.hide() + callback: () => frm.reload_doc() }); } const get_preview_dialog = (frm, action) => { - return new frappe.ui.Dialog({ + const dialog = new frappe.ui.Dialog({ title: __("Preview"), wide: 1, fields: [ @@ -262,16 +261,17 @@ const get_preview_dialog = (frm, action) => { "fieldtype": "HTML" } ], - primary_action: () => action(frm), + primary_action: () => action(frm) || dialog.hide(), primary_action_label: __('Generate IRN') }); + return dialog; } const show_einvoice_preview = (frm, einvoice) => { const preview_dialog = get_preview_dialog(frm, request_irn_generation); - // initialize empty e-invoice fields - einvoice.Irn = einvoice.AckNo = ''; einvoice.AckDate = frappe.datetime.nowdate(); + // initialize e-invoice fields + einvoice["Irn"] = einvoice["AckNo"] = ''; einvoice["AckDt"] = frappe.datetime.nowdate(); frm.doc.signed_einvoice = JSON.stringify(einvoice); // initialize preview wrapper From 554ce04e0df3292669dc6bc6cafe339a1e4351c7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 18:59:01 +0530 Subject: [PATCH 110/137] fix: error handling --- erpnext/regional/india/e_invoice/utils.py | 80 ++++++++++++++++------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index ed7187846c7..dc932a15e24 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -308,14 +308,17 @@ def make_einvoice(invoice): "Errors: ", json.dumps(errors, indent=4) ]) frappe.log_error(title="E Invoice Validation Failed", message=message) - if len(errors) > 1: - li = ['
  • '+ d +'
  • ' for d in errors] - frappe.throw("
      {}
    ".format(''.join(li)), title=_('E Invoice Validation Failed')) - else: - frappe.throw(errors[0], title=_('E Invoice Validation Failed')) + throw_error_list(errors, _('E Invoice Validation Failed')) return einvoice +def throw_error_list(errors, title): + if len(errors) > 1: + li = ['
  • '+ d +'
  • ' for d in errors] + frappe.throw("
      {}
    ".format(''.join(li)), title=title) + else: + frappe.throw(errors[0], title=title) + def validate_einvoice(validations, einvoice, errors=[]): for fieldname, field_validation in validations.items(): value = einvoice.get(fieldname, None) @@ -406,6 +409,7 @@ class GSPConnector(): "response": json.dumps(res, indent=4) if res else None }) request_log.insert(ignore_permissions=True) + frappe.db.commit() def fetch_auth_token(self): headers = { @@ -487,11 +491,11 @@ class GSPConnector(): Contact ERPNext support to resolve the issue.') else: - self.log_error(res) raise RequestFailed except RequestFailed: - self.raise_error() + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) except Exception: self.log_error(data) @@ -506,11 +510,11 @@ class GSPConnector(): if res.get('success'): return res.get('result') else: - self.log_error(res) raise RequestFailed except RequestFailed: - self.raise_error() + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) except Exception: self.log_error() @@ -536,11 +540,11 @@ class GSPConnector(): self.update_invoice() else: - self.log_error(res) raise RequestFailed except RequestFailed: - self.raise_error() + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) except Exception: self.log_error(data) @@ -577,11 +581,11 @@ class GSPConnector(): self.update_invoice() else: - self.log_error(res) raise RequestFailed except RequestFailed: - self.raise_error() + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) except Exception: self.log_error(data) @@ -608,15 +612,37 @@ class GSPConnector(): self.update_invoice() else: - self.log_error(res) raise RequestFailed except RequestFailed: - self.raise_error() + errors = self.sanitize_error_message(res.get('message')) + self.raise_error(errors=errors) except Exception: self.log_error(data) self.raise_error(True) + + def sanitize_error_message(self, message): + ''' + message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, + 3095 : Supplier GSTIN is inactive' + we search for string between ':' to extract error messages + errors = [ + ': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ', + ': Test' + ] + then we trim down the message by looping over errors + ''' + errors = re.findall(': [^:]+', message) + for idx, e in enumerate(errors): + # remove colons + errors[idx] = errors[idx].replace(':', '').strip() + # if not last + if idx != len(errors) - 1: + # remove last 7 chars eg: ', 3095 ' + errors[idx] = errors[idx][:-6] + + return errors def log_error(self, data={}): if not isinstance(data, dict): @@ -625,23 +651,27 @@ class GSPConnector(): seperator = "--" * 50 err_tb = traceback.format_exc() err_msg = str(sys.exc_info()[1]) - data = json.dumps(data, default=str, indent=4) + data = json.dumps(data, indent=4) message = "\n".join([ "Error", err_msg, seperator, "Data:", data, seperator, "Exception:", err_tb ]) - frappe.log_error(title="E Invoicing Error", message=message) + frappe.log_error(title=_('E Invoice Request Failed'), message=message) - def raise_error(self, raise_exception=False): - link_to_error_list = 'Error Log' - frappe.msgprint( - _('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' - ) + def raise_error(self, raise_exception=False, errors=[]): + title = _('E Invoice Request Failed') + if errors: + throw_error_list(errors, title) + else: + link_to_error_list = 'Error Log' + frappe.msgprint( + _('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list), + title=title, + raise_exception=raise_exception, + indicator='red' + ) def set_einvoice_data(self, res): enc_signed_invoice = res.get('SignedInvoice') From 54e9063215fda68735ad4ed4213cb5b8c9fa7eec Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 19:14:43 +0530 Subject: [PATCH 111/137] feat: add docs link to e invoice settings --- .../doctype/e_invoice_settings/e_invoice_settings.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js index 9ea43e70150..cc2d9f06d2d 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -1,4 +1,11 @@ // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('E Invoice Settings', {}); +frappe.ui.form.on('E Invoice Settings', { + refresh(frm) { + const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing'; + frm.dashboard.set_headline( + __("Read {0} for more information on E Invoicing features.", [`documentation`]) + ); + } +}); From 67cfb6c370ab415f18aa7232c0ff82f76f440e09 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 15 Dec 2020 09:32:02 +0530 Subject: [PATCH 112/137] fix(Asset): set current asset value before calculating difference amount (#24119) --- .../doctype/asset_value_adjustment/asset_value_adjustment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py index 155597e8565..e0c9319a02d 100644 --- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py +++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py @@ -12,8 +12,8 @@ from frappe.model.document import Document class AssetValueAdjustment(Document): def validate(self): self.validate_date() - self.set_difference_amount() self.set_current_asset_value() + self.set_difference_amount() def on_submit(self): self.make_depreciation_entry() From c51b4d041661d5b5b5346022f0b5ebb2a2eb6278 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Tue, 15 Dec 2020 14:41:35 +0530 Subject: [PATCH 113/137] fix: po_no getting removed on save --- erpnext/controllers/selling_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 5b7d76d0f08..6e091c527ff 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -375,7 +375,7 @@ class SellingController(StockController): self.set_pos_for_delivery_note() def set_pos_for_sales_invoice(self): - po_nos = [] + po_nos = [self.po_no] if self.po_no else [] if self.po_no: po_nos.append(self.po_no) self.get_po_nos('Sales Order', 'sales_order', po_nos) From a8cda3f013bd242e0d978f49d36980bb10a22894 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Tue, 15 Dec 2020 14:48:44 +0530 Subject: [PATCH 114/137] fix: po_no not cleaned up on save --- erpnext/controllers/selling_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 6e091c527ff..c355c1b6d4f 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -383,7 +383,7 @@ class SellingController(StockController): self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(',')))) def set_pos_for_delivery_note(self): - po_nos = [] + po_nos = [self.po_no] if self.po_no else [] if self.po_no: po_nos.append(self.po_no) self.get_po_nos('Sales Order', 'against_sales_order', po_nos) From 65680ada87917ac5e309cbbd174ebe7743b25dc2 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 16 Dec 2020 17:02:41 +0530 Subject: [PATCH 115/137] Revert "fix: po_not retaining on save" --- erpnext/controllers/selling_controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index c355c1b6d4f..5b7d76d0f08 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -375,7 +375,7 @@ class SellingController(StockController): self.set_pos_for_delivery_note() def set_pos_for_sales_invoice(self): - po_nos = [self.po_no] if self.po_no else [] + po_nos = [] if self.po_no: po_nos.append(self.po_no) self.get_po_nos('Sales Order', 'sales_order', po_nos) @@ -383,7 +383,7 @@ class SellingController(StockController): self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(',')))) def set_pos_for_delivery_note(self): - po_nos = [self.po_no] if self.po_no else [] + po_nos = [] if self.po_no: po_nos.append(self.po_no) self.get_po_nos('Sales Order', 'against_sales_order', po_nos) From 9f848fe65c352bb93b11ea617e5ef6023dd129ae Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 4 Dec 2020 18:07:46 +0530 Subject: [PATCH 116/137] fix: Tax template update on supplier --- erpnext/regional/india/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 87bd5f70a27..2b6eedec3f8 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -533,9 +533,6 @@ def get_address_details(data, doc, company_address, billing_address): if doc.gst_category == 'SEZ': data.toStateCode = 99 - if doc.gst_category == 'SEZ': - data.toStateCode = 99 - return data def get_item_list(data, doc): From 37a64b6485d55c0ff911604c62e380445bb12b0c Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 17 Nov 2020 20:34:51 +0530 Subject: [PATCH 117/137] fix: place of supply change on address change --- erpnext/public/js/controllers/buying.js | 1 + erpnext/public/js/utils.js | 13 +++++++++++++ erpnext/regional/india/utils.py | 3 +++ 3 files changed, 17 insertions(+) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index b45efa22656..853b67fadae 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -133,6 +133,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ supplier_address: function() { erpnext.utils.get_address_display(this.frm); erpnext.utils.set_taxes_from_address(this.frm, "supplier_address", "supplier_address", "supplier_address"); + erpnext.utils.set_place_of_supply(this.frm) }, buying_price_list: function() { diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 6e2409cf875..3727bb1c58c 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -116,6 +116,19 @@ $.extend(erpnext.utils, { } }, + set_place_of_supply: function(frm){ + frappe.call({ + method: "erpnext.regional.india.utils.get_place_of_supply", + args: { + "party_details": frm.doc, + "doctype": frm.doc.doctype + }, + callback: function(r){ + frm.set_value("place_of_supply", r.message) + } + }) + }, + add_indicator_for_multicompany: function(frm, info) { frm.dashboard.stats_area.removeClass('hidden'); frm.dashboard.stats_area_row.addClass('flex'); diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 2b6eedec3f8..beded408690 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -144,6 +144,9 @@ def test_method(): def get_place_of_supply(party_details, doctype): if not frappe.get_meta('Address').has_field('gst_state'): return + if isinstance(party_details, string_types): + party_details = json.loads(party_details) + party_details = frappe._dict(party_details) if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): address_name = party_details.customer_address or party_details.shipping_address_name From 79ff4d0e8f4aacc080f33ed61380d4f9b21315fe Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 18 Nov 2020 12:51:13 +0530 Subject: [PATCH 118/137] fix: reversing previous commits and adding condition in regional controller --- erpnext/public/js/controllers/buying.js | 1 - erpnext/public/js/utils.js | 13 ------------- erpnext/regional/india/utils.py | 3 --- 3 files changed, 17 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 853b67fadae..b45efa22656 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -133,7 +133,6 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ supplier_address: function() { erpnext.utils.get_address_display(this.frm); erpnext.utils.set_taxes_from_address(this.frm, "supplier_address", "supplier_address", "supplier_address"); - erpnext.utils.set_place_of_supply(this.frm) }, buying_price_list: function() { diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 3727bb1c58c..6e2409cf875 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -116,19 +116,6 @@ $.extend(erpnext.utils, { } }, - set_place_of_supply: function(frm){ - frappe.call({ - method: "erpnext.regional.india.utils.get_place_of_supply", - args: { - "party_details": frm.doc, - "doctype": frm.doc.doctype - }, - callback: function(r){ - frm.set_value("place_of_supply", r.message) - } - }) - }, - add_indicator_for_multicompany: function(frm, info) { frm.dashboard.stats_area.removeClass('hidden'); frm.dashboard.stats_area_row.addClass('flex'); diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index beded408690..2b6eedec3f8 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -144,9 +144,6 @@ def test_method(): def get_place_of_supply(party_details, doctype): if not frappe.get_meta('Address').has_field('gst_state'): return - if isinstance(party_details, string_types): - party_details = json.loads(party_details) - party_details = frappe._dict(party_details) if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): address_name = party_details.customer_address or party_details.shipping_address_name From aa2a7abe4e2a584715276cdd13820e7db924bb76 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 18 Nov 2020 15:57:16 +0530 Subject: [PATCH 119/137] fix: linter issue for translation syntax --- erpnext/regional/india/utils.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 2b6eedec3f8..76db1363179 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -93,7 +93,8 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): total += digit factor = 2 if factor == 1 else 1 if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: - frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label)) + frappe.throw(_("""Invalid {0}! The check digit validation has failed. + Please ensure you've typed the {0} correctly.""").format(label)) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): @@ -168,7 +169,7 @@ def get_regional_address_details(party_details, doctype, company): if is_internal_transfer(party_details, doctype): party_details.taxes_and_charges = '' party_details.taxes = '' - return party_details + return if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" @@ -176,11 +177,11 @@ def get_regional_address_details(party_details, doctype, company): get_tax_template_for_sez(party_details, master_doctype, company, 'Customer') get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges'): + if party_details.get('taxes_and_charges') and return_taxes: return party_details if not party_details.company_gstin: - return party_details + return elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" @@ -188,15 +189,15 @@ def get_regional_address_details(party_details, doctype, company): get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier') get_tax_template_based_on_category(master_doctype, company, party_details) - if party_details.get('taxes_and_charges'): + if party_details.get('taxes_and_charges') and return_taxes: return party_details if not party_details.supplier_gstin: - return party_details + return - if not party_details.place_of_supply: return party_details + if not party_details.place_of_supply: return - if not party_details.company_gstin: return party_details + if not party_details.company_gstin: return if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice", @@ -206,11 +207,12 @@ def get_regional_address_details(party_details, doctype, company): default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2]) if not default_tax: - return party_details + return party_details["taxes_and_charges"] = default_tax party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) - return party_details + if return_taxes: + return party_details def update_party_details(party_details, doctype): for address_field in ['shipping_address', 'company_address', 'supplier_address', 'shipping_address_name', 'customer_address']: @@ -249,7 +251,7 @@ def get_tax_template(master_doctype, company, is_inter_state, state_code): if tax_category.gst_state == number_state_mapping[state_code] or \ (not default_tax and not tax_category.gst_state): default_tax = frappe.db.get_value(master_doctype, - {'company': company, 'disabled': 0, 'tax_category': tax_category.name}, 'name') + {'disabled': 0, 'tax_category': tax_category.name}, 'name') return default_tax def get_tax_template_for_sez(party_details, master_doctype, company, party_type): From e5121183208debaec01b1671833cc3a0c805db25 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 17 Dec 2020 13:26:11 +0530 Subject: [PATCH 120/137] fix: indentation --- .../stock_settings/stock_settings.json | 1906 ++++++++--------- 1 file changed, 953 insertions(+), 953 deletions(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 4b7e6444954..887d6cb7704 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -1,955 +1,955 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2013-06-24 16:37:54", - "custom": 0, - "description": "Settings", - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Item Code", - "fetch_if_empty": 0, - "fieldname": "item_naming_by", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Item Naming By", - "length": 0, - "no_copy": 0, - "options": "Item Code\nNaming Series", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "item_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Item Group", - "length": 0, - "no_copy": 0, - "options": "Item Group", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default Stock UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "default_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sample_retention_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sample Retention Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "valuation_method", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Valuation Method", - "length": 0, - "no_copy": 0, - "options": "FIFO\nMoving Average", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", - "fetch_if_empty": 0, - "fieldname": "over_delivery_receipt_allowance", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Over Delivery/Receipt Allowance (%)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Stop", - "fetch_if_empty": 0, - "fieldname": "action_if_quality_inspection_is_not_submitted", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Action if Quality inspection is not submitted", - "length": 0, - "no_copy": 0, - "options": "Stop\nWarn", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "show_barcode_field", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Barcode Field", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "clean_description_html", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Convert Item Description to Clean HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_insert_price_list_rate_if_missing", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto insert Price List rate if missing", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "allow_negative_stock", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Negative Stock", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "automatically_set_serial_nos_based_on_fifo", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Automatically Set Serial Nos based on FIFO", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "set_qty_in_transactions_based_on_serial_no_input", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Set Qty in Transactions based on Serial No Input", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_material_request", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Auto Material Request", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_indent", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Raise Material Request when stock reaches re-order level", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "reorder_email_notify", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notify by Email on creation of automatic Material Request", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "freeze_stock_entries", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Freeze Stock Entries", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_frozen_upto", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Stock Frozen Upto", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_frozen_upto_days", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Freeze Stocks Older Than [Days]", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "stock_auth_role", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Role Allowed to edit frozen stock", - "length": 0, - "no_copy": 0, - "options": "Role", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "batch_id_sb", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Batch Identification", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "use_naming_series", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Use Naming Series", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "BATCH-", - "depends_on": "eval:doc.use_naming_series==1", - "fetch_if_empty": 0, - "fieldname": "naming_series_prefix", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Naming Series Prefix", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-cog", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2019-07-04 01:19:07.738045", - "modified_by": "Administrator", - "module": "Stock", - "name": "Stock Settings", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2013-06-24 16:37:54", + "custom": 0, + "description": "Settings", + "docstatus": 0, + "doctype": "DocType", + "editable_grid": 0, + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Item Code", + "fetch_if_empty": 0, + "fieldname": "item_naming_by", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Item Naming By", + "length": 0, + "no_copy": 0, + "options": "Item Code\nNaming Series", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fetch_if_empty": 0, + "fieldname": "item_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Default Item Group", + "length": 0, + "no_copy": 0, + "options": "Item Group", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Default Stock UOM", + "length": 0, + "no_copy": 0, + "options": "UOM", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "default_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "sample_retention_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sample Retention Warehouse", + "length": 0, + "no_copy": 0, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "valuation_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Valuation Method", + "length": 0, + "no_copy": 0, + "options": "FIFO\nMoving Average", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", + "fetch_if_empty": 0, + "fieldname": "over_delivery_receipt_allowance", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Over Delivery/Receipt Allowance (%)", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Stop", + "fetch_if_empty": 0, + "fieldname": "action_if_quality_inspection_is_not_submitted", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Action if Quality inspection is not submitted", + "length": 0, + "no_copy": 0, + "options": "Stop\nWarn", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "show_barcode_field", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Show Barcode Field", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "clean_description_html", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Convert Item Description to Clean HTML", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_insert_price_list_rate_if_missing", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto insert Price List rate if missing", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "allow_negative_stock", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allow Negative Stock", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "automatically_set_serial_nos_based_on_fifo", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Automatically Set Serial Nos based on FIFO", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fetch_if_empty": 0, + "fieldname": "set_qty_in_transactions_based_on_serial_no_input", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Set Qty in Transactions based on Serial No Input", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_material_request", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Auto Material Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "auto_indent", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Raise Material Request when stock reaches re-order level", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "reorder_email_notify", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notify by Email on creation of automatic Material Request", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "freeze_stock_entries", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Freeze Stock Entries", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_frozen_upto", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Stock Frozen Upto", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_frozen_upto_days", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Freeze Stocks Older Than [Days]", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "stock_auth_role", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Role Allowed to edit frozen stock", + "length": 0, + "no_copy": 0, + "options": "Role", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "batch_id_sb", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Batch Identification", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fetch_if_empty": 0, + "fieldname": "use_naming_series", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Use Naming Series", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "BATCH-", + "depends_on": "eval:doc.use_naming_series==1", + "fetch_if_empty": 0, + "fieldname": "naming_series_prefix", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Naming Series Prefix", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "icon-cog", + "idx": 1, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2019-07-04 01:19:07.738045", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Settings", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "Stock Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_order": "ASC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 } \ No newline at end of file From 6f2a34f6d4aaff6e9e20d8e3caf5365ac26d95cd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 18 Dec 2020 13:30:25 +0530 Subject: [PATCH 121/137] fix: replace pin code with postal code --- erpnext/templates/includes/cart/cart_address.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html index 60de3af17bf..aa25c885fee 100644 --- a/erpnext/templates/includes/cart/cart_address.html +++ b/erpnext/templates/includes/cart/cart_address.html @@ -109,7 +109,7 @@ frappe.ready(() => { reqd: 1 }, { - label: __('Pin Code'), + label: __('Postal Code'), fieldname: 'pincode', fieldtype: 'Data' }, From 2274162107e0c5f191589fc99219e27d2591fc99 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Fri, 18 Dec 2020 20:18:17 +0530 Subject: [PATCH 122/137] fix: modified --- erpnext/stock/doctype/stock_settings/stock_settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 887d6cb7704..17f0d484413 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -918,7 +918,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2019-07-04 01:19:07.738045", + "modified": "2020-12-18 19:56:06.343314", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", From 8c8ab4140e773142128087455554484f17dbc2a2 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 21 Dec 2020 18:41:40 +0530 Subject: [PATCH 123/137] fix: remove test supplier pincode --- erpnext/regional/india/e_invoice/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index dc932a15e24..c6c1a921d71 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -264,7 +264,6 @@ def make_einvoice(invoice): doc_details = get_doc_details(invoice) value_details = get_value_details(invoice) seller_details = get_party_details(invoice.company_address) - seller_details.update({ 'pincode': 193502 }) if invoice.gst_category == 'Overseas': buyer_details = get_overseas_address_details(invoice.customer_address) From a0c8106342a486f9f0d4af93b82afc0e38271738 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 22 Dec 2020 15:55:50 +0530 Subject: [PATCH 124/137] feat: multiple gstins for e invoicing --- .../e_invoice_settings.json | 68 +++---------------- .../e_invoice_settings/e_invoice_settings.py | 9 ++- .../doctype/e_invoice_user/__init__.py | 0 .../e_invoice_user/e_invoice_user.json | 48 +++++++++++++ .../doctype/e_invoice_user/e_invoice_user.py | 10 +++ erpnext/regional/india/e_invoice/utils.py | 36 +++++++--- 6 files changed, 101 insertions(+), 70 deletions(-) create mode 100644 erpnext/regional/doctype/e_invoice_user/__init__.py create mode 100644 erpnext/regional/doctype/e_invoice_user/e_invoice_user.json create mode 100644 erpnext/regional/doctype/e_invoice_user/e_invoice_user.py diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json index 8816cbde3e4..4dcb22a54c7 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -7,16 +7,9 @@ "field_order": [ "enable", "section_break_2", - "client_id", - "client_secret", - "public_key", - "column_break_3", - "gstin", - "username", - "password", + "credentials", "auth_token", - "token_expiry", - "sek" + "token_expiry" ], "fields": [ { @@ -30,47 +23,6 @@ "fieldname": "section_break_2", "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", "fieldtype": "Data", @@ -78,23 +30,23 @@ "read_only": 1 }, { - "fieldname": "sek", - "fieldtype": "Data", - "hidden": 1, - "read_only": 1 - }, - { - "default": "0", "fieldname": "token_expiry", "fieldtype": "Datetime", "hidden": 1, "read_only": 1 + }, + { + "fieldname": "credentials", + "fieldtype": "Table", + "label": "Credentials", + "mandatory_depends_on": "enable", + "options": "E Invoice User" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-11-03 21:23:50.277305", + "modified": "2020-12-22 15:34:57.280044", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Settings", diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py index 2a4f00365f3..c24ad886ea1 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -1,9 +1,14 @@ # -*- 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 import _ from frappe.model.document import 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.')) + diff --git a/erpnext/regional/doctype/e_invoice_user/__init__.py b/erpnext/regional/doctype/e_invoice_user/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json new file mode 100644 index 00000000000..dd9d99773a3 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json @@ -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 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py new file mode 100644 index 00000000000..056c54f069d --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.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 EInvoiceUser(Document): + pass diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index c6c1a921d71..4436c95438f 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -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 doc.docstatus == 0 and doc._action == 'save': - if doc.irn: - frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) + # if doc.irn: + # frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) if len(doc.name) > 16: title = _('Document Name Too Long') 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) value_details = get_value_details(invoice) seller_details = get_party_details(invoice.company_address) + seller_details.update({ 'pincode': '504273' }) if invoice.gst_category == 'Overseas': buyer_details = get_overseas_address_details(invoice.customer_address) @@ -372,8 +373,9 @@ class RequestFailed(Exception): pass class GSPConnector(): 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.credentials = self.get_credentials() self.base_url = 'https://gsp.adaequare.com/' 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.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): - 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() - return self.credentials.auth_token + return self.e_invoice_settings.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 @@ -412,15 +428,15 @@ class GSPConnector(): def fetch_auth_token(self): headers = { - 'gspappid': self.credentials.client_id, - 'gspappsecret': self.credentials.client_secret + 'gspappid': frappe.conf.einvoice_client_id, + 'gspappsecret': frappe.conf.einvoice_client_secret } res = {} try: 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() + self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token')) + self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in')) + self.e_invoice_settings.save() except Exception: self.log_error(res) From 7f69820f04fcaf175ce0601fefe96281b0622efb Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 23 Dec 2020 14:35:54 +0530 Subject: [PATCH 125/137] fix: uncomment test condition --- erpnext/regional/india/e_invoice/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 4436c95438f..896e3e72874 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -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 doc.docstatus == 0 and doc._action == 'save': - # if doc.irn: - # frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) + if doc.irn: + frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) if len(doc.name) > 16: title = _('Document Name Too Long') msg = _('As you have E-Invoicing enabled, To be able to generate IRN for this invoice, ') From 709d50ad21169fa097f1b951d5fb8b9669c2643d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 23 Dec 2020 15:19:22 +0530 Subject: [PATCH 126/137] fix: remove test pincode --- erpnext/regional/india/e_invoice/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 896e3e72874..36915206aab 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -264,7 +264,6 @@ def make_einvoice(invoice): doc_details = get_doc_details(invoice) value_details = get_value_details(invoice) seller_details = get_party_details(invoice.company_address) - seller_details.update({ 'pincode': '504273' }) if invoice.gst_category == 'Overseas': buyer_details = get_overseas_address_details(invoice.customer_address) From da83ec0f0d3d73533bc518c939f04ee47d6be959 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 23 Dec 2020 15:21:50 +0530 Subject: [PATCH 127/137] fix: cannot cancel irn without submitting sales invoice --- erpnext/regional/india/e_invoice/einvoice.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 916d436e3f9..d794cd48184 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -19,7 +19,7 @@ erpnext.setup_einvoice_actions = (doctype) => { frm.set_df_property('ewaybill', 'read_only', 1); } - if (docstatus == 0 && !irn && !__unsaved) { + if (!irn && !__unsaved) { const action = () => { frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.get_einvoice', @@ -35,7 +35,7 @@ erpnext.setup_einvoice_actions = (doctype) => { add_custom_button(__("Generate IRN"), action); } - if (docstatus == 1 && irn && !irn_cancelled && !ewaybill) { + if (irn && !irn_cancelled && !ewaybill) { const fields = [ { "label": "Reason", @@ -108,7 +108,7 @@ erpnext.setup_einvoice_actions = (doctype) => { add_custom_button(__("Generate E-Way Bill"), action); } - if (docstatus == 1 && irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { + if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { const fields = [ { "label": "Reason", From 00306d8fd0103650dbfaf53fe2fc8b2c32e9bc0c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 23 Dec 2020 15:41:10 +0530 Subject: [PATCH 128/137] chore: code cleanup --- .../india/e_invoice/einv_template.json | 30 ++-- erpnext/regional/india/e_invoice/utils.py | 170 ++++++++++-------- 2 files changed, 111 insertions(+), 89 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json index bf6b855d2b8..e5751da5612 100644 --- a/erpnext/regional/india/e_invoice/einv_template.json +++ b/erpnext/regional/india/e_invoice/einv_template.json @@ -1,11 +1,11 @@ {{ "Version": "1.1", "TranDtls": {{ - "TaxSch": "{trans_details.tax_scheme}", - "SupTyp": "{trans_details.supply_type}", - "RegRev": "{trans_details.reverse_charge}", - "EcmGstin": "{trans_details.ecom_gstin}", - "IgstOnIntra": "{trans_details.igst_on_intra}" + "TaxSch": "{transaction_details.tax_scheme}", + "SupTyp": "{transaction_details.supply_type}", + "RegRev": "{transaction_details.reverse_charge}", + "EcmGstin": "{transaction_details.ecom_gstin}", + "IgstOnIntra": "{transaction_details.igst_on_intra}" }}, "DocDtls": {{ "Typ": "{doc_details.invoice_type}", @@ -59,16 +59,16 @@ {item_list} ], "ValDtls": {{ - "AssVal": "{value_details.base_net_total}", - "CgstVal": "{value_details.total_cgst_amt}", - "SgstVal": "{value_details.total_sgst_amt}", - "IgstVal": "{value_details.total_igst_amt}", - "CesVal": "{value_details.total_cess_amt}", - "Discount": "{value_details.invoice_discount_amt}", - "RndOffAmt": "{value_details.round_off}", - "OthChrg": "{value_details.total_other_charges}", - "TotInvVal": "{value_details.base_grand_total}", - "TotInvValFc": "{value_details.grand_total}" + "AssVal": "{invoice_value_details.base_net_total}", + "CgstVal": "{invoice_value_details.total_cgst_amt}", + "SgstVal": "{invoice_value_details.total_sgst_amt}", + "IgstVal": "{invoice_value_details.total_igst_amt}", + "CesVal": "{invoice_value_details.total_cess_amt}", + "Discount": "{invoice_value_details.invoice_discount_amt}", + "RndOffAmt": "{invoice_value_details.round_off}", + "OthChrg": "{invoice_value_details.total_other_charges}", + "TotInvVal": "{invoice_value_details.base_grand_total}", + "TotInvValFc": "{invoice_value_details.grand_total}" }}, "PayDtls": {{ "Nm": "{payment_details.payee_name}", diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 36915206aab..52f980676a5 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -29,15 +29,7 @@ def validate_einvoice_fields(doc): if doc.irn: frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed')) if len(doc.name) > 16: - title = _('Document Name Too Long') - msg = _('As you have E-Invoicing enabled, To be able to generate IRN for this invoice, ') - msg += _('document name {} exceed 16 letters. ').format(bold(_('should not'))) - msg += '

    ' - msg += _('You must {} your {} in order to have document name of {} length 16. ').format( - bold(_('modify')), bold(_('naming series')), bold(_('maximum')) - ) - msg += _('Please account for ammended document names too. ') - frappe.throw(msg, title=title) + raise_document_name_too_long_error() elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn: frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN')) @@ -45,12 +37,23 @@ def validate_einvoice_fields(doc): elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled: frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed')) +def raise_document_name_too_long_error(): + title = _('Document ID Too Long') + msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ') + msg += _('document id {} exceed 16 letters. ').format(bold(_('should not'))) + msg += '

    ' + msg += _('You must {} your {} in order to have document id of {} length 16. ').format( + bold(_('modify')), bold(_('naming series')), bold(_('maximum')) + ) + msg += _('Please account for ammended documents too. ') + frappe.throw(msg, title=title) + def read_json(name): file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name)) with open(file_path, 'r') as f: return cstr(f.read()) -def get_trans_details(invoice): +def get_transaction_details(invoice): supply_type = '' if invoice.gst_category == 'Registered Regular': supply_type = 'B2B' elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP' @@ -59,10 +62,8 @@ def get_trans_details(invoice): if not supply_type: rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export') - frappe.throw( - _('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export), - title=_('Invalid Supply Type') - ) + frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export), + title=_('Invalid Supply Type')) return frappe._dict(dict( tax_scheme='GST', @@ -97,7 +98,9 @@ def get_party_details(address_name): phone = address.get('phone') # get last 10 digit phone = phone.replace(" ", "")[-10:] if phone else '' + if state_code == 97: + # according to einvoice standard pincode = 999999 return frappe._dict(dict( @@ -136,90 +139,104 @@ def get_overseas_address_details(address_name): def get_item_list(invoice): item_list = [] - gst_accounts = get_gst_accounts(invoice.company) - gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] for d in invoice.items: - item_schema = read_json('einv_item_template') + einvoice_item_schema = read_json('einv_item_template') item = frappe._dict({}) item.update(d.as_dict()) + item.sr_no = d.idx - item.description = d.item_name - item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' - item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None - item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None item.qty = abs(item.qty) + item.description = d.item_name + item.taxable_value = abs(item.base_net_amount) + item.discount_amount = abs(item.discount_amount * item.qty) item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate) item.gross_amount = abs(item.unit_rate * item.qty) - item.discount_amount = abs(item.discount_amount * item.qty) - item.taxable_value = abs(item.base_net_amount) - for attr in [ - 'tax_rate', 'cess_rate', 'cess_nadv_amount', - 'cgst_amount', 'sgst_amount', 'igst_amount', - 'cess_amount', 'cess_nadv_amount', 'other_charges' - ]: - item[attr] = 0 + item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None + item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None + item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' - for t in invoice.taxes: - item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) - if t.account_head in gst_accounts_list: - if t.account_head in gst_accounts.cess_account: - if t.charge_type == 'On Item Quantity': - item.cess_nadv_amount += abs(item_tax_detail[1]) - else: - item.cess_rate += item_tax_detail[0] - item.cess_amount += abs(item_tax_detail[1]) - elif t.account_head in gst_accounts.igst_account: - item.tax_rate += item_tax_detail[0] - item.igst_amount += abs(item_tax_detail[1]) - elif t.account_head in gst_accounts.sgst_account: - item.tax_rate += item_tax_detail[0] - item.sgst_amount += abs(item_tax_detail[1]) - elif t.account_head in gst_accounts.cgst_account: - item.tax_rate += item_tax_detail[0] - item.cgst_amount += abs(item_tax_detail[1]) + item = update_item_taxes(invoice, item) item.total_value = abs( item.taxable_value + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges ) - einv_item = item_schema.format(item=item) + einv_item = einvoice_item_schema.format(item=item) item_list.append(einv_item) return ', '.join(item_list) -def get_value_details(invoice): +def update_item_taxes(invoice, item): gst_accounts = get_gst_accounts(invoice.company) gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] - value_details = frappe._dict(dict()) - value_details.base_net_total = abs(invoice.base_net_total) - value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0 + for attr in [ + 'tax_rate', 'cess_rate', 'cess_nadv_amount', + 'cgst_amount', 'sgst_amount', 'igst_amount', + 'cess_amount', 'cess_nadv_amount', 'other_charges' + ]: + item[attr] = 0 + + for t in invoice.taxes: + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) + if t.account_head in gst_accounts_list: + if t.account_head in gst_accounts.cess_account: + if t.charge_type == 'On Item Quantity': + item.cess_nadv_amount += abs(item_tax_detail[1]) + else: + item.cess_rate += item_tax_detail[0] + item.cess_amount += abs(item_tax_detail[1]) + elif t.account_head in gst_accounts.igst_account: + item.tax_rate += item_tax_detail[0] + item.igst_amount += abs(item_tax_detail[1]) + elif t.account_head in gst_accounts.sgst_account: + item.tax_rate += item_tax_detail[0] + item.sgst_amount += abs(item_tax_detail[1]) + elif t.account_head in gst_accounts.cgst_account: + item.tax_rate += item_tax_detail[0] + item.cgst_amount += abs(item_tax_detail[1]) + + return item + +def get_invoice_value_details(invoice): + invoice_value_details = frappe._dict(dict()) + invoice_value_details.base_net_total = abs(invoice.base_net_total) + invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0 # discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off - value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0) + invoice_value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0) disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') - value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else abs(invoice.base_rounded_total) - value_details.grand_total = abs(invoice.grand_total) if disable_rounded else abs(invoice.rounded_total) - value_details.total_cgst_amt = 0 - value_details.total_sgst_amt = 0 - value_details.total_igst_amt = 0 - value_details.total_cess_amt = 0 - value_details.total_other_charges = 0 + invoice_value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else abs(invoice.base_rounded_total) + invoice_value_details.grand_total = abs(invoice.grand_total) if disable_rounded else abs(invoice.rounded_total) + + invoice_value_details = update_invoice_taxes(invoice, invoice_value_details) + + return invoice_value_details + +def update_invoice_taxes(invoice, invoice_value_details): + gst_accounts = get_gst_accounts(invoice.company) + gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d] + + invoice_value_details.total_cgst_amt = 0 + invoice_value_details.total_sgst_amt = 0 + invoice_value_details.total_igst_amt = 0 + invoice_value_details.total_cess_amt = 0 + invoice_value_details.total_other_charges = 0 for t in invoice.taxes: if t.account_head in gst_accounts_list: if t.account_head in gst_accounts.cess_account: - value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) + invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) elif t.account_head in gst_accounts.igst_account: - value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount) + invoice_value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount) elif t.account_head in gst_accounts.sgst_account: - value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount) + invoice_value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount) elif t.account_head in gst_accounts.cgst_account: - value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount) + invoice_value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount) else: - value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount) + invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount) - return value_details + return invoice_value_details def get_payment_details(invoice): payee_name = invoice.company @@ -259,10 +276,10 @@ def get_eway_bill_details(invoice): def make_einvoice(invoice): schema = read_json('einv_template') - trans_details = get_trans_details(invoice) + transaction_details = get_transaction_details(invoice) item_list = get_item_list(invoice) doc_details = get_doc_details(invoice) - value_details = get_value_details(invoice) + invoice_value_details = get_invoice_value_details(invoice) seller_details = get_party_details(invoice.company_address) if invoice.gst_category == 'Overseas': @@ -290,9 +307,9 @@ def make_einvoice(invoice): dispatch_details = period_details = export_details = frappe._dict({}) einvoice = schema.format( - trans_details=trans_details, doc_details=doc_details, dispatch_details=dispatch_details, + transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details, seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details, - item_list=item_list, value_details=value_details, payment_details=payment_details, + item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details, period_details=period_details, prev_doc_details=prev_doc_details, export_details=export_details, eway_bill_details=eway_bill_details ) @@ -406,8 +423,11 @@ class GSPConnector(): return self.e_invoice_settings.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) + if request_type == 'post': + res = make_post_request(url, headers=headers, data=data) + else: + res = make_get_request(url, headers=headers, data=data) + self.log_request(url, headers, data, res) return res @@ -495,7 +515,8 @@ class GSPConnector(): self.set_einvoice_data(res.get('result')) elif '2150' in res.get('message'): - # IRN already generated + # IRN already generated but not updated in invoice + # Extract the IRN from the response description and fetch irn details irn = res.get('result')[0].get('Desc').get('Irn') irn_details = self.get_irn_details(irn) if irn_details: @@ -638,9 +659,10 @@ class GSPConnector(): def sanitize_error_message(self, message): ''' + On validation errors, response message looks something like this: message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 : Supplier GSTIN is inactive' - we search for string between ':' to extract error messages + we search for string between ':' to extract the error messages errors = [ ': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ', ': Test' From be58194678d46b3429902a07211d19913ab433dc Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 24 Dec 2020 10:39:24 +0530 Subject: [PATCH 129/137] fix: sider issues --- erpnext/regional/india/e_invoice/einvoice.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index d794cd48184..5ecbb0ff502 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -4,10 +4,11 @@ erpnext.setup_einvoice_actions = (doctype) => { const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); const supply_type = frm.doc.gst_category; const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type); + const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin; - if (!einvoicing_enabled || !valid_supply_type) return; + if (!einvoicing_enabled || !valid_supply_type || company_transaction) return; - const { doctype, docstatus, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; + const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; const add_custom_button = (label, action) => { if (!frm.custom_buttons[label]) { @@ -29,7 +30,7 @@ erpnext.setup_einvoice_actions = (doctype) => { const einvoice = res.message; show_einvoice_preview(frm, einvoice); } - }) + }); }; add_custom_button(__("Generate IRN"), action); @@ -241,14 +242,14 @@ const get_ewaybill_fields = (frm) => { ]; }; -const request_irn_generation = (frm, dialog) => { +const request_irn_generation = (frm) => { frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.generate_irn', args: { doctype: frm.doc.doctype, docname: frm.doc.name }, freeze: true, callback: () => frm.reload_doc() }); -} +}; const get_preview_dialog = (frm, action) => { const dialog = new frappe.ui.Dialog({ @@ -265,7 +266,7 @@ const get_preview_dialog = (frm, action) => { primary_action_label: __('Generate IRN') }); return dialog; -} +}; const show_einvoice_preview = (frm, einvoice) => { const preview_dialog = get_preview_dialog(frm, request_irn_generation); @@ -298,8 +299,8 @@ const show_einvoice_preview = (frm, einvoice) => { const style = ` .print-format { box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; } .print-preview { min-height: 0px; } - .modal-dialog { width: 720px; } - ` + .modal-dialog { width: 720px; }`; + frappe.dom.set_style(style, "custom-print-style"); preview_dialog.show(); } From ec59c6cebc85a28cb4cf7d21b7f8bee9486ef228 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 17 Dec 2020 00:12:17 +0530 Subject: [PATCH 130/137] fix: do not manufacture same serial no multiple times --- .../purchase_receipt/test_purchase_receipt.py | 2 +- erpnext/stock/doctype/serial_no/serial_no.py | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 26bcd457449..90bd4f5afd8 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -453,7 +453,7 @@ class TestPurchaseReceipt(unittest.TestCase): se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, serial_no=serial_no, basic_rate=100, do_not_submit=True) - self.assertRaises(SerialNoDuplicateError, se.submit) + se.submit() def test_auto_asset_creation(self): asset_item = "Test Asset Item" diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index f8885a91edc..325175675a9 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.model.naming import make_autoname -from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate +from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate, get_link_to_form from erpnext.stock.get_item_details import get_reserved_qty_for_so from frappe import _, ValidationError @@ -238,7 +238,7 @@ def validate_serial_no(sle, item_det): for serial_no in serial_nos: if frappe.db.exists("Serial No", serial_no): sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order", - "delivery_document_no", "delivery_document_type", "warehouse", + "delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type", "purchase_document_no", "company"], as_dict=1) if sr.item_code!=sle.item_code: @@ -246,9 +246,10 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no, sle.item_code), SerialNoItemError) - if cint(sle.actual_qty) > 0 and has_duplicate_serial_no(sr, sle): - frappe.throw(_("Serial No {0} has already been received").format(serial_no), - SerialNoDuplicateError) + if cint(sle.actual_qty) > 0 and has_serial_no_exists(sr, sle): + doc_name = frappe.bold(get_link_to_form(sr.purchase_document_type, sr.purchase_document_no)) + frappe.throw(_("Serial No {0} has already been received in the {1} #{2}") + .format(frappe.bold(serial_no), sr.purchase_document_type, doc_name), SerialNoDuplicateError) if (sr.delivery_document_no and sle.voucher_type not in ['Stock Entry', 'Stock Reconciliation'] and sle.voucher_type == sr.delivery_document_type): @@ -339,7 +340,7 @@ def validate_so_serial_no(sr, sales_order,): only deliver reserved {1} against {0}. Serial No {2} cannot be delivered""").format(sales_order, sr.item_code, sr.name)) -def has_duplicate_serial_no(sn, sle): +def has_serial_no_exists(sn, sle): if (sn.warehouse and not sle.skip_serial_no_validaiton and sle.voucher_type != 'Stock Reconciliation'): return True @@ -349,12 +350,13 @@ def has_duplicate_serial_no(sn, sle): status = False if sn.purchase_document_no: - if sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and \ - sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]: + if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and + sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]): status = True - if status and sle.voucher_type == 'Stock Entry' and \ - frappe.db.get_value('Stock Entry', sle.voucher_no, 'purpose') != 'Material Receipt': + # If status is receipt then system will allow to in-ward the delivered serial no + if (status and sle.voucher_type == 'Stock Entry' and frappe.db.get_value('Stock Entry', + sle.voucher_no, 'purpose') in ("Material Receipt", "Material Transfer")): status = False return status @@ -408,7 +410,7 @@ def auto_make_serial_nos(args): if is_new: created_numbers.append(sr.name) - form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers)) + form_links = list(map(lambda d: get_link_to_form('Serial No', d), created_numbers)) if len(form_links) == 1: frappe.msgprint(_("Serial No {0} created").format(form_links[0])) elif len(form_links) > 0: From 5f3635fbaf9ce556e9cbfc43352acbcccd076ec7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 24 Dec 2020 21:12:06 +0530 Subject: [PATCH 131/137] fix: travis --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 52f980676a5..1b746d5e10a 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -734,7 +734,7 @@ class GSPConnector(): _file = frappe.new_doc('File') _file.update({ - 'file_name': f'QRCode_{docname}.png', + 'file_name': 'QRCode_{}.png'.format(docname), 'attached_to_doctype': doctype, 'attached_to_name': docname, 'content': 'qrcode', From 246ef881a0397b7b175e715bbf1550b12bd6178f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 24 Dec 2020 21:10:16 +0530 Subject: [PATCH 132/137] fix: e invoice request log permissions --- .../e_invoice_request_log.json | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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 index 088d5cd7627..5c1c79dc047 100644 --- 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 @@ -64,7 +64,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-08 14:50:33.820452", + "modified": "2020-12-24 21:09:38.882866", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Request Log", @@ -78,6 +78,24 @@ "report": 1, "role": "System Manager", "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1 } ], "sort_field": "modified", From 7d666ccfb42a0c2b9410b21d84088086ab9856aa Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 28 Dec 2020 15:07:12 +0530 Subject: [PATCH 133/137] fix: do not override the manually added valuation rate in stock entry --- erpnext/stock/doctype/stock_entry/stock_entry.js | 9 +++++---- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 218ee130c2a..ee53e3e0e35 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -322,11 +322,12 @@ frappe.ui.form.on('Stock Entry', { method: "erpnext.stock.get_item_details.get_serial_no", args: {"args": args}, callback: function(r) { - if (!r.exe && r.message){ + if (!r.exe && r.message) { frappe.model.set_value(cdt, cdn, "serial_no", r.message); - } - if (callback) { - callback(); + + if (callback) { + callback(); + } } } }); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 123d4a96e8d..117802297b6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -83,7 +83,7 @@ class StockEntry(StockController): self.set_incoming_rate() self.validate_serialized_batch() self.set_actual_qty() - self.calculate_rate_and_amount() + self.calculate_rate_and_amount(update_finished_item_rate=False) def on_submit(self): @@ -460,7 +460,7 @@ class StockEntry(StockController): scrap_material_cost += flt(d.basic_amount) number_of_fg_items = len([t.t_warehouse for t in self.get("items") if t.t_warehouse]) - if (fg_basic_rate == 0.0 and number_of_fg_items == 1) or update_finished_item_rate: + if number_of_fg_items == 1 or update_finished_item_rate: self.set_basic_rate_for_finished_goods(raw_material_cost, scrap_material_cost) def get_args_for_incoming_rate(self, item): From 45dbfaa7f99648f3719bc7081fe039995e135308 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 28 Dec 2020 13:32:09 +0530 Subject: [PATCH 134/137] fix: invoice value set to zero if rounded total disabled --- erpnext/regional/india/e_invoice/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 1b746d5e10a..931bb40c6b9 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -206,9 +206,8 @@ def get_invoice_value_details(invoice): invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0 # discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off invoice_value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0) - disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') - invoice_value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else abs(invoice.base_rounded_total) - invoice_value_details.grand_total = abs(invoice.grand_total) if disable_rounded else abs(invoice.rounded_total) + invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total) + invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total) invoice_value_details = update_invoice_taxes(invoice, invoice_value_details) From edec82bb9eb726611ecad6f8d69c2152c3b3157b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Dec 2020 13:35:04 +0530 Subject: [PATCH 135/137] fix: unit rate & gross amount calculation --- erpnext/regional/india/e_invoice/utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 931bb40c6b9..864ca0ea3d1 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -146,12 +146,12 @@ def get_item_list(invoice): item.update(d.as_dict()) item.sr_no = d.idx - item.qty = abs(item.qty) - item.description = d.item_name - item.taxable_value = abs(item.base_net_amount) item.discount_amount = abs(item.discount_amount * item.qty) - item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate) - item.gross_amount = abs(item.unit_rate * item.qty) + item.description = d.item_name + item.qty = abs(item.qty) + item.unit_rate = abs(item.base_net_amount / item.qty) + item.gross_amount = abs(item.base_net_amount) + item.taxable_value = abs(item.base_net_amount) item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None @@ -364,7 +364,8 @@ def validate_einvoice(validations, einvoice, errors=[]): einvoice[fieldname] = str(value) elif value_type == 'number': is_integer = '.' not in str(field_validation.get('maximum')) - einvoice[fieldname] = flt(value, 2) if not is_integer else cint(value) + precision = 3 if '.999' in str(field_validation.get('maximum')) else 2 + einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value) value = einvoice[fieldname] max_length = field_validation.get('maxLength') From 5c107b99bd6f314bc2d50febfe3f443eb1b94f27 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 30 Dec 2020 13:07:05 +0530 Subject: [PATCH 136/137] feat: change log for version 12.16.0 --- erpnext/change_log/v12/v12_16_0.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 erpnext/change_log/v12/v12_16_0.md diff --git a/erpnext/change_log/v12/v12_16_0.md b/erpnext/change_log/v12/v12_16_0.md new file mode 100644 index 00000000000..62ec8d340e3 --- /dev/null +++ b/erpnext/change_log/v12/v12_16_0.md @@ -0,0 +1,11 @@ +## ERPNext v12.16.0 Release Note + +### Feature + +- GST E-invoicing for India ([#24184](https://github.com/frappe/erpnext/pull/24184)) + +### Fixes + +- Do not override the manually added valuation rate in stock entry ([#24221](https://github.com/frappe/erpnext/pull/24221)) + +- Do not manufacture same serial no multiple times ([#24163](https://github.com/frappe/erpnext/pull/24163)) \ No newline at end of file From 2cf2697e74637cc3e2b55f4d7f73abcb4c904b3f Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 30 Dec 2020 13:32:29 +0550 Subject: [PATCH 137/137] bumped to version 12.16.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 65c4eacd549..87dd15589e3 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '12.15.0' +__version__ = '12.16.0' def get_default_company(user=None): '''Get default company for user'''