From 614559f234411b8d4b558f506eadd9aa51ab9c39 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 13 Dec 2018 16:36:35 +0100 Subject: [PATCH 01/35] fix(pro-plan): Actual Qty of RM considering children in case source warehouse is a group warehouse --- .../manufacturing/doctype/production_plan/production_plan.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 7d11ae4993..d4620cd952 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -499,7 +499,8 @@ def get_bin_details(row): conditions = "" warehouse = row.source_warehouse or row.default_warehouse or row.warehouse if warehouse: - conditions = " and warehouse='{0}'".format(frappe.db.escape(warehouse)) + lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) + conditions = " and exists(select name from `tabWarehouse` where lft >= {0} and rgt <= {1} and name=`tabBin`.warehouse)".format(lft, rgt) item_projected_qty = frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty, ifnull(sum(actual_qty),0) as actual_qty from `tabBin` From 52a692ee084e9614d430a4e5425f2ad625ee7950 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 26 Dec 2018 00:40:17 +0530 Subject: [PATCH 02/35] [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 af75d9a384d1ef53b8c89756cb2e1c8d450f2c55 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 28 Dec 2018 14:47:51 +0530 Subject: [PATCH 03/35] fix(website): Pagination in child item groups Item groups pages which had child item groups were not paged at all despite having next, prev buttons --- erpnext/setup/doctype/item_group/item_group.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 6bc9036195..f258f7d295 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -136,7 +136,10 @@ def get_child_groups_for_list_in_html(item_group, start, limit, search): rgt = ('<', item_group.rgt), ), or_filters = search_filters, - order_by = 'weightage desc, name asc') + order_by = 'weightage desc, name asc', + start = start, + limit = limit + ) return [get_item_for_list_in_html(r) for r in data] From fcbe410c2f1e606a30ebe7589ed8e03d7b22daf5 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 28 Dec 2018 16:31:05 +0530 Subject: [PATCH 04/35] feat(stock-reco): Fetch items based on group warehouse --- .../stock_reconciliation.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 257434fb89..dacb53a587 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -270,24 +270,28 @@ class StockReconciliation(StockController): @frappe.whitelist() def get_items(warehouse, posting_date, posting_time): - items = frappe.get_list("Bin", fields=["item_code"], filters={"warehouse": warehouse}, as_list=1) + lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) + items = frappe.db.sql("""select item_code, warehouse from tabBin + where exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=`tabBin`.warehouse) + """, (lft, rgt)) - items += frappe.get_list("Item", fields=["name"], filters= {"is_stock_item": 1, "has_serial_no": 0, - "has_batch_no": 0, "has_variants": 0, "disabled": 0, "default_warehouse": warehouse}, - as_list=1) + items += frappe.db.sql("""select name, default_warehouse from tabItem + where exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=`tabItem`.default_warehouse) + and is_stock_item = 1 and has_serial_no = 0 and has_batch_no = 0 and has_variants = 0 and disabled = 0 + """, (lft, rgt)) res = [] - for item in set(items): - stock_bal = get_stock_balance(item[0], warehouse, posting_date, posting_time, + for item, wh in set(items): + stock_bal = get_stock_balance(item, wh, posting_date, posting_time, with_valuation_rate=True) - if frappe.db.get_value("Item",item[0],"disabled") == 0: + if frappe.db.get_value("Item", item, "disabled") == 0: res.append({ - "item_code": item[0], - "warehouse": warehouse, + "item_code": item, + "warehouse": wh, "qty": stock_bal[0], - "item_name": frappe.db.get_value('Item', item[0], 'item_name'), + "item_name": frappe.db.get_value('Item', item, 'item_name'), "valuation_rate": stock_bal[1], "current_qty": stock_bal[0], "current_valuation_rate": stock_bal[1] From 3637e14e9fa5bb0c8edf71ce65e0b22201befa1a Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 28 Dec 2018 17:11:52 +0530 Subject: [PATCH 05/35] refractor --- erpnext/projects/doctype/task/task.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 649d73a63f..53e3a5b68e 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -43,6 +43,10 @@ class Task(NestedSet): if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'")) + doc = frappe.get_doc("Project",self.project) + if self.exp_end_date and doc.expected_end_date and getdate(self.exp_end_date) > getdate(doc.expected_end_date) : + frappe.throw(_("Expected end date cannot be after Project: "+doc.name+" Expected end date")) + def validate_status(self): if self.status!=self.get_db_value("status") and self.status == "Closed": for d in self.depends_on: From a3b5f5f6e42c90e007b34240f7bc7f65198dc54e Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 1 Jan 2019 14:11:59 +0530 Subject: [PATCH 06/35] Test case --- erpnext/projects/doctype/task/task.py | 6 ++++-- erpnext/projects/doctype/task/test_task.py | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 53e3a5b68e..855e2006f9 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -12,6 +12,7 @@ from frappe.utils.nestedset import NestedSet class CircularReferenceError(frappe.ValidationError): pass +class EndDateConnotGreaterThanProjecteEndDateError(frappe.ValidationError): pass class Task(NestedSet): nsm_parent_field = 'parent_task' @@ -43,9 +44,10 @@ class Task(NestedSet): if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'")) - doc = frappe.get_doc("Project",self.project) + doc = frappe.get_doc("Project", self.project) if self.exp_end_date and doc.expected_end_date and getdate(self.exp_end_date) > getdate(doc.expected_end_date) : - frappe.throw(_("Expected end date cannot be after Project: "+doc.name+" Expected end date")) + frappe.msgprint(_("Expected end date cannot be after Project: '{0}' Expected end date").format(doc.name)) + raise EndDateConnotGreaterThanProjecteEndDateError def validate_status(self): if self.status!=self.get_db_value("status") and self.status == "Closed": diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 0966b76807..058f6b8d0d 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -5,7 +5,7 @@ import frappe import unittest from frappe.utils import getdate, nowdate, add_days -from erpnext.projects.doctype.task.task import CircularReferenceError +from erpnext.projects.doctype.task.task import CircularReferenceError, EndDateConnotGreaterThanProjecteEndDateError class TestTask(unittest.TestCase): def test_circular_reference(self): @@ -97,7 +97,16 @@ class TestTask(unittest.TestCase): self.assertEqual(frappe.db.get_value("Task", task.name, "status"), "Overdue") -def create_task(subject, start=None, end=None, depends_on=None, project=None): + def test_end_date_validation(self): + task_end = create_task("Testing_Enddate_validation", add_days(nowdate(), -10), add_days(nowdate(), 5), save=False) + pro = frappe.get_doc("Project", task_end.project) + pro.expected_end_date = add_days(nowdate(), 20) + pro.save() + self.assertRaises(EndDateConnotGreaterThanProjecteEndDateError, task_end.save) + + + +def create_task(subject, start=None, end=None, save=True, depends_on=None, project=None): if not frappe.db.exists("Task", subject): task = frappe.new_doc('Task') task.status = "Open" @@ -105,7 +114,8 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None): task.exp_start_date = start or nowdate() task.exp_end_date = end or nowdate() task.project = project or "_Test Project" - task.save() + if save: + task.save() else: task = frappe.get_doc("Task", subject) @@ -113,6 +123,7 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None): task.append("depends_on", { "task": depends_on }) - task.save() + if save: + task.save() return task \ No newline at end of file From fac6b5962765515309e98b65c6baa08059632500 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 1 Jan 2019 16:33:22 +0500 Subject: [PATCH 07/35] Exclude opening invoices in Gross Profit Report --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 2ed664c9bf..1bb3a8825f 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -316,7 +316,7 @@ class GrossProfitGenerator(object): on `tabSales Invoice Item`.parent = `tabSales Invoice`.name {sales_team_table} where - `tabSales Invoice`.docstatus=1 {conditions} {match_cond} + `tabSales Invoice`.docstatus=1 and `tabSales Invoice`.is_opening='No' {conditions} {match_cond} order by `tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc""" .format(conditions=conditions, sales_person_cols=sales_person_cols, From e194a655df8d856fccfb0a37b7e1b3969015f3c3 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 2 Jan 2019 16:09:34 +0500 Subject: [PATCH 08/35] fix: is_opening != 'Yes' --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 +- erpnext/setup/doctype/company/company_dashboard.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 1bb3a8825f..56bd7c2a87 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -316,7 +316,7 @@ class GrossProfitGenerator(object): on `tabSales Invoice Item`.parent = `tabSales Invoice`.name {sales_team_table} where - `tabSales Invoice`.docstatus=1 and `tabSales Invoice`.is_opening='No' {conditions} {match_cond} + `tabSales Invoice`.docstatus=1 and `tabSales Invoice`.is_opening!='Yes' {conditions} {match_cond} order by `tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc""" .format(conditions=conditions, sales_person_cols=sales_person_cols, diff --git a/erpnext/setup/doctype/company/company_dashboard.py b/erpnext/setup/doctype/company/company_dashboard.py index 61332267e7..5efcf3839f 100644 --- a/erpnext/setup/doctype/company/company_dashboard.py +++ b/erpnext/setup/doctype/company/company_dashboard.py @@ -13,7 +13,7 @@ def get_data(): 'goal_doctype_link': 'company', 'goal_field': 'base_grand_total', 'date_field': 'posting_date', - 'filter_str': 'docstatus = 1', + 'filter_str': "docstatus = 1 and is_opening != 'Yes'", 'aggregation': 'sum' }, From 016f29f09b5714f51ebb0f4e1c5d7590935df4e4 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 2 Jan 2019 16:22:22 +0500 Subject: [PATCH 09/35] fix: error due to passing None in scrub --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 56bd7c2a87..7ecc695520 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -200,7 +200,7 @@ class GrossProfitGenerator(object): def skip_row(self, row, product_bundles): if self.filters.get("group_by") != "Invoice": - if not row.get(scrub(self.filters.get("group_by"))): + if not row.get(scrub(self.filters.get("group_by", ""))): return True elif row.get("is_return") == 1: return True From 4c8c50e464680ceb4e5742120b05d232fe60deac Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 2 Jan 2019 17:52:28 +0530 Subject: [PATCH 10/35] test case fixed --- erpnext/projects/doctype/task/task.py | 9 +++++---- erpnext/projects/doctype/task/test_task.py | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 855e2006f9..63db6cdea5 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -44,10 +44,11 @@ class Task(NestedSet): if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'")) - doc = frappe.get_doc("Project", self.project) - if self.exp_end_date and doc.expected_end_date and getdate(self.exp_end_date) > getdate(doc.expected_end_date) : - frappe.msgprint(_("Expected end date cannot be after Project: '{0}' Expected end date").format(doc.name)) - raise EndDateConnotGreaterThanProjecteEndDateError + if(self.project): + doc = frappe.get_doc("Project", self.project) + if self.exp_end_date and doc.expected_end_date and getdate(self.exp_end_date) > getdate(doc.expected_end_date) : + frappe.msgprint(_("Expected end date cannot be after Project: '{0}' Expected end date").format(doc.name)) + raise EndDateConnotGreaterThanProjecteEndDateError def validate_status(self): if self.status!=self.get_db_value("status") and self.status == "Closed": diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 058f6b8d0d..1585e798be 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -9,7 +9,7 @@ from erpnext.projects.doctype.task.task import CircularReferenceError, EndDateCo class TestTask(unittest.TestCase): def test_circular_reference(self): - task1 = create_task("_Test Task 1", nowdate(), add_days(nowdate(), 10)) + task1 = create_task("_Test Task 1", add_days(nowdate(), -15), add_days(nowdate(), -10)) task2 = create_task("_Test Task 2", add_days(nowdate(), 11), add_days(nowdate(), 15), task1.name) task3 = create_task("_Test Task 3", add_days(nowdate(), 11), add_days(nowdate(), 15), task2.name) @@ -98,15 +98,15 @@ class TestTask(unittest.TestCase): self.assertEqual(frappe.db.get_value("Task", task.name, "status"), "Overdue") def test_end_date_validation(self): - task_end = create_task("Testing_Enddate_validation", add_days(nowdate(), -10), add_days(nowdate(), 5), save=False) + task_end = create_task("Testing_Enddate_validation", add_days(nowdate(), 35), add_days(nowdate(), 45), save=False) pro = frappe.get_doc("Project", task_end.project) - pro.expected_end_date = add_days(nowdate(), 20) + pro.expected_end_date = add_days(nowdate(), 40) pro.save() self.assertRaises(EndDateConnotGreaterThanProjecteEndDateError, task_end.save) -def create_task(subject, start=None, end=None, save=True, depends_on=None, project=None): +def create_task(subject, start=None, end=None, depends_on=None, project=None, save=True): if not frappe.db.exists("Task", subject): task = frappe.new_doc('Task') task.status = "Open" From 53c040f83874a8c50e82e942197995829bed5815 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 3 Jan 2019 12:50:18 +0530 Subject: [PATCH 11/35] 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 47c9826b6f12d87ec5ac254bbc00e3f45222d385 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 3 Jan 2019 15:24:59 +0530 Subject: [PATCH 12/35] test(reco-warehouse): Get items for group warehouse --- .../stock_reconciliation/stock_reconciliation.py | 1 - .../test_stock_reconciliation.py | 15 ++++++++++++++- erpnext/stock/doctype/warehouse/test_warehouse.py | 6 ++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index dacb53a587..561868f8a3 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -286,7 +286,6 @@ def get_items(warehouse, posting_date, posting_time): with_valuation_rate=True) if frappe.db.get_value("Item", item, "disabled") == 0: - res.append({ "item_code": item, "warehouse": wh, diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 212bb51185..78ff915011 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -10,7 +10,9 @@ from frappe.utils import flt, nowdate, nowtime from erpnext.accounts.utils import get_stock_and_account_difference from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after -from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError +from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items +from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse +from erpnext.stock.doctype.item.test_item import make_item class TestStockReconciliation(unittest.TestCase): def setUp(self): @@ -79,6 +81,17 @@ class TestStockReconciliation(unittest.TestCase): set_perpetual_inventory(0) + def test_get_items(self): + create_warehouse("_Test Warehouse Group 1", {"is_group": 1}) + create_warehouse("_Test Warehouse Ledger 1", {"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC"}) + item1 = make_item("_Test Stock Reco Item", {"default_warehouse": "_Test Warehouse Ledger 1 - _TC", + "is_stock_item": 1, "opening_stock": 100, "valuation_rate": 100}) + + items = get_items("_Test Warehouse Group 1 - _TC", nowdate(), nowtime()) + + self.assertEqual(["_Test Stock Reco Item", "_Test Warehouse Ledger 1 - _TC", 100], + [items[0]["item_code"], items[0]["warehouse"], items[0]["qty"]]) + def insert_existing_sle(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index d010313534..961d0a7614 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -90,7 +90,7 @@ class TestWarehouse(unittest.TestCase): self.assertTrue(frappe.db.get_value("Warehouse", filters={"account": "Test Warehouse for Merging 2 - _TC"})) -def create_warehouse(warehouse_name): +def create_warehouse(warehouse_name, properties=None): if not frappe.db.exists("Warehouse", warehouse_name + " - _TC"): w = frappe.new_doc("Warehouse") w.warehouse_name = warehouse_name @@ -98,11 +98,13 @@ def create_warehouse(warehouse_name): w.company = "_Test Company" make_account_for_warehouse(warehouse_name, w) w.account = warehouse_name + " - _TC" + if properties: + w.update(properties) w.save() def make_account_for_warehouse(warehouse_name, warehouse_obj): if not frappe.db.exists("Account", warehouse_name + " - _TC"): - parent_account = frappe.db.get_value('Account', + parent_account = frappe.db.get_value('Account', {'company': warehouse_obj.company, 'is_group':1, 'account_type': 'Stock'},'name') account = create_account(account_name=warehouse_name, \ account_type="Stock", parent_account= parent_account, company=warehouse_obj.company) \ No newline at end of file From 547229fef12221fc4e1f9c86eafb0397d361feb3 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 3 Jan 2019 23:20:38 +0530 Subject: [PATCH 13/35] 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 f2b29a0488af667965a3bde1b21c3ced3994161b Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 4 Jan 2019 10:54:05 +0530 Subject: [PATCH 14/35] typo --- erpnext/projects/doctype/task/task.py | 11 ++++++----- erpnext/projects/doctype/task/test_task.py | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 63db6cdea5..a470ed9aa4 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -12,7 +12,7 @@ from frappe.utils.nestedset import NestedSet class CircularReferenceError(frappe.ValidationError): pass -class EndDateConnotGreaterThanProjecteEndDateError(frappe.ValidationError): pass +class EndDateCannotGreaterThanProjecteEndDateError(frappe.ValidationError): pass class Task(NestedSet): nsm_parent_field = 'parent_task' @@ -45,10 +45,11 @@ class Task(NestedSet): frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'")) if(self.project): - doc = frappe.get_doc("Project", self.project) - if self.exp_end_date and doc.expected_end_date and getdate(self.exp_end_date) > getdate(doc.expected_end_date) : - frappe.msgprint(_("Expected end date cannot be after Project: '{0}' Expected end date").format(doc.name)) - raise EndDateConnotGreaterThanProjecteEndDateError + if frappe.db.exists("Project", self.project): + doc = frappe.get_doc("Project", self.project) + if self.exp_end_date and doc.expected_end_date and getdate(self.exp_end_date) > getdate(doc.expected_end_date) : + frappe.msgprint(_("Expected end date cannot be after Project: '{0}' Expected end date").format(doc.name)) + raise EndDateCannotGreaterThanProjecteEndDateError def validate_status(self): if self.status!=self.get_db_value("status") and self.status == "Closed": diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index 1585e798be..b9d7958751 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -5,7 +5,7 @@ import frappe import unittest from frappe.utils import getdate, nowdate, add_days -from erpnext.projects.doctype.task.task import CircularReferenceError, EndDateConnotGreaterThanProjecteEndDateError +from erpnext.projects.doctype.task.task import CircularReferenceError, EndDateCannotGreaterThanProjecteEndDateError class TestTask(unittest.TestCase): def test_circular_reference(self): @@ -102,7 +102,7 @@ class TestTask(unittest.TestCase): pro = frappe.get_doc("Project", task_end.project) pro.expected_end_date = add_days(nowdate(), 40) pro.save() - self.assertRaises(EndDateConnotGreaterThanProjecteEndDateError, task_end.save) + self.assertRaises(EndDateCannotGreaterThanProjecteEndDateError, task_end.save) From 68aedfd1764a0925ae8761fe8b782f2531d1e936 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 4 Jan 2019 11:00:07 +0530 Subject: [PATCH 15/35] typo --- erpnext/projects/doctype/task/task.py | 5 ++--- erpnext/projects/doctype/task/test_task.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index a470ed9aa4..08abba31f4 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -12,7 +12,7 @@ from frappe.utils.nestedset import NestedSet class CircularReferenceError(frappe.ValidationError): pass -class EndDateCannotGreaterThanProjecteEndDateError(frappe.ValidationError): pass +class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass class Task(NestedSet): nsm_parent_field = 'parent_task' @@ -48,8 +48,7 @@ class Task(NestedSet): if frappe.db.exists("Project", self.project): doc = frappe.get_doc("Project", self.project) if self.exp_end_date and doc.expected_end_date and getdate(self.exp_end_date) > getdate(doc.expected_end_date) : - frappe.msgprint(_("Expected end date cannot be after Project: '{0}' Expected end date").format(doc.name)) - raise EndDateCannotGreaterThanProjecteEndDateError + frappe.throw(_("Expected end date cannot be after Project: '{0}' Expected end date").format(doc.name), EndDateCannotBeGreaterThanProjectEndDateError) def validate_status(self): if self.status!=self.get_db_value("status") and self.status == "Closed": diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py index b9d7958751..6fb5412473 100644 --- a/erpnext/projects/doctype/task/test_task.py +++ b/erpnext/projects/doctype/task/test_task.py @@ -5,7 +5,7 @@ import frappe import unittest from frappe.utils import getdate, nowdate, add_days -from erpnext.projects.doctype.task.task import CircularReferenceError, EndDateCannotGreaterThanProjecteEndDateError +from erpnext.projects.doctype.task.task import CircularReferenceError, EndDateCannotBeGreaterThanProjectEndDateError class TestTask(unittest.TestCase): def test_circular_reference(self): @@ -102,7 +102,7 @@ class TestTask(unittest.TestCase): pro = frappe.get_doc("Project", task_end.project) pro.expected_end_date = add_days(nowdate(), 40) pro.save() - self.assertRaises(EndDateCannotGreaterThanProjecteEndDateError, task_end.save) + self.assertRaises(EndDateCannotBeGreaterThanProjectEndDateError, task_end.save) From 75ab0426326d2b6ba8c98115f6ce168551d39b7c Mon Sep 17 00:00:00 2001 From: Himanshu Date: Fri, 4 Jan 2019 17:13:43 +0530 Subject: [PATCH 16/35] 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 8e47a543f640c15a3f791cbbf2b27b9e348e9972 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 4 Jan 2019 12:07:17 +0530 Subject: [PATCH 17/35] (fix) multiple years in yearly view --- erpnext/selling/report/sales_analytics/sales_analytics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index c078a08249..46cb5ff022 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -235,7 +235,7 @@ class Analytics(object): }.get(self.filters.range, 1) self.periodic_daterange = [] - for dummy in range(1, 53, increment): + for dummy in range(1, 53): if self.filters.range == "Weekly": period_end_date = from_date + relativedelta(days=6) else: From 676a106d7a73ca3a638dbc900206bdeebcff276c Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 4 Jan 2019 13:17:31 +0530 Subject: [PATCH 18/35] (refactor) removed unnecessary code --- erpnext/selling/report/sales_analytics/sales_analytics.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index 46cb5ff022..8ecf3254d0 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -40,7 +40,7 @@ class Analytics(object): "fieldtype": "Data", "width": 140 }) - for dummy, end_date in self.periodic_daterange: + for end_date in self.periodic_daterange: period = self.get_period(end_date) self.columns.append({ "label": _(period), @@ -169,7 +169,7 @@ class Analytics(object): "entity_name": self.entity_names.get(entity) } total = 0 - for dummy, end_date in self.periodic_daterange: + for end_date in self.periodic_daterange: period = self.get_period(end_date) amount = flt(period_data.get(period, 0.0)) row[scrub(period)] = amount @@ -188,7 +188,7 @@ class Analytics(object): "indent": self.depth_map.get(d.name) } total = 0 - for dummy, end_date in self.periodic_daterange: + for end_date in self.periodic_daterange: period = self.get_period(end_date) amount = flt(self.entity_periodic_data.get(d.name, {}).get(period, 0.0)) row[scrub(period)] = amount @@ -243,8 +243,8 @@ class Analytics(object): if period_end_date > to_date: period_end_date = to_date - self.periodic_daterange.append([from_date, period_end_date]) + self.periodic_daterange.append(period_end_date) from_date = period_end_date + relativedelta(days=1) if period_end_date == to_date: break From e2ea987610e8f31cf9d955d9d08f1c4fa7455ddf Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 4 Jan 2019 23:34:54 +0530 Subject: [PATCH 19/35] fixed edge cases for yearly, weekly and monthly views --- .../report/sales_analytics/sales_analytics.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index 8ecf3254d0..3239fc626f 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _, scrub -from frappe.utils import getdate, flt +from frappe.utils import getdate, flt, add_to_date, add_days from six import iteritems from erpnext.accounts.utils import get_fiscal_year @@ -219,12 +219,11 @@ class Analytics(object): period = "Quarter " + str(((posting_date.month-1)//3)+1) +" " + str(posting_date.year) else: year = get_fiscal_year(posting_date, company=self.filters.company) - period = str(year[2]) - + period = str(year[0]) return period def get_period_date_ranges(self): - from dateutil.relativedelta import relativedelta + from dateutil.relativedelta import relativedelta, MO from_date, to_date = getdate(self.filters.from_date), getdate(self.filters.to_date) increment = { @@ -234,18 +233,26 @@ class Analytics(object): "Yearly": 12 }.get(self.filters.range, 1) + if self.filters.range in ['Monthly', 'Quarterly']: + from_date = from_date.replace(day = 1) + elif self.filters.range == "Yearly": + from_date = get_fiscal_year(from_date)[1] + else: + from_date = from_date + relativedelta(from_date, weekday=MO(-1)) + self.periodic_daterange = [] for dummy in range(1, 53): if self.filters.range == "Weekly": - period_end_date = from_date + relativedelta(days=6) + period_end_date = add_days(from_date, 6) else: - period_end_date = from_date + relativedelta(months=increment, days=-1) + period_end_date = add_to_date(from_date, months=increment, days=-1) if period_end_date > to_date: period_end_date = to_date self.periodic_daterange.append(period_end_date) - from_date = period_end_date + relativedelta(days=1) + + from_date = add_days(period_end_date, 1) if period_end_date == to_date: break From d40743a570948f5d1cd113755bdede4cb51e8d1a Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Jan 2019 13:38:43 +0530 Subject: [PATCH 20/35] 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 1516e296095a90d61a227fde20d97f50c2f0fa80 Mon Sep 17 00:00:00 2001 From: hiousi Date: Tue, 8 Jan 2019 18:34:08 +0100 Subject: [PATCH 21/35] [fix] variant can not get its price get_price_list_rate() return empty dict before trying to get the item price for a variant. --- erpnext/stock/get_item_details.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 692fe5d0fb..7be4f9f8fb 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -369,6 +369,10 @@ def get_price_list_rate(args, item_doc, out): validate_conversion_rate(args, meta) price_list_rate = get_price_list_rate_for(args, item_doc.name) or 0 + + # variant + if not price_list_rate and item_doc.variant_of: + price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) # insert in database if not price_list_rate: @@ -376,10 +380,6 @@ def get_price_list_rate(args, item_doc, out): insert_item_price(args) return {} - # variant - if not price_list_rate and item_doc.variant_of: - price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) - out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \ / flt(args.conversion_rate) From 96714384354bd5d4bec5a68173d0e6950116f418 Mon Sep 17 00:00:00 2001 From: hiousi Date: Tue, 8 Jan 2019 22:23:45 +0100 Subject: [PATCH 22/35] Update get_item_details.py --- erpnext/stock/get_item_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 7be4f9f8fb..3f85521207 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -369,7 +369,7 @@ def get_price_list_rate(args, item_doc, out): validate_conversion_rate(args, meta) price_list_rate = get_price_list_rate_for(args, item_doc.name) or 0 - + # variant if not price_list_rate and item_doc.variant_of: price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) From 2689dea72a15a0bd2949a4e481a23f5bfb213360 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 9 Jan 2019 13:43:06 +0500 Subject: [PATCH 23/35] fix: Give higher precedence to set_warehouse in get_item_details while setting item level warehouse --- erpnext/public/js/controllers/transaction.js | 1 + erpnext/stock/get_item_details.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 035c58d6d2..9dd9bebb4d 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -416,6 +416,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ item_code: item.item_code, barcode: item.barcode, serial_no: item.serial_no, + set_warehouse: me.frm.doc.set_warehouse, warehouse: item.warehouse, customer: me.frm.doc.customer, supplier: me.frm.doc.supplier, diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 692fe5d0fb..bafe545d6f 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -36,6 +36,7 @@ def get_item_details(args): "is_subcontracted": "Yes" / "No", "ignore_pricing_rule": 0/1 "project": "" + "set_warehouse": "" } """ args = process_args(args) @@ -189,7 +190,6 @@ def get_basic_details(args, item): "project": "", barcode: "", serial_no: "", - warehouse: "", currency: "", update_stock: "", price_list: "", @@ -219,7 +219,7 @@ def get_basic_details(args, item): item_defaults = get_item_defaults(item.name, args.company) item_group_defaults = get_item_group_defaults(item.name, args.company) - warehouse = user_default_warehouse or item_defaults.get("default_warehouse") or\ + warehouse = args.get("set_warehouse") or user_default_warehouse or item_defaults.get("default_warehouse") or\ item_group_defaults.get("default_warehouse") or args.warehouse if args.get('doctype') == "Material Request" and not args.get('material_request_type'): From 2825b929c1bb0c84c067e1dcff0a31feffbf7146 Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Wed, 9 Jan 2019 19:15:10 +0530 Subject: [PATCH 24/35] 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 25/35] 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 26/35] 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 0d208851a4af29af77ee33df86a1e76969c539d0 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 10 Jan 2019 17:56:11 +0530 Subject: [PATCH 27/35] Fix(stock-reco): Fixed codacy issues --- .../doctype/stock_reconciliation/test_stock_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 78ff915011..bc991b4e15 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -84,7 +84,7 @@ class TestStockReconciliation(unittest.TestCase): def test_get_items(self): create_warehouse("_Test Warehouse Group 1", {"is_group": 1}) create_warehouse("_Test Warehouse Ledger 1", {"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC"}) - item1 = make_item("_Test Stock Reco Item", {"default_warehouse": "_Test Warehouse Ledger 1 - _TC", + make_item("_Test Stock Reco Item", {"default_warehouse": "_Test Warehouse Ledger 1 - _TC", "is_stock_item": 1, "opening_stock": 100, "valuation_rate": 100}) items = get_items("_Test Warehouse Group 1 - _TC", nowdate(), nowtime()) From cd8908362a26f6ca672ddad5ac89f63c12cfef9e Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 11 Jan 2019 12:23:23 +0530 Subject: [PATCH 28/35] Changes Requested Done --- erpnext/projects/doctype/task/task.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 08abba31f4..371fc5c79b 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -46,9 +46,9 @@ class Task(NestedSet): if(self.project): if frappe.db.exists("Project", self.project): - doc = frappe.get_doc("Project", self.project) - if self.exp_end_date and doc.expected_end_date and getdate(self.exp_end_date) > getdate(doc.expected_end_date) : - frappe.throw(_("Expected end date cannot be after Project: '{0}' Expected end date").format(doc.name), EndDateCannotBeGreaterThanProjectEndDateError) + expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") + if self.exp_end_date and expected_end_date and getdate(self.exp_end_date) > getdate(expected_end_date) : + frappe.throw(_("Expected end date cannot be after Project: '{0}' Expected end date").format(self.project), EndDateCannotBeGreaterThanProjectEndDateError) def validate_status(self): if self.status!=self.get_db_value("status") and self.status == "Closed": From ba54209c86c328568f33359af58e833051baa13d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 12 Jan 2019 17:58:00 +0530 Subject: [PATCH 29/35] [Fix] Exchange rate revaluation, get entries not working if accounts of the other currency than company currency not available (#16379) --- .../exchange_rate_revaluation.js | 2 ++ .../exchange_rate_revaluation.py | 24 ++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js index c18057eb40..779cd6197e 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js @@ -39,6 +39,8 @@ frappe.ui.form.on('Exchange Rate Revaluation', { }); frm.events.get_total_gain_loss(frm); refresh_field("accounts"); + } else { + frappe.msgprint(__("No records found")); } } }); diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index ae77516eb7..cdfe34bead 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -67,17 +67,19 @@ class ExchangeRateRevaluation(Document): and account_currency != %s order by name""",(self.company, company_currency)) - account_details = frappe.db.sql(""" - select - account, party_type, party, account_currency, - sum(debit_in_account_currency) - sum(credit_in_account_currency) as balance_in_account_currency, - sum(debit) - sum(credit) as balance - from `tabGL Entry` - where account in (%s) - group by account, party_type, party - having sum(debit) != sum(credit) - order by account - """ % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1) + account_details = [] + if accounts: + account_details = frappe.db.sql(""" + select + account, party_type, party, account_currency, + sum(debit_in_account_currency) - sum(credit_in_account_currency) as balance_in_account_currency, + sum(debit) - sum(credit) as balance + from `tabGL Entry` + where account in (%s) + group by account, party_type, party + having sum(debit) != sum(credit) + order by account + """ % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1) return account_details From 4ed7cfc515fd50a99540ee4888f1595dd2d9a665 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 14 Jan 2019 17:14:39 +0530 Subject: [PATCH 30/35] 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: From 627be1de51d5e79b3c2342118b10632eec78e1e6 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Tue, 15 Jan 2019 14:16:32 +0530 Subject: [PATCH 31/35] minor fixes for deferred calculations --- erpnext/stock/get_item_details.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 0e70338b21..cf45cc4607 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -273,7 +273,7 @@ def get_basic_details(args, item): "transaction_date": args.get("transaction_date") }) - if item.enable_deferred_revenue: + if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): out.update(calculate_service_end_date(args, item)) # calculate conversion factor @@ -310,9 +310,10 @@ def calculate_service_end_date(args, item=None): if not item: item = frappe.get_cached_doc("Item", args.item_code) - enable_deferred = "enable_deferred_revenue" if args.doctype=="Sales Invoice" else "enable_deferred_expense" - no_of_months = "no_of_months" if args.doctype=="Sales Invoice" else "no_of_months_exp" - account = "deferred_revenue_account" if args.doctype=="Sales Invoice" else "deferred_expense_account" + doctype = args.get("parenttype") or args.get("doctype") + enable_deferred = "enable_deferred_revenue" if doctype=="Sales Invoice" else "enable_deferred_expense" + no_of_months = "no_of_months" if doctype=="Sales Invoice" else "no_of_months_exp" + account = "deferred_revenue_account" if doctype=="Sales Invoice" else "deferred_expense_account" service_start_date = args.service_start_date if args.service_start_date else args.transaction_date service_end_date = add_months(service_start_date, item.get(no_of_months)) @@ -336,7 +337,7 @@ def get_default_expense_account(args, item, item_group): or args.expense_account) def get_default_deferred_account(args, item, fieldname=None): - if item.enable_deferred_revenue: + if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): return (item.get(fieldname) or args.get(fieldname) or frappe.get_cached_value('Company', args.company, "default_"+fieldname)) From 68ea608ca8c3dfd439138ce0fbc359020928783e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 15 Jan 2019 14:47:14 +0530 Subject: [PATCH 32/35] fix: import unicode_literals --- erpnext/patches.txt | 2 +- erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py | 1 + erpnext/setup/default_success_action.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index be8201a07d..7bf63a2290 100755 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -1,9 +1,9 @@ execute:import unidecode # new requirement erpnext.patches.v8_0.move_perpetual_inventory_setting +erpnext.patches.v11_0.rename_production_order_to_work_order erpnext.patches.v11_0.refactor_naming_series erpnext.patches.v11_0.refactor_autoname_naming erpnext.patches.v10_0.rename_schools_to_education -erpnext.patches.v11_0.rename_production_order_to_work_order erpnext.patches.v4_0.validate_v3_patch erpnext.patches.v4_0.fix_employee_user_id erpnext.patches.v4_0.remove_employee_role_if_no_employee diff --git a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py index 83130966d4..52d4621c7b 100644 --- a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py +++ b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals import frappe from frappe.model.rename_doc import rename_doc from frappe.model.utils.rename_field import rename_field diff --git a/erpnext/setup/default_success_action.py b/erpnext/setup/default_success_action.py index e8494a1f96..6c2a97a89c 100644 --- a/erpnext/setup/default_success_action.py +++ b/erpnext/setup/default_success_action.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals from frappe import _ doctype_list = [ From a3dd798badb176bd39c677e2cf3b8edeb3da58a3 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Tue, 15 Jan 2019 14:36:11 +0530 Subject: [PATCH 33/35] clean if conditions --- erpnext/stock/get_item_details.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index cf45cc4607..bbea9043a3 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -311,9 +311,14 @@ def calculate_service_end_date(args, item=None): item = frappe.get_cached_doc("Item", args.item_code) doctype = args.get("parenttype") or args.get("doctype") - enable_deferred = "enable_deferred_revenue" if doctype=="Sales Invoice" else "enable_deferred_expense" - no_of_months = "no_of_months" if doctype=="Sales Invoice" else "no_of_months_exp" - account = "deferred_revenue_account" if doctype=="Sales Invoice" else "deferred_expense_account" + if doctype == "Sales Invoice": + enable_deferred = "enable_deferred_revenue" + no_of_months = "no_of_months" + account = "deferred_revenue_account" + else: + enable_deferred = "enable_deferred_expense" + no_of_months = "no_of_months_exp" + account = "deferred_expense_account" service_start_date = args.service_start_date if args.service_start_date else args.transaction_date service_end_date = add_months(service_start_date, item.get(no_of_months)) From cef56b514719a7ef2b0d216bdb757a5645911a51 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Tue, 15 Jan 2019 16:26:58 +0530 Subject: [PATCH 34/35] fixes asset category field as link type and read only --- erpnext/assets/doctype/asset/asset.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 5296e22492..6b3c3cc73d 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, @@ -154,7 +155,7 @@ "columns": 0, "fetch_from": "item_code.asset_category", "fieldname": "asset_category", - "fieldtype": "Read Only", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -165,12 +166,12 @@ "label": "Asset Category", "length": 0, "no_copy": 0, - "options": "", + "options": "Asset Category", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, @@ -1881,7 +1882,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 14:44:24.507215", + "modified": "2019-01-15 16:12:48.314196", "modified_by": "Administrator", "module": "Assets", "name": "Asset", From 529cc1ca51ed556a9ec2f4dbc3268b08a29fa486 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 15 Jan 2019 19:46:36 +0600 Subject: [PATCH 35/35] bumped to version 10.1.77 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index b9d86a8e84..26755dde61 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '10.1.76' +__version__ = '10.1.77' def get_default_company(user=None): '''Get default company for user'''