From f396b35622b4bbcc8c979c839a1a364ba979f95e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 21:19:59 +0530 Subject: [PATCH 001/181] 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 18afbc0617c4683cefd2048bdbea2932998f5de0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 21:27:50 +0530 Subject: [PATCH 002/181] 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 5669a5f6188b5d30f41a6384ef67a1fed570dcde Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 21:52:00 +0530 Subject: [PATCH 003/181] 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 e73a9deb42bf40af95367093f58a477c74edbd6d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 21:59:38 +0530 Subject: [PATCH 004/181] 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 f8346ce62f9bc5fc21231bcad8c807b82d141592 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 22:02:45 +0530 Subject: [PATCH 005/181] 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 b6509ead64c5b70ce59be8e20d54c271f5c46b2c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 22:17:47 +0530 Subject: [PATCH 006/181] 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 7c427aa890d97090be63639f5a2b9dd0690ccee3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 25 Sep 2020 23:29:45 +0530 Subject: [PATCH 007/181] 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 93b3c2ce06de2ff5c803f3ecbfacefc5a94fd13e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 28 Sep 2020 12:11:35 +0530 Subject: [PATCH 008/181] 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 cc3e0bf8064d70e9df16181d6dd317a621837b2c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 11:20:18 +0530 Subject: [PATCH 009/181] 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 448ab6e3dfe3cc4fac6a5ff497f7bcad9b703d11 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 12:47:37 +0530 Subject: [PATCH 010/181] 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 dda44bb1f53cd70b51b05d3f942c36a27c657e9d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 14:17:50 +0530 Subject: [PATCH 011/181] 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 d971917ecf613d7016c4f4b95a4151cea4a5236b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 14:23:48 +0530 Subject: [PATCH 012/181] 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 418818f0eca813517d2d4408392f797cdfdd63f7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 16:25:08 +0530 Subject: [PATCH 013/181] 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 bdfd30760a0ad15f12c33bb3f2547669d7750cd0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 17:10:22 +0530 Subject: [PATCH 014/181] 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 8eda5aca09b671b75ecb66832ac260b0bbc9e9c8 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 17:36:55 +0530 Subject: [PATCH 015/181] 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 221e5993a8ab87788b089baf6da75b94a1278e57 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 18:39:13 +0530 Subject: [PATCH 016/181] 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 395ecf239222be5b1883bea68d60bfb613b6a1e1 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 18:39:35 +0530 Subject: [PATCH 017/181] 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 18666b6a7484ca80153488c3bb915af28793179b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Sep 2020 22:30:02 +0530 Subject: [PATCH 018/181] 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 7f5603b58b0..bd09253c559 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 58c7e847910..fba4d4c8483 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -106,8 +106,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: @@ -1406,3 +1412,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 5270e7beea2..d5b49a2ee22 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -357,7 +357,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 9003718481178f6add80fa51c62053bb141ff396 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 12:28:03 +0530 Subject: [PATCH 019/181] 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 cf2ebc0d6f7951e58e698bcd8b5bc0a7f3e0b211 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 12:59:27 +0530 Subject: [PATCH 020/181] 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 db13eb56c4ab94e1d073053f14096a32b8a17aa8 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 13:07:38 +0530 Subject: [PATCH 021/181] 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 7a40ad340f55e9d9dac084783d8744dcd7b9b916 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 13:14:07 +0530 Subject: [PATCH 022/181] 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 cea1a15dcd86cf9c038b96ad02bad4ad19a17a75 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 13:56:52 +0530 Subject: [PATCH 023/181] 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 175b26e6e8ddbbdf44bf8753b7e9e4d58d1105f1 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 14:32:12 +0530 Subject: [PATCH 024/181] 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 389610fb450aae3561c680fc1ddb27ec1c22db35 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 15:02:08 +0530 Subject: [PATCH 025/181] 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 59a757b3e3fcde094aa1b095798dc2f33676c095 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 15:17:30 +0530 Subject: [PATCH 026/181] 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 dfbdd0b2eed1b1a5f2137c16f32af08802313b46 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 17:10:18 +0530 Subject: [PATCH 027/181] 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 816235e99c971e12ce47e5ba351b2c0b035f35fe Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 17:14:58 +0530 Subject: [PATCH 028/181] 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 6a9af076e5c32f3a684fc3a5b0a8aa28604c41a6 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 18:24:24 +0530 Subject: [PATCH 029/181] 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 22754a9c583654242e883899437a5a3f2d761df5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 18:51:39 +0530 Subject: [PATCH 030/181] 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 ec282629887c2b824cd4e5f5b135c6a5bf0334e4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 18:52:05 +0530 Subject: [PATCH 031/181] 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 cc43805f25c1e3b69c18d78cf5bc6a409d95eac7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 30 Sep 2020 19:34:09 +0530 Subject: [PATCH 032/181] 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 08e89b88f4c45fd2dca1f096601440a787f0b27d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 6 Oct 2020 21:27:00 +0530 Subject: [PATCH 033/181] 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 ada41295bb990ef302916f51f02ab11f6d606c94 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 7 Oct 2020 11:07:11 +0530 Subject: [PATCH 034/181] 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 61824d3735fab7f59fc499988d94841759c7034b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 7 Oct 2020 11:08:11 +0530 Subject: [PATCH 035/181] 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 b3e0f482ac02c4b6c3ea22f16d4dd1d78d5ce1bd Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 7 Oct 2020 11:21:47 +0530 Subject: [PATCH 036/181] 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 9c00b227036064d8ee28fd8db492e7f856f8cc9e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 7 Oct 2020 12:26:45 +0530 Subject: [PATCH 037/181] 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 d3a6b9ed81f20e91e93c103909001ee3aa2f726f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 12:35:50 +0530 Subject: [PATCH 038/181] 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 d5b49a2ee22..fa0ab7535c6 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -358,7 +358,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 154c433a869bcd31578836b7555023baa28d390c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 19:43:25 +0530 Subject: [PATCH 039/181] 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 d6f3be0fc150c6bf412be31fd1f1ddca23716e07 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 19:47:29 +0530 Subject: [PATCH 040/181] 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 662a0fcfe975bbb2f66a02dd23e31d32ebde7b7e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 19:48:38 +0530 Subject: [PATCH 041/181] 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 08b4736ee0d46202dd06ec3319b21bd429a08437 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 21:51:53 +0530 Subject: [PATCH 042/181] 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 57dd4689e07381ba227cd5aa846a377a78f17d55 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 13 Oct 2020 15:54:25 +0530 Subject: [PATCH 043/181] 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 e6646efef39b8efaedf2a7b638168e7db2b8caab Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 21 Oct 2020 19:09:27 +0530 Subject: [PATCH 044/181] 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 cc704044bd818827ece699e0aa94e9c0d13547cc Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 21 Oct 2020 19:18:17 +0530 Subject: [PATCH 045/181] 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 e50804fb9dfd0b754db5879d53db381be9c6bcc5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 22 Oct 2020 18:21:53 +0530 Subject: [PATCH 046/181] 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 c3719b46b201efdcdb62c98c8aa77a108d5176d4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Oct 2020 20:30:08 +0530 Subject: [PATCH 047/181] 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 ba1cd741897a62b9d516e2039822732ae05e62e8 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 23 Oct 2020 21:19:55 +0530 Subject: [PATCH 048/181] 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 90a57c1f8c4bad210a4607023ea03cfa99885617 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 12:13:00 +0530 Subject: [PATCH 049/181] 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 8616684c2495506d3bd97fe73e0df1fff228c4aa Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 12:41:13 +0530 Subject: [PATCH 050/181] 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 e1e3dbcae226b6b0759bbc0124e344a3977781f3 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 12:57:46 +0530 Subject: [PATCH 051/181] 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 9363725342fc05456e6f55d10094c87cc9a0e5f9 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 13:18:33 +0530 Subject: [PATCH 052/181] 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 31ee8b4011187d2a75a190ac43dd5c6d1f92cbe5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 14:13:11 +0530 Subject: [PATCH 053/181] 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 5eb430fe51af75a9d99f5b3bf947a5be405c10df Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 17:19:51 +0530 Subject: [PATCH 054/181] 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 fa0ab7535c6..81fad7df4ad 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -358,7 +358,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 12d9bc37877982297177c5af861f8f7dc5e646cf Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 26 Oct 2020 17:59:37 +0530 Subject: [PATCH 055/181] 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 37e4c91ec231585e6da2e2bcd5d39f5f9a5092d0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Oct 2020 11:25:16 +0530 Subject: [PATCH 056/181] 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 271f35fef550e4fa4874ff5a52240dc4143e3c07 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Oct 2020 12:38:58 +0530 Subject: [PATCH 057/181] 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 9182c2e09d00757bd6bacd9c094ce330a8a8d15d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 28 Oct 2020 18:51:52 +0530 Subject: [PATCH 058/181] 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 6c064a1f95c649faa82599b652f89e51348fc2ea Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 1 Nov 2020 12:48:53 +0530 Subject: [PATCH 059/181] 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 b77375c72b67967f3252234ecc8fad85366f621a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 2 Nov 2020 14:10:38 +0530 Subject: [PATCH 060/181] 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 c313f05a8ea8503faac3f70cb9e163e60933f1ed Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 2 Nov 2020 19:52:42 +0530 Subject: [PATCH 061/181] 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 049836dff8590b0e74d517388d626868e7d631ab Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 3 Nov 2020 15:21:16 +0530 Subject: [PATCH 062/181] 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 86f87e9b53916fe1f4e4996efa81d85235c3dc5b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:19:20 +0530 Subject: [PATCH 063/181] 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 54405a4b646f8f2e776e82b63f4dedf1010b9da7 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:19:50 +0530 Subject: [PATCH 064/181] 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 ad19268b4cf161065d7e7eaf7c347e54b35ae3cd Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:23:01 +0530 Subject: [PATCH 065/181] 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 495ac7ba6b2895cd985e9ad7add4d450a381a9c0 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:28:31 +0530 Subject: [PATCH 066/181] 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 3f9390c077be0436bc0c0948fec468b4fdfd2f59 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:39:39 +0530 Subject: [PATCH 067/181] 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 b7ee2a44a06da020c1eee8fc4017aa5154aafe3d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:40:13 +0530 Subject: [PATCH 068/181] 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 778141c5691540388b45759579b8e7ebbc2cfb91 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:45:24 +0530 Subject: [PATCH 069/181] 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 63bfa30bc176e761e819084cbff79bb924add724 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:54:01 +0530 Subject: [PATCH 070/181] 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 eb9f216b4020a4a28d8c99137a9e0fc3549f2b98 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 11:58:34 +0530 Subject: [PATCH 071/181] 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 6d5a8aea9af4f8a68e06419f5a244f620e31b649 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 12:35:05 +0530 Subject: [PATCH 072/181] 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 ca944316b464e0ca02ce5f1391d6acb159a627ff Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 18:50:15 +0530 Subject: [PATCH 073/181] 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 fb9dde38f69ad8dc1efefc04592ddf34ca0b0e6c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 19:44:54 +0530 Subject: [PATCH 074/181] 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 77b8ced4a4b128f70d11c6fc11ab3477034b2071 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 22:17:26 +0530 Subject: [PATCH 075/181] 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 e3c2f0a221ae0029fd6eecf62d8616de7a9f1b1b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 4 Nov 2020 22:22:59 +0530 Subject: [PATCH 076/181] 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 464fc1f087d800fc1fff796638bcd386144fc6eb Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 10:49:21 +0530 Subject: [PATCH 077/181] 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 589a49a38a89de69ac283698a88cc9b03931f3db Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 11:34:22 +0530 Subject: [PATCH 078/181] 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 6d63a1e90af9f4e8aa64e92cebf44af601db53fe Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 11:35:34 +0530 Subject: [PATCH 079/181] 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 db4fec0314991fd3aa11923af0cc5d547605ebb4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 13:47:29 +0530 Subject: [PATCH 080/181] 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 51ddf376daabebcc6ad090698f0c44d050c3433f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 13:47:29 +0530 Subject: [PATCH 081/181] 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 91580258d787835b8b023eee4f5291eef9225fc5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 5 Nov 2020 13:56:04 +0530 Subject: [PATCH 082/181] 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 5d23c0ce8ec186471b2198aa4f3b6d3733de64cf Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 14:04:39 +0530 Subject: [PATCH 083/181] 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 eeb99d1b983dccba0a60621344e81a46d9ab4f4f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 14:05:24 +0530 Subject: [PATCH 084/181] 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 8693ac81a2127f65a9db27b0da99a677223e0cf8 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 17:53:22 +0530 Subject: [PATCH 085/181] 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 70e07118936f3648f9343666d6a81fc4e3611ec6 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 19:43:47 +0530 Subject: [PATCH 086/181] 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 020e6e980a0c7a9c330832862124440817561d96 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 19:44:37 +0530 Subject: [PATCH 087/181] 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 72ed0fbbb497e51e9f03dcde8d879dd9aff93af9 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 21:00:38 +0530 Subject: [PATCH 088/181] 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 dc4b0921f6ca62daf8829b7fa5643f6ef5d6a883 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 21:15:19 +0530 Subject: [PATCH 089/181] 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 c82c955bc24a8e2bafe7a6ac66b761cc064c267d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 7 Nov 2020 22:44:12 +0530 Subject: [PATCH 090/181] 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 707b630851659eea04c4c71740689c11ec94247f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 19 Nov 2020 18:42:39 +0530 Subject: [PATCH 091/181] fix: add remarks to sales invoice --- .../doctype/sales_invoice/sales_invoice.py | 9 +++-- erpnext/patches.txt | 3 +- .../v12_0/update_sales_invoice_remarks.py | 35 +++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 erpnext/patches/v12_0/update_sales_invoice_remarks.py diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 2f8b782356d..94bc400fe5b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext import frappe.defaults -from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate +from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, formatdate from frappe import _, msgprint, throw from erpnext.accounts.party import get_party_account, get_due_date from erpnext.controllers.stock_controller import update_gl_entries_after @@ -530,7 +530,12 @@ class SalesInvoice(SellingController): self.against_income_account = ','.join(against_acc) def add_remarks(self): - if not self.remarks: self.remarks = 'No Remarks' + if not self.remarks: + if self.po_no and self.po_date: + self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no, + formatdate(self.po_date)) + else: + self.remarks = _("No Remarks") def validate_auto_set_posting_time(self): # Don't auto set the posting date and time if invoice is amended diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b5f31bafa7e..2615830eb0a 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.update_sales_invoice_remarks \ No newline at end of file diff --git a/erpnext/patches/v12_0/update_sales_invoice_remarks.py b/erpnext/patches/v12_0/update_sales_invoice_remarks.py new file mode 100644 index 00000000000..5443d133a2e --- /dev/null +++ b/erpnext/patches/v12_0/update_sales_invoice_remarks.py @@ -0,0 +1,35 @@ +from __future__ import unicode_literals +from mmap import PAGESIZE +import frappe + +from frappe import _ +from frappe.utils import formatdate + +def execute(): + si_list = frappe.db.get_all('Sales Invoice', filters = { + 'docstatus': 1, + 'remarks': 'No Remarks', + 'po_no' : ['!=', ''], + 'po_date' : ['!=', ''] + }, + fields = ['name', 'po_no', 'po_date'] + ) + + for doc in si_list: + remarks = _("Against Customer Order {0} dated {1}").format(doc.po_no, + formatdate(doc.po_date)) + + frappe.db.set_value('Sales Invoice', doc.name, 'remarks', remarks) + + gl_entry_list = frappe.db.get_all('GL Entry', filters = { + 'voucher_type': 'Sales Invoice', + 'remarks': 'No Remarks', + 'voucher_no' : doc.name + }, + fields = ['name'] + ) + + for entry in gl_entry_list: + frappe.db.set_value('GL Entry', entry.name, 'remarks', remarks) + + \ No newline at end of file From 51a153d8c27ef27c8fc965bff7ac413e238c134f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 19 Nov 2020 19:03:22 +0530 Subject: [PATCH 092/181] fix: removed unused code --- erpnext/patches/v12_0/update_sales_invoice_remarks.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/patches/v12_0/update_sales_invoice_remarks.py b/erpnext/patches/v12_0/update_sales_invoice_remarks.py index 5443d133a2e..ae6443d3171 100644 --- a/erpnext/patches/v12_0/update_sales_invoice_remarks.py +++ b/erpnext/patches/v12_0/update_sales_invoice_remarks.py @@ -1,5 +1,4 @@ from __future__ import unicode_literals -from mmap import PAGESIZE import frappe from frappe import _ @@ -30,6 +29,4 @@ def execute(): ) for entry in gl_entry_list: - frappe.db.set_value('GL Entry', entry.name, 'remarks', remarks) - - \ No newline at end of file + frappe.db.set_value('GL Entry', entry.name, 'remarks', remarks) \ No newline at end of file From c630be6781f571ebda3f29cdd58759f75f73bcf4 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 19 Nov 2020 19:13:06 +0530 Subject: [PATCH 093/181] fix: remove unused imports --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 94bc400fe5b..1b77f1b733b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext import frappe.defaults -from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate, formatdate +from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, formatdate from frappe import _, msgprint, throw from erpnext.accounts.party import get_party_account, get_due_date from erpnext.controllers.stock_controller import update_gl_entries_after From 3e526b8ca85f61d6bdfbf87313c6e3f5b48059df Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 23 Nov 2020 16:19:50 +0530 Subject: [PATCH 094/181] 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 27043270293b03b7cb6b149101b074e44c9ae529 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 23 Nov 2020 16:34:40 +0530 Subject: [PATCH 095/181] 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 9655edabd915906d0487e76a7cd853fe4534c6d6 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 23 Nov 2020 16:37:00 +0530 Subject: [PATCH 096/181] 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 3252d9d943120fced343f31df47b463b84bd6433 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 23 Nov 2020 16:41:51 +0530 Subject: [PATCH 097/181] 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 f76b0dea446eb0e73b15c844b7b9882bc038412f 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 098/181] 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 1cd57a324cc6a9a851ef2c2684e280cefd416e62 Mon Sep 17 00:00:00 2001 From: Anuja P Date: Thu, 10 Dec 2020 22:05:09 +0530 Subject: [PATCH 099/181] 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 3dbf88ba6f431c51b5ed57f6db38d77a700b38ab Mon Sep 17 00:00:00 2001 From: pateljannat Date: Fri, 11 Dec 2020 10:25:08 +0530 Subject: [PATCH 100/181] 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 bbce16e00c0d35a775c496bca80465d778d50a15 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 21 Nov 2020 21:36:44 +0530 Subject: [PATCH 101/181] fix: Validation for duplicate Tax Category --- erpnext/hooks.py | 3 +++ erpnext/regional/india/utils.py | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5270e7beea2..1dc6b6b4170 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -239,6 +239,9 @@ doc_events = { "Website Settings": { "validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products" }, + "Tax Category": { + "validate": "erpnext.regional.india.utils.validate_tax_category" + }, "Sales Invoice": { "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"], "on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel", diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 5872c69fd16..c73bac1c500 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -51,6 +51,13 @@ def validate_gstin_for_india(doc, method): frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.") .format(doc.gst_state_number)) +def validate_tax_category(doc, method): + if doc.get('gst_state') and frappe.db.get_value('Tax category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}): + if doc.is_inter_state: + frappe.throw(_("Inter State tax category for GST State {0} already exists").format(doc.gst_state)) + else: + frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state)) + def update_gst_category(doc, method): for link in doc.links: if link.link_doctype in ['Customer', 'Supplier']: @@ -500,7 +507,7 @@ def get_address_details(data, doc, company_address, billing_address): data.transType = 1 data.actualToStateCode = data.toStateCode shipping_address = billing_address - + if doc.gst_category == 'SEZ': data.toStateCode = 99 From 22e770353f6796539e32bc0c8d2c511597c6b9c6 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 24 Nov 2020 08:08:47 +0530 Subject: [PATCH 102/181] fix: Translation issue --- erpnext/regional/india/utils.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index c73bac1c500..144e6ecc093 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -92,8 +92,7 @@ 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'): @@ -744,4 +743,4 @@ def make_regional_gl_entries(gl_entries, doc): }, account_currency, item=tax) ) - return gl_entries \ No newline at end of file + return gl_entries From ee0b08ae9c4d6a7a74a71dc0a9753bead359ff96 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 24 Nov 2020 13:52:54 +0530 Subject: [PATCH 103/181] 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 9760eb9d5777e330895a3ac7d2060cda5943eccc Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 25 Nov 2020 13:17:02 +0530 Subject: [PATCH 104/181] 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 26f488d8c9a..f9f21a03c5f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1909,6 +1909,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 57e6759088c3e63c494110cc8b54fe0bd223bb3a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 2 Dec 2020 12:59:20 +0530 Subject: [PATCH 105/181] 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 5d6849ac7f221844a185d3ce947c895bf874b63b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 2 Dec 2020 17:51:51 +0530 Subject: [PATCH 106/181] 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 f9f21a03c5f..eee46f989f0 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1838,94 +1838,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() @@ -1941,6 +1854,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 5327637b37215cb5f74a9f965a4c15d8c4b8cfac Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 3 Dec 2020 10:59:41 +0530 Subject: [PATCH 107/181] 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 42b028a3ba834b53d63bc5cd40b51260f89fef69 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 8 Dec 2020 14:59:02 +0530 Subject: [PATCH 108/181] 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 64e9275c4aa97ac5c4b811acc99597ed4a1121c6 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 9 Dec 2020 12:17:23 +0530 Subject: [PATCH 109/181] 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 390cac216059d86493f525734589abde709bb79e Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 9 Dec 2020 12:29:25 +0530 Subject: [PATCH 110/181] 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 eee46f989f0..32fca75efcf 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1982,7 +1982,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 5c74cdd0d2c3a448e63742eae8f702474e9d6ceb Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 16:40:23 +0530 Subject: [PATCH 111/181] 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 cde452add7598e35d9933ca8107fdcae711f8e61 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 16:51:25 +0530 Subject: [PATCH 112/181] 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 d67affdfae162adecd56b32d0fc5338c08383740 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 17:15:36 +0530 Subject: [PATCH 113/181] 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 e6a07d5508017f662e971c36c02ac149597e2418 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 17:31:31 +0530 Subject: [PATCH 114/181] 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 695e5519721c2c142d6e6f5d38943f02031cc91a Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 18:59:01 +0530 Subject: [PATCH 115/181] 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 9daf4418eab9bf88d7178c2ab183cc35329e4918 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Dec 2020 19:14:43 +0530 Subject: [PATCH 116/181] 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 b4dfba54b698491d9f41ae2eba921864e21b767a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 15 Dec 2020 09:32:02 +0530 Subject: [PATCH 117/181] 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 e46a72b101bedabd7f63c8fa44a333edf788c868 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Tue, 15 Dec 2020 14:41:35 +0530 Subject: [PATCH 118/181] 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 1f2ec254930e0a3f084036f5158ec4ae70c4bdfb Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Date: Tue, 15 Dec 2020 14:48:44 +0530 Subject: [PATCH 119/181] 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 18635e4dd1756001ddb5e37e88a1a4c44e1fe255 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 16 Dec 2020 17:02:41 +0530 Subject: [PATCH 120/181] 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 9b893643c0d2fae5d5d253ff53b44b5d76cb2507 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 4 Dec 2020 18:07:46 +0530 Subject: [PATCH 121/181] fix: Tax template update on supplier --- erpnext/regional/india/taxes.js | 1 + erpnext/regional/india/utils.py | 30 +++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 44891a76a0b..950ba069fb1 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -16,6 +16,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { 'shipping_address': frm.doc.shipping_address || '', 'shipping_address_name': frm.doc.shipping_address_name || '', 'customer_address': frm.doc.customer_address || '', + 'supplier_address': frm.doc.supplier_address, 'customer': frm.doc.customer, 'supplier': frm.doc.supplier, 'supplier_gstin': frm.doc.supplier_gstin, diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 5872c69fd16..dea22488747 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -12,6 +12,7 @@ from erpnext.regional.india import number_state_mapping from six import string_types from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.utils import get_account_currency +from frappe.model.utils import get_fetch_values def validate_gstin_for_india(doc, method): if hasattr(doc, 'gst_state') and doc.gst_state: @@ -155,7 +156,15 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N party_details = json.loads(party_details) party_details = frappe._dict(party_details) + update_party_details(party_details, doctype) + party_details.place_of_supply = get_place_of_supply(party_details, doctype) + + if is_internal_transfer(party_details, doctype): + party_details.taxes_and_charges = '' + party_details.taxes = '' + return party_details + if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" @@ -196,8 +205,23 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N party_details["taxes_and_charges"] = default_tax party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) - if return_taxes: - return party_details + 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']: + if party_details.get(address_field): + party_details.update(get_fetch_values(doctype, address_field, party_details.get(address_field))) + +def is_internal_transfer(party_details, doctype): + if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): + destination_gstin = party_details.company_gstin + elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): + destination_gstin = party_details.supplier_gstin + + if party_details.gstin == destination_gstin: + return True + else: + False def get_tax_template_based_on_category(master_doctype, company, party_details): if not party_details.get('tax_category'): @@ -500,7 +524,7 @@ def get_address_details(data, doc, company_address, billing_address): data.transType = 1 data.actualToStateCode = data.toStateCode shipping_address = billing_address - + if doc.gst_category == 'SEZ': data.toStateCode = 99 From 2cf9cc02e10a905c486967b472fbe830b86ad085 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 17 Nov 2020 09:47:10 +0530 Subject: [PATCH 122/181] fix: place of supply change when address changes --- erpnext/public/js/utils.js | 15 +++++++++++++++ erpnext/regional/india/utils.py | 1 + erpnext/selling/sales_common.js | 1 + 3 files changed, 17 insertions(+) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 6e2409cf875..6b9fb73b04a 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -304,6 +304,21 @@ $.extend(erpnext.utils, { } frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); + }, + + 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){ + if(r.message){ + frm.set_value("place_of_supply", r.message) + } + } + }) } }); diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index dea22488747..e12ed5f4adb 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -136,6 +136,7 @@ def test_method(): '''test function''' return 'overridden' +@frappe.whitelist() def get_place_of_supply(party_details, doctype): if not frappe.get_meta('Address').has_field('gst_state'): return diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 15a9a1ab882..802fb5661db 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -117,6 +117,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ customer_address: function() { erpnext.utils.get_address_display(this.frm, "customer_address"); erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name"); + erpnext.utils.set_place_of_supply(this.frm) }, shipping_address_name: function() { From 58fae20ff97f3722cae54c31adc8046a25c6557e Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 17 Nov 2020 20:34:51 +0530 Subject: [PATCH 123/181] fix: place of supply change on address change --- erpnext/public/js/controllers/buying.js | 1 + erpnext/public/js/utils.js | 28 ++++++++++++------------- erpnext/regional/india/utils.py | 3 +++ 3 files changed, 17 insertions(+), 15 deletions(-) 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 6b9fb73b04a..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'); @@ -304,21 +317,6 @@ $.extend(erpnext.utils, { } frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); - }, - - 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){ - if(r.message){ - frm.set_value("place_of_supply", r.message) - } - } - }) } }); diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index e12ed5f4adb..a27e94addba 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -139,6 +139,9 @@ def test_method(): @frappe.whitelist() 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 b2db8031d5368c0118d6d2a0c8a1886c478c6dad Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 18 Nov 2020 12:51:13 +0530 Subject: [PATCH 124/181] 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/taxes.js | 4 ++++ erpnext/regional/india/utils.py | 18 +++++++----------- erpnext/selling/sales_common.js | 1 - 5 files changed, 11 insertions(+), 26 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/taxes.js b/erpnext/regional/india/taxes.js index 950ba069fb1..33de01adad8 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -35,6 +35,10 @@ erpnext.setup_auto_gst_taxation = (doctype) => { callback: function(r) { if(r.message) { frm.set_value('taxes_and_charges', r.message.taxes_and_charges); + frm.set_value('place_of_supply', r.message.place_of_supply); + } else if (frm.doc.is_internal_supplier || frm.doc.is_internal_customer) { + frm.set_value('taxes_and_charges', ''); + frm.set_value('taxes', []); } } }); diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index a27e94addba..1fba7a3ac86 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -136,12 +136,8 @@ def test_method(): '''test function''' return 'overridden' -@frappe.whitelist() 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 @@ -175,11 +171,11 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N 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') and return_taxes: + if party_details.get('taxes_and_charges'): return party_details if not party_details.company_gstin: - return + return party_details elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" @@ -187,15 +183,15 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N 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') and return_taxes: + if party_details.get('taxes_and_charges'): return party_details if not party_details.supplier_gstin: - return + return party_details - if not party_details.place_of_supply: return + if not party_details.place_of_supply: return party_details - if not party_details.company_gstin: return + if not party_details.company_gstin: return party_details 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", @@ -205,7 +201,7 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2]) if not default_tax: - return + return party_details party_details["taxes_and_charges"] = default_tax party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 802fb5661db..15a9a1ab882 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -117,7 +117,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ customer_address: function() { erpnext.utils.get_address_display(this.frm, "customer_address"); erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name"); - erpnext.utils.set_place_of_supply(this.frm) }, shipping_address_name: function() { From 1a401b1427395ec04560cc3fc8d1ec479bf17b71 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Wed, 18 Nov 2020 15:57:16 +0530 Subject: [PATCH 125/181] fix: linter issue for translation syntax --- erpnext/regional/india/utils.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 1fba7a3ac86..6df17df166f 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -87,7 +87,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): 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))) + 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'): @@ -163,7 +163,7 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N 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" @@ -171,11 +171,11 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N 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" @@ -183,15 +183,15 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N 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", @@ -201,11 +201,12 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N 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']: @@ -244,7 +245,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 284a19c99d0d01e051a7164e6e15c0560356274e Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 19 Nov 2020 11:37:08 +0530 Subject: [PATCH 126/181] fix: company filter added again --- erpnext/regional/india/utils.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 6df17df166f..e1054a6ffd1 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -163,7 +163,7 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N if is_internal_transfer(party_details, doctype): party_details.taxes_and_charges = '' party_details.taxes = '' - return + return party_details if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" @@ -171,11 +171,11 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N 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') and return_taxes: + if party_details.get('taxes_and_charges'): return party_details if not party_details.company_gstin: - return + return party_details elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" @@ -183,15 +183,15 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N 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') and return_taxes: + if party_details.get('taxes_and_charges'): return party_details if not party_details.supplier_gstin: - return + return party_details - if not party_details.place_of_supply: return + if not party_details.place_of_supply: return party_details - if not party_details.company_gstin: return + if not party_details.company_gstin: return party_details 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", @@ -201,12 +201,11 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2]) if not default_tax: - return + return party_details party_details["taxes_and_charges"] = default_tax party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) - if return_taxes: - return party_details + 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']: @@ -245,7 +244,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, - {'disabled': 0, 'tax_category': tax_category.name}, 'name') + {'company': company, '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 7fb22fd652772b217b9218ee59441f30972bd759 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 19 Nov 2020 20:11:45 +0530 Subject: [PATCH 127/181] fix: removing return_taxes condition --- erpnext/regional/india/taxes.js | 3 +-- erpnext/regional/india/utils.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index 33de01adad8..b3da21c2bb3 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -29,8 +29,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => { args: { party_details: JSON.stringify(party_details), doctype: frm.doc.doctype, - company: frm.doc.company, - return_taxes: 1 + company: frm.doc.company }, callback: function(r) { if(r.message) { diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index e1054a6ffd1..66380b83e12 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -150,8 +150,7 @@ def get_place_of_supply(party_details, doctype): return cstr(address.gst_state_number) + "-" + cstr(address.gst_state) @frappe.whitelist() -def get_regional_address_details(party_details, doctype, company, return_taxes=None): - +def get_regional_address_details(party_details, doctype, company): if isinstance(party_details, string_types): party_details = json.loads(party_details) party_details = frappe._dict(party_details) From 12f95c41b9c4b912b90ef9850b085c71b6969321 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 16 Dec 2020 13:00:55 +0530 Subject: [PATCH 128/181] fix: Tax template update on customer address change --- erpnext/regional/india/taxes.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js index b3da21c2bb3..455879294a8 100644 --- a/erpnext/regional/india/taxes.js +++ b/erpnext/regional/india/taxes.js @@ -9,6 +9,9 @@ erpnext.setup_auto_gst_taxation = (doctype) => { tax_category: function(frm) { frm.trigger('get_tax_template'); }, + customer_address: function(frm) { + frm.trigger('get_tax_template'); + }, get_tax_template: function(frm) { if (!frm.doc.company) return; From 723e4f2a22f96be0b7af3a4c8179ee07da4dae4f Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 17 Dec 2020 13:26:11 +0530 Subject: [PATCH 129/181] 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 bd0cb9d2edb9ed9604751ddc352539717563e104 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 17 Dec 2020 18:59:42 +0530 Subject: [PATCH 130/181] Typo in tax category doctype query --- erpnext/regional/india/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 144e6ecc093..ab1795ed279 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -52,7 +52,7 @@ def validate_gstin_for_india(doc, method): .format(doc.gst_state_number)) def validate_tax_category(doc, method): - if doc.get('gst_state') and frappe.db.get_value('Tax category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}): + if doc.get('gst_state') and frappe.db.get_value('Tax Category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}): if doc.is_inter_state: frappe.throw(_("Inter State tax category for GST State {0} already exists").format(doc.gst_state)) else: From 8427dd2ca833ea9e0e63b12bbc8642b112055262 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 18 Dec 2020 13:30:25 +0530 Subject: [PATCH 131/181] 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 983df4463a4ea4b58e6643315544bba8d6fb08e0 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Fri, 18 Dec 2020 20:18:17 +0530 Subject: [PATCH 132/181] 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 c28379075a2ef8dd2bdcb56a7c61e18be6a24693 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 21 Dec 2020 18:41:40 +0530 Subject: [PATCH 133/181] 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 6db07c027829c1f51917955271a93494be102e5b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 22 Dec 2020 15:55:50 +0530 Subject: [PATCH 134/181] 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 7f794e5bb67454272ecc80878aa7a4cd2f5cbf98 Mon Sep 17 00:00:00 2001 From: Sun Howwrongbum Date: Sat, 29 Jun 2019 20:48:27 +0530 Subject: [PATCH 135/181] fix(pos): loyalty details not rendering in cart area --- erpnext/accounts/doctype/loyalty_program/loyalty_program.py | 2 +- erpnext/selling/page/point_of_sale/point_of_sale.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py index d2d852229e2..fe8362f60f4 100644 --- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py @@ -38,7 +38,7 @@ def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=Non @frappe.whitelist() def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0): lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent) - loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program) + loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program or lp_details.loyalty_program) lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)) # sort collection rule, first item on list will be lowest min_spent diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index a4a39ca4d03..e426c932ffb 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -155,7 +155,7 @@ erpnext.pos.PointOfSale = class PointOfSale { var me = this; if (this.frm.doc.customer) { frappe.call({ - method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details", + method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details_with_points", args: { "customer": me.frm.doc.customer, "expiry_date": me.frm.doc.posting_date, From e9b580aba6c85462c4f421d40d102422cc2e52b1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 24 Jul 2019 12:06:12 +0530 Subject: [PATCH 136/181] fix: pos not working --- erpnext/accounts/doctype/loyalty_program/loyalty_program.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py index fe8362f60f4..e2435defa97 100644 --- a/erpnext/accounts/doctype/loyalty_program/loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/loyalty_program.py @@ -38,7 +38,10 @@ def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=Non @frappe.whitelist() def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0): lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent) - loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program or lp_details.loyalty_program) + loyalty_program_name = loyalty_program or lp_details.loyalty_program + if not loyalty_program_name: return + + loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program_name) lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)) # sort collection rule, first item on list will be lowest min_spent From 21461823f168f391166f6693e4ab3cd9e326743f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 23 Dec 2020 14:35:54 +0530 Subject: [PATCH 137/181] 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 1bbda81c9f1dd8c512f6670ccfaf689e520eff7c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 23 Dec 2020 15:19:22 +0530 Subject: [PATCH 138/181] 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 81cbd0dc50a211e6c013bb33549c4e80280cc917 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 23 Dec 2020 15:21:50 +0530 Subject: [PATCH 139/181] 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 6d3fc0d02090e773edf0fdc573ad45d18f6515c8 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 23 Dec 2020 15:41:10 +0530 Subject: [PATCH 140/181] 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 c2a41371d26cf08926fcef88469cc5426d17ca0d Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 24 Dec 2020 10:39:24 +0530 Subject: [PATCH 141/181] 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 e6d300e35cabb39bf9705f00231a269031f46ab3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 17 Dec 2020 00:12:17 +0530 Subject: [PATCH 142/181] 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 becae59af0b29ce1d8182dbe76d5ca5b51709825 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 24 Dec 2020 21:12:06 +0530 Subject: [PATCH 143/181] 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 a683b14c642815c3e83aa04642dcca27f82cad6f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 24 Dec 2020 21:10:16 +0530 Subject: [PATCH 144/181] 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 b9d62d7f8e28c0d6a8559af5a5b5d22128c3387e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 28 Dec 2020 15:07:12 +0530 Subject: [PATCH 145/181] 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 8178782c006..48244000375 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 bb431792740..122e8b205fa 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 d8193304431e70a51203c72b1ebe331b22ca46dd Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 28 Dec 2020 13:32:09 +0530 Subject: [PATCH 146/181] 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 d94a97b6ad5547444768e30eefc5ac97c32b9852 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 29 Dec 2020 13:35:04 +0530 Subject: [PATCH 147/181] 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 945c0e86deee034443e630801a37e4edc0a9c067 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 31 Dec 2020 13:17:59 +0530 Subject: [PATCH 148/181] fix: cannot submit e-invoice if legal name not found --- 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 864ca0ea3d1..321e807b7f7 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -88,7 +88,7 @@ def get_party_details(address_name): gstin = address.get('gstin') gstin_details = get_gstin_details(gstin) - legal_name = gstin_details.get('LegalName') + legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName') location = gstin_details.get('AddrLoc') or address.get('city') state_code = gstin_details.get('StateCode') pincode = gstin_details.get('AddrPncd') From ef06f3288f5475f54daecb281b60065165371ed3 Mon Sep 17 00:00:00 2001 From: vorasmit Date: Fri, 1 Jan 2021 10:48:44 +0530 Subject: [PATCH 149/181] Delete update_sales_invoice_remarks.py --- .../v12_0/update_sales_invoice_remarks.py | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 erpnext/patches/v12_0/update_sales_invoice_remarks.py diff --git a/erpnext/patches/v12_0/update_sales_invoice_remarks.py b/erpnext/patches/v12_0/update_sales_invoice_remarks.py deleted file mode 100644 index ae6443d3171..00000000000 --- a/erpnext/patches/v12_0/update_sales_invoice_remarks.py +++ /dev/null @@ -1,32 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe import _ -from frappe.utils import formatdate - -def execute(): - si_list = frappe.db.get_all('Sales Invoice', filters = { - 'docstatus': 1, - 'remarks': 'No Remarks', - 'po_no' : ['!=', ''], - 'po_date' : ['!=', ''] - }, - fields = ['name', 'po_no', 'po_date'] - ) - - for doc in si_list: - remarks = _("Against Customer Order {0} dated {1}").format(doc.po_no, - formatdate(doc.po_date)) - - frappe.db.set_value('Sales Invoice', doc.name, 'remarks', remarks) - - gl_entry_list = frappe.db.get_all('GL Entry', filters = { - 'voucher_type': 'Sales Invoice', - 'remarks': 'No Remarks', - 'voucher_no' : doc.name - }, - fields = ['name'] - ) - - for entry in gl_entry_list: - frappe.db.set_value('GL Entry', entry.name, 'remarks', remarks) \ No newline at end of file From 82b4749166eaf2970dbf35b98e92237e09df67ca Mon Sep 17 00:00:00 2001 From: vorasmit Date: Fri, 1 Jan 2021 10:49:48 +0530 Subject: [PATCH 150/181] Update patches.txt --- erpnext/patches.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2615830eb0a..b9d48069238 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -678,4 +678,3 @@ 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.update_sales_invoice_remarks \ No newline at end of file From 40b37f09fd91c207a59417262f9807c4f66eeb4b Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 4 Jan 2021 11:48:45 +0530 Subject: [PATCH 152/181] fix: cannot save asset category withouut depr posting date --- erpnext/assets/doctype/asset/asset.js | 2 ++ erpnext/assets/doctype/asset_category/asset_category.js | 1 - .../doctype/asset_finance_book/asset_finance_book.json | 9 ++------- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 78d36f14d82..93781d9e0ef 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -136,6 +136,8 @@ frappe.ui.form.on('Asset', { if (frm.doc.docstatus == 0) { frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); + frm.set_df_property('depreciation_start_date', 'reqd', 1, frm.doc.name, 'finance_books'); + frm.refresh_field('finance_books'); } }, diff --git a/erpnext/assets/doctype/asset_category/asset_category.js b/erpnext/assets/doctype/asset_category/asset_category.js index 74963c2aa96..0adcf64bfc2 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.js +++ b/erpnext/assets/doctype/asset_category/asset_category.js @@ -50,6 +50,5 @@ frappe.ui.form.on('Asset Category', { } }; }); - } }); diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index d422876047e..89d88d950ac 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -1,5 +1,4 @@ { - "actions": [], "creation": "2018-05-08 14:44:37.095570", "doctype": "DocType", "editable_grid": 1, @@ -54,9 +53,7 @@ "fieldname": "depreciation_start_date", "fieldtype": "Date", "in_list_view": 1, - "label": "Depreciation Posting Date", - "mandatory_depends_on": "eval:parent.doctype == 'Asset'", - "reqd": 1 + "label": "Depreciation Posting Date" }, { "default": "0", @@ -84,10 +81,8 @@ "label": "Rate of Depreciation" } ], - "index_web_pages_for_search": 1, "istable": 1, - "links": [], - "modified": "2020-10-30 15:22:29.119868", + "modified": "2020-12-30 15:43:03.188256", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", From bf490c3a27a35de958b7461ff9b5b96ad58384e0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 4 Jan 2021 15:32:13 +0530 Subject: [PATCH 153/181] fix: pricing rule not working for offline POS --- erpnext/accounts/doctype/sales_invoice/pos.py | 16 +++-- erpnext/accounts/page/pos/pos.js | 58 +++++++++++++++---- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 64c7f491bb7..140b63fd334 100755 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -153,8 +153,8 @@ def update_multi_mode_option(doc, pos_profile): def get_mode_of_payment(doc): return frappe.db.sql(""" - select mpa.default_account, mpa.parent, mp.type as type - from `tabMode of Payment Account` mpa,`tabMode of Payment` mp + select mpa.default_account, mpa.parent, mp.type as type + from `tabMode of Payment Account` mpa,`tabMode of Payment` mp where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""", {'company': doc.company}, as_dict=1) @@ -394,6 +394,14 @@ def get_pricing_rule_data(doc): between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31') order by priority desc, name desc""", {'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1) + + for row in pricing_rules: + if row.apply_on: + doctype = "Pricing Rule " + row.apply_on + apply_on = frappe.scrub(row.apply_on) + row[apply_on] = [d.get(apply_on) for d in frappe.get_all(doctype, + filters = {"parent": row.name}, fields = [apply_on])] + return pricing_rules @@ -434,10 +442,10 @@ def make_invoice(pos_profile, doc_list={}, email_queue_list={}, customers_list={ name_list.append(name) email_queue = make_email_queue(email_queue_list) - + if isinstance(pos_profile, string_types): pos_profile = json.loads(pos_profile) - + customers = get_customers_list(pos_profile) return { 'invoice': name_list, diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index b9b1d293d47..2819371f3e3 100755 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -2018,34 +2018,57 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ apply_pricing_rule: function () { var me = this; + + var remove_item = false; $.each(this.frm.doc["items"], function (n, item) { var pricing_rule = me.get_pricing_rule(item) me.validate_pricing_rule(pricing_rule) if (pricing_rule.length) { - item.pricing_rule = pricing_rule[0].name; - item.margin_type = pricing_rule[0].margin_type; - item.price_list_rate = pricing_rule[0].price || item.price_list_rate; - item.margin_rate_or_amount = pricing_rule[0].margin_rate_or_amount; - item.discount_percentage = pricing_rule[0].discount_percentage || 0.0; - me.apply_pricing_rule_on_item(item) + if (pricing_rule[0].price_or_product_discount == "Price") { + item.pricing_rule = pricing_rule[0].name; + item.margin_type = pricing_rule[0].margin_type; + item.price_list_rate = pricing_rule[0].price || item.price_list_rate; + item.margin_rate_or_amount = pricing_rule[0].margin_rate_or_amount; + item.discount_percentage = pricing_rule[0].discount_percentage || 0.0; + me.apply_pricing_rule_on_item(item) + } else { + me.child = frappe.model.add_child(me.frm.doc, me.frm.doc.doctype + " Item", "items"); + me.child.item_code = pricing_rule[0].same_item ? item.item_code : pricing_rule[0].free_item; + me.child.item_name = pricing_rule[0].same_item ? item.item_name : pricing_rule[0].free_item; + me.child.stock_uom = pricing_rule[0].same_item ? item.stock_uom : pricing_rule[0].free_item_uom; + me.child.uom = pricing_rule[0].same_item ? item.uom : pricing_rule[0].free_item_uom; + me.child.conversion_factor = 1; + me.child.qty = pricing_rule.qty || 1; + me.child.is_free_item = 1; + me.child.brand = pricing_rule[0].same_item ? item.brand : ""; + me.child.description = pricing_rule[0].same_item ? item.description : pricing_rule[0].free_item; + } } else if (item.pricing_rule) { item.price_list_rate = me.price_list_data[item.item_code] item.margin_rate_or_amount = 0.0; item.discount_percentage = 0.0; item.pricing_rule = null; me.apply_pricing_rule_on_item(item) + } else if (item.is_free_item) { + remove_item = true; + item.qty = 0 } if(item.discount_percentage > 0) { me.apply_pricing_rule_on_item(item) } - }) + }); + + if (remove_item) { + this.remove_zero_qty_items_from_cart(); + } }, get_pricing_rule: function (item) { var me = this; return $.grep(this.pricing_rules, function (data) { - if (item.qty >= data.min_qty && (item.qty <= (data.max_qty ? data.max_qty : item.qty))) { + me.get_mixed_min_max_qty_and_amt(data, item); + if (data.mixed_qty >= data.min_qty && (data.mixed_qty <= (data.max_qty ? data.max_qty : data.mixed_qty))) { if (me.validate_item_condition(data, item)) { if (in_list(['Customer', 'Customer Group', 'Territory', 'Campaign'], data.applicable_for)) { return me.validate_condition(data) @@ -2057,11 +2080,26 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }) }, + get_mixed_min_max_qty_and_amt: function(data, item) { + var apply_on = frappe.model.scrub(data.apply_on); + data.mixed_qty = 0.0 + if (data.mixed_conditions && in_list(data[apply_on], item[apply_on])) { + this.frm.doc.items.forEach(d => { + if (in_list(data[apply_on], d[apply_on])) { + data.mixed_qty += d.qty; + data.mixed_amt += d.amount; + } + }); + } else { + data.mixed_qty = item.qty; + data.mixed_amt = item.amount; + } + }, + validate_item_condition: function (data, item) { var apply_on = frappe.model.scrub(data.apply_on); - return (data.apply_on == 'Item Group') - ? this.validate_item_group(data.item_group, item.item_group) : (data[apply_on] == item[apply_on]); + return in_list(data[apply_on], item[apply_on]); }, validate_item_group: function (pr_item_group, cart_item_group) { From bf1e0139e275b5bb61d48c17f001889491bf6287 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 4 Jan 2021 16:23:14 +0530 Subject: [PATCH 154/181] fix: incorrect source warehouse in stock entry --- erpnext/stock/doctype/stock_entry/stock_entry.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 122e8b205fa..1a68b66a396 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1010,7 +1010,7 @@ class StockEntry(StockController): wo = frappe.get_doc("Work Order", self.work_order) wo_items = frappe.get_all('Work Order Item', filters={'parent': self.work_order}, - fields=["item_code", "required_qty", "consumed_qty", "transferred_qty"] + fields=["item_code", "required_qty", "consumed_qty", "transferred_qty", "source_warehouse"] ) work_order_qty = wo.material_transferred_for_manufacturing or wo.qty @@ -1028,9 +1028,13 @@ class StockEntry(StockController): qty = req_qty_each * flt(self.fg_completed_qty) if qty > 0: + from_warehouse = wo.wip_warehouse + if wo.skip_transfer and not wo.from_wip_warehouse: + from_warehouse = item.source_warehouse + self.add_to_stock_entry_detail({ item.item_code: { - "from_warehouse": wo.wip_warehouse, + "from_warehouse": from_warehouse, "to_warehouse": "", "qty": qty, "item_name": item.item_name, From ba95d3287637b368b1fe33ed8e88e1859997874e Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Mon, 4 Jan 2021 17:14:50 +0530 Subject: [PATCH 155/181] Revert "fix: function imports in account_balance_timeline.py" --- .../account_balance_timeline/account_balance_timeline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index 71a23d30b57..a9b3d7c4cc2 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -6,8 +6,8 @@ import frappe, json from frappe import _ from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form from erpnext.accounts.report.general_ledger.general_ledger import execute -from frappe.core.page.dashboard.dashboard import cache_source -from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending +from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan +from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending from frappe.utils.nestedset import get_descendants_of From cdc0b8e4350a499476f6bee67e239c8606b16b8f Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 4 Jan 2021 18:59:48 +0530 Subject: [PATCH 156/181] fix: Dont validate warehouse values between MR to Stock Entry - Remove validation that checks if warehouse in Stock Entry is the same as MR that it was pulled from --- erpnext/stock/doctype/stock_entry/stock_entry.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 122e8b205fa..ffdfa486204 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1241,9 +1241,8 @@ class StockEntry(StockController): mreq_item = frappe.db.get_value("Material Request Item", {"name": item.material_request_item, "parent": item.material_request}, ["item_code", "warehouse", "idx"], as_dict=True) - if mreq_item.item_code != item.item_code or \ - mreq_item.warehouse != (item.s_warehouse if self.purpose== "Material Issue" else item.t_warehouse): - frappe.throw(_("Item or Warehouse for row {0} does not match Material Request").format(item.idx), + if mreq_item.item_code != item.item_code: + frappe.throw(_("Item for row {0} does not match Material Request").format(item.idx), frappe.MappingMismatchError) def validate_batch(self): From cdd950dee5ea6bd11ac9a708fc8f70baee496ac8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 5 Jan 2021 09:25:50 +0530 Subject: [PATCH 157/181] fix: Ignore customer and supplier while deleting company transactions (#24280) --- erpnext/setup/doctype/company/delete_company_transactions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index 9979bc51e3a..15b6d522e1d 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -26,7 +26,8 @@ def delete_company_transactions(company_name): tabDocField where fieldtype='Link' and options='Company'"""): if doctype not in ("Account", "Cost Center", "Warehouse", "Budget", "Party Account", "Employee", "Sales Taxes and Charges Template", - "Purchase Taxes and Charges Template", "POS Profile", 'BOM'): + "Purchase Taxes and Charges Template", "POS Profile", 'BOM', + "Item default", "Customer", "Supplier"): delete_for_doctype(doctype, company_name) # reset company values From 00865b9af302d6e359f0723234cf9d9a4bdd7d9b Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 5 Jan 2021 13:32:46 +0530 Subject: [PATCH 158/181] fix(e-invoicing): minor calculation fixes (#24283) --- .../controllers/sales_and_purchase_return.py | 2 + erpnext/public/js/controllers/transaction.js | 1 + erpnext/regional/india/e_invoice/utils.py | 47 +++++++++++++------ erpnext/stock/get_item_details.py | 4 +- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 18b5daf128a..c3a15c58ce9 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -279,6 +279,7 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.po_detail = source_doc.po_detail target_doc.pr_detail = source_doc.pr_detail target_doc.purchase_invoice_item = source_doc.name + target_doc.price_list_rate = 0 elif doctype == "Delivery Note": target_doc.against_sales_order = source_doc.against_sales_order @@ -297,6 +298,7 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.dn_detail = source_doc.dn_detail target_doc.expense_account = source_doc.expense_account target_doc.sales_invoice_item = source_doc.name + target_doc.price_list_rate = 0 if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 58fb8e17996..665517fd4c7 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -521,6 +521,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ company: me.frm.doc.company, order_type: me.frm.doc.order_type, is_pos: cint(me.frm.doc.is_pos), + is_return: cint(me.frm.doc.is_return), is_subcontracted: me.frm.doc.is_subcontracted, transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date, ignore_pricing_rule: me.frm.doc.ignore_pricing_rule, diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 321e807b7f7..14be8296e52 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -92,21 +92,18 @@ def get_party_details(address_name): location = gstin_details.get('AddrLoc') or address.get('city') state_code = gstin_details.get('StateCode') 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') - phone = address.get('phone') - # get last 10 digit - phone = phone.replace(" ", "")[-10:] if phone else '' + address_line1 = '{} {}'.format(gstin_details.get('AddrBno') or "", gstin_details.get('AddrFlno') or "") + address_line2 = '{} {}'.format(gstin_details.get('AddrBnm') or "", gstin_details.get('AddrSt') or "") if state_code == 97: # according to einvoice standard pincode = 999999 return frappe._dict(dict( - 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 + gstin=gstin, legal_name=legal_name, + location=location, pincode=pincode, + state_code=state_code, address_line1=address_line1, + address_line2=address_line2 )) def get_gstin_details(gstin): @@ -146,16 +143,18 @@ def get_item_list(invoice): item.update(d.as_dict()) item.sr_no = d.idx - item.discount_amount = abs(item.discount_amount * item.qty) - item.description = d.item_name + item.description = d.item_name.replace('"', '\\"') + 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.discount_amount = abs(item.discount_amount * item.qty) + item.unit_rate = abs(item.base_amount / item.qty) + item.gross_amount = abs(item.base_amount) + item.taxable_value = abs(item.base_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 item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y' + item.serial_no = "" item = update_item_taxes(invoice, item) @@ -272,7 +271,25 @@ def get_eway_bill_details(invoice): vehicle_type=vehicle_type[invoice.gst_vehicle_type] )) +def validate_mandatory_fields(invoice): + if not invoice.company_address: + frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields')) + if not invoice.customer_address: + frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields')) + if not frappe.db.get_value('Address', invoice.company_address, 'gstin'): + frappe.throw( + _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'), + title=_('Missing Fields') + ) + if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'): + frappe.throw( + _('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'), + title=_('Missing Fields') + ) + def make_einvoice(invoice): + validate_mandatory_fields(invoice) + schema = read_json('einv_template') transaction_details = get_transaction_details(invoice) @@ -358,7 +375,7 @@ def validate_einvoice(validations, einvoice, errors=[]): # remove empty dicts einvoice.pop(fieldname, None) continue - + # convert to int or str if value_type == 'string': einvoice[fieldname] = str(value) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 48b4dc858c4..8e61c461d51 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -72,7 +72,9 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru update_party_blanket_order(args, out) - get_price_list_rate(args, item, out) + if not doc or cint(doc.get('is_return')) == 0: + # get price list rate only if the invoice is not a credit or debit note + get_price_list_rate(args, item, out) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args)) From 4e12c708976bbc54c3e8265dc2a7c576319de172 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 5 Jan 2021 14:54:52 +0530 Subject: [PATCH 159/181] fix: allow to override the basic rate for the finished good --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 1a68b66a396..c5f7f32274a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -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 number_of_fg_items == 1 or update_finished_item_rate: + if (fg_basic_rate == 0.0 and 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 ff524e676fcbe3097b1a8ff66ae587839ba06e31 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 5 Jan 2021 15:09:54 +0530 Subject: [PATCH 160/181] fix: 'Set Basic Rate Manually' field is now available for Manufacure type stock entry --- erpnext/stock/doctype/stock_entry/stock_entry.py | 6 ++++-- .../doctype/stock_entry_detail/stock_entry_detail.json | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index c5f7f32274a..1a719b2202a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -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): @@ -488,6 +488,8 @@ class StockEntry(StockController): if self.purpose in ["Manufacture", "Repack"]: for d in self.get("items"): + if d.set_basic_rate_manually: continue + if (d.transfer_qty and (d.bom_no or d.t_warehouse) and (getattr(self, "pro_doc", frappe._dict()).scrap_warehouse != d.t_warehouse)): @@ -499,7 +501,7 @@ class StockEntry(StockController): if raw_material_cost and self.purpose == "Manufacture": d.basic_rate = flt((raw_material_cost - scrap_material_cost) / flt(d.transfer_qty), d.precision("basic_rate")) d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount")) - elif self.purpose == "Repack" and total_fg_qty and not d.set_basic_rate_manually: + elif self.purpose == "Repack" and total_fg_qty: d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty) d.basic_amount = d.basic_rate * flt(d.qty) diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 9d397df8bcd..29869d01968 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -494,7 +494,7 @@ }, { "default": "0", - "depends_on": "eval:parent.purpose===\"Repack\" && doc.t_warehouse", + "depends_on": "eval:in_list([\"Repack\", \"Manufacture\"], parent.purpose) && doc.t_warehouse", "fieldname": "set_basic_rate_manually", "fieldtype": "Check", "label": "Set Basic Rate Manually" @@ -502,7 +502,7 @@ ], "idx": 1, "istable": 1, - "modified": "2020-09-04 12:12:35.668198", + "modified": "2021-01-05 15:05:04.891447", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", From d5abab4208a34b8128fc75628436b55f97551e82 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 6 Jan 2021 09:34:44 +0530 Subject: [PATCH 161/181] fix: tax calculation on salary slip for the first month (#24272) (#24309) * fix: tax calculation on salary slip for the first month * fix: net pay precision issue * fix: net pay precision issue Co-authored-by: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Co-authored-by: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> --- erpnext/hr/doctype/employee/employee.json | 3 +- erpnext/hr/doctype/salary_slip/salary_slip.py | 34 ++++++++++++------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index 4a60dba30e5..fa02ac4ea7e 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -782,7 +782,7 @@ "icon": "fa fa-user", "idx": 24, "image_field": "image", - "modified": "2020-01-09 04:23:55.611366", + "modified": "2020-01-09 05:23:55.611366", "modified_by": "Administrator", "module": "HR", "name": "Employee", @@ -824,7 +824,6 @@ "write": 1 } ], - "quick_entry": 1, "search_fields": "employee_name", "show_name_in_global_search": 1, "sort_field": "modified", diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 774aa51daa1..6e6ae4351c8 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -299,14 +299,17 @@ class SalarySlip(TransactionBase): def calculate_net_pay(self): if self.salary_structure: self.calculate_component_amounts("earnings") - self.gross_pay = self.get_component_totals("earnings") + self.gross_pay = self.get_component_totals("earnings", depends_on_payment_days=1) if self.salary_structure: self.calculate_component_amounts("deductions") - self.total_deduction = self.get_component_totals("deductions") - + self.set_loan_repayment() - + self.set_component_amounts_based_on_payment_days() + self.set_net_pay() + + def set_net_pay(self): + self.total_deduction = self.get_component_totals("deductions") self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment)) self.rounded_total = rounded(self.net_pay) @@ -323,8 +326,6 @@ class SalarySlip(TransactionBase): else: self.add_tax_components(payroll_period) - self.set_component_amounts_based_on_payment_days(component_type) - def add_structure_components(self, component_type): data = self.get_data_for_eval() for struct_row in self._salary_structure_doc.get(component_type): @@ -679,7 +680,7 @@ class SalarySlip(TransactionBase): cint(row.depends_on_payment_days) and cint(self.total_working_days) and (not self.salary_slip_based_on_timesheet or getdate(self.start_date) < joining_date or - getdate(self.end_date) > relieving_date + (relieving_date and getdate(self.end_date) > relieving_date) )): additional_amount = flt((flt(row.additional_amount) * flt(self.payment_days) / cint(self.total_working_days)), row.precision("additional_amount")) @@ -812,15 +813,21 @@ class SalarySlip(TransactionBase): struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary return struct_row - def get_component_totals(self, component_type): + def get_component_totals(self, component_type, depends_on_payment_days=0): + joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, + ["date_of_joining", "relieving_date"]) + total = 0.0 for d in self.get(component_type): if not d.do_not_include_in_total: - d.amount = flt(d.amount, d.precision("amount")) - total += d.amount + if depends_on_payment_days: + amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] + else: + amount = flt(d.amount, d.precision("amount")) + total += amount return total - def set_component_amounts_based_on_payment_days(self, component_type): + def set_component_amounts_based_on_payment_days(self): joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -830,8 +837,9 @@ class SalarySlip(TransactionBase): if not joining_date: frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) - for d in self.get(component_type): - d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] + for component_type in ("earnings", "deductions"): + for d in self.get(component_type): + d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount")) def set_loan_repayment(self): self.set('loans', []) From 3ddebae3fbc2678f48ef8c1a31f7d1a4c79f4f46 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 6 Jan 2021 10:49:27 +0530 Subject: [PATCH 162/181] refactor: fetch & validate address from erpnext rather than gst portal (#24299) --- erpnext/regional/india/e_invoice/utils.py | 54 ++++++++++++++--------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 14be8296e52..9cd4c7dc310 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -15,7 +15,7 @@ from frappe import _, bold from pyqrcode import create as qrcreate 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 +from frappe.utils.data import cstr, cint, formatdate as format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form def validate_einvoice_fields(doc): einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) @@ -84,26 +84,32 @@ def get_doc_details(invoice): )) def get_party_details(address_name): - address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] - gstin = address.get('gstin') + d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] - gstin_details = get_gstin_details(gstin) - legal_name = gstin_details.get('LegalName') or gstin_details.get('TradeName') - location = gstin_details.get('AddrLoc') or address.get('city') - state_code = gstin_details.get('StateCode') - pincode = gstin_details.get('AddrPncd') - address_line1 = '{} {}'.format(gstin_details.get('AddrBno') or "", gstin_details.get('AddrFlno') or "") - address_line2 = '{} {}'.format(gstin_details.get('AddrBnm') or "", gstin_details.get('AddrSt') or "") + if (not d.gstin + or not d.city + or not d.pincode + or not d.address_title + or not d.address_line1 + or not d.gst_state_number): - if state_code == 97: + frappe.throw( + msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format( + get_link_to_form('Address', address_name) + ), + title=_('Missing Address Fields') + ) + + if d.gst_state_number == 97: # according to einvoice standard pincode = 999999 return frappe._dict(dict( - gstin=gstin, legal_name=legal_name, - location=location, pincode=pincode, - state_code=state_code, address_line1=address_line1, - address_line2=address_line2 + gstin=d.gstin, legal_name=d.address_title, + location=d.city, pincode=d.pincode, + state_code=d.gst_state_number, + address_line1=d.address_line1, + address_line2=d.address_line2 )) def get_gstin_details(gstin): @@ -124,14 +130,22 @@ def get_gstin_details(gstin): 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'] + address_title, address_line1, address_line2, city = frappe.db.get_value( + 'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city'] ) + if not address_title or not address_line1 or not city: + frappe.throw( + msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format( + get_link_to_form('Address', address_name) + ), + title=_('Missing Address Fields') + ) + 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 + gstin='URP', legal_name=address_title, location=city, + address_line1=address_line1, address_line2=address_line2, + pincode=999999, state_code=96, place_of_supply=96 )) def get_item_list(invoice): From 79e3700499492f9b763ec0331d6a416cf53d486f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 8 Jan 2021 12:36:51 +0530 Subject: [PATCH 163/181] fix: payment entry multi-currency issue --- .../accounts/doctype/payment_entry/payment_entry.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 3883637e363..0bd54cd6055 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -187,7 +187,7 @@ frappe.ui.form.on('Payment Entry', { frm.toggle_display("base_received_amount", ( frm.doc.paid_to_account_currency != company_currency && - frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency + frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency && frm.doc.base_paid_amount != frm.doc.base_received_amount )); @@ -386,6 +386,8 @@ frappe.ui.form.on('Payment Entry', { set_account_currency_and_balance: function(frm, account, currency_field, balance_field, callback_function) { + + var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; if (frm.doc.posting_date && account) { frappe.call({ method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details", @@ -412,6 +414,14 @@ frappe.ui.form.on('Payment Entry', { if(!frm.doc.paid_amount && frm.doc.received_amount) frm.events.received_amount(frm); + + if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency + && frm.doc.paid_amount != frm.doc.received_amount) { + if (company_currency != frm.doc.paid_from_account_currency && + frm.doc.payment_type == "Pay") { + frm.doc.paid_amount = frm.doc.received_amount; + } + } } }, () => { From 0abed806a3d1e50916ebeb7469387d235a3f1423 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 8 Dec 2020 12:20:07 +0530 Subject: [PATCH 164/181] feat(reports): get item details from Item doctype instead of the Transaction --- .../item_wise_purchase_register.py | 21 ++++--- .../item_wise_sales_register.py | 58 ++++++++++--------- .../item_wise_sales_history.py | 32 ++++++---- 3 files changed, 67 insertions(+), 44 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 1f78c7a006f..c2a62a3c6d8 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -8,6 +8,7 @@ from frappe.utils import flt from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts, get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row, get_group_by_conditions) +from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details def execute(filters=None): return _execute(filters) @@ -23,7 +24,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum aii_account_map = get_aii_accounts() if item_list: itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency, - doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges") + doctype='Purchase Invoice', tax_doctype='Purchase Taxes and Charges') po_pr_map = get_purchase_receipts_against_purchase_order(item_list) @@ -35,10 +36,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum if filters.get('group_by'): grand_total = get_grand_total(filters, 'Purchase Invoice') + item_details = get_item_details() + for d in item_list: if not d.stock_qty: continue + item_record = item_details.get(d.item_code) + purchase_receipt = None if d.purchase_receipt: purchase_receipt = d.purchase_receipt @@ -49,8 +54,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row = { 'item_code': d.item_code, - 'item_name': d.item_name, - 'item_group': d.item_group, + 'item_name': item_record.item_name, + 'item_group': item_record.item_group, 'description': d.description, 'invoice': d.parent, 'posting_date': d.posting_date, @@ -82,10 +87,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum for tax in tax_columns: item_tax = itemised_tax.get(d.name, {}).get(tax, {}) row.update({ - frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0), - frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0), + frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0), + frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0), }) - total_tax += flt(item_tax.get("tax_amount")) + total_tax += flt(item_tax.get('tax_amount')) row.update({ 'total_tax': total_tax, @@ -317,8 +322,8 @@ def get_items(filters, additional_query_columns): select `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, - `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`, - `tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.description, + `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, + `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 92a22e62f14..d22111c9f5a 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -8,6 +8,7 @@ from frappe.utils import flt, cstr from frappe.model.meta import get_field_precision from frappe.utils.xlsxutils import handle_html from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments +from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details def execute(filters=None): return _execute(filters) @@ -17,7 +18,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum filters.update({"from_date": filters.get("date_range") and filters.get("date_range")[0], "to_date": filters.get("date_range") and filters.get("date_range")[1]}) columns = get_columns(additional_table_columns, filters) - company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency") + company_currency = frappe.get_cached_value('Company', filters.get('company'), 'default_currency') item_list = get_items(filters, additional_query_columns) if item_list: @@ -34,7 +35,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum if filters.get('group_by'): grand_total = get_grand_total(filters, 'Sales Invoice') + customer_details = get_customer_details() + item_details = get_item_details() + for d in item_list: + customer_record = customer_details.get(d.customer) + item_record = item_details.get(d.item_code) + delivery_note = None if d.delivery_note: delivery_note = d.delivery_note @@ -46,14 +53,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row = { 'item_code': d.item_code, - 'item_name': d.item_name, - 'item_group': d.item_group, + 'item_name': item_record.item_name, + 'item_group': item_record.item_group, 'description': d.description, 'invoice': d.parent, 'posting_date': d.posting_date, 'customer': d.customer, - 'customer_name': d.customer_name, - 'customer_group': d.customer_group, + 'customer_name': customer_record.customer_name, + 'customer_group': customer_record.customer_group, } if additional_query_columns: @@ -91,10 +98,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum for tax in tax_columns: item_tax = itemised_tax.get(d.name, {}).get(tax, {}) row.update({ - frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0), - frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0), + frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0), + frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0), }) - total_tax += flt(item_tax.get("tax_amount")) + total_tax += flt(item_tax.get('tax_amount')) row.update({ 'total_tax': total_tax, @@ -227,7 +234,7 @@ def get_columns(additional_table_columns, filters): if filters.get('group_by') != 'Terriotory': columns.extend([ { - 'label': _("Territory"), + 'label': _('Territory'), 'fieldname': 'territory', 'fieldtype': 'Link', 'options': 'Territory', @@ -382,13 +389,12 @@ def get_items(filters, additional_query_columns): `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, - `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name, - `tabSales Invoice Item`.item_group, `tabSales Invoice Item`.description, `tabSales Invoice Item`.sales_order, - `tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account, - `tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty, - `tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_rate, - `tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name, - `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail, + `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, + `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note, + `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center, + `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom, + `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, + `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail, `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} from `tabSales Invoice`, `tabSales Invoice Item` where `tabSales Invoice`.name = `tabSales Invoice Item`.parent @@ -425,14 +431,14 @@ def get_deducted_taxes(): return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'") def get_tax_accounts(item_list, columns, company_currency, - doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): + doctype='Sales Invoice', tax_doctype='Sales Taxes and Charges'): import json item_row_map = {} tax_columns = [] invoice_item_row = {} itemised_tax = {} - tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"), + tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field('tax_amount'), currency=company_currency) or 2 for d in item_list: @@ -477,8 +483,8 @@ def get_tax_accounts(item_list, columns, company_currency, tax_rate = tax_data tax_amount = 0 - if charge_type == "Actual" and not tax_rate: - tax_rate = "NA" + if charge_type == 'Actual' and not tax_rate: + tax_rate = 'NA' item_net_amount = sum([flt(d.base_net_amount) for d in item_row_map.get(parent, {}).get(item_code, [])]) @@ -492,17 +498,17 @@ def get_tax_accounts(item_list, columns, company_currency, if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value) itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ - "tax_rate": tax_rate, - "tax_amount": tax_value + 'tax_rate': tax_rate, + 'tax_amount': tax_value }) except ValueError: continue - elif charge_type == "Actual" and tax_amount: + elif charge_type == 'Actual' and tax_amount: for d in invoice_item_row.get(parent, []): itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ - "tax_rate": "NA", - "tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total, + 'tax_rate': 'NA', + 'tax_amount': flt((tax_amount * d.base_net_amount) / d.base_net_total, tax_amount_precision) }) @@ -564,7 +570,7 @@ def add_total_row(data, filters, prev_group_by_value, item, total_row_map, }) total_row_map.setdefault('total_row', { - subtotal_display_field: "Total", + subtotal_display_field: 'Total', 'stock_qty': 0.0, 'amount': 0.0, 'bold': 1, diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index f1b8bc34efb..12c072b9f4a 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -10,8 +10,8 @@ from frappe.utils.nestedset import get_descendants_of def execute(filters=None): filters = frappe._dict(filters or {}) if filters.from_date > filters.to_date: - frappe.throw(_('From Date cannot be greater than To Date')) - + frappe.throw(_("From Date cannot be greater than To Date")) + columns = get_columns(filters) data = get_data(filters) return columns, data @@ -145,14 +145,16 @@ def get_data(filters): company_list.append(filters.get("company")) customer_details = get_customer_details() + item_details = get_item_details() sales_order_records = get_sales_order_details(company_list, filters) for record in sales_order_records: customer_record = customer_details.get(record.customer) + item_record = item_details.get(record.item_code) row = { "item_code": record.item_code, - "item_name": record.item_name, - "item_group": record.item_group, + "item_name": item_record.item_name, + "item_group": item_record.item_group, "description": record.description, "quantity": record.qty, "uom": record.uom, @@ -187,8 +189,8 @@ def get_conditions(filters): return conditions def get_customer_details(): - details = frappe.get_all('Customer', - fields=['name', 'customer_name', "customer_group"]) + details = frappe.get_all("Customer", + fields=["name", "customer_name", "customer_group"]) customer_details = {} for d in details: customer_details.setdefault(d.name, frappe._dict({ @@ -197,15 +199,25 @@ def get_customer_details(): })) return customer_details +def get_item_details(): + details = frappe.db.get_all("Item", + fields=["item_code", "item_name", "item_group"]) + item_details = {} + for d in details: + item_details.setdefault(d.item_code, frappe._dict({ + "item_name": d.item_name, + "item_group": d.item_group + })) + return item_details + def get_sales_order_details(company_list, filters): conditions = get_conditions(filters) return frappe.db.sql(""" SELECT - so_item.item_code, so_item.item_name, so_item.item_group, - so_item.description, so_item.qty, so_item.uom, - so_item.base_rate, so_item.base_amount, so.name, - so.transaction_date, so.customer, so.territory, + so_item.item_code, so_item.description, so_item.qty, + so_item.uom, so_item.base_rate, so_item.base_amount, + so.name, so.transaction_date, so.customer,so.territory, so.project, so_item.delivered_qty, so_item.billed_amt, so.company FROM From e0e5783b3ede9e96c9357937068c481fc64a82d4 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 8 Dec 2020 12:32:19 +0530 Subject: [PATCH 165/181] fix: import statement --- .../item_wise_purchase_register/item_wise_purchase_register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index c2a62a3c6d8..b435a9a53cc 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -8,7 +8,7 @@ from frappe.utils import flt from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts, get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row, get_group_by_conditions) -from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details +from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details def execute(filters=None): return _execute(filters) From 830626d86ace74ce7fbf63aaaaf83d94e5b34a8d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 13 Jan 2021 00:05:29 +0530 Subject: [PATCH 166/181] fix: incorrect key --- erpnext/controllers/buying_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 68fc331e218..59c371200c8 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -296,7 +296,7 @@ class BuyingController(StockController): raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {}) consumed_qty = raw_material_data.get('qty', 0) - consumed_serial_nos = raw_material_data.get('serial_nos', '') + consumed_serial_nos = raw_material_data.get('serial_no', '') consumed_batch_nos = raw_material_data.get('batch_nos', '') transferred_qty = raw_material.qty From 4d835641887291f601d60a8991948dd31a92b4b7 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Wed, 13 Jan 2021 20:57:15 +0530 Subject: [PATCH 167/181] fix: check for tax_rate (#24348) --- erpnext/regional/report/gstr_1/gstr_1.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 28b77c5b694..61c41bbddf4 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -255,15 +255,16 @@ class Gstr1Report(object): for item_code, tax_amounts in item_wise_tax_detail.items(): tax_rate = tax_amounts[0] - if cgst_or_sgst: - tax_rate *= 2 - if parent not in self.cgst_sgst_invoices: - self.cgst_sgst_invoices.append(parent) + if tax_rate: + if cgst_or_sgst: + tax_rate *= 2 + if parent not in self.cgst_sgst_invoices: + self.cgst_sgst_invoices.append(parent) - rate_based_dict = self.items_based_on_tax_rate\ - .setdefault(parent, {}).setdefault(tax_rate, []) - if item_code not in rate_based_dict: - rate_based_dict.append(item_code) + rate_based_dict = self.items_based_on_tax_rate\ + .setdefault(parent, {}).setdefault(tax_rate, []) + if item_code not in rate_based_dict: + rate_based_dict.append(item_code) except ValueError: continue if unidentified_gst_accounts: From a250d415aaa6d9ea1eb152160c56676462756f9f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 14 Jan 2021 11:47:57 +0530 Subject: [PATCH 168/181] fix: extra transferred qty has not consumed against work order --- .../doctype/work_order/test_work_order.py | 43 ++++++++++++++++++- .../stock/doctype/stock_entry/stock_entry.py | 8 +++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index dac61b8e974..b11fdb04745 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -526,7 +526,6 @@ class TestWorkOrder(unittest.TestCase): ste1.submit() ste_cancel_list.append(ste1) - print(wo_order.name) ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2)) self.assertEquals(ste3.fg_completed_qty, 2) @@ -539,6 +538,48 @@ class TestWorkOrder(unittest.TestCase): frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0) + def test_extra_material_transfer(self): + frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0) + frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", + "Material Transferred for Manufacture") + + wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4) + + ste_cancel_list = [] + ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item", + target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0) + ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100", + target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0) + + ste_cancel_list.extend([ste1, ste2]) + + itemwise_qty = {} + s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4)) + for row in s.items: + row.qty = row.qty + 2 + itemwise_qty.setdefault(row.item_code, row.qty) + + s.submit() + ste_cancel_list.append(s) + + ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) + for ste_row in ste3.items: + if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: + self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) + + ste3.submit() + ste_cancel_list.append(ste3) + + ste2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2)) + for ste_row in ste2.items: + if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse: + self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2) + + for ste_doc in ste_cancel_list: + ste_doc.cancel() + + frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM") + def get_scrap_item_details(bom_no): scrap_items = {} for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 1a719b2202a..ba65cd149c7 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1115,7 +1115,10 @@ class StockEntry(StockController): else: qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty else: - qty = req_qty_each * flt(self.fg_completed_qty) + if self.flags.backflush_based_on == "Material Transferred for Manufacture": + qty = (item.qty/trans_qty) * flt(self.fg_completed_qty) + else: + qty = req_qty_each * flt(self.fg_completed_qty) elif backflushed_materials.get(item.item_code): for d in backflushed_materials.get(item.item_code): @@ -1123,7 +1126,8 @@ class StockEntry(StockController): if (qty > req_qty): qty = (qty/trans_qty) * flt(self.fg_completed_qty) - if consumed_qty: + if consumed_qty and frappe.db.get_single_value("Manufacturing Settings", + "material_consumption"): qty -= consumed_qty if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')): From 2eea0f003f037fbf8d18ab4078b54de821d1e901 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 14 Jan 2021 12:20:39 +0530 Subject: [PATCH 169/181] fix: test for raising MR-SE mismatch error - Make test add mismatched item code instead of warehouse, since warehouse can be different. --- .../stock/doctype/material_request/test_material_request.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 19924b16363..dccfc047354 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -427,6 +427,7 @@ class TestMaterialRequest(unittest.TestCase): "basic_rate": 1.0 }) se_doc.get("items")[1].update({ + "item_code": "_Test Item Home Desktop 100", "qty": 3.0, "transfer_qty": 3.0, "s_warehouse": "_Test Warehouse 1 - _TC", @@ -537,7 +538,7 @@ class TestMaterialRequest(unittest.TestCase): mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture', uom="_Test UOM 1", conversion_factor=12) - + requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') self.assertEqual(requested_qty, existing_requested_qty + 120) From 8862166223a3394811680ed33e20285f9f0b96e6 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 14 Jan 2021 12:22:23 +0530 Subject: [PATCH 170/181] fix(e-invoicing): minor ux fixes (#24366) --- .../e_invoice_request_log.json | 7 +++--- .../e_invoice_settings.json | 9 ++++++- erpnext/regional/india/e_invoice/utils.py | 24 +++++++++++-------- 3 files changed, 25 insertions(+), 15 deletions(-) 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 5c1c79dc047..3034370feac 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 @@ -24,9 +24,8 @@ }, { "fieldname": "reference_invoice", - "fieldtype": "Link", - "label": "Reference Invoice", - "options": "Sales Invoice" + "fieldtype": "Data", + "label": "Reference Invoice" }, { "fieldname": "headers", @@ -64,7 +63,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-12-24 21:09:38.882866", + "modified": "2021-01-13 12:06:57.253111", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Request Log", 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 4dcb22a54c7..db8bda75bfd 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -7,6 +7,7 @@ "field_order": [ "enable", "section_break_2", + "sandbox_mode", "credentials", "auth_token", "token_expiry" @@ -41,12 +42,18 @@ "label": "Credentials", "mandatory_depends_on": "enable", "options": "E Invoice User" + }, + { + "default": "0", + "fieldname": "sandbox_mode", + "fieldtype": "Check", + "label": "Sandbox Mode" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-12-22 15:34:57.280044", + "modified": "2021-01-13 12:04:49.449199", "modified_by": "Administrator", "module": "Regional", "name": "E Invoice Settings", diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 9cd4c7dc310..dd98b279019 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -421,18 +421,22 @@ class RequestFailed(Exception): pass class GSPConnector(): def __init__(self, doctype=None, docname=None): self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings') + sandbox_mode = self.e_invoice_settings.sandbox_mode + 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' - 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' - + # authenticate url is same for sandbox & live + self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token' + self.base_url = 'https://gsp.adaequare.com' if not sandbox_mode else 'https://gsp.adaequare.com/test' + + self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel' + self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn' + self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice' + self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin' + self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi' + self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' + def get_credentials(self): if self.invoice: gstin = self.get_seller_gstin() @@ -765,7 +769,7 @@ class GSPConnector(): _file = frappe.new_doc('File') _file.update({ - 'file_name': 'QRCode_{}.png'.format(docname), + 'file_name': 'QRCode_{}.png'.format(docname.replace('/', '-')), 'attached_to_doctype': doctype, 'attached_to_name': docname, 'content': 'qrcode', From bbec5ccc83fc398f1771741f1fc257a454654e62 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 7 Jan 2021 15:25:39 +0530 Subject: [PATCH 171/181] fix: Company Wise Valuation Rate for RM in BOM --- erpnext/manufacturing/doctype/bom/bom.js | 17 +++++++++++------ erpnext/manufacturing/doctype/bom/bom.py | 24 ++++++++++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 74169c80d35..89291263b64 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -253,7 +253,7 @@ cur_frm.cscript.hour_rate = function(doc) { cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate; -cur_frm.cscript.bom_no = function(doc, cdt, cdn) { +cur_frm.cscript.bom_no = function(doc, cdt, cdn) { get_bom_material_detail(doc, cdt, cdn, false); }; @@ -261,17 +261,22 @@ cur_frm.cscript.is_default = function(doc) { if (doc.is_default) cur_frm.set_value("is_active", 1); }; -var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) { +var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) { + if (!doc.company) { + frappe.throw({message: __("Please select a Company first."), title: __("Mandatory")}); + } + var d = locals[cdt][cdn]; if (d.item_code) { return frappe.call({ doc: doc, method: "get_bom_material_detail", args: { - 'item_code': d.item_code, - 'bom_no': d.bom_no != null ? d.bom_no: '', + "company": doc.company, + "item_code": d.item_code, + "bom_no": d.bom_no != null ? d.bom_no: '', "scrap_items": scrap_items, - 'qty': d.qty, + "qty": d.qty, "stock_qty": d.stock_qty, "include_item_in_manufacturing": d.include_item_in_manufacturing, "uom": d.uom, @@ -309,7 +314,7 @@ cur_frm.cscript.rate = function(doc, cdt, cdn) { } if (d.bom_no) { - frappe.msgprint(__("You can not change rate if BOM mentioned agianst any item")); + frappe.msgprint(__("You cannot change the rate if BOM is mentioned against any Item.")); get_bom_material_detail(doc, cdt, cdn, scrap_items); } else { erpnext.bom.calculate_rm_cost(doc); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 14bcbaac6ff..0bc6a70376b 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -51,6 +51,10 @@ class BOM(WebsiteGenerator): def validate(self): self.route = frappe.scrub(self.name).replace('_', '-') + + if not self.company: + frappe.throw(_("Please select a Company first."), title=_("Mandatory")) + self.clear_operations() self.validate_main_item() self.validate_currency() @@ -122,6 +126,7 @@ class BOM(WebsiteGenerator): self.validate_bom_currecny(item) ret = self.get_bom_material_detail({ + "company": self.company, "item_code": item.item_code, "item_name": item.item_name, "bom_no": item.bom_no, @@ -236,6 +241,7 @@ class BOM(WebsiteGenerator): for d in self.get("items"): rate = self.get_rm_rate({ + "company": self.company, "item_code": d.item_code, "bom_no": d.bom_no, "qty": d.qty, @@ -288,10 +294,20 @@ class BOM(WebsiteGenerator): """ Get weighted average of valuation rate from all warehouses """ total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0 - for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin` - where item_code=%s""", args['item_code'], as_dict=1): - total_qty += flt(d.actual_qty) - total_value += flt(d.stock_value) + item_bins = frappe.db.sql(""" + select + bin.actual_qty, bin.stock_value + from + `tabBin` bin, `tabWarehouse` warehouse + where + bin.item_code=%(item)s + and bin.warehouse = warehouse.name + and warehouse.company=%(company)s""", + {"item": args['item_code'], "company": args['company']}, as_dict=1) + + for d in item_bins: + total_qty += flt(d.actual_qty) + total_value += flt(d.stock_value) if total_qty: valuation_rate = total_value / total_qty From 8791d11359b565fe7ad387d4028d99a93a1c82bb Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 8 Jan 2021 09:56:04 +0530 Subject: [PATCH 172/181] fix: Back Update from QC based on Batch No --- .../quality_inspection/quality_inspection.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index c3bb5141849..f23f8461124 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -52,10 +52,21 @@ class QualityInspection(Document): doctype = 'Stock Entry Detail' if self.reference_type and self.reference_name: - frappe.db.sql("""update `tab{child_doc}` t1, `tab{parent_doc}` t2 - set t1.quality_inspection = %s, t2.modified = %s - where t1.parent = %s and t1.item_code = %s and t1.parent = t2.name""" - .format(parent_doc=self.reference_type, child_doc=doctype), + conditions = "" + if self.batch_no: + conditions += " and t1.batch_no = '%s'"%(self.batch_no) + + frappe.db.sql(""" + UPDATE + `tab{child_doc}` t1, `tab{parent_doc}` t2 + SET + t1.quality_inspection = %s, t2.modified = %s + WHERE + t1.parent = %s + and t1.item_code = %s + and t1.parent = t2.name + {conditions} + """.format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions), (quality_inspection, self.modified, self.reference_name, self.item_code)) @frappe.whitelist() From f88e908598f12c05f329742bbbea594f0beb1afb Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 8 Jan 2021 18:35:49 +0530 Subject: [PATCH 173/181] fix: Remove QI link on cancel wherever same QI name exists --- .../stock/doctype/quality_inspection/quality_inspection.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index f23f8461124..3bdecafdaba 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -53,9 +53,12 @@ class QualityInspection(Document): if self.reference_type and self.reference_name: conditions = "" - if self.batch_no: + if self.batch_no and self.docstatus == 1: conditions += " and t1.batch_no = '%s'"%(self.batch_no) + if self.docstatus == 2: # if cancel, then remove qi link wherever same name + conditions += " and t1.quality_inspection = '%s'"%(self.name) + frappe.db.sql(""" UPDATE `tab{child_doc}` t1, `tab{parent_doc}` t2 From 75cfbe132ba541106791a59a614ed6a212683427 Mon Sep 17 00:00:00 2001 From: Afshan Date: Thu, 14 Jan 2021 13:51:01 +0530 Subject: [PATCH 174/181] fix: calculation of remaining_sub_periods if relieving date before month start date --- erpnext/hr/doctype/payroll_period/payroll_period.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/payroll_period/payroll_period.py b/erpnext/hr/doctype/payroll_period/payroll_period.py index 6956c382854..0b500ea60d6 100644 --- a/erpnext/hr/doctype/payroll_period/payroll_period.py +++ b/erpnext/hr/doctype/payroll_period/payroll_period.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt +from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt, add_months from frappe.model.document import Document from erpnext.hr.utils import get_holidays_for_employee @@ -88,6 +88,8 @@ def get_period_factor(employee, start_date, end_date, payroll_frequency, payroll period_start = joining_date if relieving_date and getdate(relieving_date) < getdate(period_end): period_end = relieving_date + if month_diff(period_end, start_date) > 1: + start_date = add_months(start_date, - (month_diff(period_end, start_date)+1)) total_sub_periods, remaining_sub_periods = 0.0, 0.0 From 32bdb691df520c17845ea32aad7097ba9d8f9b30 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 14 Jan 2021 19:21:27 +0530 Subject: [PATCH 175/181] fix: bom stock report UoM correction (#24363) --- .../report/bom_stock_report/bom_stock_report.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py index 75ebcbc971b..1c6758e6f36 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py @@ -20,6 +20,7 @@ def get_columns(): _("Item") + ":Link/Item:150", _("Description") + "::300", _("BOM Qty") + ":Float:160", + _("BOM UoM") + "::160", _("Required Qty") + ":Float:120", _("In Stock Qty") + ":Float:120", _("Enough Parts to Build") + ":Float:200", @@ -32,7 +33,7 @@ def get_bom_stock(filters): bom = filters.get("bom") table = "`tabBOM Item`" - qty_field = "qty" + qty_field = "stock_qty" qty_to_produce = filters.get("qty_to_produce", 1) if int(qty_to_produce) <= 0: @@ -40,7 +41,6 @@ def get_bom_stock(filters): if filters.get("show_exploded_view"): table = "`tabBOM Explosion Item`" - qty_field = "stock_qty" if filters.get("warehouse"): warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1) @@ -59,6 +59,7 @@ def get_bom_stock(filters): bom_item.item_code, bom_item.description , bom_item.{qty_field}, + bom_item.stock_uom, bom_item.{qty_field} * {qty_to_produce} / bom.quantity, sum(ledger.actual_qty) as actual_qty, sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity))) From d2442da6f29629cf782c97ef049bd02ffccf0109 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Thu, 14 Jan 2021 19:22:47 +0530 Subject: [PATCH 176/181] fix: last purchase rate not updating when voucher cancelled if only one voucher is present - v12 (#24323) * fix: update last purchase rate * chore: use orm for update Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- erpnext/buying/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py index 47b48665b60..79542d482ad 100644 --- a/erpnext/buying/utils.py +++ b/erpnext/buying/utils.py @@ -35,9 +35,7 @@ def update_last_purchase_rate(doc, is_submit): frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx)) # update last purchsae rate - if last_purchase_rate: - frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""", - (flt(last_purchase_rate), d.item_code)) + frappe.db.set_value('Item', d.item_code, 'last_purchase_rate', flt(last_purchase_rate)) def validate_for_items(doc): items = [] From f2b5f3a84272a71dc54e5e1259910c0bf1d19138 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 14 Jan 2021 19:25:40 +0530 Subject: [PATCH 177/181] fix: for not having fiscal year while creating new company (#24161) * fix: Fiscal year fix while creating new company * minor fix Co-authored-by: Nabin Hait --- erpnext/regional/india/setup.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 3cfcaa4d9c8..8f2137a87aa 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -7,7 +7,7 @@ import frappe, os, json from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.permissions import add_permission, update_permission_property from erpnext.regional.india import states -from erpnext.accounts.utils import get_fiscal_year +from erpnext.accounts.utils import get_fiscal_year, FiscalYearError from frappe.utils import today def setup(company=None, patch=True): @@ -567,13 +567,18 @@ def set_salary_components(docs): def set_tax_withholding_category(company): accounts = [] + fiscal_year = None abbr = frappe.get_value("Company", company, "abbr") tds_account = frappe.get_value("Account", 'TDS Payable - {0}'.format(abbr), 'name') if company and tds_account: accounts = [dict(company=company, account=tds_account)] - fiscal_year = get_fiscal_year(today(), company=company)[0] + try: + fiscal_year = get_fiscal_year(today(), verbose=0, company=company)[0] + except FiscalYearError: + pass + docs = get_tds_details(accounts, fiscal_year) for d in docs: @@ -588,11 +593,14 @@ def set_tax_withholding_category(company): if accounts: doc.append("accounts", accounts[0]) - # if fiscal year don't match with any of the already entered data, append rate row - fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year] - if not fy_exist: - doc.append("rates", d.get('rates')[0]) - + if fiscal_year: + # if fiscal year don't match with any of the already entered data, append rate row + fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year] + if not fy_exist: + doc.append("rates", d.get('rates')[0]) + + doc.flags.ignore_permissions = True + doc.flags.ignore_mandatory = True doc.save() def set_tds_account(docs, company): From 35c2d2324d1c80d8d67d838042dd218e9ab4e82c Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 14 Jan 2021 19:45:13 +0530 Subject: [PATCH 178/181] fix: Ignore group cost center validation for period closing voucher (#24374) --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 077a11a9be6..4702b8a434f 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -138,7 +138,8 @@ class GLEntry(Document): frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}") .format(self.voucher_type, self.voucher_no, self.cost_center, self.company)) - if not self.flags.from_repost and self.cost_center and _check_is_group(): + if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \ + and self.cost_center and _check_is_group(): frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) From 7a40a2b2e2ac162caa0b7fc4847cc7df93c860f8 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Thu, 14 Jan 2021 19:46:19 +0530 Subject: [PATCH 179/181] fix: subscription prepaid date validation (#24373) --- erpnext/accounts/doctype/subscription/subscription.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index ae0059cd3cd..8cc5cb8e99b 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -333,7 +333,7 @@ class Subscription(Document): if not self.generate_invoice_at_period_start: return False - if self.is_new_subscription(): + if self.is_new_subscription() and getdate(nowdate()) >= getdate(self.current_invoice_start): return True # Check invoice dates and make sure it doesn't have outstanding invoices From 748f8145b66489c010074dfbb887029535342666 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 14 Jan 2021 19:53:51 +0530 Subject: [PATCH 180/181] feat: show transporter address in sales invoice (#23731) * feat: show transporter address in sales invoice * feat: multiple transporter address * fix: update all eway-bill fields * fix: travis * fix: travis * fix: travis * fix: travis * fix: travis Co-authored-by: Nabin Hait --- .../doctype/sales_invoice/regional/india.js | 10 ++ erpnext/hooks.py | 3 +- erpnext/patches.txt | 1 + .../v12_0/add_transporter_address_field.py | 150 ++++++++++++++++++ erpnext/regional/india/party.js | 2 +- erpnext/regional/india/setup.py | 30 +++- erpnext/regional/india/utils.py | 25 +++ 7 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 erpnext/patches/v12_0/add_transporter_address_field.py diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js index ba672c9368d..ca15626e19c 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js @@ -14,6 +14,16 @@ frappe.ui.form.on("Sales Invoice", { }; }); + frm.set_query('transporter_address', function (doc) { + return { + query: 'frappe.contacts.doctype.address.address.address_query', + filters: { + link_doctype: 'Supplier', + link_name: doc.transporter + } + } + }); + frm.set_query('driver', function(doc) { return { filters: { diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bf81805833b..219e426d782 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -245,7 +245,8 @@ doc_events = { "Sales Invoice": { "on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"], "on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel", - "on_trash": "erpnext.regional.check_deletion_permission" + "on_trash": "erpnext.regional.check_deletion_permission", + "validate": "erpnext.regional.india.utils.set_transporter_address" }, "Purchase Invoice": { "validate": "erpnext.regional.india.utils.update_grand_total_for_rcm" diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e6066a9fead..08eda7e2c43 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -678,4 +678,5 @@ 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.add_transporter_address_field #2020-10-27 erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02 diff --git a/erpnext/patches/v12_0/add_transporter_address_field.py b/erpnext/patches/v12_0/add_transporter_address_field.py new file mode 100644 index 00000000000..78da58f4e03 --- /dev/null +++ b/erpnext/patches/v12_0/add_transporter_address_field.py @@ -0,0 +1,150 @@ +from __future__ import unicode_literals +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + fields = [ + { + 'fieldname': 'transporter_info', + 'label': 'Transporter Info', + 'fieldtype': 'Section Break', + 'insert_after': 'terms', + 'collapsible': 1, + 'collapsible_depends_on': 'transporter', + 'print_hide': 1 + }, + { + 'fieldname': 'transporter', + 'label': 'Transporter', + 'fieldtype': 'Link', + 'insert_after': 'transporter_info', + 'options': 'Supplier', + 'print_hide': 1 + }, + { + 'fieldname': 'transporter_name', + 'label': 'Transporter Name', + 'fieldtype': 'Data', + 'insert_after': 'transporter', + 'fetch_from': 'transporter.name', + 'read_only': 1, + 'print_hide': 1, + 'translatable': 0 + }, + { + 'fieldname': 'gst_transporter_id', + 'label': 'GST Transporter ID', + 'fieldtype': 'Data', + 'insert_after': 'transporter_name', + 'fetch_from': 'transporter.gst_transporter_id', + 'print_hide': 1, + 'translatable': 0 + }, + { + 'fieldname': 'driver', + 'label': 'Driver', + 'fieldtype': 'Link', + 'insert_after': 'gst_transporter_id', + 'options': 'Driver', + 'print_hide': 1 + }, + { + 'fieldname': 'lr_no', + 'label': 'Transport Receipt No', + 'fieldtype': 'Data', + 'insert_after': 'driver', + 'print_hide': 1, + 'translatable': 0 + }, + { + 'fieldname': 'vehicle_no', + 'label': 'Vehicle No', + 'fieldtype': 'Data', + 'insert_after': 'lr_no', + 'print_hide': 1, + 'translatable': 0 + }, + { + 'fieldname': 'distance', + 'label': 'Distance (in km)', + 'fieldtype': 'Float', + 'insert_after': 'vehicle_no', + 'print_hide': 1 + }, + { + 'fieldname': 'transporter_col_break', + 'fieldtype': 'Column Break', + 'insert_after': 'distance' + }, + { + 'fieldname': 'transporter_address', + 'label': 'Transporter Address Name', + 'fieldtype': 'Link', + 'insert_after': 'transporter_col_break', + 'options': 'Address', + 'print_hide': 1 + }, + { + 'fieldname': 'transporter_address_display', + 'label': 'Transporter Address Preview', + 'fieldtype': 'Small Text', + 'insert_after': 'transporter_address', + 'read_only': 1, + 'print_hide': 1, + 'translatable': 0 + }, + { + 'fieldname': 'mode_of_transport', + 'label': 'Mode of Transport', + 'fieldtype': 'Select', + 'options': '\nRoad\nAir\nRail\nShip', + 'default': 'Road', + 'insert_after': 'transporter_address_display', + 'print_hide': 1, + 'translatable': 0 + }, + { + 'fieldname': 'driver_name', + 'label': 'Driver Name', + 'fieldtype': 'Data', + 'insert_after': 'mode_of_transport', + 'fetch_from': 'driver.full_name', + 'print_hide': 1, + 'translatable': 0 + }, + { + 'fieldname': 'lr_date', + 'label': 'Transport Receipt Date', + 'fieldtype': 'Date', + 'insert_after': 'driver_name', + 'default': 'Today', + 'print_hide': 1 + }, + { + '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': 'Regular', + 'insert_after': 'lr_date', + 'print_hide': 1, + 'translatable': 0 + }, + { + 'fieldname': 'ewaybill', + 'label': 'e-Way Bill No.', + 'fieldtype': 'Data', + 'depends_on': 'eval:(doc.docstatus === 1)', + 'allow_on_submit': 1, + 'insert_after': 'tax_id', + 'translatable': 0 + } + ] + + create_custom_fields({ 'Sales Invoice': fields }, update=True) + frappe.reload_doctype('Sales Invoice') \ No newline at end of file diff --git a/erpnext/regional/india/party.js b/erpnext/regional/india/party.js index 402a387c4a1..8e9c9a5f94e 100644 --- a/erpnext/regional/india/party.js +++ b/erpnext/regional/india/party.js @@ -22,4 +22,4 @@ erpnext.setup_gst_reminder_button = (doctype) => { } } }); -}; +}; \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 8f2137a87aa..4954b3438dc 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -273,11 +273,21 @@ def make_custom_fields(update=True): 'options': 'Supplier', 'print_hide': 1 }, + { + 'fieldname': 'transporter_name', + 'label': 'Transporter Name', + 'fieldtype': 'Data', + 'insert_after': 'transporter', + 'fetch_from': 'transporter.name', + 'read_only': 1, + 'print_hide': 1, + 'translatable': 0 + }, { 'fieldname': 'gst_transporter_id', 'label': 'GST Transporter ID', 'fieldtype': 'Data', - 'insert_after': 'transporter', + 'insert_after': 'transporter_name', 'fetch_from': 'transporter.gst_transporter_id', 'print_hide': 1, 'translatable': 0 @@ -319,11 +329,18 @@ def make_custom_fields(update=True): 'insert_after': 'distance' }, { - 'fieldname': 'transporter_name', - 'label': 'Transporter Name', - 'fieldtype': 'Data', + 'fieldname': 'transporter_address', + 'label': 'Transporter Address Name', + 'fieldtype': 'Link', 'insert_after': 'transporter_col_break', - 'fetch_from': 'transporter.name', + 'options': 'Address', + 'print_hide': 1 + }, + { + 'fieldname': 'transporter_address_display', + 'label': 'Transporter Address Preview', + 'fieldtype': 'Small Text', + 'insert_after': 'transporter_address', 'read_only': 1, 'print_hide': 1, 'translatable': 0 @@ -333,7 +350,8 @@ def make_custom_fields(update=True): 'label': 'Mode of Transport', 'fieldtype': 'Select', 'options': '\nRoad\nAir\nRail\nShip', - 'insert_after': 'transporter_name', + 'default': 'Road', + 'insert_after': 'transporter_address_display', 'print_hide': 1, 'translatable': 0 }, diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 2b6eedec3f8..89677433392 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -12,6 +12,7 @@ from erpnext.regional.india import number_state_mapping from six import string_types from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.utils import get_account_currency +from frappe.contacts.doctype.address.address import get_address_display from frappe.model.utils import get_fetch_values def validate_gstin_for_india(doc, method): @@ -137,6 +138,30 @@ def get_itemised_tax_breakup_data(doc, account_wise=False): def set_place_of_supply(doc, method=None): doc.place_of_supply = get_place_of_supply(doc, doc.doctype) +def set_transporter_address(doc, method=None): + country = frappe.get_cached_value('Company', doc.company, 'country') + if country != 'India': + return + + if doc.get("transporter_address"): + # once supplier is set, address can be selected from multiple transporter addresses + doc.transporter_address_display = get_address_display(doc.get("transporter_address")) + return + + transporter_address = frappe.db.get_value("Dynamic Link", { + 'link_doctype': 'Supplier', + 'link_name': doc.get('transporter'), + 'parenttype': 'Address' + }, "parent") + + if not transporter_address: + doc.transporter_address = "" + doc.transporter_address_display = "" + return + + doc.transporter_address = transporter_address + doc.transporter_address_display = get_address_display(transporter_address) + # don't remove this function it is used in tests def test_method(): '''test function''' From a8de2321a5e66c12d0cb7c5f3d3f9f6c40b95a29 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 14 Jan 2021 21:21:12 +0530 Subject: [PATCH 181/181] fix: Payment Period based on invoice date report fix/refactor (#24377) * fix: Payment Period based on invoice date report fix/refactor * fix: minor --- .../payment_period_based_on_invoice_date.py | 122 +++++++++++++++--- 1 file changed, 105 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py index 57a1231f5a9..98731d32ae9 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py @@ -59,23 +59,111 @@ def validate_filters(filters): def get_columns(filters): return [ - _("Payment Document") + ":: 100", - _("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":140", - _("Party Type") + "::100", - _("Party") + ":Dynamic Link/Party Type:140", - _("Posting Date") + ":Date:100", - _("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"), - _("Invoice Posting Date") + ":Date:130", - _("Payment Due Date") + ":Date:130", - _("Debit") + ":Currency:120", - _("Credit") + ":Currency:120", - _("Remarks") + "::150", - _("Age") +":Int:40", - "0-30:Currency:100", - "30-60:Currency:100", - "60-90:Currency:100", - _("90-Above") + ":Currency:100", - _("Delay in payment (Days)") + "::150" + { + "fieldname": "payment_document", + "label": _("Payment Document Type"), + "fieldtype": "Data", + "width": 100 + }, + { + "fieldname": "payment_entry", + "label": _("Payment Document"), + "fieldtype": "Dynamic Link", + "options": "payment_document", + "width": 160 + }, + { + "fieldname": "party_type", + "label": _("Party Type"), + "fieldtype": "Data", + "width": 100 + }, + { + "fieldname": "party", + "label": _("Party"), + "fieldtype": "Dynamic Link", + "options": "party_type", + "width": 160 + }, + { + "fieldname": "posting_date", + "label": _("Posting Date"), + "fieldtype": "Date", + "width": 100 + }, + { + "fieldname": "invoice", + "label": _("Invoice"), + "fieldtype": "Link", + "options": "Purchase Invoice" if filters.get("payment_type") == _("Outgoing") else "Sales Invoice", + "width": 160 + }, + { + "fieldname": "invoice_posting_date", + "label": _("Invoice Posting Date"), + "fieldtype": "Date", + "width": 100 + }, + { + "fieldname": "due_date", + "label": _("Payment Due Date"), + "fieldtype": "Date", + "width": 100 + }, + { + "fieldname": "debit", + "label": _("Debit"), + "fieldtype": "Currency", + "width": 140 + }, + { + "fieldname": "credit", + "label": _("Credit"), + "fieldtype": "Currency", + "width": 140 + }, + { + "fieldname": "remarks", + "label": _("Remarks"), + "fieldtype": "Data", + "width": 200 + }, + { + "fieldname": "age", + "label": _("Age"), + "fieldtype": "Int", + "width": 50 + }, + { + "fieldname": "range1", + "label": _("0-30"), + "fieldtype": "Currency", + "width": 140 + }, + { + "fieldname": "range2", + "label": _("30-60"), + "fieldtype": "Currency", + "width": 140 + }, + { + "fieldname": "range3", + "label": _("60-90"), + "fieldtype": "Currency", + "width": 140 + }, + { + "fieldname": "range4", + "label": _("90 Above"), + "fieldtype": "Currency", + "width": 140 + }, + { + "fieldname": "delay_in_payment", + "label": _("Delay in payment (Days)"), + "fieldtype": "Int", + "width": 100 + } ] def get_conditions(filters):