From 614559f234411b8d4b558f506eadd9aa51ab9c39 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 13 Dec 2018 16:36:35 +0100 Subject: [PATCH 01/50] 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 7d11ae4993c..d4620cd952e 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/50] [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 b17345c2e75..2a6853aa67d 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 45546e348aa..123fd552c6d 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/50] 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 6bc90361954..f258f7d2954 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/50] 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 257434fb895..dacb53a587c 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/50] 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 649d73a63f1..53e3a5b68e0 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/50] 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 53e3a5b68e0..855e2006f99 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 0966b768072..058f6b8d0d8 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/50] 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 2ed664c9bf7..1bb3a8825ff 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/50] 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 1bb3a8825ff..56bd7c2a870 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 61332267e7a..5efcf3839f6 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/50] 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 56bd7c2a870..7ecc6955204 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/50] 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 855e2006f99..63db6cdea5f 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 058f6b8d0d8..1585e798be8 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/50] 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 b878a1ea5d0..3f293ce14f3 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/50] 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 dacb53a587c..561868f8a3e 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 212bb51185d..78ff9150117 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 d010313534b..961d0a76140 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/50] 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 3f293ce14f3..aad998b9c1a 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/50] 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 63db6cdea5f..a470ed9aa40 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 1585e798be8..b9d79587512 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/50] 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 a470ed9aa40..08abba31f4d 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 b9d79587512..6fb54124732 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/50] 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 aad998b9c1a..52956052aa2 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/50] (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 c078a082491..46cb5ff022d 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/50] (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 46cb5ff022d..8ecf3254d0b 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/50] 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 8ecf3254d0b..3239fc626f9 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/50] 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 52956052aa2..9b7edc5338f 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 41d189a34c9e9e861315b9f2f29525a7dbe3669b Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Mon, 7 Jan 2019 14:25:58 +0530 Subject: [PATCH 21/50] fix(demo): Look for "Total" instead of "'Total'" in report results --- erpnext/demo/user/purchase.py | 4 ++-- erpnext/demo/user/stock.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/demo/user/purchase.py b/erpnext/demo/user/purchase.py index 69930448dc9..86757dfaaa5 100644 --- a/erpnext/demo/user/purchase.py +++ b/erpnext/demo/user/purchase.py @@ -64,7 +64,7 @@ def work(): report = "Material Requests for which Supplier Quotations are not created" for row in query_report.run(report)["result"][:random.randint(1, 3)]: - if row[0] != "'Total'": + if row[0] != "Total": sq = frappe.get_doc(make_supplier_quotation(row[0])) sq.transaction_date = frappe.flags.current_date sq.supplier = supplier @@ -79,7 +79,7 @@ def work(): from erpnext.stock.doctype.material_request.material_request import make_purchase_order report = "Requested Items To Be Ordered" for row in query_report.run(report)["result"][:how_many("Purchase Order")]: - if row[0] != "'Total'": + if row[0] != "Total": try: po = frappe.get_doc(make_purchase_order(row[0])) po.supplier = supplier diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py index 6036a2621f9..60b1edcac59 100644 --- a/erpnext/demo/user/stock.py +++ b/erpnext/demo/user/stock.py @@ -25,7 +25,7 @@ def make_purchase_receipt(): if random.random() < 0.6: from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt report = "Purchase Order Items To Be Received" - po_list =list(set([r[0] for r in query_report.run(report)["result"] if r[0]!="'Total'"]))[:random.randint(1, 10)] + po_list =list(set([r[0] for r in query_report.run(report)["result"] if r[0]!="Total"]))[:random.randint(1, 10)] for po in po_list: pr = frappe.get_doc(make_purchase_receipt(po)) @@ -49,7 +49,7 @@ def make_delivery_note(): from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note report = "Ordered Items To Be Delivered" for so in list(set([r[0] for r in query_report.run(report)["result"] - if r[0]!="'Total'"]))[:random.randint(1, 3)]: + if r[0]!="Total"]))[:random.randint(1, 3)]: dn = frappe.get_doc(make_delivery_note(so)) dn.posting_date = frappe.flags.current_date for d in dn.get("items"): From 7bc692d48d642a5df88f1296f60fa293e12fd3af Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Mon, 7 Jan 2019 19:01:47 +0530 Subject: [PATCH 22/50] feat(naming): Deferred naming for Stock Ledger Entry and GL Entry For fast lockless insertions use hash as autonaming method for temporarily naming Stock Ledger Entry and GL Entry In an hourly run scheduled job rename these entries using autoname options --- .../accounts/doctype/gl_entry/gl_entry.json | 1720 +++++++++-------- erpnext/accounts/doctype/gl_entry/gl_entry.py | 23 +- .../doctype/gl_entry/test_gl_entry.py | 31 + erpnext/hooks.py | 4 +- .../stock_ledger_entry.json | 1604 +++++++-------- .../stock_ledger_entry/stock_ledger_entry.py | 9 +- 6 files changed, 1760 insertions(+), 1631 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 66d527da4d8..333d39aae65 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -1,904 +1,938 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "ACC-GLE-.YYYY.-.#####", - "beta": 0, - "creation": "2013-01-10 16:34:06", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "ACC-GLE-.YYYY.-.#####", + "beta": 0, + "creation": "2013-01-10 16:34:06", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 0, "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "posting_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Posting Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "posting_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "posting_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Posting Date", + "length": 0, + "no_copy": 0, + "oldfieldname": "posting_date", + "oldfieldtype": "Date", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "transaction_date", - "fieldtype": "Date", - "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": "Transaction Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "transaction_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "transaction_date", + "fieldtype": "Date", + "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": "Transaction Date", + "length": 0, + "no_copy": 0, + "oldfieldname": "transaction_date", + "oldfieldtype": "Date", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Account", - "length": 0, - "no_copy": 0, - "oldfieldname": "account", - "oldfieldtype": "Link", - "options": "Account", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Account", + "length": 0, + "no_copy": 0, + "oldfieldname": "account", + "oldfieldtype": "Link", + "options": "Account", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "party_type", - "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": "Party Type", - "length": 0, - "no_copy": 0, - "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "party_type", + "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": "Party Type", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "party", - "fieldtype": "Dynamic 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": 1, - "label": "Party", - "length": 0, - "no_copy": 0, - "options": "party_type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "party", + "fieldtype": "Dynamic 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": 1, + "label": "Party", + "length": 0, + "no_copy": 0, + "options": "party_type", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cost_center", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Cost Center", - "length": 0, - "no_copy": 0, - "oldfieldname": "cost_center", - "oldfieldtype": "Link", - "options": "Cost Center", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cost_center", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Cost Center", + "length": 0, + "no_copy": 0, + "oldfieldname": "cost_center", + "oldfieldtype": "Link", + "options": "Cost Center", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "debit", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Debit Amount", - "length": 0, - "no_copy": 0, - "oldfieldname": "debit", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "debit", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Debit Amount", + "length": 0, + "no_copy": 0, + "oldfieldname": "debit", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "credit", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Credit Amount", - "length": 0, - "no_copy": 0, - "oldfieldname": "credit", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "credit", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Credit Amount", + "length": 0, + "no_copy": 0, + "oldfieldname": "credit", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "account_currency", - "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": "Account Currency", - "length": 0, - "no_copy": 0, - "options": "Currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "account_currency", + "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": "Account Currency", + "length": 0, + "no_copy": 0, + "options": "Currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "debit_in_account_currency", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Debit Amount in Account Currency", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "debit_in_account_currency", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Debit Amount in Account Currency", + "length": 0, + "no_copy": 0, + "options": "currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "credit_in_account_currency", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Credit Amount in Account Currency", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "credit_in_account_currency", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Credit Amount in Account Currency", + "length": 0, + "no_copy": 0, + "options": "currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "against", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Against", - "length": 0, - "no_copy": 0, - "oldfieldname": "against", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "against", + "fieldtype": "Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Against", + "length": 0, + "no_copy": 0, + "oldfieldname": "against", + "oldfieldtype": "Text", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "against_voucher_type", - "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": "Against Voucher Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "against_voucher_type", - "oldfieldtype": "Data", - "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "against_voucher_type", + "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": "Against Voucher Type", + "length": 0, + "no_copy": 0, + "oldfieldname": "against_voucher_type", + "oldfieldtype": "Data", + "options": "DocType", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "against_voucher", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Against Voucher", - "length": 0, - "no_copy": 0, - "oldfieldname": "against_voucher", - "oldfieldtype": "Data", - "options": "against_voucher_type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "against_voucher", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Against Voucher", + "length": 0, + "no_copy": 0, + "oldfieldname": "against_voucher", + "oldfieldtype": "Data", + "options": "against_voucher_type", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "voucher_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Voucher Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "voucher_type", - "oldfieldtype": "Select", - "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "voucher_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Voucher Type", + "length": 0, + "no_copy": 0, + "oldfieldname": "voucher_type", + "oldfieldtype": "Select", + "options": "DocType", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "voucher_no", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Voucher No", - "length": 0, - "no_copy": 0, - "oldfieldname": "voucher_no", - "oldfieldtype": "Data", - "options": "voucher_type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Voucher No", + "length": 0, + "no_copy": 0, + "oldfieldname": "voucher_no", + "oldfieldtype": "Data", + "options": "voucher_type", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "voucher_detail_no", - "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": "Voucher Detail No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "voucher_detail_no", + "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": "Voucher Detail No", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "project", - "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": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "project", + "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": "Project", + "length": 0, + "no_copy": 0, + "options": "Project", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "remarks", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Remarks", - "length": 0, - "no_copy": 1, - "oldfieldname": "remarks", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "remarks", + "fieldtype": "Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Remarks", + "length": 0, + "no_copy": 1, + "oldfieldname": "remarks", + "oldfieldtype": "Text", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_opening", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Opening", - "length": 0, - "no_copy": 0, - "oldfieldname": "is_opening", - "oldfieldtype": "Select", - "options": "No\nYes", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_opening", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Is Opening", + "length": 0, + "no_copy": 0, + "oldfieldname": "is_opening", + "oldfieldtype": "Select", + "options": "No\nYes", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_advance", - "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": "Is Advance", - "length": 0, - "no_copy": 0, - "oldfieldname": "is_advance", - "oldfieldtype": "Select", - "options": "No\nYes", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_advance", + "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": "Is Advance", + "length": 0, + "no_copy": 0, + "oldfieldname": "is_advance", + "oldfieldtype": "Select", + "options": "No\nYes", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "fiscal_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Fiscal Year", - "length": 0, - "no_copy": 0, - "oldfieldname": "fiscal_year", - "oldfieldtype": "Select", - "options": "Fiscal Year", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "fiscal_year", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Fiscal Year", + "length": 0, + "no_copy": 0, + "oldfieldname": "fiscal_year", + "oldfieldtype": "Select", + "options": "Fiscal Year", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "finance_book", - "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": "Finance Book", - "length": 0, - "no_copy": 0, - "options": "Finance Book", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "finance_book", + "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": "Finance Book", + "length": 0, + "no_copy": 0, + "options": "Finance Book", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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", + "fieldname": "to_rename", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "To Rename", + "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": 1, + "set_only_once": 0, + "translatable": 0, "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-list", - "idx": 1, - "image_view": 0, - "in_create": 1, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 16:15:45.156222", - "modified_by": "Administrator", - "module": "Accounts", - "name": "GL Entry", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-list", + "idx": 1, + "image_view": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-01-07 07:05:00.366399", + "modified_by": "Administrator", + "module": "Accounts", + "name": "GL Entry", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Auditor", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 0, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 1, + "role": "Auditor", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "voucher_no,account,posting_date,against_voucher", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "voucher_no,account,posting_date,against_voucher", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, "track_views": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index db93cf905f9..ad16714c6af 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -6,14 +6,21 @@ import frappe, erpnext from frappe import _ from frappe.utils import flt, fmt_money, getdate, formatdate from frappe.model.document import Document +from frappe.model.naming import set_name_from_naming_options from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency exclude_from_linked_with = True - class GLEntry(Document): + def autoname(self): + """ + Temporarily name doc for fast insertion + name will be changed using autoname options (in a scheduled job) + """ + self.name = frappe.generate_hash(txt="", length=10) + def validate(self): self.flags.ignore_submit_comment = True self.check_mandatory() @@ -231,3 +238,17 @@ def update_against_account(voucher_type, voucher_no): if d.against != new_against: frappe.db.set_value("GL Entry", d.name, "against", new_against) + + +def rename_gle_sle_docs(): + for doctype in ["GL Entry", "Stock Ledger Entry"]: + rename_temporarily_named_docs(doctype) + +def rename_temporarily_named_docs(doctype): + """Rename temporarily named docs using autoname options""" + docs_to_rename = frappe.get_all(doctype, {"to_rename": "1"}, order_by="creation") + for doc in docs_to_rename: + oldname = doc.name + set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc) + newname = doc.name + frappe.db.sql("""UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s""".format(doctype), (newname, oldname)) diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py index 6cc9bc8fbb6..01ddd292ace 100644 --- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/test_gl_entry.py @@ -3,7 +3,9 @@ from __future__ import unicode_literals import frappe, unittest +from frappe.model.naming import parse_naming_series from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry +from erpnext.accounts.doctype.gl_entry.gl_entry import rename_gle_sle_docs class TestGLEntry(unittest.TestCase): def test_round_off_entry(self): @@ -23,3 +25,32 @@ class TestGLEntry(unittest.TestCase): and debit = 0 and credit = '.01'""", jv.name) self.assertTrue(round_off_entry) + + def test_rename_entries(self): + je = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 100, submit=True) + rename_gle_sle_docs() + naming_series = parse_naming_series(parts=frappe.get_meta("GL Entry").autoname.split(".")[:-1]) + + je = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 100, submit=True) + + gl_entries = frappe.get_all("GL Entry", + fields=["name", "to_rename"], + filters={"voucher_type": "Journal Entry", "voucher_no": je.name}, + order_by="creation" + ) + self.assertTrue(all(entry.to_rename == 1 for entry in gl_entries)) + old_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0] + + rename_gle_sle_docs() + + new_gl_entries = frappe.get_all("GL Entry", + fields=["name", "to_rename"], + filters={"voucher_type": "Journal Entry", "voucher_no": je.name}, + order_by="creation" + ) + self.assertTrue(all(entry.to_rename == 0 for entry in new_gl_entries)) + + self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries))) + + new_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0] + self.assertEquals(old_naming_series_current_value + 2, new_naming_series_current_value) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index dfe30b3d6b7..93c85d45f5f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -235,7 +235,9 @@ scheduler_events = { "hourly": [ 'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails', "erpnext.accounts.doctype.subscription.subscription.process_all", - "erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details" + "erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details", + "erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs", + ], "daily": [ "erpnext.stock.reorder_item.reorder_item", diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index 2bd8ba3c295..947f94853e2 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -1,840 +1,874 @@ { - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "MAT-SLE-.YYYY.-.#####", - "beta": 0, - "creation": "2013-01-29 19:25:42", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Other", - "editable_grid": 0, - "engine": "InnoDB", + "allow_copy": 1, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "MAT-SLE-.YYYY.-.#####", + "beta": 0, + "creation": "2013-01-29 19:25:42", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Other", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "item_code", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Item Code", + "length": 0, + "no_copy": 0, + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "100px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "serial_no", - "fieldtype": "Text", - "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": "Serial No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "serial_no", + "fieldtype": "Text", + "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": "Serial No", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "100px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "batch_no", - "fieldtype": "Data", - "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": "Batch No", - "length": 0, - "no_copy": 0, - "oldfieldname": "batch_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "batch_no", + "fieldtype": "Data", + "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": "Batch No", + "length": 0, + "no_copy": 0, + "oldfieldname": "batch_no", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Warehouse", - "length": 0, - "no_copy": 0, - "oldfieldname": "warehouse", - "oldfieldtype": "Link", - "options": "Warehouse", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Warehouse", + "length": 0, + "no_copy": 0, + "oldfieldname": "warehouse", + "oldfieldtype": "Link", + "options": "Warehouse", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "100px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "posting_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Posting Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "posting_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "posting_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Posting Date", + "length": 0, + "no_copy": 0, + "oldfieldname": "posting_date", + "oldfieldtype": "Date", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "100px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "posting_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Posting Time", - "length": 0, - "no_copy": 0, - "oldfieldname": "posting_time", - "oldfieldtype": "Time", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "posting_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Posting Time", + "length": 0, + "no_copy": 0, + "oldfieldname": "posting_time", + "oldfieldtype": "Time", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "100px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "voucher_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Voucher Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "voucher_type", - "oldfieldtype": "Data", - "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "voucher_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Voucher Type", + "length": 0, + "no_copy": 0, + "oldfieldname": "voucher_type", + "oldfieldtype": "Data", + "options": "DocType", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "150px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "voucher_no", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Voucher No", - "length": 0, - "no_copy": 0, - "oldfieldname": "voucher_no", - "oldfieldtype": "Data", - "options": "voucher_type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "voucher_no", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Voucher No", + "length": 0, + "no_copy": 0, + "oldfieldname": "voucher_no", + "oldfieldtype": "Data", + "options": "voucher_type", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "150px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "voucher_detail_no", - "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": "Voucher Detail No", - "length": 0, - "no_copy": 0, - "oldfieldname": "voucher_detail_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "voucher_detail_no", + "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": "Voucher Detail No", + "length": 0, + "no_copy": 0, + "oldfieldname": "voucher_detail_no", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "150px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "actual_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Actual Quantity", - "length": 0, - "no_copy": 0, - "oldfieldname": "actual_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "actual_qty", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Actual Quantity", + "length": 0, + "no_copy": 0, + "oldfieldname": "actual_qty", + "oldfieldtype": "Currency", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "150px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "incoming_rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Incoming Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "incoming_rate", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "incoming_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Incoming Rate", + "length": 0, + "no_copy": 0, + "oldfieldname": "incoming_rate", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "outgoing_rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Outgoing Rate", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "outgoing_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Outgoing Rate", + "length": 0, + "no_copy": 0, + "options": "Company:company:default_currency", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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": 0, - "in_standard_filter": 0, - "label": "Stock UOM", - "length": 0, - "no_copy": 0, - "oldfieldname": "stock_uom", - "oldfieldtype": "Data", - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 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": 0, + "in_standard_filter": 0, + "label": "Stock UOM", + "length": 0, + "no_copy": 0, + "oldfieldname": "stock_uom", + "oldfieldtype": "Data", + "options": "UOM", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "150px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty_after_transaction", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Actual Qty After Transaction", - "length": 0, - "no_copy": 0, - "oldfieldname": "bin_aqat", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "qty_after_transaction", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Actual Qty After Transaction", + "length": 0, + "no_copy": 0, + "oldfieldname": "bin_aqat", + "oldfieldtype": "Currency", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "150px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "valuation_rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Valuation Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "valuation_rate", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "valuation_rate", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Valuation Rate", + "length": 0, + "no_copy": 0, + "oldfieldname": "valuation_rate", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "150px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_value", - "fieldtype": "Currency", - "hidden": 0, - "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 Value", - "length": 0, - "no_copy": 0, - "oldfieldname": "stock_value", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "stock_value", + "fieldtype": "Currency", + "hidden": 0, + "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 Value", + "length": 0, + "no_copy": 0, + "oldfieldname": "stock_value", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_value_difference", - "fieldtype": "Currency", - "hidden": 0, - "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 Value Difference", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "stock_value_difference", + "fieldtype": "Currency", + "hidden": 0, + "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 Value Difference", + "length": 0, + "no_copy": 0, + "options": "Company:company:default_currency", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_queue", - "fieldtype": "Text", - "hidden": 1, - "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 Queue (FIFO)", - "length": 0, - "no_copy": 0, - "oldfieldname": "fcfs_stack", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "stock_queue", + "fieldtype": "Text", + "hidden": 1, + "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 Queue (FIFO)", + "length": 0, + "no_copy": 0, + "oldfieldname": "fcfs_stack", + "oldfieldtype": "Text", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "project", - "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": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "project", + "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": "Project", + "length": 0, + "no_copy": 0, + "options": "Project", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Data", - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "oldfieldname": "company", + "oldfieldtype": "Data", + "options": "Company", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "150px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "fiscal_year", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Fiscal Year", - "length": 0, - "no_copy": 0, - "oldfieldname": "fiscal_year", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "fiscal_year", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Fiscal Year", + "length": 0, + "no_copy": 0, + "oldfieldname": "fiscal_year", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "150px", + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_cancelled", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Cancelled", - "length": 0, - "no_copy": 0, - "options": "\nNo\nYes", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_cancelled", + "fieldtype": "Select", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Is Cancelled", + "length": 0, + "no_copy": 0, + "options": "\nNo\nYes", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 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", + "fieldname": "to_rename", + "fieldtype": "Check", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "To Rename", + "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": 1, + "set_only_once": 0, + "translatable": 0, "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 1, - "icon": "fa fa-list", - "idx": 1, - "image_view": 0, - "in_create": 1, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 16:15:42.956035", - "modified_by": "Administrator", - "module": "Stock", - "name": "Stock Ledger Entry", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 1, + "icon": "fa fa-list", + "idx": 1, + "image_view": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-01-07 07:04:37.523024", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Ledger Entry", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 0, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 0, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 0, + "track_seen": 0, "track_views": 0 } \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 66a294dbba7..5fe89d6e227 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -16,6 +16,13 @@ class StockFreezeError(frappe.ValidationError): pass exclude_from_linked_with = True class StockLedgerEntry(Document): + def autoname(self): + """ + Temporarily name doc for fast insertion + name will be changed using autoname options (in a scheduled job) + """ + self.name = frappe.generate_hash(txt="", length=10) + def validate(self): self.flags.ignore_submit_comment = True from erpnext.stock.utils import validate_warehouse_company @@ -132,4 +139,4 @@ def on_doctype_update(): frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"]) frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"]) - + From 972f659fd53ef474f0474e6c51091bc8f3d12ca4 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Mon, 7 Jan 2019 21:50:04 +0530 Subject: [PATCH 23/50] fix(naming): Use creation instead of name to order GL Entries --- erpnext/accounts/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index d3566feaa94..2a80305fb4a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -655,7 +655,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None): or (voucher_type not in ('Journal Entry', 'Payment Entry'))) group by voucher_type, voucher_no having (invoice_amount - payment_amount) > 0.005 - order by posting_date, name""".format( + order by posting_date, creation""".format( dr_or_cr=dr_or_cr, invoice = invoice, payment_dr_or_cr=payment_dr_or_cr, From 0c16424d7da6fc0916e68f0b75398d970d8206c0 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Mon, 7 Jan 2019 22:07:13 +0530 Subject: [PATCH 24/50] fix(naming): Use creation instead of name to order SLEs --- erpnext/accounts/report/gross_profit/gross_profit.py | 2 +- erpnext/controllers/selling_controller.py | 2 +- erpnext/controllers/stock_controller.py | 2 +- erpnext/manufacturing/doctype/bom/bom.py | 2 +- erpnext/startup/report_data_map.py | 2 +- erpnext/stock/doctype/bin/bin.py | 2 +- erpnext/stock/doctype/serial_no/serial_no.py | 2 +- erpnext/stock/doctype/stock_entry/test_stock_entry.py | 2 +- erpnext/stock/report/stock_ageing/stock_ageing.py | 2 +- erpnext/stock/report/stock_balance/stock_balance.py | 2 +- erpnext/stock/report/stock_ledger/stock_ledger.py | 2 +- erpnext/stock/stock_balance.py | 4 ++-- erpnext/stock/stock_ledger.py | 6 +++--- erpnext/stock/utils.py | 2 +- 14 files changed, 17 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 2ed664c9bf7..3f6f1bb9717 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -329,7 +329,7 @@ class GrossProfitGenerator(object): where company=%(company)s order by item_code desc, warehouse desc, posting_date desc, - posting_time desc, name desc""", self.filters, as_dict=True) + posting_time desc, creation desc""", self.filters, as_dict=True) self.sle = {} for r in res: if (r.item_code, r.warehouse) not in self.sle: diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 684a2cdbcbe..b8a361fa5f4 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -179,7 +179,7 @@ class SellingController(StockController): last_valuation_rate = frappe.db.sql(""" SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s AND warehouse = %s AND valuation_rate > 0 - ORDER BY posting_date DESC, posting_time DESC, name DESC LIMIT 1 + ORDER BY posting_date DESC, posting_time DESC, creation DESC LIMIT 1 """, (it.item_code, it.warehouse)) if last_valuation_rate: last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 63e89ab6e35..0c7927438aa 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -413,7 +413,7 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no from `tabStock Ledger Entry` sle where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} - order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""".format(condition=condition), + order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc""".format(condition=condition), tuple([posting_date, posting_time] + values), as_dict=True): future_stock_vouchers.append([d.voucher_type, d.voucher_no]) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index a8053cdf4a1..c2a576a1047 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -246,7 +246,7 @@ class BOM(WebsiteGenerator): last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` where item_code = %s and valuation_rate > 0 - order by posting_date desc, posting_time desc, name desc limit 1""", args['item_code']) + order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code']) valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0 diff --git a/erpnext/startup/report_data_map.py b/erpnext/startup/report_data_map.py index fec2deaf41d..1eaf738dde5 100644 --- a/erpnext/startup/report_data_map.py +++ b/erpnext/startup/report_data_map.py @@ -79,7 +79,7 @@ data_map = { "actual_qty as qty", "voucher_type", "voucher_no", "project", "incoming_rate as incoming_rate", "stock_uom", "serial_no", "qty_after_transaction", "valuation_rate"], - "order_by": "posting_date, posting_time, name", + "order_by": "posting_date, posting_time, creation", "links": { "item_code": ["Item", "name"], "warehouse": ["Warehouse", "name"], diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index f86a77dea1d..aa5c69e64fa 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -60,7 +60,7 @@ class Bin(Document): select * from `tabStock Ledger Entry` where item_code = %s and warehouse = %s - order by timestamp(posting_date, posting_time) asc, name asc + order by timestamp(posting_date, posting_time) asc, creation asc limit 1 """, (self.item_code, self.warehouse), as_dict=1) return sle and sle[0] or None diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index d070af05c88..8602c95bec4 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -136,7 +136,7 @@ class SerialNo(StockController): sle_dict = {} for sle in frappe.db.sql("""select * from `tabStock Ledger Entry` where serial_no like %s and item_code=%s and ifnull(is_cancelled, 'No')='No' - order by posting_date desc, posting_time desc, name desc""", + order by posting_date desc, posting_time desc, creation desc""", ("%%%s%%" % self.name, self.item_code), as_dict=1): if self.name.upper() in get_serial_nos(sle.serial_no): if sle.actual_qty > 0: diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index d9df18d212c..9095075b41f 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -27,7 +27,7 @@ def get_sle(**args): values.append(value) return frappe.db.sql("""select * from `tabStock Ledger Entry` %s - order by timestamp(posting_date, posting_time) desc, name desc limit 1"""% condition, + order by timestamp(posting_date, posting_time) desc, creation desc limit 1"""% condition, values, as_dict=1) class TestStockEntry(unittest.TestCase): diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index ecdd96ac4f3..ae3d72ee4fa 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -81,7 +81,7 @@ def get_stock_ledger_entries(filters): company = %(company)s and posting_date <= %(to_date)s {sle_conditions} - order by posting_date, posting_time, sle.name"""\ + order by posting_date, posting_time, sle.creation"""\ .format(item_conditions=get_item_conditions(filters), sle_conditions=get_sle_conditions(filters)), filters, as_dict=True) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index cc1112c0c00..14b1852a7f7 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -130,7 +130,7 @@ def get_stock_ledger_entries(filters, items): from `tabStock Ledger Entry` sle force index (posting_sort_index) where sle.docstatus < 2 %s %s - order by sle.posting_date, sle.posting_time, sle.name""" % + order by sle.posting_date, sle.posting_time, sle.creation""" % (item_conditions_sql, conditions), as_dict=1) def get_item_warehouse_map(filters, sle): diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 805b314ca10..acb3df55417 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -77,7 +77,7 @@ def get_stock_ledger_entries(filters, items): posting_date between %(from_date)s and %(to_date)s {sle_conditions} {item_conditions_sql} - order by posting_date asc, posting_time asc, name asc"""\ + order by posting_date asc, posting_time asc, creation asc"""\ .format( sle_conditions=get_sle_conditions(filters), item_conditions_sql = item_conditions_sql diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 09d4e438406..0dbb7f82bef 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -59,7 +59,7 @@ def repost_actual_qty(item_code, warehouse, allow_zero_rate=False): def get_balance_qty_from_sle(item_code, warehouse): balance_qty = frappe.db.sql("""select qty_after_transaction from `tabStock Ledger Entry` where item_code=%s and warehouse=%s and is_cancelled='No' - order by posting_date desc, posting_time desc, name desc + order by posting_date desc, posting_time desc, creation desc limit 1""", (item_code, warehouse)) return flt(balance_qty[0][0]) if balance_qty else 0.0 @@ -235,7 +235,7 @@ def repost_all_stock_vouchers(): vouchers = frappe.db.sql("""select distinct voucher_type, voucher_no from `tabStock Ledger Entry` sle where voucher_type != "Serial No" and sle.warehouse in (%s) - order by posting_date, posting_time, name""" % + order by posting_date, posting_time, creation""" % ', '.join(['%s']*len(warehouses_with_account)), tuple(warehouses_with_account)) rejected = [] diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 0ef2afa4a81..c8706b291e0 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -432,7 +432,7 @@ def get_stock_ledger_entries(previous_sle, operator=None, order="desc", limit=No where item_code = %%(item_code)s and ifnull(is_cancelled, 'No')='No' %(conditions)s - order by timestamp(posting_date, posting_time) %(order)s, name %(order)s + order by timestamp(posting_date, posting_time) %(order)s, creation %(order)s %(limit)s %(for_update)s""" % { "conditions": conditions, "limit": limit or "", @@ -450,14 +450,14 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, from `tabStock Ledger Entry` where item_code = %s and warehouse = %s and valuation_rate >= 0 - order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse)) + order by posting_date desc, posting_time desc, creation desc limit 1""", (item_code, warehouse)) if not last_valuation_rate: # Get valuation rate from last sle for the item against any warehouse last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` where item_code = %s and valuation_rate > 0 - order by posting_date desc, posting_time desc, name desc limit 1""", item_code) + order by posting_date desc, posting_time desc, creation desc limit 1""", item_code) if last_valuation_rate: return flt(last_valuation_rate[0][0]) # as there is previous records, it might come with zero rate diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index de31c54f968..c89fc109735 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -62,7 +62,7 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None): SELECT item_code, stock_value, name, warehouse FROM `tabStock Ledger Entry` sle WHERE posting_date <= %s {0} - ORDER BY timestamp(posting_date, posting_time) DESC, name DESC + ORDER BY timestamp(posting_date, posting_time) DESC, creation DESC """.format(condition), values, as_dict=1) sle_map = {} From 85c6a0f00290dfe0b2894375647ace4c79f77368 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Mon, 7 Jan 2019 22:54:18 +0530 Subject: [PATCH 25/50] fix(test): Do not rely on order by name --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index f9364e2d847..a1b93f8d94b 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -729,7 +729,7 @@ class TestSalesInvoice(unittest.TestCase): # check gl entries gl_entries = frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s - order by account asc, debit asc""", si.name, as_dict=1) + order by account asc, debit asc, credit asc""", si.name, as_dict=1) self.assertTrue(gl_entries) stock_in_hand = get_inventory_account('_Test Company') @@ -762,7 +762,7 @@ class TestSalesInvoice(unittest.TestCase): set_perpetual_inventory(0) frappe.db.sql("delete from `tabPOS Profile`") - + def test_pos_si_without_payment(self): set_perpetual_inventory() make_pos_profile() From 1516e296095a90d61a227fde20d97f50c2f0fa80 Mon Sep 17 00:00:00 2001 From: hiousi Date: Tue, 8 Jan 2019 18:34:08 +0100 Subject: [PATCH 26/50] [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 692fe5d0fbe..7be4f9f8fb0 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 27/50] 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 7be4f9f8fb0..3f85521207f 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 28/50] 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 035c58d6d2a..9dd9bebb4da 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 692fe5d0fbe..bafe545d6f0 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 29/50] 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 9b7edc5338f..c4bfe915dc6 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 30/50] 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 c4bfe915dc6..9f161afd576 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 31/50] 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 9f161afd576..fd0eb34abc9 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 32/50] 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 78ff9150117..bc991b4e153 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 33/50] 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 08abba31f4d..371fc5c79b4 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 d254af9fea74ea06591687de0e7f82eb7f4ff85b Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 11 Jan 2019 15:06:34 +0530 Subject: [PATCH 34/50] fix(patch): set_backup_limit.py --- erpnext/projects/doctype/timesheet/timesheet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 0eabb3921f7..8692440dfa1 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -42,12 +42,12 @@ frappe.ui.form.on("Timesheet", { if(frm.doc.docstatus==1) { if(frm.doc.per_billed < 100 && frm.doc.total_billable_hours && frm.doc.total_billable_hours > frm.doc.total_billed_hours){ frm.add_custom_button(__('Create Sales Invoice'), function() { frm.trigger("make_invoice") }, - "fa fa-file-alt"); + "fa fa-file-text"); } if(!frm.doc.salary_slip && frm.doc.employee){ frm.add_custom_button(__('Create Salary Slip'), function() { frm.trigger("make_salary_slip") }, - "fa fa-file-alt"); + "fa fa-file-text"); } } From 2cd49f51e2996c578ccea87013ce30acd419a73e Mon Sep 17 00:00:00 2001 From: Fisher Yu Date: Fri, 11 Jan 2019 18:01:33 +0800 Subject: [PATCH 35/50] fix issue #16284, child table copy (#16285) * fix issue #16284, child table copy V11 Child table from Clipboard Excel error, can't multiply sequence by non-int of type 'float' * Update get_item_details.py consider the empty string case * Update get_item_details.py --- erpnext/stock/get_item_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 692fe5d0fbe..4910c428ee3 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -252,8 +252,8 @@ def get_basic_details(args, item): item.get("taxes")))), "uom": args.uom, "min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "", - "qty": args.qty or 1.0, - "stock_qty": args.qty or 1.0, + "qty": flt(args.qty) or 1.0, + "stock_qty": flt(args.qty) or 1.0, "price_list_rate": 0.0, "base_price_list_rate": 0.0, "rate": 0.0, From ba54209c86c328568f33359af58e833051baa13d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 12 Jan 2019 17:58:00 +0530 Subject: [PATCH 36/50] [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 c18057eb404..779cd6197e9 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 ae77516eb70..cdfe34beadb 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 cccc78e197df6b8167d5faba45a72c7d3b62cae1 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Mon, 14 Jan 2019 14:36:54 +0530 Subject: [PATCH 37/50] fix: Qms icon color and label fix (#16293) * qms color fix according to palette * changed qms icon and color * patch to change icon * fix no module named v12_0 * removed spacing in desktop icon * fix: line break * renamed QM to Quality * patch to rename quality management to quality * resolve merge conflicts --- erpnext/config/desktop.py | 8 ++++---- erpnext/patches.txt | 1 + erpnext/patches/v12_0/change_qms_icon.py | 10 ++++++++++ .../rename_quality_management_to_quality.py | 10 ++++++++++ .../customer_feedback/test_customer_feedback.py | 17 +---------------- 5 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 erpnext/patches/v12_0/change_qms_icon.py create mode 100644 erpnext/patches/v12_0/rename_quality_management_to_quality.py diff --git a/erpnext/config/desktop.py b/erpnext/config/desktop.py index 2202b9e4ada..cc7ee02f2a4 100644 --- a/erpnext/config/desktop.py +++ b/erpnext/config/desktop.py @@ -575,9 +575,9 @@ def get_data(): }, { "module_name": "Quality Management", - "color": "blue", - "icon": "octicon octicon-package", + "color": "#1abc9c", + "icon": "fa fa-check-square-o", "type": "module", - "label": _("Quality Management") + "label": _("Quality") } - ] + ] \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1da7eba491a..36d4c769d60 100755 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -580,6 +580,7 @@ erpnext.patches.v11_0.update_delivery_trip_status erpnext.patches.v10_0.repost_gle_for_purchase_receipts_with_rejected_items erpnext.patches.v11_0.set_missing_gst_hsn_code erpnext.patches.v11_0.rename_bom_wo_fields +erpnext.patches.v12_0.change_qms_icon erpnext.patches.v12_0.rename_learn_to_help erpnext.patches.v12_0.rename_accounts_desktop_icon_to_accounting erpnext.patches.v12_0.replace_project_list_desktop_icon_with_projects_module_desktop_icon diff --git a/erpnext/patches/v12_0/change_qms_icon.py b/erpnext/patches/v12_0/change_qms_icon.py new file mode 100644 index 00000000000..2b6c09a0d05 --- /dev/null +++ b/erpnext/patches/v12_0/change_qms_icon.py @@ -0,0 +1,10 @@ +import frappe + +def execute(): + qms_icons = frappe.get_all("Desktop Icon", filters={ + "module_name": "Quality Management" + }) + + for icon in qms_icons: + frappe.db.set_value('Desktop Icon', icon.name, 'color', '#1abc9c') + frappe.db.set_value('Desktop Icon', icon.name, 'icon', 'fa fa-check-square-o') diff --git a/erpnext/patches/v12_0/rename_quality_management_to_quality.py b/erpnext/patches/v12_0/rename_quality_management_to_quality.py new file mode 100644 index 00000000000..cdcf191b961 --- /dev/null +++ b/erpnext/patches/v12_0/rename_quality_management_to_quality.py @@ -0,0 +1,10 @@ +import frappe +from frappe import _ + +def execute(): + quality_management_module_icons = frappe.get_all('Desktop Icon', filters={ + 'module_name': 'Quality Management' + }, fields=['name']) + + for icon in quality_management_module_icons: + frappe.db.set_value('Desktop Icon', icon.name, 'label', _('Quality')) \ No newline at end of file diff --git a/erpnext/quality_management/doctype/customer_feedback/test_customer_feedback.py b/erpnext/quality_management/doctype/customer_feedback/test_customer_feedback.py index 2ac652530ed..b5d6141938a 100644 --- a/erpnext/quality_management/doctype/customer_feedback/test_customer_feedback.py +++ b/erpnext/quality_management/doctype/customer_feedback/test_customer_feedback.py @@ -28,19 +28,4 @@ def create_feedback(): def get_feedback(): feedback = frappe.get_list("Customer Feedback", limit=1) - return feedback[0] - -#def create_feedback_template(): -# template = frappe.get_doc({ -# "doctype": "Customer Feedback Template", -# "template": "_Test Customer Feedback Template", -# "scope": "Company", -# "feedback_parameter": [ -# { -# "parameter": "_Test Customer Feedback Template Parameter", -# } -# ] -# }) -# template_exist = frappe.get_list("Customer Feedback Template", filters={"template": ""+ template.template +""}, fields=["name"]) -# if len(template_exist) == 0: -# template.insert() \ No newline at end of file + return feedback[0] \ No newline at end of file From 4ed7cfc515fd50a99540ee4888f1595dd2d9a665 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 14 Jan 2019 17:14:39 +0530 Subject: [PATCH 38/50] 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 47e214e1b48..9cec1c0dc66 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 29caea156a4..1207c5d128e 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 39/50] 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 0e70338b214..cf45cc46073 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 40/50] 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 be8201a07de..7bf63a2290d 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 83130966d4c..52d4621c7b7 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 e8494a1f965..6c2a97a89c5 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 41/50] 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 cf45cc46073..bbea9043a35 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 42/50] 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 5296e224929..6b3c3cc73d9 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 43/50] 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 b9d86a8e84a..26755dde61f 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''' From fccb1e55ec01f80764bf656e75bac370b5e76db7 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 16 Jan 2019 14:36:55 +0500 Subject: [PATCH 44/50] fix(Gross Profit Report): corrected fieldnames in return invoice query --- 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 2ed664c9bf7..4d455da994c 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -184,7 +184,7 @@ class GrossProfitGenerator(object): def get_returned_invoice_items(self): returned_invoices = frappe.db.sql(""" select - si.name, si_item.item_code, si_item.qty, si_item.base_amount, si.return_against + si.name, si_item.item_code, si_item.stock_qty as qty, si_item.base_net_amount as base_amount, si.return_against from `tabSales Invoice` si, `tabSales Invoice Item` si_item where From 34766c0c50ec5bb69d4baab2824b6d3b54792cbd Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Thu, 20 Dec 2018 18:50:24 +0530 Subject: [PATCH 45/50] BIS issue fixes --- erpnext/controllers/buying_controller.py | 4 +- .../production_plan/production_plan.py | 112 +++++++++++++----- .../doctype/sales_order/sales_order.js | 4 +- .../doctype/sales_order/sales_order.py | 37 ++++-- 4 files changed, 113 insertions(+), 44 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 541e56d781c..664bce4e4fa 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -678,7 +678,9 @@ class BuyingController(StockController): frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name) def validate_schedule_date(self): - if not self.schedule_date and self.get("items"): + if not self.get("items"): + return + if not self.schedule_date: self.schedule_date = min([d.schedule_date for d in self.get("items")]) if self.schedule_date: diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 9bf38581679..046377ec764 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -7,7 +7,7 @@ import frappe, json from frappe import msgprint, _ from frappe.model.document import Document from erpnext.manufacturing.doctype.bom.bom import validate_bom_no -from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime +from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime, ceil from erpnext.manufacturing.doctype.work_order.work_order import get_item_details from six import string_types from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults @@ -376,13 +376,16 @@ def get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock for d in frappe.db.sql("""select bei.item_code, item.default_bom as bom, ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, item.item_name, bei.description, bei.stock_uom, item.min_order_qty, bei.source_warehouse, - item.default_material_request_type, item.min_order_qty, item_default.default_warehouse + item.default_material_request_type, item.min_order_qty, item_default.default_warehouse, + item.purchase_uom, item_uom.conversion_factor from `tabBOM Explosion Item` bei JOIN `tabBOM` bom ON bom.name = bei.parent JOIN `tabItem` item ON item.name = bei.item_code LEFT JOIN `tabItem Default` item_default ON item_default.parent = item.name and item_default.company=%s + LEFT JOIN `tabUOM Conversion Detail` item_uom + ON item.name = item_uom.parent and item_uom.uom = item.purchase_uom where bei.docstatus < 2 and bom.name=%s and item.is_stock_item in (1, {0}) @@ -399,13 +402,15 @@ def get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_ item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse, item.default_bom as default_bom, bom_item.description as description, bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty, - item_default.default_warehouse + item_default.default_warehouse, item.purchase_uom, item_uom.conversion_factor FROM `tabBOM Item` bom_item JOIN `tabBOM` bom ON bom.name = bom_item.parent JOIN tabItem item ON bom_item.item_code = item.name LEFT JOIN `tabItem Default` item_default ON item.name = item_default.parent and item_default.company = %(company)s + LEFT JOIN `tabUOM Conversion Detail` item_uom + ON item.name = item_uom.parent and item_uom.uom = item.purchase_uom where bom.name = %(bom)s and bom_item.docstatus < 2 @@ -431,17 +436,30 @@ def get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_ return bom_wise_item_details def add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, row, data, warehouse, company): + print("add item in material request") total_qty = row.qty * planned_qty projected_qty, actual_qty = get_bin_details(row) requested_qty = 0 if ignore_existing_ordered_qty: requested_qty = total_qty - else: + elif total_qty > projected_qty: requested_qty = total_qty - projected_qty if requested_qty > 0 and requested_qty < row.min_order_qty: requested_qty = row.min_order_qty item_group_defaults = get_item_group_defaults(item, company) + + if not row.purchase_uom: + row.purchase_uom = row.stock_uom + + if row.purchase_uom != row.stock_uom: + if not row.conversion_factor: + frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(row.purchase_uom, row.stock_uom, item)) + + requested_qty = requested_qty / row.conversion_factor + if frappe.db.get_value("UOM", row.purchase_uom, "must_be_whole_number"): + requested_qty = ceil(requested_qty) + print(row) if requested_qty > 0: doc.setdefault('mr_items', []).append({ 'item_code': item, @@ -487,8 +505,8 @@ def get_sales_orders(self): "project": self.project, "item": self.item_code, "company": self.company - }, as_dict=1) + }, as_dict=1) return open_so @frappe.whitelist() @@ -511,6 +529,7 @@ def get_bin_details(row): @frappe.whitelist() def get_items_for_material_requests(doc, company=None): + print("get items for material request") if isinstance(doc, string_types): doc = frappe._dict(json.loads(doc)) @@ -520,34 +539,69 @@ def get_items_for_material_requests(doc, company=None): for data in po_items: warehouse = None bom_wise_item_details = {} - - if data.get('required_qty'): - planned_qty = data.get('required_qty') - bom_no = data.get('bom') - ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') - include_non_stock_items = 1 - warehouse = data.get('for_warehouse') - if data.get('include_exploded_items'): - include_subcontracted_items = 1 + if data.get("bom"): + print(doc),print("-------------------------------------------------") + if data.get('required_qty'): + planned_qty = data.get('required_qty') + bom_no = data.get('bom') + ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') + include_non_stock_items = 1 + warehouse = data.get('for_warehouse') + if data.get('include_exploded_items'): + include_subcontracted_items = 1 + else: + include_subcontracted_items = 0 else: - include_subcontracted_items = 0 - else: - planned_qty = data.get('planned_qty') - bom_no = data.get('bom_no') - include_subcontracted_items = doc.get('include_subcontracted_items') - company = doc.get('company') - include_non_stock_items = doc.get('include_non_stock_items') - ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') - if not planned_qty: - frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx'))) + planned_qty = data.get('planned_qty') + bom_no = data.get('bom_no') + include_subcontracted_items = doc.get('include_subcontracted_items') + company = doc.get('company') + include_non_stock_items = doc.get('include_non_stock_items') + ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') + if not planned_qty: + frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx'))) - if data.get('include_exploded_items') and bom_no and include_subcontracted_items: - # fetch exploded items from BOM - bom_wise_item_details = get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock_items) + if data.get('include_exploded_items') and bom_no and include_subcontracted_items: + # fetch exploded items from BOM + bom_wise_item_details = get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock_items) + else: + bom_wise_item_details = get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_stock_items, include_subcontracted_items, 1) + for item, item_details in bom_wise_item_details.items(): + print(item),print(item_details) + if item_details.qty > 0: + add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, item_details, data, warehouse, company) else: - bom_wise_item_details = get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_stock_items, include_subcontracted_items, 1) - for item, item_details in bom_wise_item_details.items(): + sales_order_item = frappe.get_doc('Sales OrderItem', data.sales_order_item).as_dict() + planned_qty = data.get('required_qty') + ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') + item = doc.item_code + purchase_uom = sales_order_item.uom + stock_uom = sales_order_item.stock_uom + conversion_factor = sales_order_item.conversion_factor + qty = doc.required_qty + if not purchase_uom == stock_uom: + qty = qty / conversion_factor + if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): + qty = ceil(qty) + item_details = { + 'item_name' = sales_order_item.item_name, + 'default_bom' = doc.bom, + 'purchase_uom' = purchase_uom, + 'default_warehouse' = doc.warehouse, + 'min_order_qty' = + 'default_material_request_type' = + 'qty' = qty, + 'is_sub_contracted' = , + 'item_code' = doc.item_code, + 'description' = sales_order_item.description, + 'stock_uom' = stock_uom, + 'conversion_factor' = conversion_factor, + 'source_warehouse' = , + } + warehouse = doc.warehouse + company = if item_details.qty > 0: add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, item_details, data, warehouse, company) + print(doc),print("-------------------------------------------------") return doc['mr_items'] diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 54d7654c2dd..ab2f6ee8e30 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -344,6 +344,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( label: __('Include Exploded Items')}, {fieldtype:'Check', fieldname:'ignore_existing_ordered_qty', label: __('Ignore Existing Ordered Qty')}, + {fieldtype:'Check', fieldname:'include_raw_materials_from_sales_order', + label: __('Include raw materials from sales order')}, { fieldtype:'Table', fieldname: 'items', description: __('Select BOM, Qty and For Warehouse'), @@ -367,7 +369,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( } ] var d = new frappe.ui.Dialog({ - title: __("Select from Items having BOM"), + title: __("Items for Raw Material Request"), fields: fields, primary_action: function() { var data = d.get_values(); diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 229f4f68370..1460bc8b694 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -341,11 +341,11 @@ class SalesOrder(SellingController): delivered_qty += item.delivered_qty tot_qty += item.qty - + if tot_qty != 0: self.db_set("per_delivered", flt(delivered_qty/tot_qty) * 100, update_modified=False) - + def set_indicator(self): """Set indicator for portal""" @@ -372,20 +372,19 @@ class SalesOrder(SellingController): def get_work_order_items(self, for_raw_material_request=0): '''Returns items with BOM that already do not have a linked work order''' items = [] - for table in [self.items, self.packed_items]: for i in table: bom = get_default_bom_item(i.item_code) - if bom: - stock_qty = i.qty if i.doctype == 'Packed Item' else i.stock_qty - if not for_raw_material_request: - total_work_order_qty = flt(frappe.db.sql('''select sum(qty) from `tabWork Order` - where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2''', (i.item_code, self.name, i.name))[0][0]) - pending_qty = stock_qty - total_work_order_qty - else: - pending_qty = stock_qty + stock_qty = i.qty if i.doctype == 'Packed Item' else i.stock_qty + if not for_raw_material_request: + total_work_order_qty = flt(frappe.db.sql('''select sum(qty) from `tabWork Order` + where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2''', (i.item_code, self.name, i.name))[0][0]) + pending_qty = stock_qty - total_work_order_qty + else: + pending_qty = stock_qty - if pending_qty: + if pending_qty: + if bom: items.append(dict( name= i.name, item_code= i.item_code, @@ -395,6 +394,16 @@ class SalesOrder(SellingController): required_qty = pending_qty if for_raw_material_request else 0, sales_order_item = i.name )) + else: + items.append(dict( + name= i.name, + item_code= i.item_code, + bom = '', + warehouse = i.warehouse, + pending_qty = pending_qty, + required_qty = pending_qty if for_raw_material_request else 0, + sales_order_item = i.name + )) return items def on_recurring(self, reference_doc, auto_repeat_doc): @@ -923,10 +932,12 @@ def make_raw_material_request(items, company, sales_order, project=None): for item in items.get('items'): item["include_exploded_items"] = items.get('include_exploded_items') item["ignore_existing_ordered_qty"] = items.get('ignore_existing_ordered_qty') + item["include_raw_materials_from_sales_order"] = items.get('include_raw_materials_from_sales_order') raw_materials = get_items_for_material_requests(items, company) if not raw_materials: frappe.msgprint(_("Material Request not created, as quantity for Raw Materials already available.")) + return material_request = frappe.new_doc('Material Request') material_request.update(dict( @@ -951,4 +962,4 @@ def make_raw_material_request(items, company, sales_order, project=None): material_request.flags.ignore_permissions = 1 material_request.run_method("set_missing_values") material_request.submit() - return material_request \ No newline at end of file + return material_request From 693e81db48a0a9315fa27ae4da291af31085c262 Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Fri, 21 Dec 2018 17:56:28 +0530 Subject: [PATCH 46/50] more bis fixes --- .../production_plan/production_plan.py | 99 +++++++++---------- 1 file changed, 45 insertions(+), 54 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 046377ec764..f57a9112bcd 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -436,8 +436,7 @@ def get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_ return bom_wise_item_details def add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, row, data, warehouse, company): - print("add item in material request") - total_qty = row.qty * planned_qty + total_qty = row['qty'] * planned_qty projected_qty, actual_qty = get_bin_details(row) requested_qty = 0 @@ -445,29 +444,27 @@ def add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered requested_qty = total_qty elif total_qty > projected_qty: requested_qty = total_qty - projected_qty - if requested_qty > 0 and requested_qty < row.min_order_qty: - requested_qty = row.min_order_qty + if requested_qty > 0 and requested_qty < row['min_order_qty']: + requested_qty = row['min_order_qty'] item_group_defaults = get_item_group_defaults(item, company) - if not row.purchase_uom: - row.purchase_uom = row.stock_uom + if not row['purchase_uom']: + row['purchase_uom'] = row['stock_uom'] - if row.purchase_uom != row.stock_uom: - if not row.conversion_factor: - frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(row.purchase_uom, row.stock_uom, item)) - - requested_qty = requested_qty / row.conversion_factor - if frappe.db.get_value("UOM", row.purchase_uom, "must_be_whole_number"): + if row['purchase_uom'] != row['stock_uom']: + if not row['conversion_factor']: + frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(row['purchase_uom'], row['stock_uom'], item)) + requested_qty = requested_qty / row['conversion_factor'] + if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"): requested_qty = ceil(requested_qty) - print(row) if requested_qty > 0: doc.setdefault('mr_items', []).append({ 'item_code': item, - 'item_name': row.item_name, + 'item_name': row['item_name'], 'quantity': requested_qty, - 'warehouse': warehouse or row.source_warehouse or row.default_warehouse or item_group_defaults.get("default_warehouse"), + 'warehouse': warehouse or row.get('source_warehouse') or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), 'actual_qty': actual_qty, - 'min_order_qty': row.min_order_qty, + 'min_order_qty': row['min_order_qty'], 'sales_order': data.get('sales_order') }) @@ -515,7 +512,7 @@ def get_bin_details(row): row = frappe._dict(json.loads(row)) conditions = "" - warehouse = row.source_warehouse or row.default_warehouse or row.warehouse + warehouse = row.get('source_warehouse') or row.get('default_warehouse') if 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) @@ -523,13 +520,12 @@ def get_bin_details(row): 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` where item_code = %(item_code)s {conditions} - """.format(conditions=conditions), { "item_code": row.item_code }, as_list=1) + """.format(conditions=conditions), { "item_code": row['item_code'] }, as_list=1) return item_projected_qty and item_projected_qty[0] or (0,0) @frappe.whitelist() def get_items_for_material_requests(doc, company=None): - print("get items for material request") if isinstance(doc, string_types): doc = frappe._dict(json.loads(doc)) @@ -540,7 +536,6 @@ def get_items_for_material_requests(doc, company=None): warehouse = None bom_wise_item_details = {} if data.get("bom"): - print(doc),print("-------------------------------------------------") if data.get('required_qty'): planned_qty = data.get('required_qty') bom_no = data.get('bom') @@ -565,43 +560,39 @@ def get_items_for_material_requests(doc, company=None): # fetch exploded items from BOM bom_wise_item_details = get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock_items) else: - bom_wise_item_details = get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_stock_items, include_subcontracted_items, 1) + bom_wise_item_details = get_subitems(doc, data, bom_wise_item_details, bom_no, company, + include_non_stock_items, include_subcontracted_items, 1) for item, item_details in bom_wise_item_details.items(): - print(item),print(item_details) if item_details.qty > 0: - add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, item_details, data, warehouse, company) + add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, + item, item_details, data, warehouse, company) else: - sales_order_item = frappe.get_doc('Sales OrderItem', data.sales_order_item).as_dict() + item_master = frappe.get_doc('Item', data['item_code']).as_dict() planned_qty = data.get('required_qty') - ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') - item = doc.item_code - purchase_uom = sales_order_item.uom - stock_uom = sales_order_item.stock_uom - conversion_factor = sales_order_item.conversion_factor - qty = doc.required_qty - if not purchase_uom == stock_uom: - qty = qty / conversion_factor - if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): - qty = ceil(qty) - item_details = { - 'item_name' = sales_order_item.item_name, - 'default_bom' = doc.bom, - 'purchase_uom' = purchase_uom, - 'default_warehouse' = doc.warehouse, - 'min_order_qty' = - 'default_material_request_type' = - 'qty' = qty, - 'is_sub_contracted' = , - 'item_code' = doc.item_code, - 'description' = sales_order_item.description, - 'stock_uom' = stock_uom, - 'conversion_factor' = conversion_factor, - 'source_warehouse' = , - } - warehouse = doc.warehouse - company = - if item_details.qty > 0: - add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, item_details, data, warehouse, company) - print(doc),print("-------------------------------------------------") + + purchase_uom = item_master.purchase_uom or item_master.stock_uom + conversion_factor = 0 + for d in item_master.get("uoms"): + if d.uom == purchase_uom: + conversion_factor = d.conversion_factor + + item_details = frappe._dict({ + 'item_name' : item_master.item_name, + 'default_bom' : doc.bom, + 'purchase_uom' : purchase_uom, + 'default_warehouse': item_master.default_warehouse, + 'min_order_qty' : item_master.min_order_qty, + 'default_material_request_type' : item_master.default_material_request_type, + 'qty': 1, + 'is_sub_contracted' : item_master.is_subcontracted_item, + 'item_code' : item_master.name, + 'description' : item_master.description, + 'stock_uom' : item_master.stock_uom, + 'conversion_factor' : conversion_factor, + }) + + if item_details['qty'] > 0: + add_item_in_material_request_items(doc, planned_qty, data.get('ignore_existing_ordered_qty'), item_master.name, + item_details, data, data.get('for_warehouse'), company) return doc['mr_items'] From 3dde94941902e35fdafb1532486c940fe6d1f2ed Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 27 Dec 2018 14:24:08 +0530 Subject: [PATCH 47/50] feat(mr_against_so): Merge same items from different BOMs --- .../production_plan/production_plan.py | 149 ++--- .../production_planning_tool.py | 552 ------------------ .../doctype/sales_order/sales_order.js | 2 - .../doctype/sales_order/sales_order.py | 2 +- 4 files changed, 84 insertions(+), 621 deletions(-) delete mode 100644 erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index f57a9112bcd..1ad6e64d415 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -9,7 +9,7 @@ from frappe.model.document import Document from erpnext.manufacturing.doctype.bom.bom import validate_bom_no from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime, ceil from erpnext.manufacturing.doctype.work_order.work_order import get_item_details -from six import string_types +from six import string_types, iteritems from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults class ProductionPlan(Document): @@ -372,9 +372,9 @@ class ProductionPlan(Document): else : msgprint(_("No material request created")) -def get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock_items): +def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1): for d in frappe.db.sql("""select bei.item_code, item.default_bom as bom, - ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, item.item_name, + ifnull(sum(bei.stock_qty/ifnull(bom.quantity, 1)), 0)*%s as qty, item.item_name, bei.description, bei.stock_uom, item.min_order_qty, bei.source_warehouse, item.default_material_request_type, item.min_order_qty, item_default.default_warehouse, item.purchase_uom, item_uom.conversion_factor @@ -390,15 +390,16 @@ def get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock bei.docstatus < 2 and bom.name=%s and item.is_stock_item in (1, {0}) group by bei.item_code, bei.stock_uom""".format(0 if include_non_stock_items else 1), - (company, bom_no), as_dict=1): - bom_wise_item_details.setdefault(d.get('item_code'), d) - return bom_wise_item_details + (planned_qty, company, bom_no), as_dict=1): + item_details.setdefault(d.get('item_code'), d) + return item_details -def get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_stock_items, include_subcontracted_items, parent_qty): +def get_subitems(doc, data, item_details, bom_no, company, include_non_stock_items, + include_subcontracted_items, parent_qty, planned_qty=1): items = frappe.db.sql(""" SELECT bom_item.item_code, default_material_request_type, item.item_name, - ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, + ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(planned_qty)s, 0) as qty, item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse, item.default_bom as default_bom, bom_item.description as description, bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty, @@ -418,25 +419,27 @@ def get_subitems(doc, data, bom_wise_item_details, bom_no, company, include_non_ group by bom_item.item_code""".format(0 if include_non_stock_items else 1),{ 'bom': bom_no, 'parent_qty': parent_qty, + 'planned_qty': planned_qty, 'company': company }, as_dict=1) for d in items: if not data.get('include_exploded_items') or not d.default_bom: - if d.item_code in bom_wise_item_details: - bom_wise_item_details[d.item_code].qty = bom_wise_item_details[d.item_code].qty + d.qty + if d.item_code in item_details: + item_details[d.item_code].qty = item_details[d.item_code].qty + d.qty else: - bom_wise_item_details[d.item_code] = d + item_details[d.item_code] = d if data.get('include_exploded_items') and d.default_bom: if ((d.default_material_request_type in ["Manufacture", "Purchase"] and not d.is_sub_contracted) or (d.is_sub_contracted and include_subcontracted_items)): if d.qty > 0: - get_subitems(doc, data, bom_wise_item_details, d.default_bom, company, include_non_stock_items, include_subcontracted_items, d.qty) - return bom_wise_item_details + get_subitems(doc, data, item_details, d.default_bom, company, + include_non_stock_items, include_subcontracted_items, d.qty) + return item_details -def add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, item, row, data, warehouse, company): - total_qty = row['qty'] * planned_qty +def get_material_request_items(row, sales_order, company, ignore_existing_ordered_qty, warehouse): + total_qty = row['qty'] projected_qty, actual_qty = get_bin_details(row) requested_qty = 0 @@ -446,27 +449,31 @@ def add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered requested_qty = total_qty - projected_qty if requested_qty > 0 and requested_qty < row['min_order_qty']: requested_qty = row['min_order_qty'] - item_group_defaults = get_item_group_defaults(item, company) + item_group_defaults = get_item_group_defaults(row.item_code, company) if not row['purchase_uom']: row['purchase_uom'] = row['stock_uom'] if row['purchase_uom'] != row['stock_uom']: if not row['conversion_factor']: - frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}").format(row['purchase_uom'], row['stock_uom'], item)) + frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}") + .format(row['purchase_uom'], row['stock_uom'], item)) requested_qty = requested_qty / row['conversion_factor'] + if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"): requested_qty = ceil(requested_qty) + if requested_qty > 0: - doc.setdefault('mr_items', []).append({ - 'item_code': item, - 'item_name': row['item_name'], + return { + 'item_code': row.item_code, + 'item_name': row.item_name, 'quantity': requested_qty, - 'warehouse': warehouse or row.get('source_warehouse') or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), + 'warehouse': warehouse or row.get('source_warehouse') \ + or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), 'actual_qty': actual_qty, 'min_order_qty': row['min_order_qty'], - 'sales_order': data.get('sales_order') - }) + 'sales_order': sales_order + } def get_sales_orders(self): so_filter = item_filter = "" @@ -525,74 +532,84 @@ def get_bin_details(row): return item_projected_qty and item_projected_qty[0] or (0,0) @frappe.whitelist() -def get_items_for_material_requests(doc, company=None): +def get_items_for_material_requests(doc, sales_order=None, company=None): if isinstance(doc, string_types): doc = frappe._dict(json.loads(doc)) doc['mr_items'] = [] po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') + company = doc.get('company') + so_item_details = frappe._dict() for data in po_items: - warehouse = None - bom_wise_item_details = {} + warehouse = data.get('for_warehouse') + ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or doc.get('ignore_existing_ordered_qty') + planned_qty = data.get('required_qty') or data.get('planned_qty') + item_details = {} if data.get("bom"): if data.get('required_qty'): - planned_qty = data.get('required_qty') bom_no = data.get('bom') - ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') include_non_stock_items = 1 - warehouse = data.get('for_warehouse') - if data.get('include_exploded_items'): - include_subcontracted_items = 1 - else: - include_subcontracted_items = 0 + include_subcontracted_items = 1 if data.get('include_exploded_items') else 0 else: - planned_qty = data.get('planned_qty') bom_no = data.get('bom_no') include_subcontracted_items = doc.get('include_subcontracted_items') - company = doc.get('company') include_non_stock_items = doc.get('include_non_stock_items') - ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') + if not planned_qty: frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx'))) - if data.get('include_exploded_items') and bom_no and include_subcontracted_items: - # fetch exploded items from BOM - bom_wise_item_details = get_exploded_items(bom_wise_item_details, company, bom_no, include_non_stock_items) - else: - bom_wise_item_details = get_subitems(doc, data, bom_wise_item_details, bom_no, company, - include_non_stock_items, include_subcontracted_items, 1) - for item, item_details in bom_wise_item_details.items(): - if item_details.qty > 0: - add_item_in_material_request_items(doc, planned_qty, ignore_existing_ordered_qty, - item, item_details, data, warehouse, company) + if bom_no: + if data.get('include_exploded_items') and include_subcontracted_items: + # fetch exploded items from BOM + item_details = get_exploded_items(item_details, + company, bom_no,include_non_stock_items, planned_qty=planned_qty) + else: + item_details = get_subitems(doc, data, item_details, bom_no, company, + include_non_stock_items, include_subcontracted_items, 1, planned_qty=planned_qty) else: item_master = frappe.get_doc('Item', data['item_code']).as_dict() - planned_qty = data.get('required_qty') - purchase_uom = item_master.purchase_uom or item_master.stock_uom conversion_factor = 0 for d in item_master.get("uoms"): if d.uom == purchase_uom: conversion_factor = d.conversion_factor - item_details = frappe._dict({ - 'item_name' : item_master.item_name, - 'default_bom' : doc.bom, - 'purchase_uom' : purchase_uom, - 'default_warehouse': item_master.default_warehouse, - 'min_order_qty' : item_master.min_order_qty, - 'default_material_request_type' : item_master.default_material_request_type, - 'qty': 1, - 'is_sub_contracted' : item_master.is_subcontracted_item, - 'item_code' : item_master.name, - 'description' : item_master.description, - 'stock_uom' : item_master.stock_uom, - 'conversion_factor' : conversion_factor, - }) + item_details[item_master.name] = frappe._dict( + { + 'item_name' : item_master.item_name, + 'default_bom' : doc.bom, + 'purchase_uom' : purchase_uom, + 'default_warehouse': item_master.default_warehouse, + 'min_order_qty' : item_master.min_order_qty, + 'default_material_request_type' : item_master.default_material_request_type, + 'qty': planned_qty or 1, + 'is_sub_contracted' : item_master.is_subcontracted_item, + 'item_code' : item_master.name, + 'description' : item_master.description, + 'stock_uom' : item_master.stock_uom, + 'conversion_factor' : conversion_factor, + } + ) - if item_details['qty'] > 0: - add_item_in_material_request_items(doc, planned_qty, data.get('ignore_existing_ordered_qty'), item_master.name, - item_details, data, data.get('for_warehouse'), company) + if not sales_order: + sales_order = doc.get("sales_order") - return doc['mr_items'] + for item_code, details in iteritems(item_details): + so_item_details.setdefault(sales_order, frappe._dict()) + if item_code in so_item_details.get(sales_order, {}): + so_item_details[sales_order][item_code]['qty'] = so_item_details[sales_order][item_code].get("qty", 0) + flt(details.qty) + else: + so_item_details[sales_order][item_code] = details + + mr_items = [] + for sales_order, item_code in iteritems(so_item_details): + item_dict = so_item_details[sales_order] + for details in item_dict.values(): + if details.qty > 0: + items = get_material_request_items(details, sales_order, company, + ignore_existing_ordered_qty, warehouse) + if items: + mr_items.append(items) + + return mr_items diff --git a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py deleted file mode 100644 index 323aaf9d626..00000000000 --- a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py +++ /dev/null @@ -1,552 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and - -from frappe import msgprint, _ - -from frappe.model.document import Document -from erpnext.manufacturing.doctype.bom.bom import validate_bom_no -from erpnext.manufacturing.doctype.work_order.work_order import get_item_details - -class ProductionPlanningTool(Document): - def clear_table(self, table_name): - self.set(table_name, []) - - def validate_company(self): - if not self.company: - frappe.throw(_("Please enter Company")) - - def get_open_sales_orders(self): - """ Pull sales orders which are pending to deliver based on criteria selected""" - so_filter = item_filter = "" - if self.from_date: - so_filter += " and so.transaction_date >= %(from_date)s" - if self.to_date: - so_filter += " and so.transaction_date <= %(to_date)s" - if self.customer: - so_filter += " and so.customer = %(customer)s" - if self.project: - so_filter += " and so.project = %(project)s" - - if self.fg_item: - item_filter += " and so_item.item_code = %(item)s" - - open_so = frappe.db.sql(""" - select distinct so.name, so.transaction_date, so.customer, so.base_grand_total - from `tabSales Order` so, `tabSales Order Item` so_item - where so_item.parent = so.name - and so.docstatus = 1 and so.status not in ("Stopped", "Closed") - and so.company = %(company)s - and so_item.qty > so_item.delivered_qty {0} {1} - and (exists (select name from `tabBOM` bom where bom.item=so_item.item_code - and bom.is_active = 1) - or exists (select name from `tabPacked Item` pi - where pi.parent = so.name and pi.parent_item = so_item.item_code - and exists (select name from `tabBOM` bom where bom.item=pi.item_code - and bom.is_active = 1))) - """.format(so_filter, item_filter), { - "from_date": self.from_date, - "to_date": self.to_date, - "customer": self.customer, - "project": self.project, - "item": self.fg_item, - "company": self.company - }, as_dict=1) - - self.add_so_in_table(open_so) - - def add_so_in_table(self, open_so): - """ Add sales orders in the table""" - self.clear_table("sales_orders") - - so_list = [] - for r in open_so: - if cstr(r['name']) not in so_list: - pp_so = self.append('sales_orders', {}) - pp_so.sales_order = r['name'] - pp_so.sales_order_date = cstr(r['transaction_date']) - pp_so.customer = cstr(r['customer']) - pp_so.grand_total = flt(r['base_grand_total']) - - def get_pending_material_requests(self): - """ Pull Material Requests that are pending based on criteria selected""" - mr_filter = item_filter = "" - if self.from_date: - mr_filter += " and mr.transaction_date >= %(from_date)s" - if self.to_date: - mr_filter += " and mr.transaction_date <= %(to_date)s" - if self.warehouse: - mr_filter += " and mr_item.warehouse = %(warehouse)s" - - if self.fg_item: - item_filter += " and mr_item.item_code = %(item)s" - - pending_mr = frappe.db.sql(""" - select distinct mr.name, mr.transaction_date - from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item - where mr_item.parent = mr.name - and mr.material_request_type = "Manufacture" - and mr.docstatus = 1 - and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1} - and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code - and bom.is_active = 1)) - """.format(mr_filter, item_filter), { - "from_date": self.from_date, - "to_date": self.to_date, - "warehouse": self.warehouse, - "item": self.fg_item - }, as_dict=1) - - self.add_mr_in_table(pending_mr) - - def add_mr_in_table(self, pending_mr): - """ Add Material Requests in the table""" - self.clear_table("material_requests") - - mr_list = [] - for r in pending_mr: - if cstr(r['name']) not in mr_list: - mr = self.append('material_requests', {}) - mr.material_request = r['name'] - mr.material_request_date = cstr(r['transaction_date']) - - def get_items(self): - if self.get_items_from == "Sales Order": - self.get_so_items() - elif self.get_items_from == "Material Request": - self.get_mr_items() - - def get_so_items(self): - so_list = [d.sales_order for d in self.get('sales_orders') if d.sales_order] - if not so_list: - msgprint(_("Please enter Sales Orders in the above table")) - return [] - - item_condition = "" - if self.fg_item: - item_condition = ' and so_item.item_code = "{0}"'.format(frappe.db.escape(self.fg_item)) - - items = frappe.db.sql("""select distinct parent, item_code, warehouse, - (qty - delivered_qty)*conversion_factor as pending_qty - from `tabSales Order Item` so_item - where parent in (%s) and docstatus = 1 and qty > delivered_qty - and exists (select name from `tabBOM` bom where bom.item=so_item.item_code - and bom.is_active = 1) %s""" % \ - (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1) - - if self.fg_item: - item_condition = ' and pi.item_code = "{0}"'.format(frappe.db.escape(self.fg_item)) - - packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as warehouse, - (((so_item.qty - so_item.delivered_qty) * pi.qty) / so_item.qty) - as pending_qty - from `tabSales Order Item` so_item, `tabPacked Item` pi - where so_item.parent = pi.parent and so_item.docstatus = 1 - and pi.parent_item = so_item.item_code - and so_item.parent in (%s) and so_item.qty > so_item.delivered_qty - and exists (select name from `tabBOM` bom where bom.item=pi.item_code - and bom.is_active = 1) %s""" % \ - (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1) - - self.add_items(items + packed_items) - - def get_mr_items(self): - mr_list = [d.material_request for d in self.get('material_requests') if d.material_request] - if not mr_list: - msgprint(_("Please enter Material Requests in the above table")) - return [] - - item_condition = "" - if self.fg_item: - item_condition = ' and mr_item.item_code = "' + frappe.db.escape(self.fg_item, percent=False) + '"' - - items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, - (qty - ordered_qty) as pending_qty - from `tabMaterial Request Item` mr_item - where parent in (%s) and docstatus = 1 and qty > ordered_qty - and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code - and bom.is_active = 1) %s""" % \ - (", ".join(["%s"] * len(mr_list)), item_condition), tuple(mr_list), as_dict=1) - - self.add_items(items) - - - def add_items(self, items): - self.clear_table("items") - for p in items: - item_details = get_item_details(p['item_code']) - pi = self.append('items', {}) - pi.warehouse = p['warehouse'] - pi.item_code = p['item_code'] - pi.description = item_details and item_details.description or '' - pi.stock_uom = item_details and item_details.stock_uom or '' - pi.bom_no = item_details and item_details.bom_no or '' - pi.planned_qty = flt(p['pending_qty']) - pi.pending_qty = flt(p['pending_qty']) - - if self.get_items_from == "Sales Order": - pi.sales_order = p['parent'] - elif self.get_items_from == "Material Request": - pi.material_request = p['parent'] - pi.material_request_item = p['name'] - - def validate_data(self): - self.validate_company() - for d in self.get('items'): - if not d.bom_no: - frappe.throw(_("Please select BOM for Item in Row {0}".format(d.idx))) - else: - validate_bom_no(d.item_code, d.bom_no) - - if not flt(d.planned_qty): - frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx)) - - def raise_work_orders(self): - """It will raise work order (Draft) for all distinct FG items""" - self.validate_data() - - from erpnext.utilities.transaction_base import validate_uom_is_integer - validate_uom_is_integer(self, "stock_uom", "planned_qty") - - items = self.get_production_items() - - wo_list = [] - frappe.flags.mute_messages = True - - for key in items: - work_order = self.create_work_order(items[key]) - if work_order: - wo_list.append(work_order) - - frappe.flags.mute_messages = False - - if wo_list: - wo_list = ["""%s""" % \ - (p, p) for p in wo_list] - msgprint(_("{0} created").format(comma_and(wo_list))) - else : - msgprint(_("No Work Orders created")) - - def get_production_items(self): - item_dict = {} - for d in self.get("items"): - item_details= { - "production_item" : d.item_code, - "sales_order" : d.sales_order, - "material_request" : d.material_request, - "material_request_item" : d.material_request_item, - "bom_no" : d.bom_no, - "description" : d.description, - "stock_uom" : d.stock_uom, - "company" : self.company, - "wip_warehouse" : "", - "fg_warehouse" : d.warehouse, - "status" : "Draft", - "project" : frappe.db.get_value("Sales Order", d.sales_order, "project") - } - - """ Club similar BOM and item for processing in case of Sales Orders """ - if self.get_items_from == "Material Request": - item_details.update({ - "qty": d.planned_qty - }) - item_dict[(d.item_code, d.material_request_item, d.warehouse)] = item_details - - else: - item_details.update({ - "qty":flt(item_dict.get((d.item_code, d.sales_order, d.warehouse),{}) - .get("qty")) + flt(d.planned_qty) - }) - item_dict[(d.item_code, d.sales_order, d.warehouse)] = item_details - - return item_dict - - def create_work_order(self, item_dict): - """Create work order. Called from Production Planning Tool""" - from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse - warehouse = get_default_warehouse() - wo = frappe.new_doc("Work Order") - wo.update(item_dict) - wo.set_work_order_operations() - if warehouse: - wo.wip_warehouse = warehouse.get('wip_warehouse') - if not wo.fg_warehouse: - wo.fg_warehouse = warehouse.get('fg_warehouse') - - try: - wo.insert() - return wo.name - except OverProductionError: - pass - - def get_so_wise_planned_qty(self): - """ - bom_dict { - bom_no: ['sales_order', 'qty'] - } - """ - bom_dict = {} - for d in self.get("items"): - if self.get_items_from == "Material Request": - bom_dict.setdefault(d.bom_no, []).append([d.material_request_item, flt(d.planned_qty)]) - else: - bom_dict.setdefault(d.bom_no, []).append([d.sales_order, flt(d.planned_qty)]) - return bom_dict - - def download_raw_materials(self): - """ Create csv data for required raw material to produce finished goods""" - self.validate_data() - bom_dict = self.get_so_wise_planned_qty() - self.get_raw_materials(bom_dict) - return self.get_csv() - - def get_raw_materials(self, bom_dict,non_stock_item=0): - """ Get raw materials considering sub-assembly items - { - "item_code": [qty_required, description, stock_uom, min_order_qty] - } - """ - item_list = [] - precision = frappe.get_precision("BOM Item", "stock_qty") - - for bom, so_wise_qty in bom_dict.items(): - bom_wise_item_details = {} - if self.use_multi_level_bom and self.only_raw_materials and self.include_subcontracted: - # get all raw materials with sub assembly childs - # Did not use qty_consumed_per_unit in the query, as it leads to rounding loss - for d in frappe.db.sql("""select fb.item_code, - ifnull(sum(fb.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, - fb.description, fb.stock_uom, item.min_order_qty - from `tabBOM Explosion Item` fb, `tabBOM` bom, `tabItem` item - where bom.name = fb.parent and item.name = fb.item_code - and (item.is_sub_contracted_item = 0 or ifnull(item.default_bom, "")="") - """ + ("and item.is_stock_item = 1","")[non_stock_item] + """ - and fb.docstatus<2 and bom.name=%(bom)s - group by fb.item_code, fb.stock_uom""", {"bom":bom}, as_dict=1): - bom_wise_item_details.setdefault(d.item_code, d) - else: - # Get all raw materials considering SA items as raw materials, - # so no childs of SA items - bom_wise_item_details = self.get_subitems(bom_wise_item_details, bom,1, \ - self.use_multi_level_bom,self.only_raw_materials, self.include_subcontracted,non_stock_item) - - for item, item_details in bom_wise_item_details.items(): - for so_qty in so_wise_qty: - item_list.append([item, flt(flt(item_details.qty) * so_qty[1], precision), - item_details.description, item_details.stock_uom, item_details.min_order_qty, - so_qty[0]]) - - self.make_items_dict(item_list) - - def get_subitems(self,bom_wise_item_details, bom, parent_qty, include_sublevel, only_raw, supply_subs,non_stock_item=0): - items = frappe.db.sql(""" - SELECT - bom_item.item_code, - default_material_request_type, - ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)), 0) as qty, - item.is_sub_contracted_item as is_sub_contracted, - item.default_bom as default_bom, - bom_item.description as description, - bom_item.stock_uom as stock_uom, - item.min_order_qty as min_order_qty - FROM - `tabBOM Item` bom_item, - `tabBOM` bom, - tabItem item - where - bom.name = bom_item.parent - and bom.name = %(bom)s - and bom_item.docstatus < 2 - and bom_item.item_code = item.name - """ + ("and item.is_stock_item = 1", "")[non_stock_item] + """ - group by bom_item.item_code""", {"bom": bom, "parent_qty": parent_qty}, as_dict=1) - - for d in items: - if ((d.default_material_request_type == "Purchase" - and not (d.is_sub_contracted and only_raw and include_sublevel)) - or (d.default_material_request_type == "Manufacture" and not only_raw)): - - if d.item_code in bom_wise_item_details: - bom_wise_item_details[d.item_code].qty = bom_wise_item_details[d.item_code].qty + d.qty - else: - bom_wise_item_details[d.item_code] = d - - if include_sublevel and d.default_bom: - if ((d.default_material_request_type == "Purchase" and d.is_sub_contracted and supply_subs) - or (d.default_material_request_type == "Manufacture")): - - my_qty = 0 - projected_qty = self.get_item_projected_qty(d.item_code) - if self.create_material_requests_for_all_required_qty: - my_qty = d.qty - else: - total_required_qty = flt(bom_wise_item_details.get(d.item_code, frappe._dict()).qty) - if (total_required_qty - d.qty) < projected_qty: - my_qty = total_required_qty - projected_qty - else: - my_qty = d.qty - - if my_qty > 0: - self.get_subitems(bom_wise_item_details, - d.default_bom, my_qty, include_sublevel, only_raw, supply_subs) - - return bom_wise_item_details - - def make_items_dict(self, item_list): - if not getattr(self, "item_dict", None): - self.item_dict = {} - - for i in item_list: - self.item_dict.setdefault(i[0], []).append([flt(i[1]), i[2], i[3], i[4], i[5]]) - - def get_csv(self): - item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse', - 'Quantity Requested for Purchase', 'Ordered Qty', 'Actual Qty']] - for item in self.item_dict: - total_qty = sum([flt(d[0]) for d in self.item_dict[item]]) - item_list.append([item, self.item_dict[item][0][1], self.item_dict[item][0][2], total_qty]) - item_qty = frappe.db.sql("""select warehouse, indented_qty, ordered_qty, actual_qty - from `tabBin` where item_code = %s""", item, as_dict=1) - - i_qty, o_qty, a_qty = 0, 0, 0 - for w in item_qty: - i_qty, o_qty, a_qty = i_qty + flt(w.indented_qty), o_qty + \ - flt(w.ordered_qty), a_qty + flt(w.actual_qty) - - item_list.append(['', '', '', '', w.warehouse, flt(w.indented_qty), - flt(w.ordered_qty), flt(w.actual_qty)]) - if item_qty: - item_list.append(['', '', '', '', 'Total', i_qty, o_qty, a_qty]) - else: - item_list.append(['', '', '', '', 'Total', 0, 0, 0]) - - return item_list - - def raise_material_requests(self): - """ - Raise Material Request if projected qty is less than qty required - Requested qty should be shortage qty considering minimum order qty - """ - self.validate_data() - if not self.purchase_request_for_warehouse: - frappe.throw(_("Please enter Warehouse for which Material Request will be raised")) - - bom_dict = self.get_so_wise_planned_qty() - self.get_raw_materials(bom_dict,self.create_material_requests_non_stock_request) - - if self.item_dict: - self.create_material_request() - - def get_requested_items(self): - items_to_be_requested = frappe._dict() - - if not self.create_material_requests_for_all_required_qty: - item_projected_qty = self.get_projected_qty() - - for item, so_item_qty in self.item_dict.items(): - total_qty = sum([flt(d[0]) for d in so_item_qty]) - requested_qty = 0 - - if self.create_material_requests_for_all_required_qty: - requested_qty = total_qty - elif total_qty > item_projected_qty.get(item, 0): - # shortage - requested_qty = total_qty - flt(item_projected_qty.get(item)) - # consider minimum order qty - - if requested_qty and requested_qty < flt(so_item_qty[0][3]): - requested_qty = flt(so_item_qty[0][3]) - - # distribute requested qty SO wise - for item_details in so_item_qty: - if requested_qty: - sales_order = item_details[4] or "No Sales Order" - if self.get_items_from == "Material Request": - sales_order = "No Sales Order" - if requested_qty <= item_details[0]: - adjusted_qty = requested_qty - else: - adjusted_qty = item_details[0] - - items_to_be_requested.setdefault(item, {}).setdefault(sales_order, 0) - items_to_be_requested[item][sales_order] += adjusted_qty - requested_qty -= adjusted_qty - else: - break - - # requested qty >= total so qty, due to minimum order qty - if requested_qty: - items_to_be_requested.setdefault(item, {}).setdefault("No Sales Order", 0) - items_to_be_requested[item]["No Sales Order"] += requested_qty - - return items_to_be_requested - - def get_item_projected_qty(self,item): - conditions = "" - if self.purchase_request_for_warehouse: - conditions = " and warehouse='{0}'".format(frappe.db.escape(self.purchase_request_for_warehouse)) - - item_projected_qty = frappe.db.sql(""" - select ifnull(sum(projected_qty),0) as qty - from `tabBin` - where item_code = %(item_code)s {conditions} - """.format(conditions=conditions), { "item_code": item }, as_dict=1) - - return item_projected_qty[0].qty - - def get_projected_qty(self): - items = self.item_dict.keys() - item_projected_qty = frappe.db.sql("""select item_code, sum(projected_qty) - from `tabBin` where item_code in (%s) and warehouse=%s group by item_code""" % - (", ".join(["%s"]*len(items)), '%s'), tuple(items + [self.purchase_request_for_warehouse])) - - return dict(item_projected_qty) - - def create_material_request(self): - items_to_be_requested = self.get_requested_items() - - material_request_list = [] - if items_to_be_requested: - for item in items_to_be_requested: - item_wrapper = frappe.get_doc("Item", item) - material_request = frappe.new_doc("Material Request") - material_request.update({ - "transaction_date": nowdate(), - "status": "Draft", - "company": self.company, - "requested_by": frappe.session.user, - "schedule_date": add_days(nowdate(), cint(item_wrapper.lead_time_days)), - }) - material_request.update({"material_request_type": item_wrapper.default_material_request_type}) - - for sales_order, requested_qty in items_to_be_requested[item].items(): - material_request.append("items", { - "doctype": "Material Request Item", - "__islocal": 1, - "item_code": item, - "item_name": item_wrapper.item_name, - "description": item_wrapper.description, - "uom": item_wrapper.stock_uom, - "item_group": item_wrapper.item_group, - "brand": item_wrapper.brand, - "qty": requested_qty, - "schedule_date": add_days(nowdate(), cint(item_wrapper.lead_time_days)), - "warehouse": self.purchase_request_for_warehouse, - "sales_order": sales_order if sales_order!="No Sales Order" else None, - "project": frappe.db.get_value("Sales Order", sales_order, "project") \ - if sales_order!="No Sales Order" else None - }) - - material_request.flags.ignore_permissions = 1 - material_request.submit() - material_request_list.append(material_request.name) - - if material_request_list: - message = ["""%s""" % \ - (p, p) for p in material_request_list] - msgprint(_("Material Requests {0} created").format(comma_and(message))) - else: - msgprint(_("Nothing to request")) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index ab2f6ee8e30..b1bfeffa545 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -344,8 +344,6 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( label: __('Include Exploded Items')}, {fieldtype:'Check', fieldname:'ignore_existing_ordered_qty', label: __('Ignore Existing Ordered Qty')}, - {fieldtype:'Check', fieldname:'include_raw_materials_from_sales_order', - label: __('Include raw materials from sales order')}, { fieldtype:'Table', fieldname: 'items', description: __('Select BOM, Qty and For Warehouse'), diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 1460bc8b694..445e02b6910 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -934,7 +934,7 @@ def make_raw_material_request(items, company, sales_order, project=None): item["ignore_existing_ordered_qty"] = items.get('ignore_existing_ordered_qty') item["include_raw_materials_from_sales_order"] = items.get('include_raw_materials_from_sales_order') - raw_materials = get_items_for_material_requests(items, company) + raw_materials = get_items_for_material_requests(items, sales_order, company) if not raw_materials: frappe.msgprint(_("Material Request not created, as quantity for Raw Materials already available.")) return From 158e7dcd8bcb3e80dfb60541dbbf54800a372b2c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 4 Jan 2019 11:04:37 +0530 Subject: [PATCH 48/50] fix(bom-uom_issue)Fetch rate in BOM from Price List based on UOM --- erpnext/manufacturing/doctype/bom/bom.js | 4 +- erpnext/manufacturing/doctype/bom/bom.py | 58 +++++++++++----- .../doctype/bom_item/bom_item.json | 67 ++++++++++++++++++- erpnext/public/js/controllers/transaction.js | 1 + erpnext/regional/report/gstr_1/gstr_1.py | 4 +- erpnext/stock/get_item_details.py | 11 +-- 6 files changed, 121 insertions(+), 24 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 2615b31782c..7b5339b4f0b 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -121,9 +121,11 @@ frappe.ui.form.on("BOM", { freeze: true, args: { update_parent: true, - from_child_bom:false + from_child_bom:false, + save: false }, callback: function(r) { + refresh_field("items"); if(!r.exc) frm.refresh_fields(); } }); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 54ffa069126..57a1cc25936 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -8,6 +8,7 @@ from frappe import _ from erpnext.setup.utils import get_exchange_rate from frappe.website.website_generator import WebsiteGenerator from erpnext.stock.get_item_details import get_conversion_factor +from erpnext.stock.get_item_details import get_price_list_rate import functools @@ -109,7 +110,11 @@ class BOM(WebsiteGenerator): "item_name": item.item_name, "bom_no": item.bom_no, "stock_qty": item.stock_qty, - "include_item_in_manufacturing": item.include_item_in_manufacturing + "include_item_in_manufacturing": item.include_item_in_manufacturing, + "qty": item.qty, + "uom": item.uom, + "stock_uom": item.stock_uom, + "conversion_factor": item.conversion_factor }) for r in ret: if not item.get(r): @@ -141,7 +146,7 @@ class BOM(WebsiteGenerator): 'uom' : item and args['stock_uom'] or '', 'conversion_factor': 1, 'bom_no' : args['bom_no'], - 'rate' : rate / self.conversion_rate if self.conversion_rate else rate, + 'rate' : rate, 'qty' : args.get("qty") or args.get("stock_qty") or 1, 'stock_qty' : args.get("qty") or args.get("stock_qty") or 1, 'base_rate' : rate, @@ -173,35 +178,56 @@ class BOM(WebsiteGenerator): elif self.rm_cost_as_per == "Price List": if not self.buying_price_list: frappe.throw(_("Please select Price List")) - rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list, - "item_code": arg["item_code"]}, "price_list_rate") or 0.0 - - price_list_currency = frappe.db.get_value("Price List", - self.buying_price_list, "currency") - if price_list_currency != self.company_currency(): - rate = flt(rate * self.conversion_rate) + args = frappe._dict({ + "doctype": "BOM", + "price_list": self.buying_price_list, + "qty": arg.get("qty"), + "uom": arg.get("uom") or arg.get("stock_uom"), + "stock_uom": arg.get("stock_uom"), + "transaction_type": "buying", + "company": self.company, + "currency": self.currency, + "conversion_rate": self.conversion_rate or 1, + "conversion_factor": arg.get("conversion_factor") or 1, + "plc_conversion_rate": 1 + }) + item_doc = frappe.get_doc("Item", arg.get("item_code")) + out = frappe._dict() + get_price_list_rate(args, item_doc, out) + rate = out.price_list_rate if not rate: - frappe.msgprint(_("{0} not found for Item {1}") - .format(self.rm_cost_as_per, arg["item_code"]), alert=True) + if self.rm_cost_as_per == "Price List": + frappe.msgprint(_("Price not found for item {0} and price list {1}") + .format(arg["item_code"], self.buying_price_list), alert=True) + else: + frappe.msgprint(_("{0} not found for item {1}") + .format(self.rm_cost_as_per, arg["item_code"]), alert=True) return flt(rate) - def update_cost(self, update_parent=True, from_child_bom=False): + def update_cost(self, update_parent=True, from_child_bom=False, save=True): if self.docstatus == 2: return existing_bom_cost = self.total_cost for d in self.get("items"): - rate = self.get_rm_rate({"item_code": d.item_code, "bom_no": d.bom_no}) - if rate: - d.rate = rate * flt(d.conversion_factor) / flt(self.conversion_rate) + d.rate = self.get_rm_rate({ + "item_code": d.item_code, + "bom_no": d.bom_no, + "qty": d.qty, + "uom": d.uom, + "stock_uom": d.stock_uom, + "conversion_factor": d.conversion_factor + }) + d.amount = flt(d.rate) * flt(d.qty) if self.docstatus == 1: self.flags.ignore_validate_update_after_submit = True self.calculate_cost() - self.save() + if save: + self.save() self.update_exploded_items() # update parent BOMs diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index 8d4d69b09a9..754540eb6bc 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -1023,6 +1023,71 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "operation", + "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": "Item operation", + "length": 0, + "no_copy": 0, + "options": "Operation", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "allow_alternative_item", + "fieldtype": "Check", + "hidden": 1, + "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 Alternative Item", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "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, @@ -1035,7 +1100,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-12-28 16:38:56.529079", + "modified": "2018-11-23 15:05:55.187136", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 9dd9bebb4da..4ef8b2e8be4 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -441,6 +441,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ weight_per_unit: item.weight_per_unit, weight_uom: item.weight_uom, uom : item.uom, + stock_uom: item.stock_uom, pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '', cost_center: item.cost_center } diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index fa2d2afce6e..0c7d066216a 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -120,7 +120,7 @@ class Gstr1Report(object): and customer in ('{0}')""".format("', '".join([frappe.db.escape(c.name) for c in customers])) if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): - b2c_limit = frappe.db.get_single_value('GSt Settings', 'b2c_limit') + b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') if not b2c_limit: frappe.throw(_("Please set B2C Limit in GST Settings.")) @@ -201,7 +201,7 @@ class Gstr1Report(object): if unidentified_gst_accounts: frappe.msgprint(_("Following accounts might be selected in GST Settings:") + "
" + "
".join(unidentified_gst_accounts), alert=True) - + # Build itemised tax for export invoices where tax table is blank for invoice, items in iteritems(self.invoice_items): if invoice not in self.items_based_on_tax_rate \ diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index bbea9043a35..9db04cd0ab4 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -370,6 +370,8 @@ def get_price_list_rate(args, item_doc, out): meta = frappe.get_meta(args.parenttype or args.doctype) if meta.get_field("currency") or args.get('currency'): + pl_details = get_price_list_currency_and_exchange_rate(args) + args.update(pl_details) validate_price_list(args) if meta.get_field("currency") and args.price_list: validate_conversion_rate(args, meta) @@ -554,9 +556,10 @@ def validate_conversion_rate(args, meta): validate_conversion_rate(args.price_list_currency, args.plc_conversion_rate, meta.get_label("plc_conversion_rate"), args.company) - args.plc_conversion_rate = flt(args.plc_conversion_rate, - get_field_precision(meta.get_field("plc_conversion_rate"), - frappe._dict({"fields": args}))) + if meta.get_field("plc_conversion_rate"): + args.plc_conversion_rate = flt(args.plc_conversion_rate, + get_field_precision(meta.get_field("plc_conversion_rate"), + frappe._dict({"fields": args}))) def get_party_item_code(args, item_doc, out): if args.transaction_type=="selling" and args.customer: @@ -792,7 +795,7 @@ def get_price_list_uom_dependant(price_list): if not result: throw(_("Price List {0} is disabled or does not exist").format(price_list)) - return result.price_not_uom_dependant + return not result.price_not_uom_dependant def get_price_list_currency_and_exchange_rate(args): From 78ccbe24a3c548353f01c4a6deb3c9fe9c69ec39 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 14 Jan 2019 20:03:45 +0530 Subject: [PATCH 49/50] fix(test): BOM raw materials rate from price list as per uom --- erpnext/manufacturing/doctype/bom/test_bom.py | 5 +++-- .../doctype/production_plan/production_plan.py | 7 +++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 5b8acaf81f9..1129025a694 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -76,7 +76,7 @@ class TestBOM(unittest.TestCase): # update cost of all BOMs based on latest valuation rate update_cost() - + # check if new valuation rate updated in all BOMs for d in frappe.db.sql("""select rate from `tabBOM Item` where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""", as_dict=1): @@ -97,6 +97,7 @@ class TestBOM(unittest.TestCase): self.assertEqual(bom.base_total_cost, 486000) def test_bom_cost_multi_uom_multi_currency(self): + frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependant", 1) for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)): frappe.db.sql("delete from `tabItem Price` where price_list='_Test Price List' and item_code=%s", item_code) @@ -105,7 +106,7 @@ class TestBOM(unittest.TestCase): item_price.item_code = item_code item_price.price_list_rate = rate item_price.insert() - + bom = frappe.copy_doc(test_records[2]) bom.set_rate_of_sub_assembly_item_based_on_bom = 0 bom.rm_cost_as_per = "Price List" diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 1ad6e64d415..6c84ef15caf 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -457,7 +457,7 @@ def get_material_request_items(row, sales_order, company, ignore_existing_ordere if row['purchase_uom'] != row['stock_uom']: if not row['conversion_factor']: frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}") - .format(row['purchase_uom'], row['stock_uom'], item)) + .format(row['purchase_uom'], row['stock_uom'], row.item_code)) requested_qty = requested_qty / row['conversion_factor'] if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"): @@ -546,7 +546,7 @@ def get_items_for_material_requests(doc, sales_order=None, company=None): ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or doc.get('ignore_existing_ordered_qty') planned_qty = data.get('required_qty') or data.get('planned_qty') item_details = {} - if data.get("bom"): + if data.get("bom") or data.get("bom_no"): if data.get('required_qty'): bom_no = data.get('bom') include_non_stock_items = 1 @@ -563,7 +563,7 @@ def get_items_for_material_requests(doc, sales_order=None, company=None): if data.get('include_exploded_items') and include_subcontracted_items: # fetch exploded items from BOM item_details = get_exploded_items(item_details, - company, bom_no,include_non_stock_items, planned_qty=planned_qty) + company, bom_no, include_non_stock_items, planned_qty=planned_qty) else: item_details = get_subitems(doc, data, item_details, bom_no, company, include_non_stock_items, include_subcontracted_items, 1, planned_qty=planned_qty) @@ -591,7 +591,6 @@ def get_items_for_material_requests(doc, sales_order=None, company=None): 'conversion_factor' : conversion_factor, } ) - if not sales_order: sales_order = doc.get("sales_order") From 3dd5f55412a5ea1aee51f978366e398abe39ebbb Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 16 Jan 2019 16:20:05 +0530 Subject: [PATCH 50/50] feat(stock-reco): Fetch items in stock reco based on group warehouse --- erpnext/regional/india/utils.py | 2 +- erpnext/stock/doctype/item/test_item.py | 3 +- .../stock_reconciliation.py | 44 ++++++++++++------- .../test_stock_reconciliation.py | 12 ++--- 4 files changed, 38 insertions(+), 23 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 78aeee7dd7a..a1fba072b2e 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 not hasattr(doc, 'gstin'): return - doc.gstin = doc.gstin.upper().strip() + doc.gstin = doc.gstin.upper().strip() if doc.gstin else "" if not doc.gstin or doc.gstin == 'NA': return diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index abdd6765bd3..d02559ebcb1 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -378,7 +378,7 @@ def make_item_variant(): test_records = frappe.get_test_records('Item') -def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None): +def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, opening_stock=None): if not frappe.db.exists("Item", item_code): item = frappe.new_doc("Item") item.item_code = item_code @@ -386,6 +386,7 @@ def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None) item.description = item_code item.item_group = "All Item Groups" item.is_stock_item = is_stock_item or 1 + item.opening_stock = opening_stock or 0 item.valuation_rate = valuation_rate or 0.0 item.append("item_defaults", { "default_warehouse": warehouse or '_Test Warehouse - _TC', diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 6b5bb2825f5..eb60ce56e91 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -271,27 +271,39 @@ class StockReconciliation(StockController): @frappe.whitelist() def get_items(warehouse, posting_date, posting_time, company): - items = frappe.db.sql('''select i.name, i.item_name from `tabItem` i, `tabBin` bin where i.name=bin.item_code - and i.disabled=0 and bin.warehouse=%s''', (warehouse), as_dict=True) + lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) + items = frappe.db.sql(""" + select i.name, i.item_name, bin.warehouse + from tabBin bin, tabItem i + where i.name=bin.item_code and i.disabled=0 + and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse) + """, (lft, rgt)) - items += frappe.db.sql('''select i.name, i.item_name from `tabItem` i, `tabItem Default` id where i.name = id.parent - and i.is_stock_item=1 and i.has_serial_no=0 and i.has_batch_no=0 and i.has_variants=0 and i.disabled=0 - and id.default_warehouse=%s and id.company=%s group by i.name''', (warehouse, company), as_dict=True) + items += frappe.db.sql(""" + select i.name, i.item_name, id.default_warehouse + from tabItem i, `tabItem Default` id + where i.name = id.parent + and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse) + and i.is_stock_item = 1 and i.has_serial_no = 0 and i.has_batch_no = 0 + and i.has_variants = 0 and i.disabled = 0 and id.company=%s + group by i.name + """, (lft, rgt, company)) res = [] - for item in items: - qty, rate = get_stock_balance(item.name, warehouse, posting_date, posting_time, + for d in set(items): + stock_bal = get_stock_balance(d[0], d[2], posting_date, posting_time, with_valuation_rate=True) - res.append({ - "item_code": item.name, - "warehouse": warehouse, - "qty": qty, - "item_name": item.item_name, - "valuation_rate": rate, - "current_qty": qty, - "current_valuation_rate": rate - }) + if frappe.db.get_value("Item", d[0], "disabled") == 0: + res.append({ + "item_code": d[0], + "warehouse": d[2], + "qty": stock_bal[0], + "item_name": d[1], + "valuation_rate": stock_bal[1], + "current_qty": stock_bal[0], + "current_valuation_rate": stock_bal[1] + }) return res diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index bc991b4e153..2dc585b8d63 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -12,7 +12,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_per from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after 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 +from erpnext.stock.doctype.item.test_item import create_item class TestStockReconciliation(unittest.TestCase): def setUp(self): @@ -83,11 +83,13 @@ 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"}) - make_item("_Test Stock Reco Item", {"default_warehouse": "_Test Warehouse Ledger 1 - _TC", - "is_stock_item": 1, "opening_stock": 100, "valuation_rate": 100}) + create_warehouse("_Test Warehouse Ledger 1", + {"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC"}) - items = get_items("_Test Warehouse Group 1 - _TC", nowdate(), nowtime()) + create_item("_Test Stock Reco Item", is_stock_item=1, valuation_rate=100, + warehouse="_Test Warehouse Ledger 1 - _TC", opening_stock=100) + + items = get_items("_Test Warehouse Group 1 - _TC", nowdate(), nowtime(), "_Test Company") self.assertEqual(["_Test Stock Reco Item", "_Test Warehouse Ledger 1 - _TC", 100], [items[0]["item_code"], items[0]["warehouse"], items[0]["qty"]])