From 52a692ee084e9614d430a4e5425f2ad625ee7950 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 26 Dec 2018 00:40:17 +0530 Subject: [PATCH 1/9] [Fix] Not able to delete customer if contact is available --- erpnext/selling/doctype/customer/customer.py | 5 +++++ erpnext/selling/doctype/customer/test_customer.py | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index b17345c2e7..2a6853aa67 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -173,6 +173,11 @@ class Customer(TransactionBase): frappe.throw(_("""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}""").format(outstanding_amt)) def on_trash(self): + if self.customer_primary_contact: + frappe.db.sql("""update `tabCustomer` + set customer_primary_contact=null, mobile_no=null, email_id=null + where name=%s""", self.name) + delete_contact_and_address('Customer', self.name) if self.lead_name: frappe.db.sql("update `tabLead` set status='Interested' where name=%s", self.lead_name) diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index 45546e348a..123fd552c6 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -96,6 +96,15 @@ class TestCustomer(unittest.TestCase): so.save() + def test_delete_customer_contact(self): + customer = frappe.get_doc( + get_customer_dict('_Test Customer for delete')).insert(ignore_permissions=True) + + customer.mobile_no = "8989889890" + customer.save() + self.assertTrue(customer.customer_primary_contact) + frappe.delete_doc('Customer', customer.name) + def test_disabled_customer(self): make_test_records("Item") From 53c040f83874a8c50e82e942197995829bed5815 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 3 Jan 2019 12:50:18 +0530 Subject: [PATCH 2/9] Gst number validation fix --- 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 b878a1ea5d..3f293ce14f 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -11,7 +11,7 @@ def validate_gstin_for_india(doc, method): if doc.gstin: doc.gstin = doc.gstin.upper() if doc.gstin not in ["NA", "na"]: - p = re.compile("[0-9]{2}[a-zA-Z]{5}[0-9]{4}[a-zA-Z]{1}[1-9A-Za-z]{1}[Z]{1}[0-9a-zA-Z]{1}") + p = re.compile("[0-9]{2}[0-9A-Za-z]{10}[0-9A-Za-z]{1}[0-9a-zA-Z]{1}[0-9a-zA-Z]{1}") if not p.match(doc.gstin): frappe.throw(_("Invalid GSTIN or Enter NA for Unregistered")) From 547229fef12221fc4e1f9c86eafb0397d361feb3 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 3 Jan 2019 23:20:38 +0530 Subject: [PATCH 3/9] PAN card validation in GST --- 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 3f293ce14f..aad998b9c1 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -11,7 +11,7 @@ def validate_gstin_for_india(doc, method): if doc.gstin: doc.gstin = doc.gstin.upper() if doc.gstin not in ["NA", "na"]: - p = re.compile("[0-9]{2}[0-9A-Za-z]{10}[0-9A-Za-z]{1}[0-9a-zA-Z]{1}[0-9a-zA-Z]{1}") + p = re.compile("[0-9]{2}[a-zA-Z]{4}[0-9a-zA-Z]{1}[0-9]{4}[a-zA-Z]{1}[1-9a-zA-Z]{1}[0-9a-zA-Z]{1}[0-9a-zA-Z]{1}") if not p.match(doc.gstin): frappe.throw(_("Invalid GSTIN or Enter NA for Unregistered")) From 75ab0426326d2b6ba8c98115f6ce168551d39b7c Mon Sep 17 00:00:00 2001 From: Himanshu Date: Fri, 4 Jan 2019 17:13:43 +0530 Subject: [PATCH 4/9] removed lowercase regex --- 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 aad998b9c1..52956052aa 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -11,7 +11,7 @@ def validate_gstin_for_india(doc, method): if doc.gstin: doc.gstin = doc.gstin.upper() if doc.gstin not in ["NA", "na"]: - p = re.compile("[0-9]{2}[a-zA-Z]{4}[0-9a-zA-Z]{1}[0-9]{4}[a-zA-Z]{1}[1-9a-zA-Z]{1}[0-9a-zA-Z]{1}[0-9a-zA-Z]{1}") + p = re.compile("[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}[0-9A-Z]{1}") if not p.match(doc.gstin): frappe.throw(_("Invalid GSTIN or Enter NA for Unregistered")) From d40743a570948f5d1cd113755bdede4cb51e8d1a Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Jan 2019 13:38:43 +0530 Subject: [PATCH 5/9] fix: 14th digit may not be zero --- 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 52956052aa..9b7edc5338 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -11,7 +11,7 @@ def validate_gstin_for_india(doc, method): if doc.gstin: doc.gstin = doc.gstin.upper() if doc.gstin not in ["NA", "na"]: - p = re.compile("[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}[0-9A-Z]{1}") + p = re.compile("[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}") if not p.match(doc.gstin): frappe.throw(_("Invalid GSTIN or Enter NA for Unregistered")) From 2825b929c1bb0c84c067e1dcff0a31feffbf7146 Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Wed, 9 Jan 2019 19:15:10 +0530 Subject: [PATCH 6/9] fix(GSTIN Validation - india): added checksum validation for GSTIN --- erpnext/regional/india/utils.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 9b7edc5338..c4bfe915dc 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -12,8 +12,8 @@ def validate_gstin_for_india(doc, method): doc.gstin = doc.gstin.upper() if doc.gstin not in ["NA", "na"]: p = re.compile("[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}") - if not p.match(doc.gstin): - frappe.throw(_("Invalid GSTIN or Enter NA for Unregistered")) + if not p.match(doc.gstin) or doc.gstin != get_gstin_with_check_digit(doc.gstin[:-1]): + frappe.throw(_("Invalid GSTIN!! Check for typos or Enter NA for Unregistered")) if not doc.gst_state: if doc.state in states: @@ -25,6 +25,28 @@ def validate_gstin_for_india(doc, method): frappe.throw(_("First 2 digits of GSTIN should match with State number {0}") .format(doc.gst_state_number)) +def get_gstin_with_check_digit(gstin_without_check_digit): + ''' Function to get the check digit for the gstin. + + param: gstin_without_check_digit + return: GSTIN with check digit + ''' + factor = 1 + total = 0 + code_point_chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + input_chars = gstin_without_check_digit.strip() + if not input_chars: + frappe.throw(_("GSTIN supplied for checkdigit calculation is blank")) + mod = len(code_point_chars) + for char in input_chars: + digit = factor * code_point_chars.find(char) + if digit < 0: + frappe.throw(_("GSTIN supplied for checkdigit contains invalid character")) + digit = (digit / mod) + (digit % mod) + total += digit + factor = 2 if factor == 1 else 1 + return ''.join([gstin_without_check_digit,code_point_chars[((mod - (total % mod)) % mod)]]) + def get_itemised_tax_breakup_header(item_doctype, tax_accounts): if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts From 07cf4e8f5b03f0a7229ac0120162b73a124bd8bb Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 10 Jan 2019 11:07:51 +0530 Subject: [PATCH 7/9] fix: use division consistent with Python 3 & other changes --- erpnext/regional/india/utils.py | 55 +++++++++++++++++---------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index c4bfe915dc..9f161afd57 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -8,44 +8,47 @@ def validate_gstin_for_india(doc, method): if not hasattr(doc, 'gstin'): return - if doc.gstin: - doc.gstin = doc.gstin.upper() - if doc.gstin not in ["NA", "na"]: - p = re.compile("[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}") - if not p.match(doc.gstin) or doc.gstin != get_gstin_with_check_digit(doc.gstin[:-1]): - frappe.throw(_("Invalid GSTIN!! Check for typos or Enter NA for Unregistered")) + doc.gstin = doc.gstin.upper().strip() + if not doc.gstin or doc.gstin == 'NA': + return - if not doc.gst_state: - if doc.state in states: - doc.gst_state = doc.state + if len(doc.gstin) != 15: + frappe.throw(_("Invalid GSTIN! A GSTIN must have 15 characters.")) - if doc.gst_state: - doc.gst_state_number = state_numbers[doc.gst_state] - if doc.gstin and doc.gstin != "NA" and doc.gst_state_number != doc.gstin[:2]: - frappe.throw(_("First 2 digits of GSTIN should match with State number {0}") - .format(doc.gst_state_number)) + p = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$") + if not p.match(doc.gstin): + frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the format of GSTIN.")) -def get_gstin_with_check_digit(gstin_without_check_digit): - ''' Function to get the check digit for the gstin. + validate_gstin_check_digit(doc.gstin) - param: gstin_without_check_digit - return: GSTIN with check digit - ''' + if not doc.gst_state and doc.state: + state = doc.state.lower() + states_lowercase = {s.lower():s for s in states} + if state in states_lowercase: + doc.gst_state = states_lowercase[state] + else: + return + + doc.gst_state_number = state_numbers[doc.gst_state] + if doc.gst_state_number != doc.gstin[:2]: + frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.") + .format(doc.gst_state_number)) + +def validate_gstin_check_digit(gstin): + ''' Function to validate the check digit of the GSTIN.''' factor = 1 total = 0 code_point_chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' - input_chars = gstin_without_check_digit.strip() - if not input_chars: - frappe.throw(_("GSTIN supplied for checkdigit calculation is blank")) mod = len(code_point_chars) + input_chars = gstin[:-1] for char in input_chars: digit = factor * code_point_chars.find(char) - if digit < 0: - frappe.throw(_("GSTIN supplied for checkdigit contains invalid character")) - digit = (digit / mod) + (digit % mod) + digit = (digit // mod) + (digit % mod) total += digit factor = 2 if factor == 1 else 1 - return ''.join([gstin_without_check_digit,code_point_chars[((mod - (total % mod)) % mod)]]) + if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: + frappe.throw(_("Invalid GSTIN! The check digit validation has failed. " + + "Please ensure you've typed the GSTIN correctly.")) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): From f99e013ebce325632615e2f8ba38737e70080b6d Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 10 Jan 2019 11:57:24 +0530 Subject: [PATCH 8/9] fix: gstin validation should work when there is no state (#16378) --- erpnext/regional/india/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 9f161afd57..fd0eb34abc 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -21,7 +21,9 @@ def validate_gstin_for_india(doc, method): validate_gstin_check_digit(doc.gstin) - if not doc.gst_state and doc.state: + if not doc.gst_state: + if not doc.state: + return state = doc.state.lower() states_lowercase = {s.lower():s for s in states} if state in states_lowercase: From 4ed7cfc515fd50a99540ee4888f1595dd2d9a665 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 14 Jan 2019 17:14:39 +0530 Subject: [PATCH 9/9] tests(cost-center-company): Validate cost center's company and revent tests --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 5 ++--- .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 47e214e1b4..9cec1c0dc6 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -18,15 +18,14 @@ class GLEntry(Document): self.flags.ignore_submit_comment = True self.check_mandatory() self.validate_and_set_fiscal_year() + self.pl_must_have_cost_center() + self.validate_cost_center() if not self.flags.from_repost: - self.pl_must_have_cost_center() self.check_pl_account() - self.validate_cost_center() self.validate_party() self.validate_currency() - def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False): if not from_repost: self.validate_account_details(adv_adj) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 29caea156a..1207c5d128 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -325,7 +325,8 @@ def make_purchase_receipt(**args): "conversion_factor": args.conversion_factor or 1.0, "serial_no": args.serial_no, "stock_uom": args.stock_uom or "_Test UOM", - "uom": args.uom or "_Test UOM" + "uom": args.uom or "_Test UOM", + "cost_center": "_Test Cost Center - _TC" }) if not args.do_not_save: