From 614559f234411b8d4b558f506eadd9aa51ab9c39 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 13 Dec 2018 16:36:35 +0100 Subject: [PATCH 001/112] 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 12f5cefc12f0582ca944d6ae85b7c827cfe9f0d2 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 25 Dec 2018 16:06:19 +0530 Subject: [PATCH 002/112] Loyalty program issue fix in customer dashboard --- erpnext/accounts/party.py | 19 +++++- erpnext/public/js/utils.js | 61 +++++++++++++------- erpnext/selling/doctype/customer/customer.js | 5 -- erpnext/selling/doctype/customer/customer.py | 10 +--- 4 files changed, 59 insertions(+), 36 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 7a91bd2dc5e..c324292398e 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -457,7 +457,7 @@ def get_timeline_data(doctype, name): return out -def get_dashboard_info(party_type, party): +def get_dashboard_info(party_type, party, loyalty_program=None): current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True) doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice" @@ -479,6 +479,17 @@ def get_dashboard_info(party_type, party): fields=["company", "sum(grand_total) as grand_total", "sum(base_grand_total) as base_grand_total"] ) + if party_type == "Customer": + loyalty_point_details = frappe._dict(frappe.get_all("Loyalty Point Entry", + filters={ + 'customer': party, + 'expiry_date': ('>=', getdate()), + }, + group_by="company", + fields=["company", "sum(loyalty_points) as loyalty_points"], + as_list =1 + )) + company_wise_billing_this_year = frappe._dict() for d in company_wise_grand_total: @@ -506,12 +517,18 @@ def get_dashboard_info(party_type, party): total_unpaid = flt(company_wise_total_unpaid.get(d.company)) + if loyalty_point_details: + loyalty_points = loyalty_point_details.get(d.company) + info = {} info["billing_this_year"] = flt(billing_this_year) if billing_this_year else 0 info["currency"] = party_account_currency info["total_unpaid"] = flt(total_unpaid) if total_unpaid else 0 info["company"] = d.company + if party_type == "Customer" and loyalty_point_details: + info["loyalty_points"] = loyalty_points + if party_type == "Supplier": info["total_unpaid"] = -1 * info["total_unpaid"] diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 0784b027987..d8bf1bdda9e 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -105,33 +105,52 @@ $.extend(erpnext.utils, { if(frm.doc.__onload && frm.doc.__onload.dashboard_info) { var company_wise_info = frm.doc.__onload.dashboard_info; if(company_wise_info.length > 1) { - frm.dashboard.stats_area.removeClass('hidden'); - frm.dashboard.stats_area_row.addClass('flex'); - frm.dashboard.stats_area_row.css('flex-wrap', 'wrap'); company_wise_info.forEach(function(info) { - frm.dashboard.stats_area_row.append( - '
'+ - '
'+info.company+'
'+ - ''+ - ''+ - '
' - ); + erpnext.utils.add_indicator_for_multicompany(frm, info); }); - } - else if (company_wise_info.length === 1) { - frm.dashboard.stats_area.removeClass('hidden'); - frm.dashboard.stats_area_row.append( - '
Annual Billing: ' - +format_currency(company_wise_info[0].billing_this_year, company_wise_info[0].currency)+'
' + - '
Total Unpaid: ' - +format_currency(company_wise_info[0].total_unpaid, company_wise_info[0].currency)+'
' - ); + } else if (company_wise_info.length === 1) { + frm.dashboard.add_indicator(__('Annual Billing: {0}', + [format_currency(company_wise_info[0].billing_this_year, company_wise_info[0].currency)]), 'blue'); + frm.dashboard.add_indicator(__('Total Unpaid: {0}', + [format_currency(company_wise_info[0].total_unpaid, company_wise_info[0].currency)]), + company_wise_info[0].total_unpaid ? 'orange' : 'green'); + + if(company_wise_info[0].loyalty_points) { + frm.dashboard.add_indicator(__('Loyalty Points: {0}', + [company_wise_info[0].loyalty_points]), 'blue'); + } } } }, + add_indicator_for_multicompany: function(frm, info) { + frm.dashboard.stats_area.removeClass('hidden'); + frm.dashboard.stats_area_row.addClass('flex'); + frm.dashboard.stats_area_row.css('flex-wrap', 'wrap'); + + var color = info.total_unpaid ? 'orange' : 'green'; + + var indicator = $('
'+ + '
'+info.company+'
'+ + + ''+ + + ''+ + + + '
').appendTo(frm.dashboard.stats_area_row); + + if(info.loyalty_points){ + $('').appendTo(indicator); + } + + return indicator; + }, + get_party_name: function(party_type) { var dict = {'Customer': 'customer_name', 'Supplier': 'supplier_name', 'Employee': 'employee_name', 'Member': 'member_name'}; diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index d687c85e3a0..3b0e75ff6ca 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -113,11 +113,6 @@ frappe.ui.form.on("Customer", { // indicator erpnext.utils.set_party_dashboard_indicators(frm); - // - if (frm.doc.__onload.dashboard_info.loyalty_point) { - frm.dashboard.add_indicator(__('Loyalty Point: {0}', [frm.doc.__onload.dashboard_info.loyalty_point]), 'blue'); - } - } else { frappe.contacts.clear_address_and_contact(frm); } diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index d285704eb4c..0da82724ac8 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -23,10 +23,7 @@ class Customer(TransactionBase): self.load_dashboard_info() def load_dashboard_info(self): - info = get_dashboard_info(self.doctype, self.name) - loyalty_point_details = self.get_loyalty_points() - if loyalty_point_details and loyalty_point_details.get("loyalty_points"): - info["loyalty_point"] = loyalty_point_details.loyalty_points + info = get_dashboard_info(self.doctype, self.name, self.loyalty_program) self.set_onload('dashboard_info', info) def autoname(self): @@ -36,11 +33,6 @@ class Customer(TransactionBase): else: set_name_by_naming_series(self) - def get_loyalty_points(self): - if self.loyalty_program: - from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_details - return get_loyalty_details(self.name, self.loyalty_program) - def get_customer_name(self): if frappe.db.get_value("Customer", self.customer_name): count = frappe.db.sql("""select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabCustomer From 64238ee91f73206ce09801370ed4fd984e1897c2 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 25 Dec 2018 16:28:39 +0530 Subject: [PATCH 003/112] (Codacy Fix) Fixed indenttion --- erpnext/public/js/utils.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index d8bf1bdda9e..38b42b70c88 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -109,16 +109,16 @@ $.extend(erpnext.utils, { erpnext.utils.add_indicator_for_multicompany(frm, info); }); } else if (company_wise_info.length === 1) { - frm.dashboard.add_indicator(__('Annual Billing: {0}', - [format_currency(company_wise_info[0].billing_this_year, company_wise_info[0].currency)]), 'blue'); - frm.dashboard.add_indicator(__('Total Unpaid: {0}', - [format_currency(company_wise_info[0].total_unpaid, company_wise_info[0].currency)]), - company_wise_info[0].total_unpaid ? 'orange' : 'green'); + frm.dashboard.add_indicator(__('Annual Billing: {0}', + [format_currency(company_wise_info[0].billing_this_year, company_wise_info[0].currency)]), 'blue'); + frm.dashboard.add_indicator(__('Total Unpaid: {0}', + [format_currency(company_wise_info[0].total_unpaid, company_wise_info[0].currency)]), + company_wise_info[0].total_unpaid ? 'orange' : 'green'); - if(company_wise_info[0].loyalty_points) { - frm.dashboard.add_indicator(__('Loyalty Points: {0}', - [company_wise_info[0].loyalty_points]), 'blue'); - } + if(company_wise_info[0].loyalty_points) { + frm.dashboard.add_indicator(__('Loyalty Points: {0}', + [company_wise_info[0].loyalty_points]), 'blue'); + } } } }, From 52a692ee084e9614d430a4e5425f2ad625ee7950 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 26 Dec 2018 00:40:17 +0530 Subject: [PATCH 004/112] [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 a1d015cc2feff475d867bbc9e27cc62cb340dc49 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 26 Dec 2018 11:01:02 +0530 Subject: [PATCH 005/112] Initialized loyalty_point_details --- erpnext/accounts/party.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index c324292398e..05d4d0737c3 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -479,6 +479,8 @@ def get_dashboard_info(party_type, party, loyalty_program=None): fields=["company", "sum(grand_total) as grand_total", "sum(base_grand_total) as base_grand_total"] ) + loyalty_point_details = [] + if party_type == "Customer": loyalty_point_details = frappe._dict(frappe.get_all("Loyalty Point Entry", filters={ From 22e05b24f3e78ebe5d82e338b93d70b78f5ab54e Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 26 Dec 2018 17:16:34 +0530 Subject: [PATCH 006/112] Fix for NoneType comparison with int error --- erpnext/stock/doctype/serial_no/serial_no.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 2039a2699b9..1568a7ae4d2 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -139,7 +139,7 @@ class SerialNo(StockController): order by posting_date desc, posting_time desc, name 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: + if cint(sle.actual_qty) > 0: sle_dict.setdefault("incoming", []).append(sle) else: sle_dict.setdefault("outgoing", []).append(sle) @@ -218,7 +218,7 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no, sle.item_code), SerialNoItemError) - if sle.actual_qty > 0 and has_duplicate_serial_no(sr, sle): + if cint(sle.actual_qty) > 0 and has_duplicate_serial_no(sr, sle): frappe.throw(_("Serial No {0} has already been received").format(serial_no), SerialNoDuplicateError) @@ -228,7 +228,7 @@ def validate_serial_no(sle, item_det): if return_against and return_against != sr.delivery_document_no: frappe.throw(_("Serial no {0} has been already returned").format(sr.name)) - if sle.actual_qty < 0: + if cint(sle.actual_qty) < 0: if sr.warehouse!=sle.warehouse: frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no, sle.warehouse), SerialNoWarehouseError) @@ -279,16 +279,16 @@ def validate_serial_no(sle, item_det): "parent": sales_invoice, "item_code": sle.item_code}, "sales_order") if sales_order and get_reserved_qty_for_so(sales_order, sle.item_code): validate_so_serial_no(sr, sales_order) - elif sle.actual_qty < 0: + elif cint(sle.actual_qty) < 0: # transfer out frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError) - elif sle.actual_qty < 0 or not item_det.serial_no_series: + elif cint(sle.actual_qty) < 0 or not item_det.serial_no_series: frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code), SerialNoRequiredError) elif serial_nos: for serial_no in serial_nos: sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1) - if sr and sle.actual_qty < 0 and sr.warehouse != sle.warehouse: + if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse: frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) @@ -320,7 +320,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): in Manufacture / Repack type Stock Entry """ allow_serial_nos = False - if sle.voucher_type=="Stock Entry" and sle.actual_qty > 0: + if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0: stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) if stock_entry.purpose in ("Repack", "Manufacture"): for d in stock_entry.get("items"): @@ -332,7 +332,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): return allow_serial_nos def update_serial_nos(sle, item_det): - if sle.is_cancelled == "No" and not sle.serial_no and sle.actual_qty > 0 \ + if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \ and item_det.has_serial_no == 1 and item_det.serial_no_series: serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) frappe.db.set(sle, "serial_no", serial_nos) From ffb411d9e2a0162d6cf168de504bbd990ec0e0f7 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 27 Dec 2018 14:12:05 +0530 Subject: [PATCH 007/112] Validation while updating items in sales order --- erpnext/controllers/accounts_controller.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 86ceb2e4abf..56ae25e92b3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1073,6 +1073,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name): data = json.loads(trans_items) for d in data: child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) + + if flt(d.get("qty")) < child_item.delivered_qty: + frappe.throw(_("Cannot set quantity less than delivered quantity")) + child_item.qty = flt(d.get("qty")) if child_item.billed_amt > (flt(d.get("rate")) * flt(d.get("qty"))): From 77cde832d6a180b167935f6d536fd5c14b3ac591 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 27 Dec 2018 15:56:51 +0530 Subject: [PATCH 008/112] Added validation for update items in purchase order as well --- erpnext/controllers/accounts_controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 56ae25e92b3..fe6b39dd053 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1074,9 +1074,12 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name): for d in data: child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) - if flt(d.get("qty")) < child_item.delivered_qty: + if parent_doctype == "Sales Order" and flt(d.get("qty")) < child_item.delivered_qty: frappe.throw(_("Cannot set quantity less than delivered quantity")) + if parent_doctype == "Purchase Order" and flt(d.get("qty")) < child_item.received_qty: + frappe.throw(_("Cannot set quantity less than received quantity")) + child_item.qty = flt(d.get("qty")) if child_item.billed_amt > (flt(d.get("rate")) * flt(d.get("qty"))): From af75d9a384d1ef53b8c89756cb2e1c8d450f2c55 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 28 Dec 2018 14:47:51 +0530 Subject: [PATCH 009/112] 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 010/112] 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 0c8411d085b9f0f6e3db645d24ae90503631d313 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 28 Dec 2018 16:53:00 +0530 Subject: [PATCH 011/112] test cases sucess --- erpnext/controllers/stock_controller.py | 20 +++++++++++-------- .../setup/doctype/item_group/item_group.py | 2 +- .../test_quality_inspection.py | 13 +++++++++--- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 63e89ab6e35..5ea3dee2162 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -14,6 +14,7 @@ from erpnext.stock import get_warehouse_account_map class QualityInspectionRequiredError(frappe.ValidationError): pass class QualityInspectionRejectedError(frappe.ValidationError): pass +class QualityInspectionNotSubmittedError(frappe.ValidationError): pass class StockController(AccountsController): def validate(self): @@ -338,18 +339,21 @@ class StockController(AccountsController): qa_required = True elif self.doctype == "Stock Entry" and not d.quality_inspection and d.t_warehouse: qa_required = True + if self.docstatus == 1 and d.quality_inspection: + qa_doc = frappe.get_doc("Quality Inspection", d.quality_inspection) + if qa_doc.docstatus == 0: + link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection) + frappe.msgprint(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx)) + raise QualityInspectionNotSubmittedError - if qa_required: + qa_failed = any([r.status=="Rejected" for r in qa_doc.readings]) + if qa_failed: + frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}") + .format(d.idx, d.item_code), QualityInspectionRejectedError) + elif qa_required : frappe.msgprint(_("Quality Inspection required for Item {0}").format(d.item_code)) if self.docstatus==1: raise QualityInspectionRequiredError - elif self.docstatus == 1: - if d.quality_inspection: - qa_doc = frappe.get_doc("Quality Inspection", d.quality_inspection) - qa_failed = any([r.status=="Rejected" for r in qa_doc.readings]) - if qa_failed: - frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}") - .format(d.idx, d.item_code), QualityInspectionRejectedError) def update_blanket_order(self): diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 6bc90361954..2c185019eb7 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -147,7 +147,7 @@ def adjust_qty_for_expired_items(data): if item.get('has_batch_no') and item.get('website_warehouse'): stock_qty_dict = get_qty_in_stock( item.get('name'), 'website_warehouse', item.get('website_warehouse')) - qty = stock_qty_dict.stock_qty[0][0] + qty = stock_qty_dict.stock_qty[0][0] if stock_qty_dict.stock_qty else 0 item['in_stock'] = 1 if qty else 0 adjusted_data.append(item) diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index 60cc9a09723..0f0b4016e26 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -6,7 +6,7 @@ import unittest from frappe.utils import nowdate from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note -from erpnext.controllers.stock_controller import QualityInspectionRejectedError, QualityInspectionRequiredError +from erpnext.controllers.stock_controller import QualityInspectionRejectedError, QualityInspectionRequiredError, QualityInspectionNotSubmittedError # test_records = frappe.get_test_records('Quality Inspection') @@ -19,7 +19,7 @@ class TestQualityInspection(unittest.TestCase): dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) self.assertRaises(QualityInspectionRequiredError, dn.submit) - qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected") + qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, status="Rejected", submit=True) dn.reload() self.assertRaises(QualityInspectionRejectedError, dn.submit) @@ -27,6 +27,12 @@ class TestQualityInspection(unittest.TestCase): dn.reload() dn.submit() + def test_qa_not_submit(self): + dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True) + qa = create_quality_inspection(reference_type="Delivery Note", reference_name=dn.name, submit = False) + dn.items[0].quality_inspection = qa.name + self.assertRaises(QualityInspectionNotSubmittedError, dn.submit) + def create_quality_inspection(**args): args = frappe._dict(args) qa = frappe.new_doc("Quality Inspection") @@ -42,6 +48,7 @@ def create_quality_inspection(**args): "status": args.status }) qa.save() - qa.submit() + if args.submit: + qa.submit() return qa From 3637e14e9fa5bb0c8edf71ce65e0b22201befa1a Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 28 Dec 2018 17:11:52 +0530 Subject: [PATCH 012/112] 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 013/112] 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 0885485a61d760f096bf25692722d6d4555de59f Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Tue, 1 Jan 2019 15:00:16 +0530 Subject: [PATCH 014/112] changes --- erpnext/controllers/stock_controller.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 5ea3dee2162..0b19b7aaf9a 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -343,8 +343,7 @@ class StockController(AccountsController): qa_doc = frappe.get_doc("Quality Inspection", d.quality_inspection) if qa_doc.docstatus == 0: link = frappe.utils.get_link_to_form('Quality Inspection', d.quality_inspection) - frappe.msgprint(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx)) - raise QualityInspectionNotSubmittedError + frappe.throw(_("Quality Inspection: {0} is not submitted for the item: {1} in row {2}").format(link, d.item_code, d.idx), QualityInspectionNotSubmittedError) qa_failed = any([r.status=="Rejected" for r in qa_doc.readings]) if qa_failed: @@ -432,4 +431,4 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) - return gl_entries \ No newline at end of file + return gl_entries From fac6b5962765515309e98b65c6baa08059632500 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 1 Jan 2019 16:33:22 +0500 Subject: [PATCH 015/112] 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 318dc422bccf53211f5030a057a0150d44d2907a Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 1 Jan 2019 18:30:33 +0530 Subject: [PATCH 016/112] Updated test Cases for update_child_rate_qty --- erpnext/selling/doctype/sales_order/test_sales_order.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 65e91bc2476..0eb19e3417c 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -286,6 +286,9 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(get_reserved_qty(), existing_reserved_qty + 3) + trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 2, 'docname': so.items[0].name}]) + self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) + def test_warehouse_user(self): frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com") frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com") @@ -760,7 +763,7 @@ def make_sales_order(**args): }) so.delivery_date = add_days(so.transaction_date, 10) - + if not args.do_not_save: so.insert() if not args.do_not_submit: From 8300f5ede7abcd6c784ac6b9fbbff4a870eca866 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 1 Jan 2019 20:28:49 +0530 Subject: [PATCH 017/112] fix: Codacy issues and added test case --- .../doctype/loyalty_program/test_loyalty_program.py | 8 ++++++++ erpnext/public/js/utils.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py index bf7a0bcfa42..8a1d6a27bea 100644 --- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py @@ -7,6 +7,7 @@ import frappe import unittest from frappe.utils import today, cint, flt, getdate from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points +from erpnext.accounts.party import get_dashboard_info class TestLoyaltyProgram(unittest.TestCase): @classmethod @@ -144,6 +145,13 @@ class TestLoyaltyProgram(unittest.TestCase): frappe.get_doc('Sales Invoice', d.name).cancel() frappe.delete_doc('Sales Invoice', d.name) + def test_loyalty_points_for_dashboard(self): + doc = frappe.get_doc('Customer', 'Test Loyalty Customer') + company_wise_info = get_dashboard_info("Customer", doc.name, doc.loyalty_program) + + for d in company_wise_info: + self.assertTrue(d.loyalty_points) + def get_points_earned(self): def get_returned_amount(): returned_amount = frappe.db.sql(""" diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 902f83ff075..64085a83cdf 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -113,7 +113,7 @@ $.extend(erpnext.utils, { [format_currency(company_wise_info[0].billing_this_year, company_wise_info[0].currency)]), 'blue'); frm.dashboard.add_indicator(__('Total Unpaid: {0}', [format_currency(company_wise_info[0].total_unpaid, company_wise_info[0].currency)]), - company_wise_info[0].total_unpaid ? 'orange' : 'green'); + company_wise_info[0].total_unpaid ? 'orange' : 'green'); if(company_wise_info[0].loyalty_points) { frm.dashboard.add_indicator(__('Loyalty Points: {0}', From c349ae26b99dc8e97efc5e8cc8766f34dff25992 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 2 Jan 2019 09:31:11 +0530 Subject: [PATCH 018/112] Add patch to rename additional salary component - Should fix https://pastebin.com/xsNHPAh6 (happens when clicking links option in menu of Employee master) --- erpnext/patches.txt | 1 + ...me_additional_salary_component_additional_salary.py | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9b8a69d2b21..be8201a07de 100755 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -580,3 +580,4 @@ 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.v11_0.rename_additional_salary_component_additional_salary \ No newline at end of file diff --git a/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py b/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py new file mode 100644 index 00000000000..8fa876dd743 --- /dev/null +++ b/erpnext/patches/v11_0/rename_additional_salary_component_additional_salary.py @@ -0,0 +1,10 @@ +import frappe + +# this patch should have been included with this PR https://github.com/frappe/erpnext/pull/14302 + +def execute(): + if frappe.db.table_exists("Additional Salary Component"): + if not frappe.db.table_exists("Additional Salary"): + frappe.rename_doc("DocType", "Additional Salary Component", "Additional Salary") + + frappe.delete_doc('DocType', "Additional Salary Component") From 9c0c63325914935820093981d11b0c6b65196c6f Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 2 Jan 2019 11:31:55 +0530 Subject: [PATCH 019/112] fix: Added validation for stock reconciliation in stock ledger entry --- .../stock/doctype/stock_ledger_entry/stock_ledger_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3c60896d0a0..c18e61f9751 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -31,7 +31,7 @@ class StockLedgerEntry(Document): self.check_stock_frozen_date() self.actual_amt_check() - if not self.get("via_landed_cost_voucher"): + if not self.get("via_landed_cost_voucher") and self.voucher_type != 'Stock Reconciliation': from erpnext.stock.doctype.serial_no.serial_no import process_serial_no process_serial_no(self) @@ -132,4 +132,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 5865fcca3cf9096ee4099f0f6296a919593adc89 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 2 Jan 2019 14:36:07 +0530 Subject: [PATCH 020/112] fix: expense head of asset items in purchase invoice --- .../doctype/purchase_invoice/purchase_invoice.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index bfdf451f440..d28dc936bb0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -206,6 +206,10 @@ class PurchaseInvoice(BuyingController): stock_not_billed_account = self.get_company_default("stock_received_but_not_billed") stock_items = self.get_stock_items() + asset_items = [d.is_fixed_asset for d in self.items if d.is_fixed_asset] + if len(asset_items) > 0: + asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed") + if self.update_stock: self.validate_item_code() self.validate_warehouse() @@ -226,7 +230,8 @@ class PurchaseInvoice(BuyingController): item.expense_account = warehouse_account[item.warehouse]["account"] else: item.expense_account = stock_not_billed_account - + elif item.is_fixed_asset and d.pr_detail: + item.expense_account = asset_received_but_not_billed elif not item.expense_account and for_validate: throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name)) @@ -360,7 +365,10 @@ class PurchaseInvoice(BuyingController): def get_gl_entries(self, warehouse_account=None): self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company) - self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed") + if self.auto_accounting_for_stock: + self.stock_received_but_not_billed = self.get_company_default("stock_received_but_not_billed") + else: + self.stock_received_but_not_billed = None self.expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") self.negative_expense_to_be_booked = 0.0 gl_entries = [] From 772e2c4e20330097dc271be1146e192f7520eff2 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 2 Jan 2019 15:59:58 +0530 Subject: [PATCH 021/112] test cases --- erpnext/assets/doctype/asset/test_asset.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index d855873d5ad..c5a7c3d0c3a 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -9,6 +9,7 @@ from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, ad from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset from erpnext.assets.doctype.asset.asset import make_sales_invoice, make_purchase_invoice from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice class TestAsset(unittest.TestCase): def setUp(self): @@ -494,6 +495,15 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) + def test_expence_head(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=2, rate=200000.0, location="Test Location") + + doc = make_invoice(pr.name) + + self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) + + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() From e194a655df8d856fccfb0a37b7e1b3969015f3c3 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 2 Jan 2019 16:09:34 +0500 Subject: [PATCH 022/112] 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 023/112] 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 024/112] 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 1ff1fc47251012753b4b72248e83e29a3bef9435 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 2 Jan 2019 17:56:08 +0530 Subject: [PATCH 025/112] [Fix] Negative amount showing in the bank clearance summary --- .../bank_clearance_summary.py | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py index 1ec0abc3bf1..13424dbcb56 100644 --- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py +++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py @@ -35,16 +35,22 @@ def get_conditions(filters): def get_entries(filters): conditions = get_conditions(filters) - journal_entries = frappe.db.sql("""select "Journal Entry", jv.name, jv.posting_date, - jv.cheque_no, jv.clearance_date, jvd.against_account, (jvd.debit - jvd.credit) - from `tabJournal Entry Account` jvd, `tabJournal Entry` jv - where jvd.parent = jv.name and jv.docstatus=1 and jvd.account = %(account)s {0} - order by posting_date DESC, jv.name DESC""".format(conditions), filters, as_list=1) + journal_entries = frappe.db.sql("""SELECT + "Journal Entry", jv.name, jv.posting_date, jv.cheque_no, jv.clearance_date, jvd.against_account, + if((jvd.debit - jvd.credit) < 0, (jvd.debit - jvd.credit) * -1, (jvd.debit - jvd.credit)) + FROM + `tabJournal Entry Account` jvd, `tabJournal Entry` jv + WHERE + jvd.parent = jv.name and jv.docstatus=1 and jvd.account = %(account)s {0} + order by posting_date DESC, jv.name DESC""".format(conditions), filters, as_list=1) - payment_entries = frappe.db.sql("""select "Payment Entry", name, posting_date, - reference_no, clearance_date, party, if(paid_from=%(account)s, paid_amount, received_amount) - from `tabPayment Entry` - where docstatus=1 and (paid_from = %(account)s or paid_to = %(account)s) {0} - order by posting_date DESC, name DESC""".format(conditions), filters, as_list=1) + payment_entries = frappe.db.sql("""SELECT + "Payment Entry", name, posting_date, reference_no, clearance_date, party, + if(paid_from=%(account)s, paid_amount, received_amount) + FROM + `tabPayment Entry` + WHERE + docstatus=1 and (paid_from = %(account)s or paid_to = %(account)s) {0} + order by posting_date DESC, name DESC""".format(conditions), filters, as_list=1) return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate())) \ No newline at end of file From 53c040f83874a8c50e82e942197995829bed5815 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 3 Jan 2019 12:50:18 +0530 Subject: [PATCH 026/112] 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 027/112] 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 32f054b901d7636fe52c748a9ab85b8e4a0855cc Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 3 Jan 2019 22:57:18 +0530 Subject: [PATCH 028/112] Added customer group column in item-wise-sales-history report --- .../item_wise_sales_history/item_wise_sales_history.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.json b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.json index 7f9ef884c6d..88e6f273959 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.json +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.json @@ -1,18 +1,18 @@ { "add_total_row": 1, - "apply_user_permissions": 1, "creation": "2013-05-23 17:42:24", "disabled": 0, "docstatus": 0, "doctype": "Report", "idx": 3, "is_standard": "Yes", - "modified": "2018-06-18 16:42:05.860309", + "modified": "2019-01-03 22:52:41.519890", "modified_by": "Administrator", "module": "Selling", "name": "Item-wise Sales History", "owner": "Administrator", - "query": "select\n so_item.item_code as \"Item Code:Link/Item:120\",\n\tso_item.item_name as \"Item Name::120\",\n so_item.item_group as \"Item Group:Link/Item Group:120\",\n\tso_item.description as \"Description::150\",\n\tso_item.qty as \"Qty:Data:100\",\n\tso_item.uom as \"UOM:Link/UOM:80\",\n\tso_item.base_rate as \"Rate:Currency:120\",\n\tso_item.base_amount as \"Amount:Currency:120\",\n\tso.name as \"Sales Order:Link/Sales Order:120\",\n\tso.transaction_date as \"Transaction Date:Date:140\",\n\tso.customer as \"Customer:Link/Customer:130\",\n cu.customer_name as \"Customer Name::150\",\n\tso.territory as \"Territory:Link/Territory:130\",\n so.project as \"Project:Link/Project:130\",\n\tifnull(so_item.delivered_qty, 0) as \"Delivered Qty:Float:120\",\n\tifnull(so_item.billed_amt, 0) as \"Billed Amount:Currency:120\",\n\tso.company as \"Company:Link/Company:\"\nfrom\n\t`tabSales Order` so, `tabSales Order Item` so_item, `tabCustomer` cu\nwhere\n\tso.name = so_item.parent and so.customer=cu.name\n\tand so.docstatus = 1\norder by so.name desc", + "prepared_report": 0, + "query": "select\n so_item.item_code as \"Item Code:Link/Item:120\",\n\tso_item.item_name as \"Item Name::120\",\n so_item.item_group as \"Item Group:Link/Item Group:120\",\n\tso_item.description as \"Description::150\",\n\tso_item.qty as \"Qty:Data:100\",\n\tso_item.uom as \"UOM:Link/UOM:80\",\n\tso_item.base_rate as \"Rate:Currency:120\",\n\tso_item.base_amount as \"Amount:Currency:120\",\n\tso.name as \"Sales Order:Link/Sales Order:120\",\n\tso.transaction_date as \"Transaction Date:Date:140\",\n\tso.customer as \"Customer:Link/Customer:130\",\n cu.customer_name as \"Customer Name::150\",\n\tcu.customer_group as \"Customer Group:Link/Customer Group:130\",\n\tso.territory as \"Territory:Link/Territory:130\",\n \tso.project as \"Project:Link/Project:130\",\n\tifnull(so_item.delivered_qty, 0) as \"Delivered Qty:Float:120\",\n\tifnull(so_item.billed_amt, 0) as \"Billed Amount:Currency:120\",\n\tso.company as \"Company:Link/Company:\"\nfrom\n\t`tabSales Order` so, `tabSales Order Item` so_item, `tabCustomer` cu\nwhere\n\tso.name = so_item.parent and so.customer=cu.name\n\tand so.docstatus = 1\norder by so.name desc", "ref_doctype": "Sales Order", "report_name": "Item-wise Sales History", "report_type": "Query Report", From 547229fef12221fc4e1f9c86eafb0397d361feb3 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 3 Jan 2019 23:20:38 +0530 Subject: [PATCH 029/112] 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 030/112] 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 9de81cda7755965ed45603d09fd4818bb7758e30 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 4 Jan 2019 10:54:24 +0530 Subject: [PATCH 031/112] Added Customer contact column in accounts Receivable --- .../accounts_receivable/accounts_receivable.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 121d5b02133..22d4d94cd0c 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -32,6 +32,15 @@ class ReceivablePayableReport(object): columns += [_(args.get("party_type")) + ":Link/" + args.get("party_type") + ":200"] + if args.get("party_type") == 'Customer': + columns.append({ + "label": _("Customer Contact"), + "fieldtype": "Link", + "fieldname": "contact", + "options":"Contact", + "width": 100 + }) + if party_naming_by == "Naming Series": columns += [args.get("party_type") + " Name::110"] @@ -282,6 +291,9 @@ class ReceivablePayableReport(object): if party_naming_by == "Naming Series": row += [self.get_party_name(gle.party_type, gle.party)] + if args.get("party_type") == 'Customer': + row += [self.get_customer_contact(gle.party_type, gle.party)] + # get due date if not due_date: due_date = self.voucher_details.get(gle.voucher_no, {}).get("due_date", "") @@ -407,6 +419,9 @@ class ReceivablePayableReport(object): def get_party_name(self, party_type, party_name): return self.get_party_map(party_type).get(party_name, {}).get("customer_name" if party_type == "Customer" else "supplier_name") or "" + def get_customer_contact(self, party_type, party_name): + return self.get_party_map(party_type).get(party_name, {}).get("customer_primary_contact") + def get_territory(self, party_name): return self.get_party_map("Customer").get(party_name, {}).get("territory") or "" @@ -419,7 +434,7 @@ class ReceivablePayableReport(object): def get_party_map(self, party_type): if not hasattr(self, "party_map"): if party_type == "Customer": - select_fields = "name, customer_name, territory, customer_group" + select_fields = "name, customer_name, territory, customer_group, customer_primary_contact" elif party_type == "Supplier": select_fields = "name, supplier_name, supplier_group" From 68aedfd1764a0925ae8761fe8b782f2531d1e936 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 4 Jan 2019 11:00:07 +0530 Subject: [PATCH 032/112] 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 add6bf35a323b978ccfa45cf980e937117f75734 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 4 Jan 2019 11:36:30 +0530 Subject: [PATCH 033/112] Fix: Employee Onboarding/Seperation task Assignment --- erpnext/hr/utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index f35eb5919e9..50580064317 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -54,10 +54,17 @@ class EmployeeBoardingController(Document): where parenttype='User' and role=%s''', activity.role) users = users + user_list + if "Administrator" in users: + users.remove("Administrator") + + # assign the task the users if users: + print(users) self.assign_task_to_users(task, set(users)) + users = [] + def assign_task_to_users(self, task, users): for user in users: args = { From bb6a7eb9d3b9e901c407b62ced4e1ff43973c43d Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Fri, 4 Jan 2019 12:32:51 +0530 Subject: [PATCH 034/112] refractor --- erpnext/hr/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 50580064317..eb550375667 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -60,7 +60,6 @@ class EmployeeBoardingController(Document): # assign the task the users if users: - print(users) self.assign_task_to_users(task, set(users)) users = [] From eabf260706319d20ce3d04f9c45068d4726ecb7d Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 4 Jan 2019 12:34:28 +0530 Subject: [PATCH 035/112] fix :Test cases for accounts receivable report --- .../accounts_receivable/test_accounts_receivable.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 34e6c83e016..f911eaa5c14 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -21,24 +21,24 @@ class TestAccountsReceivable(unittest.TestCase): expected_data = [[100,30], [100,50], [100,20]] - self.assertEqual(expected_data[0], report[1][0][6:8]) - self.assertEqual(expected_data[1], report[1][1][6:8]) - self.assertEqual(expected_data[2], report[1][2][6:8]) + self.assertEqual(expected_data[0], report[1][0][7:9]) + self.assertEqual(expected_data[1], report[1][1][7:9]) + self.assertEqual(expected_data[2], report[1][2][7:9]) make_payment(name) report = execute(filters) expected_data_after_payment = [[100,50], [100,20]] - self.assertEqual(expected_data_after_payment[0], report[1][0][6:8]) - self.assertEqual(expected_data_after_payment[1], report[1][1][6:8]) + self.assertEqual(expected_data_after_payment[0], report[1][0][7:9]) + self.assertEqual(expected_data_after_payment[1], report[1][1][7:9]) make_credit_note(name) report = execute(filters) expected_data_after_credit_note = [[100,100,30,100,-30]] - self.assertEqual(expected_data_after_credit_note[0], report[1][0][6:11]) + self.assertEqual(expected_data_after_credit_note[0], report[1][0][7:12]) def make_sales_invoice(): From 75ab0426326d2b6ba8c98115f6ce168551d39b7c Mon Sep 17 00:00:00 2001 From: Himanshu Date: Fri, 4 Jan 2019 17:13:43 +0530 Subject: [PATCH 036/112] 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 037/112] (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 038/112] (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 039/112] 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 5efc7973ea5177e95e05084b510d42dd0549532d Mon Sep 17 00:00:00 2001 From: finbyz Date: Sat, 5 Jan 2019 11:12:11 +0530 Subject: [PATCH 040/112] Minor Fix for Rounded Total --- erpnext/controllers/accounts_controller.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 86ceb2e4abf..140694893c8 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -604,13 +604,14 @@ class AccountsController(TransactionBase): advance.account_currency) if advance.account_currency == self.currency: - order_total = self.grand_total - formatted_order_total = fmt_money(order_total, precision=self.precision("grand_total"), - currency=advance.account_currency) + order_total = self.get("rounded_total") or self.grand_total + precision = "rounded_total" if self.get("rounded_total") else "grand_total" else: - order_total = self.base_grand_total - formatted_order_total = fmt_money(order_total, precision=self.precision("base_grand_total"), - currency=advance.account_currency) + order_total = self.get("base_rounded_total") or self.base_grand_total + precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total" + + formatted_order_total = fmt_money(order_total, precision=self.precision(precision), + currency=advance.account_currency) if self.currency == self.company_currency and advance_paid > order_total: frappe.throw(_("Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})") From 23a1b9895791edb3956e42274521417c6805fd3a Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Jan 2019 13:10:18 +0530 Subject: [PATCH 041/112] fix: remove unnecessary code --- erpnext/hr/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index eb550375667..2dc0ab0216b 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -62,8 +62,6 @@ class EmployeeBoardingController(Document): if users: self.assign_task_to_users(task, set(users)) - users = [] - def assign_task_to_users(self, task, users): for user in users: args = { From 3c74266763f2c7b1e85404a306f81fd3a798f746 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Jan 2019 13:13:16 +0530 Subject: [PATCH 042/112] fix: PEP8 recommended whitspace --- erpnext/hr/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 2dc0ab0216b..02262012f1c 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -57,7 +57,6 @@ class EmployeeBoardingController(Document): if "Administrator" in users: users.remove("Administrator") - # assign the task the users if users: self.assign_task_to_users(task, set(users)) From d40743a570948f5d1cd113755bdede4cb51e8d1a Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Jan 2019 13:38:43 +0530 Subject: [PATCH 043/112] 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 f733a390898e884bd08605aade8575d6a3047e11 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Jan 2019 14:24:01 +0530 Subject: [PATCH 044/112] fix: remove trailing whitespace --- erpnext/controllers/accounts_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 140694893c8..83ba438ad68 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -609,7 +609,7 @@ class AccountsController(TransactionBase): else: order_total = self.get("base_rounded_total") or self.base_grand_total precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total" - + formatted_order_total = fmt_money(order_total, precision=self.precision(precision), currency=advance.account_currency) From 7c45f45ebcdf5acd6f847a4b6d74ebd37da8f2ef Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 8 Jan 2019 08:34:28 +0530 Subject: [PATCH 045/112] fix: Divide by zero exception fix in item wise purchase register (#16247) * Do not prepare row if stock quantity is not available * Update item_wise_purchase_register.py --- .../item_wise_purchase_register.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 05cde5a25fe..380b208548c 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -34,6 +34,9 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum data = [] for d in item_list: + if not d.stock_qty: + continue + purchase_receipt = None if d.purchase_receipt: purchase_receipt = d.purchase_receipt @@ -105,10 +108,10 @@ def get_conditions(filters): def get_items(filters, additional_query_columns): conditions = get_conditions(filters) match_conditions = frappe.build_match_conditions("Purchase Invoice") - + if match_conditions: match_conditions = " and {0} ".format(match_conditions) - + if additional_query_columns: additional_query_columns = ', ' + ', '.join(additional_query_columns) @@ -147,4 +150,4 @@ def get_purchase_receipts_against_purchase_order(item_list): for pr in purchase_receipts: po_pr_map.setdefault(pr.po_detail, []).append(pr.parent) - return po_pr_map \ No newline at end of file + return po_pr_map From ffdadbf97f7c4db6d0c7dfd1457d892badd6b06e Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 8 Jan 2019 16:21:25 +0530 Subject: [PATCH 046/112] fix: fallback to stock uom if uom is not defined (#16329) * [minor] Code cleanup * fix: remove 'or' condition, won't execute --- 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 01ee9db1ca1..692fe5d0fbe 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -281,7 +281,7 @@ def get_basic_details(args, item): out.conversion_factor = 1.0 else: out.conversion_factor = args.conversion_factor or \ - get_conversion_factor(item.item_code, args.uom).get("conversion_factor") or 1.0 + get_conversion_factor(item.item_code, args.uom).get("conversion_factor") args.conversion_factor = out.conversion_factor out.stock_qty = out.qty * out.conversion_factor @@ -651,7 +651,7 @@ def get_conversion_factor(item_code, uom): if not conversion_factor: stock_uom = frappe.db.get_value("Item", item_code, "stock_uom") conversion_factor = get_uom_conv_factor(uom, stock_uom) - return {"conversion_factor": conversion_factor} + return {"conversion_factor": conversion_factor or 1.0} @frappe.whitelist() def get_projected_qty(item_code, warehouse): From 1836285c01bdf83022c5b4fde54733cf8c7782b0 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 8 Jan 2019 16:52:38 +0600 Subject: [PATCH 047/112] bumped to version 11.0.3-beta.34 --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 3ab6752d8cd..bf7e32ab78e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" develop_version = '12.x.x-develop' -staging_version = '11.0.3-beta.33' +staging_version = '11.0.3-beta.34' error_report_email = "support@erpnext.com" From 0f3ccba741ca1f0dfbd73f542402df83c8740f9c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 8 Jan 2019 20:23:01 +0530 Subject: [PATCH 048/112] Update test_asset.py --- erpnext/assets/doctype/asset/test_asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index c5a7c3d0c3a..65629d28181 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -495,7 +495,7 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) - def test_expence_head(self): + def test_expense_head(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=2, rate=200000.0, location="Test Location") From 1516e296095a90d61a227fde20d97f50c2f0fa80 Mon Sep 17 00:00:00 2001 From: hiousi Date: Tue, 8 Jan 2019 18:34:08 +0100 Subject: [PATCH 049/112] [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 050/112] 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 051/112] 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 d29ee9730765473afddeebc10c81e302d90dd0c8 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 9 Jan 2019 17:09:11 +0530 Subject: [PATCH 052/112] Addition of GST fields in Sales Order,Purchase Order,Purchase Receipt --- erpnext/patches.txt | 2 +- erpnext/regional/india/setup.py | 15 +- .../doctype/sales_order/sales_order.js | 14 ++ .../doctype/sales_order/sales_order.json | 128 +++++++++++++----- 4 files changed, 124 insertions(+), 35 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9b8a69d2b21..43209e3e50f 100755 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -572,7 +572,7 @@ execute:frappe.delete_doc_if_exists("Page", "sales-analytics") execute:frappe.delete_doc_if_exists("Page", "purchase-analytics") execute:frappe.delete_doc_if_exists("Page", "stock-analytics") execute:frappe.delete_doc_if_exists("Page", "production-analytics") -erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 +erpnext.patches.v11_0.ewaybill_fields_gst_india #2018-11-13 #2019-01-09 erpnext.patches.v11_0.drop_column_max_days_allowed erpnext.patches.v11_0.change_healthcare_desktop_icons erpnext.patches.v10_0.update_user_image_in_employee diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 7813cc0fd6f..ec4da0db651 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -126,6 +126,9 @@ def make_custom_fields(update=True): dict(fieldname='place_of_supply', label='Place of Supply', fieldtype='Data', insert_after='shipping_address', print_hide=1, read_only=0), + ] + + purchase_invoice_itc_fields = [ dict(fieldname='eligibility_for_itc', label='Eligibility For ITC', fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1, options='input\ninput service\ncapital goods\nineligible', default="ineligible"), @@ -152,6 +155,9 @@ def make_custom_fields(update=True): dict(fieldname='company_gstin', label='Company GSTIN', fieldtype='Data', insert_after='company_address', fetch_from='company_address.gstin', print_hide=1), + ] + + sales_invoice_shipping_fields = [ dict(fieldname='port_code', label='Port Code', fieldtype='Data', insert_after='reason_for_issuing_document', print_hide=1, depends_on="eval:doc.invoice_type=='Export' "), @@ -214,9 +220,12 @@ def make_custom_fields(update=True): dict(fieldname='gst_state_number', label='GST State Number', fieldtype='Data', insert_after='gst_state', read_only=1), ], - 'Purchase Invoice': invoice_gst_fields + purchase_invoice_gst_fields, - 'Sales Invoice': invoice_gst_fields + sales_invoice_gst_fields, - 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields, + 'Purchase Invoice': invoice_gst_fields + purchase_invoice_gst_fields + purchase_invoice_itc_fields, + 'Purchase Order': purchase_invoice_gst_fields, + 'Purchase Receipt': purchase_invoice_gst_fields, + 'Sales Invoice': invoice_gst_fields + sales_invoice_gst_fields + sales_invoice_shipping_fields, + 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields, + 'Sales Order': sales_invoice_gst_fields, 'Sales Taxes and Charges Template': inter_state_gst_field, 'Purchase Taxes and Charges Template': inter_state_gst_field, 'Item': [ diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 54d7654c2dd..96d2fc8f831 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -17,6 +17,20 @@ frappe.ui.form.on("Sales Order", { // formatter for material request item frm.set_indicator_formatter('item_code', function(doc) { return (doc.stock_qty<=doc.delivered_qty) ? "green" : "orange" }) + + frm.set_query('company_address', function(doc) { + if(!doc.company) { + frappe.throw(__('Please set Company')); + } + + return { + query: 'frappe.contacts.doctype.address.address.address_query', + filters: { + link_doctype: 'Company', + link_name: doc.company + } + }; + }) }, refresh: function(frm) { if(frm.doc.docstatus == 1 && frm.doc.status == 'To Deliver and Bill') { diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 0f3b6776ca1..b916505a0f0 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -747,6 +747,71 @@ "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_address_display", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "", + "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": "company_address", + "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": "Company Address", + "length": 0, + "no_copy": 0, + "options": "Address", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1128,6 +1193,7 @@ "label": "Price List Exchange Rate", "length": 0, "no_copy": 0, + "options": "", "permlevel": 0, "precision": "9", "print_hide": 1, @@ -1270,37 +1336,37 @@ "unique": 0 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "scan_barcode", - "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": "Scan Barcode", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "scan_barcode", + "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": "Scan Barcode", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { "allow_bulk_edit": 1, "allow_in_quick_entry": 0, @@ -4015,7 +4081,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-11-12 20:00:35.272747", + "modified": "2019-01-09 16:51:47.917329", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", From 2825b929c1bb0c84c067e1dcff0a31feffbf7146 Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Wed, 9 Jan 2019 19:15:10 +0530 Subject: [PATCH 053/112] 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 054/112] 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 055/112] 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 056/112] 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 057/112] 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 7facc34851e1874cff436494b42645762b080f16 Mon Sep 17 00:00:00 2001 From: NahuelOperto Date: Fri, 11 Jan 2019 10:46:38 -0300 Subject: [PATCH 058/112] fix: removed filter from method validate --- erpnext/selling/doctype/sales_order/sales_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index b2c6ccc7f16..a30f2fac3dc 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -141,7 +141,7 @@ class SalesOrder(SellingController): super(SalesOrder, self).validate_with_previous_doc({ "Quotation": { "ref_dn_field": "prevdoc_docname", - "compare_fields": [["company", "="], ["currency", "="]] + "compare_fields": [["company", "="]] } }) From ba54209c86c328568f33359af58e833051baa13d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Sat, 12 Jan 2019 17:58:00 +0530 Subject: [PATCH 059/112] [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 4ed7cfc515fd50a99540ee4888f1595dd2d9a665 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 14 Jan 2019 17:14:39 +0530 Subject: [PATCH 060/112] 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 9080221d46b74e78238ec8a170a68949b0b81aef Mon Sep 17 00:00:00 2001 From: NahuelOperto Date: Mon, 14 Jan 2019 12:12:56 -0300 Subject: [PATCH 061/112] fix: added test cases --- .../doctype/quotation/test_quotation.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index e79d46ae3fd..909442af2fd 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -31,6 +31,26 @@ class TestQuotation(unittest.TestCase): self.assertFalse(sales_order.get('payment_schedule')) + def test_make_sales_order_with_different_currency(self): + from erpnext.selling.doctype.quotation.quotation import make_sales_order + + quotation = frappe.copy_doc(test_records[0]) + quotation.transaction_date = nowdate() + quotation.valid_till = add_months(quotation.transaction_date, 1) + quotation.insert() + quotation.submit() + + sales_order = make_sales_order(quotation.name) + sales_order.currency = "USD" + sales_order.conversion_rate = 20.0 + sales_order.delivery_date = "2019-01-01" + sales_order.naming_series = "_T-Quotation-" + sales_order.transaction_date = nowdate() + sales_order.insert() + + self.assertEquals(sales_order.currency, "USD") + self.assertNotEqual(sales_order.currency, quotation.currency) + def test_make_sales_order(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order From 627be1de51d5e79b3c2342118b10632eec78e1e6 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Tue, 15 Jan 2019 14:16:32 +0530 Subject: [PATCH 062/112] 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 063/112] 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 df7215dcb210dc506d433a1a46dfa41101768c5c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 Jan 2019 14:48:06 +0530 Subject: [PATCH 064/112] Added supplier name in the tds report --- .../tds_computation_summary.py | 32 +++++++++++++++---- .../tds_payable_monthly.py | 32 ++++++++++++++----- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py index d81a8f3c9f9..b19f6306b73 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -8,7 +8,9 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category def execute(filters=None): validate_filters(filters) - columns = get_columns() + filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') + + columns = get_columns(filters) res = get_result(filters) return columns, res @@ -29,7 +31,8 @@ def get_result(filters): # if no supplier selected, fetch data for all tds applicable supplier # else fetch relevant data for selected supplier pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" - fields = ["name", pan+" as pan", "tax_withholding_category", "supplier_type"] + fields = ["name", pan+" as pan", "tax_withholding_category", "supplier_type", "supplier_name"] + if filters.supplier: filters.supplier = frappe.db.get_list('Supplier', {"name": filters.supplier}, fields) @@ -49,8 +52,13 @@ def get_result(filters): filters.company, filters.from_date, filters.to_date) if total_invoiced_amount or tds_deducted: - out.append([supplier.pan, supplier.name, tds.name, supplier.supplier_type, - rate, total_invoiced_amount, tds_deducted]) + row = [supplier.pan, supplier.name] + + if filters.naming_series == 'Naming Series': + row.append(supplier.supplier_name) + + row.extend([tds.name, supplier.supplier_type, rate, total_invoiced_amount, tds_deducted]) + out.append(row) return out @@ -86,7 +94,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date): return total_invoiced_amount, tds_deducted -def get_columns(): +def get_columns(filters): columns = [ { "label": _("PAN"), @@ -100,7 +108,17 @@ def get_columns(): "fieldname": "supplier", "fieldtype": "Link", "width": 180 - }, + }] + + if filters.naming_series == 'Naming Series': + columns.append({ + "label": _("Supplier Name"), + "fieldname": "supplier_name", + "fieldtype": "Data", + "width": 180 + }) + + columns.extend([ { "label": _("Section Code"), "options": "Tax Withholding Category", @@ -132,6 +150,6 @@ def get_columns(): "fieldtype": "Float", "width": 90 } - ] + ]) return columns diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 843b58f4486..e55c022452d 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -11,7 +11,7 @@ def execute(filters=None): validate_filters(filters) set_filters(filters) - columns = get_columns() + columns = get_columns(filters) if not filters["invoices"]: return columns, [] @@ -43,6 +43,7 @@ def set_filters(filters): invoices.append(d) filters["invoices"] = invoices if invoices else filters["invoices"] + filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') def get_result(filters): supplier_map, tds_docs = get_supplier_map(filters) @@ -71,9 +72,14 @@ def get_result(filters): if getdate(filters.from_date) <= gle_map[d][0].posting_date \ and getdate(filters.to_date) >= gle_map[d][0].posting_date: - out.append([supplier.pan, supplier.name, tds_doc.name, - supplier.supplier_type, rate, total_amount_credited, tds_deducted, - gle_map[d][0].posting_date, "Purchase Invoice", d]) + row = [supplier.pan, supplier.name] + + if filters.naming_series == 'Naming Series': + row.append(supplier.supplier_name) + + row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited, + tds_deducted, gle_map[d][0].posting_date, "Purchase Invoice", d]) + out.append(row) return out @@ -84,7 +90,7 @@ def get_supplier_map(filters): pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" supplier_detail = frappe.db.get_all('Supplier', {"name": ["in", [d.supplier for d in filters["invoices"]]]}, - ["tax_withholding_category", "name", pan+" as pan", "supplier_type"]) + ["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"]) for d in filters["invoices"]: supplier_map[d.get("name")] = [k for k in supplier_detail @@ -113,7 +119,7 @@ def get_gle_map(filters): return gle_map -def get_columns(): +def get_columns(filters): pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" columns = [ { @@ -128,7 +134,17 @@ def get_columns(): "fieldname": "supplier", "fieldtype": "Link", "width": 180 - }, + }] + + if filters.naming_series == 'Naming Series': + columns.append({ + "label": _("Supplier Name"), + "fieldname": "supplier_name", + "fieldtype": "Data", + "width": 180 + }) + + columns.extend([ { "label": _("Section Code"), "options": "Tax Withholding Category", @@ -178,7 +194,7 @@ def get_columns(): "options": "transaction_type", "width": 90 } - ] + ]) return columns From 876d868fa13aa03ed1c3adf3b017264537165033 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 15 Jan 2019 15:45:33 +0530 Subject: [PATCH 065/112] fix(tests): change patch order, optimise travis --- .travis.yml | 11 +++++------ erpnext/patches.txt | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 448cd40a397..e21d59511a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,17 +30,16 @@ before_script: - cd ~/frappe-bench - bench get-app erpnext $TRAVIS_BUILD_DIR - bench use test_site - - bench reinstall --yes - - bench build - - bench scheduler disable - - sed -i 's/9000/9001/g' sites/common_site_config.json - - bench start & - - sleep 10 jobs: include: - stage: test script: + - bench reinstall --yes + - bench scheduler disable + - sed -i 's/9000/9001/g' sites/common_site_config.json + - bench start & + - sleep 10 - set -e - bench run-tests env: Server Side Test diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bc6f99db849..9d5b3311236 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -1,5 +1,6 @@ execute:import unidecode # new requirement erpnext.patches.v8_0.move_perpetual_inventory_setting +erpnext.patches.v8_9.set_print_zero_amount_taxes erpnext.patches.v10_0.rename_schools_to_education erpnext.patches.v4_0.validate_v3_patch erpnext.patches.v4_0.fix_employee_user_id @@ -442,7 +443,6 @@ erpnext.patches.v8_9.add_setup_progress_actions #08-09-2017 #26-09-2017 #22-11-2 erpnext.patches.v8_9.rename_company_sales_target_field erpnext.patches.v8_8.set_bom_rate_as_per_uom erpnext.patches.v8_8.add_new_fields_in_accounts_settings -erpnext.patches.v8_9.set_print_zero_amount_taxes erpnext.patches.v8_9.set_default_customer_group erpnext.patches.v8_9.remove_employee_from_salary_structure_parent erpnext.patches.v8_9.delete_gst_doctypes_for_outside_india_accounts From bdee57ca8d30a8f078785da7ec15ac0c0982fb53 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 Jan 2019 15:52:52 +0530 Subject: [PATCH 066/112] Added finance book in trial balance and fixed cost center not working issue in trial balance --- .../report/trial_balance/trial_balance.js | 41 ++++++++++++------- .../report/trial_balance/trial_balance.py | 18 ++++++-- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index c09fa715753..194f1bd497c 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -12,21 +12,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "default": frappe.defaults.get_user_default("Company"), "reqd": 1 }, - { - "fieldname":"cost_center", - "label": __("Cost Center"), - "fieldtype": "Link", - "options": "Cost Center", - "get_query": function() { - var company = frappe.query_report.get_filter_value('company'); - return { - "doctype": "Cost Center", - "filters": { - "company": company, - } - } - } - }, { "fieldname": "fiscal_year", "label": __("Fiscal Year"), @@ -60,6 +45,27 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "Date", "default": frappe.defaults.get_user_default("year_end_date"), }, + { + "fieldname":"cost_center", + "label": __("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center", + "get_query": function() { + var company = frappe.query_report.get_filter_value('company'); + return { + "doctype": "Cost Center", + "filters": { + "company": company, + } + } + } + }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book", + }, { "fieldname": "with_period_closing_entry", "label": __("Period Closing Entry"), @@ -75,6 +81,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldname": "show_unclosed_fy_pl_balances", "label": __("Show unclosed fiscal year's P&L balances"), "fieldtype": "Check" + }, + { + "fieldname": "include_non_finance_book_entries", + "label": __("Include Non Finance Book Entries"), + "fieldtype": "Check" } ], "formatter": erpnext.financial_statements.formatter, diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 07dcd4e7a94..696e63bb2cf 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -67,11 +67,10 @@ def get_data(filters): gl_entries_by_account = {} + opening_balances = get_opening_balances(filters) set_gl_entries_by_account(filters.company, filters.from_date, filters.to_date, min_lft, max_rgt, filters, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry)) - opening_balances = get_opening_balances(filters) - total_row = calculate_values(accounts, gl_entries_by_account, opening_balances, filters, company_currency) accumulate_values_into_parents(accounts, accounts_by_name) @@ -98,6 +97,18 @@ def get_rootwise_opening_balances(filters, report_type): if not flt(filters.with_period_closing_entry): additional_conditions += " and ifnull(voucher_type, '')!='Period Closing Voucher'" + if filters.cost_center: + lft, rgt = frappe.db.get_value('Cost Center', filters.cost_center, ['lft', 'rgt']) + additional_conditions += """ and cost_center in (select name from `tabCost Center` + where lft >= %s and rgt <= %s)""" % (lft, rgt) + + if filters.finance_book: + fb_conditions = " and finance_book = %(finance_book)s" + if filters.include_non_finance_book_entries: + fb_conditions = " and (finance_book = %(finance_book)s or finance_book is null)" + + additional_conditions += fb_conditions + gle = frappe.db.sql(""" select account, sum(debit) as opening_debit, sum(credit) as opening_credit @@ -112,7 +123,8 @@ def get_rootwise_opening_balances(filters, report_type): "company": filters.company, "from_date": filters.from_date, "report_type": report_type, - "year_start_date": filters.year_start_date + "year_start_date": filters.year_start_date, + "finance_book": filters.finance_book }, as_dict=True) From a3dd798badb176bd39c677e2cf3b8edeb3da58a3 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Tue, 15 Jan 2019 14:36:11 +0530 Subject: [PATCH 067/112] 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 b2d08a498b76cbe9c1f491ed42072453002af85d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 Jan 2019 16:11:29 +0530 Subject: [PATCH 068/112] Added bank account field in the payment entry --- .../doctype/bank_account/bank_account.py | 7 + .../doctype/payment_entry/payment_entry.js | 24 ++++ .../doctype/payment_entry/payment_entry.json | 131 ++++++++++++++++-- 3 files changed, 147 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 08f8248f3d7..7a3f22b10c7 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -31,3 +31,10 @@ def make_bank_account(doctype, docname): doc.is_default = 1 return doc + + +@frappe.whitelist() +def get_bank_account(bank_account): + if bank_account: + return frappe.db.get_value('Bank Account', + bank_account, 'account') \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index afb44e8f92f..3663ffe0536 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -30,6 +30,13 @@ frappe.ui.form.on('Payment Entry', { } } }); + frm.set_query("bank_account", function() { + return{ + "filters": { + "party": frm.doc.party, + } + } + }); frm.set_query("contact_person", function() { if (frm.doc.party) { return { @@ -833,6 +840,23 @@ frappe.ui.form.on('Payment Entry', { } }) } + }, + + bank_account: function(frm) { + const field = frm.doc.payment_type == "Pay" ? "paid_from":"paid_to"; + if (frm.doc.bank_account && in_list(['Pay', 'Receive'], frm.doc.payment_type)) { + frappe.call({ + method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account", + args: { + bank_account: frm.doc.bank_account + }, + callback: function(r) { + if (r.message) { + frm.set_value(field, r.message); + } + } + }); + } } }); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index bc9062b96c9..9354c61addb 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, @@ -379,24 +380,24 @@ { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "party", - "fieldname": "contact_person", - "fieldtype": "Link", + "depends_on": "eval:in_list([\"Receive\", \"Pay\"], doc.payment_type) && doc.party_type", + "description": "", + "fieldname": "party_name", + "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_global_search": 0, + "in_global_search": 1, "in_list_view": 0, "in_standard_filter": 0, - "label": "Contact", + "label": "Party Name", "length": 0, "no_copy": 0, - "options": "Contact", "permlevel": 0, "precision": "", "print_hide": 0, @@ -444,24 +445,24 @@ { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, - "allow_on_submit": 1, + "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:in_list([\"Receive\", \"Pay\"], doc.payment_type) && doc.party_type", - "description": "", - "fieldname": "party_name", - "fieldtype": "Data", + "depends_on": "party", + "fieldname": "bank_account", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_global_search": 1, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Party Name", + "label": "Bank Account", "length": 0, "no_copy": 0, + "options": "Bank Account", "permlevel": 0, "precision": "", "print_hide": 0, @@ -509,6 +510,40 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "party", + "fieldname": "contact_person", + "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": "Contact", + "length": 0, + "no_copy": 0, + "options": "Contact", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1867,6 +1902,72 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "bank_account.bank", + "fieldname": "bank", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Bank", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "bank_account.bank_account_no", + "fieldname": "bank_account_no", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Bank Account No", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -2040,7 +2141,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-09-25 14:38:48.312629", + "modified": "2019-01-15 15:58:40.742601", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", From cef56b514719a7ef2b0d216bdb757a5645911a51 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Tue, 15 Jan 2019 16:26:58 +0530 Subject: [PATCH 069/112] 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 1a4c1e11cb770ba4369a68c527b0c189e6cd75c6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 Jan 2019 17:01:34 +0530 Subject: [PATCH 070/112] Fix: total invoiced amount in Accounts Receivable Summary report showing blank --- .../accounts_receivable_summary/accounts_receivable_summary.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 190031abb8c..0a955a79120 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -185,6 +185,9 @@ class AccountsReceivableSummary(ReceivablePayableReport): if party_naming_by == "Naming Series": cols += ["party_name"] + if args.get("party_type") == 'Customer': + cols += ["contact"] + cols += ["voucher_type", "voucher_no", "due_date"] if args.get("party_type") == "Supplier": From affeb3dfecfc6d3515f950f41a427e5abb189442 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 Jan 2019 17:38:31 +0530 Subject: [PATCH 071/112] Added on account amount field in Accounts Payable/Receivable Summary --- erpnext/accounts/party.py | 9 +++++++++ .../accounts_receivable_summary.py | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index a69a1722845..d25e6de5e61 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -562,3 +562,12 @@ def get_party_shipping_address(doctype, name): return out[0][0] else: return '' + +def get_partywise_advanced_payment_amount(party_type="Customer"): + data = frappe.db.sql(""" SELECT party, sum({0}) as amount + FROM `tabGL Entry` + WHERE party_type = %s and against_voucher is null GROUP BY party""" + .format("credit" if party_type == "Customer" else "debit") , party_type, debug=1) + + if data: + return frappe._dict(data) \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 0a955a79120..a272bfeb47b 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _, scrub from frappe.utils import flt +from erpnext.accounts.party import get_partywise_advanced_payment_amount from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport from six import iteritems @@ -24,6 +25,12 @@ class AccountsReceivableSummary(ReceivablePayableReport): credit_debit_label = "Credit Note Amt" if args.get('party_type') == 'Customer' else "Debit Note Amt" columns += [{ + "label": _("On Account Amount"), + "fieldname": "on_account_amt", + "fieldtype": "Currency", + "options": "currency", + "width": 100 + },{ "label": _("Total Invoiced Amt"), "fieldname": "total_invoiced_amt", "fieldtype": "Currency", @@ -129,12 +136,15 @@ class AccountsReceivableSummary(ReceivablePayableReport): partywise_total = self.get_partywise_total(party_naming_by, args) + partywise_advance_amount = get_partywise_advanced_payment_amount(args.get("party_type")) or {} for party, party_dict in iteritems(partywise_total): row = [party] if party_naming_by == "Naming Series": row += [self.get_party_name(args.get("party_type"), party)] + row += [partywise_advance_amount.get(party, 0)] + row += [ party_dict.invoiced_amt, party_dict.paid_amt, party_dict.credit_amt, party_dict.outstanding_amt, party_dict.range1, party_dict.range2, party_dict.range3, party_dict.range4, From 529cc1ca51ed556a9ec2f4dbc3268b08a29fa486 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 15 Jan 2019 19:46:36 +0600 Subject: [PATCH 072/112] 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 073/112] 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 074/112] 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 075/112] 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 076/112] 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 077/112] 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 078/112] 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 079/112] 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"]]) From 1ed819bb10be3bf89c3e2b6bc8126991937fde3e Mon Sep 17 00:00:00 2001 From: ks093 Date: Thu, 17 Jan 2019 12:09:04 +0530 Subject: [PATCH 080/112] Refresh quotation_series field to show series --- .../doctype/shopping_cart_settings/shopping_cart_settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js index 3219d7a5cd0..e1510f53354 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js @@ -5,6 +5,7 @@ $.extend(cur_frm.cscript, { onload: function() { if(cur_frm.doc.__onload && cur_frm.doc.__onload.quotation_series) { cur_frm.fields_dict.quotation_series.df.options = cur_frm.doc.__onload.quotation_series; + cur_frm.refresh_field("quotation_series"); } }, refresh: function(){ From 61c781325d1ffcb14e23f93374d0880baec7fe4d Mon Sep 17 00:00:00 2001 From: ks093 Date: Thu, 17 Jan 2019 12:16:43 +0530 Subject: [PATCH 081/112] Fix to show low stock message. In case of order being placed with stock quantity more than available, message showing stock available wasn't showing due to tuple unpacking not happening correctly. --- erpnext/shopping_cart/cart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 254d7b74af3..cc3205c7d70 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -66,7 +66,7 @@ def place_order(): sales_order = frappe.get_doc(_make_sales_order(quotation.name, ignore_permissions=True)) for item in sales_order.get("items"): item.reserved_warehouse, is_stock_item = frappe.db.get_value("Item", - item.item_code, ["website_warehouse", "is_stock_item"]) or None, None + item.item_code, ["website_warehouse", "is_stock_item"]) if is_stock_item: item_stock = get_qty_in_stock(item.item_code, "website_warehouse") From f7258168095a90cd6d4f9e5d4b2374027c6a9685 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 15 Jan 2019 18:12:04 +0530 Subject: [PATCH 082/112] Enhance: Added field limit in the Payment Reconciliation to handle large entries --- .../doctype/bank_account/bank_account.py | 8 +- .../doctype/payment_entry/payment_entry.js | 2 +- .../payment_reconciliation.json | 186 +++++++++++++++++- .../payment_reconciliation.py | 9 +- erpnext/accounts/utils.py | 8 +- erpnext/controllers/accounts_controller.py | 13 +- 6 files changed, 200 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 7a3f22b10c7..7a79de56b86 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -32,9 +32,7 @@ def make_bank_account(doctype, docname): return doc - @frappe.whitelist() -def get_bank_account(bank_account): - if bank_account: - return frappe.db.get_value('Bank Account', - bank_account, 'account') \ No newline at end of file +def get_account_from_bank_acc(name): + return frappe.db.get_value('Bank Account', + name, 'account') \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 3663ffe0536..58a380c968b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -846,7 +846,7 @@ frappe.ui.form.on('Payment Entry', { const field = frm.doc.payment_type == "Pay" ? "paid_from":"paid_to"; if (frm.doc.bank_account && in_list(['Pay', 'Receive'], frm.doc.payment_type)) { frappe.call({ - method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account", + method: "erpnext.accounts.doctype.bank_account.bank_account.get_account_from_bank_acc", args: { bank_account: frm.doc.bank_account }, diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index b211b500a15..cfb24c3954c 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -1,23 +1,34 @@ { "allow_copy": 1, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, + "beta": 0, "creation": "2014-07-09 12:04:51.681583", "custom": 0, "docstatus": 0, "doctype": "DocType", "document_type": "", + "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": "company", "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": "Company", "length": 0, "no_copy": 0, @@ -26,22 +37,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, "set_only_once": 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, @@ -50,23 +69,31 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "depends_on": "", "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": 0, "label": "Party", "length": 0, "no_copy": 0, @@ -75,22 +102,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, "set_only_once": 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": "receivable_payable_account", "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": "Receivable / Payable Account", "length": 0, "no_copy": 0, @@ -100,22 +135,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, "set_only_once": 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": "bank_cash_account", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Bank / Cash Account", "length": 0, "no_copy": 0, @@ -124,22 +167,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 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": "col_break1", "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "", "length": 0, "no_copy": 0, @@ -147,22 +198,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 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": "from_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": "From Invoice Date", "length": 0, "no_copy": 0, @@ -170,22 +229,30 @@ "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, + "search_index": 0, "set_only_once": 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": "to_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": "To Invoice Date", "length": 0, "no_copy": 0, @@ -193,22 +260,30 @@ "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, + "search_index": 0, "set_only_once": 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": "minimum_amount", "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": "Minimum Invoice Amount", "length": 0, "no_copy": 0, @@ -216,22 +291,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 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": "maximum_amount", "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": "Maximum Invoice Amount", "length": 0, "no_copy": 0, @@ -239,22 +322,63 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "description": "System will fetch all the entries if limit value is zero.", + "fieldname": "limit", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Limit", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "get_unreconciled_entries", "fieldtype": "Button", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Get Unreconciled Entries", "length": 0, "no_copy": 0, @@ -262,22 +386,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 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": "sec_break1", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Unreconciled Payment Details", "length": 0, "no_copy": 0, @@ -285,22 +417,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 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": "payments", "fieldtype": "Table", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Payments", "length": 0, "no_copy": 0, @@ -309,22 +449,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 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": "reconcile", "fieldtype": "Button", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Reconcile", "length": 0, "no_copy": 0, @@ -332,22 +480,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 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": "sec_break2", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Invoice/Journal Entry Details", "length": 0, "no_copy": 0, @@ -355,22 +511,30 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 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": "invoices", "fieldtype": "Table", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Invoices", "length": 0, "no_copy": 0, @@ -379,25 +543,28 @@ "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, "hide_heading": 0, "hide_toolbar": 1, - "icon": "fa fa-resize-horizontal", + "icon": "icon-resize-horizontal", "idx": 0, + "image_view": 0, "in_create": 0, - "is_submittable": 0, "issingle": 1, "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2016-01-04 02:26:58.807921", + "modified": "2019-01-15 17:42:21.135214", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", @@ -406,7 +573,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -426,7 +592,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -445,8 +610,13 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 7cd951aba46..094ece95e81 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -25,7 +25,7 @@ class PaymentReconciliation(Document): def get_payment_entries(self): order_doctype = "Sales Order" if self.party_type=="Customer" else "Purchase Order" payment_entries = get_advance_payment_entries(self.party_type, self.party, - self.receivable_payable_account, order_doctype, against_all_orders=True) + self.receivable_payable_account, order_doctype, against_all_orders=True, limit=self.limit) return payment_entries @@ -36,6 +36,8 @@ class PaymentReconciliation(Document): bank_account_condition = "t2.against_account like %(bank_cash_account)s" \ if self.bank_cash_account else "1=1" + limit_cond = "limit %s" % (self.limit or 1000) + journal_entries = frappe.db.sql(""" select "Journal Entry" as reference_type, t1.name as reference_name, @@ -55,10 +57,11 @@ class PaymentReconciliation(Document): THEN 1=1 ELSE {bank_account_condition} END) - order by t1.posting_date + order by t1.posting_date {limit_cond} """.format(**{ "dr_or_cr": dr_or_cr, "bank_account_condition": bank_account_condition, + "limit_cond": limit_cond }), { "party_type": self.party_type, "party": self.party, @@ -80,7 +83,7 @@ class PaymentReconciliation(Document): condition = self.check_condition() non_reconciled_invoices = get_outstanding_invoices(self.party_type, self.party, - self.receivable_payable_account, condition=condition) + self.receivable_payable_account, condition=condition, limit=self.limit) self.add_invoice_entries(non_reconciled_invoices) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 6fbe97de3e1..e145a35b17f 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -615,7 +615,7 @@ def get_held_invoices(party_type, party): return held_invoices -def get_outstanding_invoices(party_type, party, account, condition=None): +def get_outstanding_invoices(party_type, party, account, condition=None, limit=1000): outstanding_invoices = [] precision = frappe.get_precision("Sales Invoice", "outstanding_amount") @@ -628,6 +628,7 @@ def get_outstanding_invoices(party_type, party, account, condition=None): invoice = 'Sales Invoice' if erpnext.get_party_account_type(party_type) == 'Receivable' else 'Purchase Invoice' held_invoices = get_held_invoices(party_type, party) + limit_cond = "limit %s" % (limit or 1000) invoice_list = frappe.db.sql(""" select @@ -655,11 +656,12 @@ 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, name {limit_cond}""".format( dr_or_cr=dr_or_cr, invoice = invoice, payment_dr_or_cr=payment_dr_or_cr, - condition=condition or "" + condition=condition or "", + limit_cond = limit_cond ), { "party_type": party_type, "party": party, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cd373b84d7e..c0643f2e6e5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -953,11 +953,12 @@ def get_advance_journal_entries(party_type, party, party_account, amount_field, return list(journal_entries) -def get_advance_payment_entries(party_type, party, party_account, - order_doctype, order_list=None, include_unallocated=True, against_all_orders=False): +def get_advance_payment_entries(party_type, party, party_account, order_doctype, + order_list=None, include_unallocated=True, against_all_orders=False, limit=1000): party_account_field = "paid_from" if party_type == "Customer" else "paid_to" payment_type = "Receive" if party_type == "Customer" else "Pay" payment_entries_against_order, unallocated_payment_entries = [], [] + limit_cond = "limit %s" % (limit or 1000) if order_list or against_all_orders: if order_list: @@ -977,8 +978,8 @@ def get_advance_payment_entries(party_type, party, party_account, t1.name = t2.parent and t1.{0} = %s and t1.payment_type = %s and t1.party_type = %s and t1.party = %s and t1.docstatus = 1 and t2.reference_doctype = %s {1} - order by t1.posting_date - """.format(party_account_field, reference_condition), + order by t1.posting_date {2} + """.format(party_account_field, reference_condition, limit_cond), [party_account, payment_type, party_type, party, order_doctype] + order_list, as_dict=1) @@ -990,8 +991,8 @@ def get_advance_payment_entries(party_type, party, party_account, where {0} = %s and party_type = %s and party = %s and payment_type = %s and docstatus = 1 and unallocated_amount > 0 - order by posting_date - """.format(party_account_field), (party_account, party_type, party, payment_type), as_dict=1) + order by posting_date {1} + """.format(party_account_field, limit_cond), (party_account, party_type, party, payment_type), as_dict=1) return list(payment_entries_against_order) + list(unallocated_payment_entries) From fa7ee0b3b67a04de136ce2673185cddc4f217cd3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 17 Jan 2019 15:30:44 +0530 Subject: [PATCH 083/112] Added dafult bank account in the customer/supplier --- .../doctype/bank_account/bank_account.py | 11 ++++-- .../doctype/payment_entry/payment_entry.js | 13 +++++-- .../doctype/payment_entry/payment_entry.py | 22 +++++++++++- erpnext/accounts/party.py | 2 +- .../accounts_receivable_summary.py | 4 +-- erpnext/buying/doctype/supplier/supplier.json | 35 ++++++++++++++++++- .../selling/doctype/customer/customer.json | 35 ++++++++++++++++++- 7 files changed, 110 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py index 7a79de56b86..b13259b55d3 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.py +++ b/erpnext/accounts/doctype/bank_account/bank_account.py @@ -33,6 +33,11 @@ def make_bank_account(doctype, docname): return doc @frappe.whitelist() -def get_account_from_bank_acc(name): - return frappe.db.get_value('Bank Account', - name, 'account') \ No newline at end of file +def get_party_bank_account(party_type, party): + return frappe.db.get_value(party_type, + party, 'default_bank_account') + +@frappe.whitelist() +def get_bank_account_details(bank_account): + return frappe.db.get_value("Bank Account", + bank_account, ['account', 'bank', 'bank_account_no'], as_dict=1) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 58a380c968b..15cc3fdbb9d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -291,7 +291,12 @@ frappe.ui.form.on('Payment Entry', { () => frm.events.get_outstanding_documents(frm), () => frm.events.hide_unhide_fields(frm), () => frm.events.set_dynamic_labels(frm), - () => { frm.set_party_account_based_on_party = false; } + () => { + frm.set_party_account_based_on_party = false; + if (r.message.bank_account) { + frm.set_value("bank_account", r.message.bank_account); + } + } ]); } } @@ -846,13 +851,15 @@ frappe.ui.form.on('Payment Entry', { const field = frm.doc.payment_type == "Pay" ? "paid_from":"paid_to"; if (frm.doc.bank_account && in_list(['Pay', 'Receive'], frm.doc.payment_type)) { frappe.call({ - method: "erpnext.accounts.doctype.bank_account.bank_account.get_account_from_bank_acc", + method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account_details", args: { bank_account: frm.doc.bank_account }, callback: function(r) { if (r.message) { - frm.set_value(field, r.message); + frm.set_value(field, r.message.account); + frm.set_value('bank', r.message.bank); + frm.set_value('bank_account_no', r.message.bank_account_no); } } }); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f213ffa6580..7f1f55005c0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -12,6 +12,7 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_ban from erpnext.setup.utils import get_exchange_rate from erpnext.accounts.general_ledger import make_gl_entries from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount +from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account, get_bank_account_details from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status from six import string_types, iteritems @@ -88,6 +89,16 @@ class PaymentEntry(AccountsController): .format(d.idx, d.reference_doctype, d.reference_name)) reference_names.append((d.reference_doctype, d.reference_name)) + def set_bank_account_data(self): + if self.bank_account: + bank_data = get_bank_account_details(self.bank_account) + + field = "paid_from" if self.payment_type == "Pay" else "paid_to" + + self.bank = bank_data.bank + self.bank_account_no = bank_data.bank_account_no + self.set(field, bank_data.account) + def validate_allocated_amount(self): for d in self.get("references"): if (flt(d.allocated_amount))> 0: @@ -670,6 +681,7 @@ def get_negative_outstanding_invoices(party_type, party, party_account, party_ac @frappe.whitelist() def get_party_details(company, party_type, party, date, cost_center=None): + bank_account = '' if not frappe.db.exists(party_type, party): frappe.throw(_("Invalid {0}: {1}").format(party_type, party)) @@ -680,13 +692,16 @@ def get_party_details(company, party_type, party, date, cost_center=None): _party_name = "title" if party_type == "Student" else party_type.lower() + "_name" party_name = frappe.db.get_value(party_type, party, _party_name) party_balance = get_balance_on(party_type=party_type, party=party, cost_center=cost_center) + if party_type in ["Customer", "Supplier"]: + bank_account = get_party_bank_account(party_type, party) return { "party_account": party_account, "party_name": party_name, "party_account_currency": account_currency, "party_balance": party_balance, - "account_balance": account_balance + "account_balance": account_balance, + "bank_account": bank_account } @@ -890,6 +905,11 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.allocate_payment_amount = 1 pe.letter_head = doc.get("letter_head") + if pe.party_type in ["Customer", "Supplier"]: + bank_account = get_party_bank_account(pe.party_type, pe.party) + pe.set("bank_account", bank_account) + pe.set_bank_account_data() + # only Purchase Invoice can be blocked individually if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked(): frappe.msgprint(_('{0} is on hold till {1}'.format(doc.name, doc.release_date))) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index d25e6de5e61..eb1146ba1ac 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -567,7 +567,7 @@ def get_partywise_advanced_payment_amount(party_type="Customer"): data = frappe.db.sql(""" SELECT party, sum({0}) as amount FROM `tabGL Entry` WHERE party_type = %s and against_voucher is null GROUP BY party""" - .format("credit" if party_type == "Customer" else "debit") , party_type, debug=1) + .format(("credit - debit") if party_type == "Customer" else "debit") , party_type, debug=1) if data: return frappe._dict(data) \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index a272bfeb47b..73ca8b48ef5 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -25,8 +25,8 @@ class AccountsReceivableSummary(ReceivablePayableReport): credit_debit_label = "Credit Note Amt" if args.get('party_type') == 'Customer' else "Debit Note Amt" columns += [{ - "label": _("On Account Amount"), - "fieldname": "on_account_amt", + "label": _("Advance Amount"), + "fieldname": "advance_amount", "fieldtype": "Currency", "options": "currency", "width": 100 diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 3b1f4e0dfcc..4586c647155 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -149,6 +149,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "default_bank_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Bank Account", + "length": 0, + "no_copy": 0, + "options": "Bank Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1463,7 +1496,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-01-07 16:52:04.660271", + "modified": "2019-01-17 13:58:08.597792", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 608c0e950ed..a82042f4a16 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -217,6 +217,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "default_bank_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Bank Account", + "length": 0, + "no_copy": 0, + "options": "Bank Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1873,7 +1906,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-10-01 10:07:34.510264", + "modified": "2019-01-17 13:10:24.360875", "modified_by": "Administrator", "module": "Selling", "name": "Customer", From 425dff93d7ad15d0458b0ac98b2f5f3767a7a254 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 17 Jan 2019 12:49:45 +0530 Subject: [PATCH 084/112] fix: Project update status code and design refactored --- erpnext/hooks.py | 10 +- .../daily_work_summary/daily_work_summary.py | 6 +- erpnext/projects/doctype/project/project.json | 3571 +++++++++-------- erpnext/projects/doctype/project/project.py | 180 +- .../project_update/project_update.json | 169 +- .../doctype/project_user/project_user.json | 197 +- .../emails/daily_project_summary.html | 46 + 7 files changed, 2275 insertions(+), 1904 deletions(-) create mode 100644 erpnext/templates/emails/daily_project_summary.html diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bf7e32ab78e..f81cb416eb1 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -223,10 +223,15 @@ doc_events = { } scheduler_events = { + "all": [ + "erpnext.projects.doctype.project.project.project_status_update_reminder" + ], "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.projects.doctype.project.project.hourly_reminder", + "erpnext.projects.doctype.project.project.collect_project_status" ], "daily": [ "erpnext.stock.reorder_item.reorder_item", @@ -245,7 +250,8 @@ scheduler_events = { "erpnext.assets.doctype.asset.asset.update_maintenance_status", "erpnext.assets.doctype.asset.asset.make_post_gl_entry", "erpnext.crm.doctype.contract.contract.update_status_for_contracts", - "erpnext.projects.doctype.project.project.update_project_sales_billing" + "erpnext.projects.doctype.project.project.update_project_sales_billing", + "erpnext.projects.doctype.project.project.send_project_status_email_to_users" ], "daily_long": [ "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms" diff --git a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py index a404b5a3e30..25cda4444bc 100644 --- a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py +++ b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py @@ -112,6 +112,10 @@ def get_user_emails_from_group(group): if isinstance(group_doc, string_types): group_doc = frappe.get_doc('Daily Work Summary Group', group) - emails = [d.email for d in group_doc.users if frappe.db.get_value("User", d.user, "enabled")] + emails = get_users_email(group_doc) return emails + +def get_users_email(doc): + return [d.email for d in doc.users + if frappe.db.get_value("User", d.user, "enabled")] diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 30e4c8ad44b..5e6c6aa0679 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -1,1872 +1,1941 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:project_name", - "beta": 0, - "creation": "2013-03-07 11:55:07", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "engine": "InnoDB", + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:project_name", + "beta": 0, + "creation": "2013-03-07 11:55:07", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "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, - "description": "", - "fieldname": "project_name", - "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": "Project Name", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "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, + "description": "", + "fieldname": "project_name", + "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": "Project Name", + "length": 0, + "no_copy": 0, + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Open", - "fieldname": "status", - "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": 1, - "label": "Status", - "length": 0, - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "Open\nCompleted\nCancelled", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "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, + "default": "Open", + "fieldname": "status", + "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": 1, + "label": "Status", + "length": 0, + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "Open\nCompleted\nCancelled", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "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": "project_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Project Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "project_type", - "oldfieldtype": "Data", - "options": "Project 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": 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_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Project Type", + "length": 0, + "no_copy": 0, + "oldfieldname": "project_type", + "oldfieldtype": "Data", + "options": "Project 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": 0, + "set_only_once": 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_active", - "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 Active", - "length": 0, - "no_copy": 0, - "oldfieldname": "is_active", - "oldfieldtype": "Select", - "options": "Yes\nNo", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_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_active", + "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 Active", + "length": 0, + "no_copy": 0, + "oldfieldname": "is_active", + "oldfieldtype": "Select", + "options": "Yes\nNo", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "Task Completion", - "fieldname": "percent_complete_method", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "% Complete Method", - "length": 0, - "no_copy": 0, - "options": "Task Completion\nTask Progress\nTask Weight", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_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, + "default": "Task Completion", + "fieldname": "percent_complete_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "% Complete Method", + "length": 0, + "no_copy": 0, + "options": "Task Completion\nTask Progress\nTask Weight", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "percent_complete", - "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "% Completed", - "length": 0, - "no_copy": 1, - "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": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "percent_complete", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "% Completed", + "length": 0, + "no_copy": 1, + "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": "column_break_5", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "department", - "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": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_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": "department", + "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": "Department", + "length": 0, + "no_copy": 0, + "options": "Department", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "priority", - "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": 1, - "label": "Priority", - "length": 0, - "no_copy": 0, - "oldfieldname": "priority", - "oldfieldtype": "Select", - "options": "Medium\nLow\nHigh", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_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": "priority", + "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": 1, + "label": "Priority", + "length": 0, + "no_copy": 0, + "oldfieldname": "priority", + "oldfieldtype": "Select", + "options": "Medium\nLow\nHigh", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "expected_start_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Expected Start Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "project_start_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": "expected_start_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Expected Start Date", + "length": 0, + "no_copy": 0, + "oldfieldname": "project_start_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": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "expected_end_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": "Expected End Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "completion_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": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "expected_end_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": "Expected End Date", + "length": 0, + "no_copy": 0, + "oldfieldname": "completion_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": 1, - "columns": 0, - "fieldname": "customer_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Details", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-user", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_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": 1, + "columns": 0, + "fieldname": "customer_details", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Customer Details", + "length": 0, + "no_copy": 0, + "oldfieldtype": "Section Break", + "options": "fa fa-user", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "customer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 1, - "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": "customer", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Customer", + "length": 0, + "no_copy": 0, + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "permlevel": 0, + "print_hide": 1, + "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": "column_break_14", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_14", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_order", - "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": "Sales Order", - "length": 0, - "no_copy": 0, - "options": "Sales Order", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_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": "sales_order", + "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": "Sales Order", + "length": 0, + "no_copy": 0, + "options": "Sales Order", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "users_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Users", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "users_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Users", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Project will be accessible on the website to these users", - "fieldname": "users", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Users", - "length": 0, - "no_copy": 0, - "options": "Project User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_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, + "description": "Project will be accessible on the website to these users", + "fieldname": "users", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Users", + "length": 0, + "no_copy": 0, + "options": "Project User", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "sb_milestones", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tasks", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-flag", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_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": "sb_milestones", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Tasks", + "length": 0, + "no_copy": 0, + "oldfieldtype": "Section Break", + "options": "fa fa-flag", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "tasks", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tasks", - "length": 0, - "no_copy": 0, - "options": "Project Task", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_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": "tasks", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Tasks", + "length": 0, + "no_copy": 0, + "options": "Project Task", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "copied_from", - "fieldtype": "Data", - "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": "Copied From", - "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": "copied_from", + "fieldtype": "Data", + "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": "Copied From", + "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": 1, - "columns": 0, - "fieldname": "section_break0", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notes", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-list", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_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": 1, + "columns": 0, + "fieldname": "section_break0", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notes", + "length": 0, + "no_copy": 0, + "oldfieldtype": "Section Break", + "options": "fa fa-list", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "notes", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notes", - "length": 0, - "no_copy": 0, - "oldfieldname": "notes", - "oldfieldtype": "Text Editor", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_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": "notes", + "fieldtype": "Text Editor", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notes", + "length": 0, + "no_copy": 0, + "oldfieldname": "notes", + "oldfieldtype": "Text Editor", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "section_break_18", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Start and End Dates", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "section_break_18", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Start and End Dates", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "actual_start_date", - "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": "Actual Start Date", - "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": "actual_start_date", + "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": "Actual Start Date", + "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": "actual_time", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Actual Time (in Hours)", - "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": "actual_time", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Actual Time (in Hours)", + "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": "column_break_20", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_20", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "actual_end_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Actual End Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "act_completion_date", - "oldfieldtype": "Date", - "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": "actual_end_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Actual End Date", + "length": 0, + "no_copy": 0, + "oldfieldname": "act_completion_date", + "oldfieldtype": "Date", + "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": 1, - "columns": 0, - "fieldname": "project_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Costing and Billing", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-money", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_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": 1, + "columns": 0, + "fieldname": "project_details", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Costing and Billing", + "length": 0, + "no_copy": 0, + "oldfieldtype": "Section Break", + "options": "fa fa-money", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "estimated_costing", - "fieldtype": "Currency", - "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": "Estimated Cost", - "length": 0, - "no_copy": 0, - "oldfieldname": "project_value", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_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": "estimated_costing", + "fieldtype": "Currency", + "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": "Estimated Cost", + "length": 0, + "no_copy": 0, + "oldfieldname": "project_value", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "total_costing_amount", - "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": "Total Costing Amount (via Timesheets)", - "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, + "description": "", + "fieldname": "total_costing_amount", + "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": "Total Costing Amount (via Timesheets)", + "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, - "description": "", - "fieldname": "total_expense_claim", - "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": "Total Expense Claim (via Expense Claims)", - "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, + "description": "", + "fieldname": "total_expense_claim", + "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": "Total Expense Claim (via Expense Claims)", + "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": "total_purchase_cost", - "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": "Total Purchase Cost (via Purchase Invoice)", - "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": "total_purchase_cost", + "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": "Total Purchase Cost (via Purchase Invoice)", + "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": "company", - "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": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "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": "company", + "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": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "column_break_28", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_28", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_sales_amount", - "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": "Total Sales Amount (via Sales Order)", - "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": "total_sales_amount", + "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": "Total Sales Amount (via Sales Order)", + "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, - "description": "", - "fieldname": "total_billable_amount", - "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": "Total Billable Amount (via Timesheets)", - "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, + "description": "", + "fieldname": "total_billable_amount", + "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": "Total Billable Amount (via Timesheets)", + "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": "total_billed_amount", - "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": "Total Billed Amount (via Sales Invoices)", - "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": "total_billed_amount", + "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": "Total Billed Amount (via Sales Invoices)", + "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": "total_consumed_material_cost", - "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": "Total Consumed Material Cost (via Stock Entry)", - "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": "total_consumed_material_cost", + "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": "Total Consumed Material Cost (via Stock Entry)", + "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": "cost_center", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Cost Center", - "length": 0, - "no_copy": 0, - "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": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Cost Center", + "length": 0, + "no_copy": 0, + "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": 1, - "columns": 0, - "fieldname": "margin", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Margin", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "margin", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Margin", + "length": 0, + "no_copy": 0, + "oldfieldtype": "Column Break", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "gross_margin", - "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": "Gross Margin", - "length": 0, - "no_copy": 0, - "oldfieldname": "gross_margin_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": "gross_margin", + "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": "Gross Margin", + "length": 0, + "no_copy": 0, + "oldfieldname": "gross_margin_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": "column_break_37", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_37", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "per_gross_margin", - "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Gross Margin %", - "length": 0, - "no_copy": 0, - "oldfieldname": "per_gross_margin", - "oldfieldtype": "Currency", - "options": "", - "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": "per_gross_margin", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Gross Margin %", + "length": 0, + "no_copy": 0, + "oldfieldname": "per_gross_margin", + "oldfieldtype": "Currency", + "options": "", + "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": 1, - "columns": 0, - "fieldname": "monitor_progress", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Monitor Progress", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_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": 1, + "columns": 0, + "fieldname": "monitor_progress", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Monitor Progress", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "collect_progress", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Collect Progress", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_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": "collect_progress", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Collect Progress", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.collect_progress == true", - "fieldname": "frequency", - "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": "Frequency To Collect Progress", - "length": 0, - "no_copy": 0, - "options": "Hourly\nTwice Daily\nDaily\nWeekly", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_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, + "depends_on": "collect_progress", + "fieldname": "holiday_list", + "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": "Holiday List", + "length": 0, + "no_copy": 0, + "options": "Holiday List", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 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": "column_break_45", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.collect_progress == true", + "fieldname": "frequency", + "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": "Frequency To Collect Progress", + "length": 0, + "no_copy": 0, + "options": "Hourly\nTwice Daily\nDaily\nWeekly", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.frequency == \"Hourly\" && doc.collect_progress == true)", - "fieldname": "from", - "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": "From", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Hourly\" && doc.collect_progress)", + "fieldname": "from_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": "From Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.frequency == \"Hourly\" && doc.collect_progress == true)", - "fieldname": "to", - "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": "To", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Hourly\" && doc.collect_progress)", + "fieldname": "to_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": "To Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.frequency == \"Twice Daily\" && doc.collect_progress == true)\n\n", - "fieldname": "first_email", - "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": "First Email", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Twice Daily\" && doc.collect_progress == true)\n\n", + "fieldname": "first_email", + "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": "First Email", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.frequency == \"Twice Daily\" && doc.collect_progress == true)", - "fieldname": "second_email", - "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": "Second Email", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Twice Daily\" && doc.collect_progress == true)", + "fieldname": "second_email", + "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": "Second Email", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.frequency == \"Daily\" && doc.collect_progress == true)", - "fieldname": "daily_time_to_send", - "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": "Time to send", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Daily\" && doc.collect_progress == true)", + "fieldname": "daily_time_to_send", + "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": "Time to send", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)", - "fieldname": "day_to_send", - "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": "Day to Send", - "length": 0, - "no_copy": 0, - "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_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, + "depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)", + "fieldname": "day_to_send", + "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": "Day to Send", + "length": 0, + "no_copy": 0, + "options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)", - "fieldname": "weekly_time_to_send", - "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": "Time to send", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:(doc.frequency == \"Weekly\" && doc.collect_progress == true)", + "fieldname": "weekly_time_to_send", + "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": "Time to send", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_45", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "collect_progress", + "description": "Message will sent to users to get their status on the project", + "fieldname": "message", + "fieldtype": "Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Message", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-puzzle-piece", - "idx": 29, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 4, - "modified": "2018-08-30 00:12:09.649654", - "modified_by": "Administrator", - "module": "Projects", - "name": "Project", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-puzzle-piece", + "idx": 29, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 4, + "modified": "2019-01-16 23:26:57.376682", + "modified_by": "Administrator", + "module": "Projects", + "name": "Project", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 1, - "print": 0, - "read": 1, - "report": 1, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 1, + "print": 0, + "read": 1, + "report": 1, + "role": "All", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Projects Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Projects Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "customer, status, priority, is_active", - "show_name_in_global_search": 1, - "sort_order": "DESC", - "timeline_field": "customer", - "track_changes": 0, - "track_seen": 1, + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "customer, status, priority, is_active", + "show_name_in_global_search": 1, + "sort_order": "DESC", + "timeline_field": "customer", + "track_changes": 0, + "track_seen": 1, "track_views": 0 } \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index de45ec30a61..a988dc03fa4 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -3,16 +3,17 @@ from __future__ import unicode_literals import frappe - -from frappe.utils import flt, getdate, get_url, now +import datetime from frappe import _ - -from frappe.model.document import Document +from six import iteritems +from email_reply_parser import EmailReplyParser +from frappe.utils import (flt, getdate, get_url, now, + nowtime, get_time, today, get_datetime, add_days) from erpnext.controllers.queries import get_filters_cond from frappe.desk.reportview import get_match_cond -import datetime - -from six import iteritems +from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_users_email +from erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group import is_holiday_today +from frappe.model.document import Document class Project(Document): def get_feed(self): @@ -406,56 +407,137 @@ def get_users_for_project(doctype, txt, searchfield, start, page_len, filters): def get_cost_center_name(project): return frappe.db.get_value("Project", project, "cost_center") -@frappe.whitelist() def hourly_reminder(): - project = frappe.db.sql("""SELECT `tabProject`.name FROM `tabProject` WHERE `tabProject`.frequency = "Hourly" and (CURTIME() BETWEEN `tabProject`.from and `tabProject`.to) AND `tabProject`.collect_progress = 1 ORDER BY `tabProject`.name;""") - create_project_update(project) + fields = ["from_time", "to_time"] + projects = get_projects_for_collect_progress("Hourly", fields) -@frappe.whitelist() -def twice_daily_reminder(): - project = frappe.db.sql("""SELECT `tabProject User`.user FROM `tabProject User` INNER JOIN `tabProject` ON `tabProject`.project_name = `tabProject User`.parent WHERE (`tabProject`.frequency = "Twice Daily") AND ((`tabProject`.first_email BETWEEN DATE_ADD(curtime(), INTERVAL -15 MINUTE) AND DATE_ADD(curtime(), INTERVAL 15 MINUTE)) OR (`tabProject`.second_email BETWEEN DATE_ADD(curtime(), INTERVAL -15 MINUTE) AND DATE_ADD(curtime(), INTERVAL 15 MINUTE))) AND `tabProject`.collect_progress = 1;""") - create_project_update(project) + for project in projects: + if (get_time(nowtime()) >= get_time(project.from_time) or + get_time(nowtime()) <= get_time(project.to_time)): + send_project_update_email_to_users(project.name) + +def project_status_update_reminder(): + daily_reminder() + twice_daily_reminder() + weekly_reminder() -@frappe.whitelist() def daily_reminder(): - project = frappe.db.sql("""SELECT `tabProject User`.user FROM `tabProject User` INNER JOIN `tabProject` ON `tabProject`.project_name = `tabProject User`.parent WHERE (`tabProject`.frequency = "Daily") AND (`tabProject`.daily_time_to_send BETWEEN DATE_ADD(curtime(), INTERVAL -15 MINUTE) AND DATE_ADD(curtime(), INTERVAL 15 MINUTE)) AND `tabProject`.collect_progress = 1;""") - create_project_update(project) + fields = ["daily_time_to_send"] + projects = get_projects_for_collect_progress("Daily", fields) -@frappe.whitelist() -def weekly(): - today = datetime.datetime.now().strftime("%A") - project = frappe.db.sql("""SELECT `tabProject User`.user FROM `tabProject User` INNER JOIN `tabProject` ON `tabProject`.project_name = `tabProject User`.parent WHERE (`tabProject`.frequency = "Weekly") AND (`tabProject`.day_to_send = %s) AND (`tabProject`.weekly_time_to_send BETWEEN DATE_ADD(curtime(), INTERVAL -15 MINUTE) AND DATE_ADD(curtime(), INTERVAL 15 MINUTE)) AND `tabProject`.collect_progress = 1""", today) - create_project_update(project) + for project in projects: + if not check_project_update_exists(project.name, project.get("daily_time_to_send")): + send_project_update_email_to_users(project.name) -#Call this function in order to generate the Project Update for a specific project -def create_project_update(project): - data = [] - date_today = datetime.date.today() - time_now = frappe.utils.now_datetime().strftime('%H:%M:%S') - for projects in project: - project_update_dict = { - "doctype" : "Project Update", - "project" : projects[0], - "date": date_today, - "time": time_now, - "naming_series": "UPDATE-.project.-.YY.MM.DD.-" +def twice_daily_reminder(): + fields = ["first_email", "second_email"] + projects = get_projects_for_collect_progress("Twice Daily", fields) + + for project in projects: + for d in fields: + if not check_project_update_exists(project.name, project.get(d)): + send_project_update_email_to_users(project.name) + +def weekly_reminder(): + fields = ["day_to_send", "weekly_time_to_send"] + projects = get_projects_for_collect_progress("Weekly", fields) + + current_day = get_datetime().strftime("%A") + for project in projects: + if current_day != project.day_to_send: + continue + + if not check_project_update_exists(project.name, project.get("weekly_time_to_send")): + send_project_update_email_to_users(project.name) + +def check_project_update_exists(project, time): + data = frappe.db.sql(""" SELECT name from `tabProject Update` + WHERE project = %s and date = %s and time >= %s """, (project, today(), time)) + + return True if data and data[0][0] else False + +def get_projects_for_collect_progress(frequency, fields): + fields.extend(["name"]) + + return frappe.get_all("Project", fields = fields, + filters = {'collect_progress': 1, 'frequency': frequency}) + +def send_project_update_email_to_users(project): + doc = frappe.get_doc('Project', project) + + if is_holiday_today(doc.holiday_list) or not doc.users: return + + project_update = frappe.get_doc({ + "doctype" : "Project Update", + "project" : project, + "sent": 0, + "date": today(), + "time": nowtime(), + "naming_series": "UPDATE-.project.-.YY.MM.DD.-", + }).insert() + + subject = "Project %s update your project status" % (project) + + incoming_email_account = frappe.db.get_value('Email Account', + dict(enable_incoming=1, default_incoming=1), 'email_id') + + frappe.sendmail(recipients=get_users_email(doc), + message=doc.message, + subject=_(subject), + reference_doctype=project_update.doctype, + reference_name=project_update.name, + reply_to=incoming_email_account + ) + +def collect_project_status(): + for data in frappe.get_all("Project Update", + {'date': today(), 'sent': 0}): + replies = frappe.get_all('Communication', + fields=['content', 'text_content', 'sender'], + filters=dict(reference_doctype="Project Update", + reference_name=data.name, + communication_type='Communication', + sent_or_received='Received'), + order_by='creation asc') + + for d in replies: + doc = frappe.get_doc("Project Update", data.name) + user_data = frappe.db.get_values("User", {"email": d.sender}, + ["full_name", "user_image", "name"], as_dict=True)[0] + + doc.append("users", { + 'user': user_data.name, + 'full_name': user_data.full_name, + 'image': user_data.user_image, + 'project_status': frappe.utils.md_to_html( + EmailReplyParser.parse_reply(d.text_content) or d.content + ) + }) + + doc.save(ignore_permissions=True) + +def send_project_status_email_to_users(): + yesterday = today() + + for d in frappe.get_all("Project Update", + {'date': yesterday, 'sent': 0}): + doc = frappe.get_doc("Project Update", d.name) + + project_doc = frappe.get_doc('Project', doc.project) + + args = { + "users": doc.users, + "title": _("Project Summary for {0}").format(yesterday) } - project_update = frappe.get_doc(project_update_dict) - project_update.insert() - #you can edit your local_host - local_host = "http://localhost:8003" - project_update_url = "" % (local_host +"/desk#Form/Project%20Update/" + (project_update.name)) + ("CREATE PROJECT UPDATE" + "") - data.append(project_update_url) - email = frappe.db.sql("""SELECT user from `tabProject User` WHERE parent = %s;""", project[0]) - for emails in email: - frappe.sendmail( - recipients=emails, - subject=frappe._(projects[0]), - header=[frappe._("Please Update your Project Status"), 'blue'], - message= project_update_url - ) - return data + frappe.sendmail(recipients=get_users_email(project_doc), + template='daily_project_summary', + args=args, + subject=_("Daily Project Summary for {0}").format(d.name), + reference_doctype="Project Update", + reference_name=d.name) + + doc.db_set('sent', 1) def update_project_sales_billing(): sales_update_frequency = frappe.db.get_single_value("Selling Settings", "sales_update_frequency") diff --git a/erpnext/projects/doctype/project_update/project_update.json b/erpnext/projects/doctype/project_update/project_update.json index 8bcf397efe8..497b2b73285 100644 --- a/erpnext/projects/doctype/project_update/project_update.json +++ b/erpnext/projects/doctype/project_update/project_update.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -13,6 +14,40 @@ "editable_grid": 1, "engine": "InnoDB", "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "naming_series", + "fieldtype": "Data", + "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": "Series", + "length": 0, + "no_copy": 0, + "options": "PROJ-UPD-.YYYY.-", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -46,6 +81,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "sent", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Sent", + "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, @@ -172,39 +240,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "progress", - "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": "How is the Project Progressing Right Now?", - "length": 0, - "no_copy": 0, - "options": "Not Updated\nGreat/Quickly\nGood/Steady\nChallenging/Slow\nProblematic/Stuck", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -238,38 +273,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "progress_details", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Progress Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -301,40 +304,6 @@ "set_only_once": 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": "", - "fieldname": "naming_series", - "fieldtype": "Data", - "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": "Series", - "length": 0, - "no_copy": 0, - "options": "PROJ-UPD-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 } ], "has_web_view": 0, @@ -347,7 +316,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 14:44:21.287709", + "modified": "2019-01-16 19:31:05.210656", "modified_by": "Administrator", "module": "Projects", "name": "Project Update", diff --git a/erpnext/projects/doctype/project_user/project_user.json b/erpnext/projects/doctype/project_user/project_user.json index 596681676f7..458028ff519 100644 --- a/erpnext/projects/doctype/project_user/project_user.json +++ b/erpnext/projects/doctype/project_user/project_user.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -44,6 +45,136 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "user.email", + "fieldname": "email", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Email", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "user.user_image", + "fieldname": "image", + "fieldtype": "Read Only", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Image", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "user.full_name", + "fieldname": "full_name", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Full Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -107,6 +238,70 @@ "set_only_once": 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": "section_break_5", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:parent.doctype == 'Project Update'", + "fieldname": "project_status", + "fieldtype": "Text", + "hidden": 0, + "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 Status", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 } ], "has_web_view": 0, @@ -119,7 +314,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-09-09 12:39:38.376816", + "modified": "2019-01-17 17:10:05.339735", "modified_by": "Administrator", "module": "Projects", "name": "Project User", diff --git a/erpnext/templates/emails/daily_project_summary.html b/erpnext/templates/emails/daily_project_summary.html new file mode 100644 index 00000000000..8b60830db62 --- /dev/null +++ b/erpnext/templates/emails/daily_project_summary.html @@ -0,0 +1,46 @@ + + +

{{ title }}

+ +
+{% for user in users %} + + + + + + + + + + +
+ {% if user.image %} + + {% else %} +
+ {{ user.full_name[0] }} +
+ {% endif %} +
+
+ {{ user.full_name }} +
+
+ + + + + + + + +
+
+ {{ user.project_status }} +
+
+ + +
+{% endfor %} \ No newline at end of file From 5a87ac0ad839c1553e8e8dd267cc98cff715ffd8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Jan 2019 18:34:09 +0530 Subject: [PATCH 085/112] fix: Inter company stock transfer for serialised items --- erpnext/stock/doctype/serial_no/serial_no.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 1568a7ae4d2..491ddeb89da 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -302,6 +302,9 @@ def has_duplicate_serial_no(sn, sle): if sn.warehouse: return True + if sn.company != sle.company: + return False + status = False if sn.purchase_document_no: if sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and \ @@ -357,6 +360,7 @@ def auto_make_serial_nos(args): sr.warehouse = args.get('warehouse') if args.get('actual_qty', 0) > 0 else None sr.batch_no = args.get('batch_no') sr.location = args.get('location') + sr.company = args.get('company') if sr.sales_order and args.get('voucher_type') == "Stock Entry" \ and not args.get('actual_qty', 0) > 0: sr.sales_order = None From 30667c160deb1d675affe47b17c1fba2b1dad503 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Jan 2019 19:19:17 +0530 Subject: [PATCH 086/112] test: Inter company stock transfer for serialised items --- erpnext/stock/doctype/serial_no/serial_no.py | 16 ++++++------- .../stock/doctype/serial_no/test_serial_no.py | 24 +++++++++++++++++++ .../stock/doctype/warehouse/test_warehouse.py | 16 +++++++++---- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 491ddeb89da..8a797c3ebcd 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -139,7 +139,7 @@ class SerialNo(StockController): order by posting_date desc, posting_time desc, name desc""", ("%%%s%%" % self.name, self.item_code), as_dict=1): if self.name.upper() in get_serial_nos(sle.serial_no): - if cint(sle.actual_qty) > 0: + if sle.actual_qty > 0: sle_dict.setdefault("incoming", []).append(sle) else: sle_dict.setdefault("outgoing", []).append(sle) @@ -218,7 +218,7 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no, sle.item_code), SerialNoItemError) - if cint(sle.actual_qty) > 0 and has_duplicate_serial_no(sr, sle): + if sle.actual_qty > 0 and has_duplicate_serial_no(sr, sle): frappe.throw(_("Serial No {0} has already been received").format(serial_no), SerialNoDuplicateError) @@ -228,7 +228,7 @@ def validate_serial_no(sle, item_det): if return_against and return_against != sr.delivery_document_no: frappe.throw(_("Serial no {0} has been already returned").format(sr.name)) - if cint(sle.actual_qty) < 0: + if sle.actual_qty < 0: if sr.warehouse!=sle.warehouse: frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no, sle.warehouse), SerialNoWarehouseError) @@ -279,16 +279,16 @@ def validate_serial_no(sle, item_det): "parent": sales_invoice, "item_code": sle.item_code}, "sales_order") if sales_order and get_reserved_qty_for_so(sales_order, sle.item_code): validate_so_serial_no(sr, sales_order) - elif cint(sle.actual_qty) < 0: + elif sle.actual_qty < 0: # transfer out frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError) - elif cint(sle.actual_qty) < 0 or not item_det.serial_no_series: + elif sle.actual_qty < 0 or not item_det.serial_no_series: frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code), SerialNoRequiredError) elif serial_nos: for serial_no in serial_nos: sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1) - if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse: + if sr and sle.actual_qty < 0 and sr.warehouse != sle.warehouse: frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) @@ -323,7 +323,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): in Manufacture / Repack type Stock Entry """ allow_serial_nos = False - if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0: + if sle.voucher_type=="Stock Entry" and sle.actual_qty > 0: stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) if stock_entry.purpose in ("Repack", "Manufacture"): for d in stock_entry.get("items"): @@ -335,7 +335,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): return allow_serial_nos def update_serial_nos(sle, item_det): - if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \ + if sle.is_cancelled == "No" and not sle.serial_no and sle.actual_qty > 0 \ and item_det.has_serial_no == 1 and item_det.serial_no_series: serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) frappe.db.set(sle, "serial_no", serial_nos) diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index 89062f9ec8e..ed70790b2ca 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -7,6 +7,12 @@ from __future__ import unicode_literals import frappe, unittest +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + test_dependencies = ["Item"] test_records = frappe.get_test_records('Serial No') @@ -29,3 +35,21 @@ class TestSerialNo(unittest.TestCase): sr.warehouse = "_Test Warehouse - _TC" self.assertTrue(SerialNoCannotCannotChangeError, sr.save) + + def test_inter_company_transfer(self): + se = make_serialized_item(target_warehouse="_Test Warehouse - _TC") + serial_nos = get_serial_nos(se.get("items")[0].serial_no) + + create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]) + + wh = create_warehouse("_Test Warehouse", company="_Test Company 1") + make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0], + company="_Test Company 1", warehouse=wh) + + serial_no = frappe.db.get_value("Serial No", serial_nos[0], ["warehouse", "company"], as_dict=1) + + self.assertEqual(serial_no.warehouse, wh) + self.assertEqual(serial_no.company, "_Test Company 1") + + def tearDown(self): + frappe.db.rollback() \ No newline at end of file diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index b033f86d8e3..dc39e101ce0 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -8,6 +8,7 @@ from erpnext import set_perpetual_inventory from frappe.test_runner import make_test_records from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account +import erpnext import frappe import unittest test_records = frappe.get_test_records('Warehouse') @@ -90,17 +91,24 @@ class TestWarehouse(unittest.TestCase): self.assertTrue(frappe.db.get_value("Warehouse", filters={"account": "Test Warehouse for Merging 2 - _TC"})) -def create_warehouse(warehouse_name, properties=None): - if not frappe.db.exists("Warehouse", warehouse_name + " - _TC"): +def create_warehouse(warehouse_name, properties=None, company=None): + if not company: + company = "_Test Company" + + warehouse_id = erpnext.encode_company_abbr(warehouse_name, company) + if not frappe.db.exists("Warehouse", warehouse_id): w = frappe.new_doc("Warehouse") w.warehouse_name = warehouse_name w.parent_warehouse = "_Test Warehouse Group - _TC" - w.company = "_Test Company" + w.company = company make_account_for_warehouse(warehouse_name, w) - w.account = warehouse_name + " - _TC" + w.account = warehouse_id if properties: w.update(properties) w.save() + return w.name + else: + return warehouse_id def make_account_for_warehouse(warehouse_name, warehouse_obj): if not frappe.db.exists("Account", warehouse_name + " - _TC"): From 6202d0ed687a282571461cbf19e0d5e7d26b068b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 18 Jan 2019 09:40:24 +0530 Subject: [PATCH 087/112] fix: NoneType comparison with int error --- erpnext/stock/doctype/serial_no/serial_no.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 8a797c3ebcd..491ddeb89da 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -139,7 +139,7 @@ class SerialNo(StockController): order by posting_date desc, posting_time desc, name 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: + if cint(sle.actual_qty) > 0: sle_dict.setdefault("incoming", []).append(sle) else: sle_dict.setdefault("outgoing", []).append(sle) @@ -218,7 +218,7 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no, sle.item_code), SerialNoItemError) - if sle.actual_qty > 0 and has_duplicate_serial_no(sr, sle): + if cint(sle.actual_qty) > 0 and has_duplicate_serial_no(sr, sle): frappe.throw(_("Serial No {0} has already been received").format(serial_no), SerialNoDuplicateError) @@ -228,7 +228,7 @@ def validate_serial_no(sle, item_det): if return_against and return_against != sr.delivery_document_no: frappe.throw(_("Serial no {0} has been already returned").format(sr.name)) - if sle.actual_qty < 0: + if cint(sle.actual_qty) < 0: if sr.warehouse!=sle.warehouse: frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no, sle.warehouse), SerialNoWarehouseError) @@ -279,16 +279,16 @@ def validate_serial_no(sle, item_det): "parent": sales_invoice, "item_code": sle.item_code}, "sales_order") if sales_order and get_reserved_qty_for_so(sales_order, sle.item_code): validate_so_serial_no(sr, sales_order) - elif sle.actual_qty < 0: + elif cint(sle.actual_qty) < 0: # transfer out frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError) - elif sle.actual_qty < 0 or not item_det.serial_no_series: + elif cint(sle.actual_qty) < 0 or not item_det.serial_no_series: frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code), SerialNoRequiredError) elif serial_nos: for serial_no in serial_nos: sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse"], as_dict=1) - if sr and sle.actual_qty < 0 and sr.warehouse != sle.warehouse: + if sr and cint(sle.actual_qty) < 0 and sr.warehouse != sle.warehouse: frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) @@ -323,7 +323,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): in Manufacture / Repack type Stock Entry """ allow_serial_nos = False - if sle.voucher_type=="Stock Entry" and sle.actual_qty > 0: + if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0: stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) if stock_entry.purpose in ("Repack", "Manufacture"): for d in stock_entry.get("items"): @@ -335,7 +335,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): return allow_serial_nos def update_serial_nos(sle, item_det): - if sle.is_cancelled == "No" and not sle.serial_no and sle.actual_qty > 0 \ + if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \ and item_det.has_serial_no == 1 and item_det.serial_no_series: serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) frappe.db.set(sle, "serial_no", serial_nos) From 7e71e132a3640eb23f8562e24af83a00c9e7b6a6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 17 Jan 2019 19:26:25 +0530 Subject: [PATCH 088/112] Added patch --- erpnext/patches.txt | 3 ++- .../v11_0/renamed_from_to_fields_in_project.py | 13 +++++++++++++ erpnext/projects/doctype/project/project.py | 7 +++---- 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 erpnext/patches/v11_0/renamed_from_to_fields_in_project.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 7bf63a2290d..a703fd97900 100755 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -580,4 +580,5 @@ 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.v11_0.rename_additional_salary_component_additional_salary \ No newline at end of file +erpnext.patches.v11_0.rename_additional_salary_component_additional_salary +erpnext.patches.v11_0.renamed_from_to_fields_in_project \ No newline at end of file diff --git a/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py b/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py new file mode 100644 index 00000000000..4f684400025 --- /dev/null +++ b/erpnext/patches/v11_0/renamed_from_to_fields_in_project.py @@ -0,0 +1,13 @@ +# 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.model.utils.rename_field import rename_field + +def execute(): + frappe.reload_doc('projects', 'doctype', 'project') + + if frappe.db.has_column('Project', 'from'): + rename_field('Project', 'from', 'from_time') + rename_field('Project', 'to', 'to_time') \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index a988dc03fa4..9654ca345eb 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import frappe -import datetime from frappe import _ from six import iteritems from email_reply_parser import EmailReplyParser @@ -476,7 +475,7 @@ def send_project_update_email_to_users(project): "naming_series": "UPDATE-.project.-.YY.MM.DD.-", }).insert() - subject = "Project %s update your project status" % (project) + subject = "For project %s, update your status" % (project) incoming_email_account = frappe.db.get_value('Email Account', dict(enable_incoming=1, default_incoming=1), 'email_id') @@ -490,7 +489,7 @@ def send_project_update_email_to_users(project): ) def collect_project_status(): - for data in frappe.get_all("Project Update", + for data in frappe.get_all("Project Update", {'date': today(), 'sent': 0}): replies = frappe.get_all('Communication', fields=['content', 'text_content', 'sender'], @@ -517,7 +516,7 @@ def collect_project_status(): doc.save(ignore_permissions=True) def send_project_status_email_to_users(): - yesterday = today() + yesterday = add_days(today(), -1) for d in frappe.get_all("Project Update", {'date': yesterday, 'sent': 0}): From 442c85c006922d8d095c8186d3fdfcc0c2f615ac Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Fri, 18 Jan 2019 21:28:37 +0530 Subject: [PATCH 089/112] fix(marketplace): Capitalize marketplace factory class to support router changes (#16428) --- erpnext/public/js/hub/hub_factory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js index 7d9fefc8b9d..2efc8261106 100644 --- a/erpnext/public/js/hub/hub_factory.js +++ b/erpnext/public/js/hub/hub_factory.js @@ -1,6 +1,6 @@ frappe.provide('erpnext.hub'); -frappe.views.marketplaceFactory = class marketplaceFactory extends frappe.views.Factory { +frappe.views.MarketplaceFactory = class MarketplaceFactory extends frappe.views.Factory { show() { is_marketplace_disabled() .then(disabled => { From 79d4400c160f98ebe639081c4a59668c6f134b7a Mon Sep 17 00:00:00 2001 From: Himanshu Date: Sat, 19 Jan 2019 14:01:38 +0530 Subject: [PATCH 090/112] fix: Offline pos fix (#16424) * Offline POS fix * Fixed items search and load items --- erpnext/accounts/page/pos/pos.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index 04c8718ad9a..c89035c33f3 100755 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -1135,18 +1135,15 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }, apply_category: function() { - frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name", (r) => { - category = this.selected_item_group || r.name; - - if(category == r.name) { - return this.item_data - } else { - return this.item_data.filter(function(element, index, array){ - return element.item_group == category; - }); - } - }) - + var me = this; + category = this.selected_item_group || "All Item Groups"; + if(category == 'All Item Groups') { + return this.item_data + } else { + return this.item_data.filter(function(element, index, array){ + return element.item_group == category; + }); + } }, bind_items_event: function() { From ff1078f2710378e44bd680cabc5414869eeccd26 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 21 Jan 2019 09:43:56 +0530 Subject: [PATCH 091/112] fix(minor): fetch payment terms if voucher nos are available --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 22d4d94cd0c..95cb351d2e7 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -190,7 +190,7 @@ class ReceivablePayableReport(object): dn_details = get_dn_details(args.get("party_type"), voucher_nos) self.voucher_details = get_voucher_details(args.get("party_type"), voucher_nos, dn_details) - if self.filters.based_on_payment_terms: + if self.filters.based_on_payment_terms and gl_entries_data: self.payment_term_map = self.get_payment_term_detail(voucher_nos) for gle in gl_entries_data: From a39f324682e9923cd3ed851d39a4f5f4aef61f00 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 21 Jan 2019 18:50:50 +0530 Subject: [PATCH 092/112] fix: Disable rounded total field's value honours docfield's default value --- erpnext/public/js/controllers/buying.js | 20 ++++++++++--------- .../authorization_rule.json | 5 +++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index cefdbe7a32e..cb9c23bb628 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -28,7 +28,10 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ if (this.frm.doc.__islocal && frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) { - this.frm.set_value("disable_rounded_total", cint(frappe.sys_defaults.disable_rounded_total)); + + var df = frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total"); + var disable = df.default || cint(frappe.sys_defaults.disable_rounded_total); + this.frm.set_value("disable_rounded_total", disable); } /* eslint-disable */ @@ -119,7 +122,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ if (doc.doctype == "Purchase Order" && item.blanket_order_rate) { item_rate = item.blanket_order_rate; } - item.discount_amount = flt(item_rate) * flt(item.discount_percentage) / 100; + item.discount_amount = flt(item_rate) * flt(item.discount_percentage) / 100; item.rate = flt((item.price_list_rate) - (item.discount_amount), precision('rate', item)); this.calculate_taxes_and_totals(); @@ -266,26 +269,26 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ d.qty = d.qty - my_qty; cur_frm.doc.items[i].stock_qty = my_qty*cur_frm.doc.items[i].conversion_factor; cur_frm.doc.items[i].qty = my_qty; - + frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + cur_frm.doc.items[i].idx + ")"); if (qty > 0) { frappe.msgprint("Splitting " + qty + " units of " + d.item_code); var newrow = frappe.model.add_child(cur_frm.doc, cur_frm.doc.items[i].doctype, "items"); item_length++; - + for (var key in cur_frm.doc.items[i]) { newrow[key] = cur_frm.doc.items[i][key]; } - + newrow.idx = item_length; newrow["stock_qty"] = newrow.conversion_factor*qty; newrow["qty"] = qty; - + newrow["material_request"] = ""; newrow["material_request_item"] = ""; - + } } }); @@ -302,7 +305,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ if (doc.auto_repeat) { frappe.call({ method:"frappe.desk.doctype.auto_repeat.auto_repeat.update_reference", - args:{ + args:{ docname: doc.auto_repeat, reference:doc.name }, @@ -427,4 +430,3 @@ erpnext.buying.get_items_from_product_bundle = function(frm) { }); dialog.show(); } - diff --git a/erpnext/setup/doctype/authorization_rule/authorization_rule.json b/erpnext/setup/doctype/authorization_rule/authorization_rule.json index e70bf5cb47d..caca56c897a 100644 --- a/erpnext/setup/doctype/authorization_rule/authorization_rule.json +++ b/erpnext/setup/doctype/authorization_rule/authorization_rule.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, @@ -352,7 +353,7 @@ "fieldname": "to_emp", "fieldtype": "Link", "hidden": 0, - "ignore_user_permissions": 0, + "ignore_user_permissions": 1, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, @@ -618,7 +619,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 16:15:42.627233", + "modified": "2019-01-21 17:10:39.822125", "modified_by": "Administrator", "module": "Setup", "name": "Authorization Rule", From 11ce45077d72316c359bcc8fe8c6ab8577f01f59 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 21 Jan 2019 18:53:05 +0530 Subject: [PATCH 093/112] [Fix] Not able to submit work order --- erpnext/manufacturing/doctype/work_order/work_order.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 7b2f9a4bff3..22d74e8ee6e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -349,7 +349,8 @@ frappe.ui.form.on("Work Order", { before_submit: function(frm) { frm.toggle_reqd(["fg_warehouse", "wip_warehouse"], true); frm.fields_dict.required_items.grid.toggle_reqd("source_warehouse", true); - frm.toggle_reqd("transfer_material_against", frm.doc.operations); + frm.toggle_reqd("transfer_material_against", + frm.doc.operations && frm.doc.operations.length > 0); frm.fields_dict.operations.grid.toggle_reqd("workstation", frm.doc.operations); }, From de718dacb21ad3592160848cc289056e7fb0455b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 21 Jan 2019 19:47:17 +0530 Subject: [PATCH 094/112] fix: Code cleanup --- erpnext/accounts/doctype/sales_invoice/pos.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 7348e1f8ece..287da08ef5f 100755 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -250,10 +250,12 @@ def get_serial_no_data(pos_profile, company): cond = "1=1" if pos_profile.get('update_stock') and pos_profile.get('warehouse'): - cond = "warehouse = '{0}'".format(pos_profile.get('warehouse')) + cond = "warehouse = %(warehouse)s" - serial_nos = frappe.db.sql("""select name, warehouse, item_code from `tabSerial No` where {0} - and company = %(company)s """.format(cond), {'company': company}, as_dict=1) + serial_nos = frappe.db.sql("""select name, warehouse, item_code + from `tabSerial No` where {0} and company = %(company)s """.format(cond),{ + 'company': company, 'warehouse': frappe.db.escape(pos_profile.get('warehouse')) + }, as_dict=1) itemwise_serial_no = {} for sn in serial_nos: From a334ed879f5a781a5ac985af555b4443dd5adfe1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 22 Jan 2019 10:41:50 +0530 Subject: [PATCH 095/112] Update __init__.py --- erpnext/regional/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 510ed587667..1bd3357a81b 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -7,5 +7,5 @@ from erpnext import get_region def check_deletion_permission(doc, method): region = get_region() - if region in ["Nepal", "France"]: - frappe.throw(_("Deletion is not permitted for country {0}".format(region))) \ No newline at end of file + if region in ["Nepal", "France"] and doc.docstatus != 0: + frappe.throw(_("Deletion is not permitted for country {0}".format(region))) From d157ae17b741d4237f9f3b75646bcf06df46c650 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 22 Jan 2019 13:26:13 +0530 Subject: [PATCH 096/112] fix(ar-summary-report): Changes to column list to match recent changes in AR report --- .../accounts_receivable_summary/accounts_receivable_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 190031abb8c..842ecdb8e93 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -180,7 +180,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): def get_voucherwise_data(self, party_naming_by, args): voucherwise_data = ReceivablePayableReport(self.filters).run(args)[1] - cols = ["posting_date", "party"] + cols = ["posting_date", "party", "customer-contact"] if party_naming_by == "Naming Series": cols += ["party_name"] From ddd9136d10c006ad8b596128bbe5844864ecd613 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 17 Jan 2019 17:53:58 +0530 Subject: [PATCH 097/112] Renamed field and added finance book column in the trial balance simple report --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 7 ------- erpnext/accounts/party.py | 2 +- erpnext/accounts/report/trial_balance/trial_balance.js | 4 ++-- erpnext/accounts/report/trial_balance/trial_balance.py | 7 ++++--- erpnext/accounts/report/trial_balance_simple/__init__.py | 0 .../report/trial_balance_simple/trial_balance_simple.json | 4 ++-- 6 files changed, 9 insertions(+), 15 deletions(-) create mode 100644 erpnext/accounts/report/trial_balance_simple/__init__.py diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 15cc3fdbb9d..30ae54b18b0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -30,13 +30,6 @@ frappe.ui.form.on('Payment Entry', { } } }); - frm.set_query("bank_account", function() { - return{ - "filters": { - "party": frm.doc.party, - } - } - }); frm.set_query("contact_person", function() { if (frm.doc.party) { return { diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index eb1146ba1ac..acb5dd1c09b 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -567,7 +567,7 @@ def get_partywise_advanced_payment_amount(party_type="Customer"): data = frappe.db.sql(""" SELECT party, sum({0}) as amount FROM `tabGL Entry` WHERE party_type = %s and against_voucher is null GROUP BY party""" - .format(("credit - debit") if party_type == "Customer" else "debit") , party_type, debug=1) + .format(("credit - debit") if party_type == "Customer" else "debit") , party_type) if data: return frappe._dict(data) \ No newline at end of file diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js index 194f1bd497c..cdc77456d0a 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.js +++ b/erpnext/accounts/report/trial_balance/trial_balance.js @@ -83,8 +83,8 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { "fieldtype": "Check" }, { - "fieldname": "include_non_finance_book_entries", - "label": __("Include Non Finance Book Entries"), + "fieldname": "include_default_book_entries", + "label": __("Include Default Book Entries"), "fieldtype": "Check" } ], diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 696e63bb2cf..6b18c5d8731 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -104,8 +104,8 @@ def get_rootwise_opening_balances(filters, report_type): if filters.finance_book: fb_conditions = " and finance_book = %(finance_book)s" - if filters.include_non_finance_book_entries: - fb_conditions = " and (finance_book = %(finance_book)s or finance_book is null)" + if filters.include_default_book_entries: + fb_conditions = " and (finance_book in (%(finance_book)s, %(company_fb)s) or finance_book is null)" additional_conditions += fb_conditions @@ -124,7 +124,8 @@ def get_rootwise_opening_balances(filters, report_type): "from_date": filters.from_date, "report_type": report_type, "year_start_date": filters.year_start_date, - "finance_book": filters.finance_book + "finance_book": filters.finance_book, + "company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book') }, as_dict=True) diff --git a/erpnext/accounts/report/trial_balance_simple/__init__.py b/erpnext/accounts/report/trial_balance_simple/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json b/erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json index ea5a97b1067..fab3a76a0c4 100644 --- a/erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json +++ b/erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json @@ -6,13 +6,13 @@ "doctype": "Report", "idx": 0, "is_standard": "Yes", - "modified": "2018-11-22 17:40:11.317567", + "modified": "2019-01-17 17:20:42.374958", "modified_by": "Administrator", "module": "Accounts", "name": "Trial Balance (Simple)", "owner": "Administrator", "prepared_report": 0, - "query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\"\nfrom `tabGL Entry`\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account", + "query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\",\n\tfinance_book as \"Finance Book:Link/Finance Book:140\"\nfrom `tabGL Entry`\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account", "ref_doctype": "GL Entry", "report_name": "Trial Balance (Simple)", "report_type": "Query Report", From 1e61dd024b13bf809d4cf5cb3ee878df8878c51c Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 22 Jan 2019 15:53:48 +0550 Subject: [PATCH 098/112] bumped to version 10.1.78 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 26755dde61f..db9ca3309e9 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.77' +__version__ = '10.1.78' def get_default_company(user=None): '''Get default company for user''' From a5270e561fd28a176eb9941ae4b9ed6bf6b97e18 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 22 Jan 2019 15:43:04 +0530 Subject: [PATCH 099/112] fix: Unicode issue in purchase invoice --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index d28dc936bb0..f2d5006cd0a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1,7 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt +# -*- coding: utf-8 -*- - +from __future__ import unicode_literals import frappe, erpnext from frappe.utils import cint, cstr, formatdate, flt, getdate, nowdate from frappe import _, throw From 64d774d178d4205963307127dd1ec1b336d9ba94 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 22 Jan 2019 16:05:56 +0550 Subject: [PATCH 100/112] bumped to version 11.0.3-beta.35 --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index f81cb416eb1..bbadd7f150f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" develop_version = '12.x.x-develop' -staging_version = '11.0.3-beta.34' +staging_version = '11.0.3-beta.35' error_report_email = "support@erpnext.com" From ecf64677991ace56b29bd1002b47b7476b704ce3 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 22 Jan 2019 17:41:56 +0530 Subject: [PATCH 101/112] fix(travis): Redis must be working for caching, so reverting (#16455) --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index e21d59511a9..9e63c0e6b04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,16 +30,16 @@ before_script: - cd ~/frappe-bench - bench get-app erpnext $TRAVIS_BUILD_DIR - bench use test_site + - bench reinstall --yes + - bench scheduler disable + - sed -i 's/9000/9001/g' sites/common_site_config.json + - bench start & + - sleep 10 jobs: include: - stage: test script: - - bench reinstall --yes - - bench scheduler disable - - sed -i 's/9000/9001/g' sites/common_site_config.json - - bench start & - - sleep 10 - set -e - bench run-tests env: Server Side Test From 2a9a867f053624fc35032dfd0d45196c155f4a59 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 22 Jan 2019 18:35:44 +0550 Subject: [PATCH 102/112] bumped to version 10.1.79 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index db9ca3309e9..ff8ca747900 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.78' +__version__ = '10.1.79' def get_default_company(user=None): '''Get default company for user''' From 5555b2802f6d3e91f03cce562c9457604528c0d7 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 22 Jan 2019 18:37:09 +0550 Subject: [PATCH 103/112] bumped to version 11.0.3-beta.36 --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bbadd7f150f..cd352de3940 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" develop_version = '12.x.x-develop' -staging_version = '11.0.3-beta.35' +staging_version = '11.0.3-beta.36' error_report_email = "support@erpnext.com" From da32916396721c3592044ef6ddef5c6316667a38 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 22 Jan 2019 19:06:06 +0530 Subject: [PATCH 104/112] fix: Enforce pricing rule based on rate on server side --- erpnext/controllers/accounts_controller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cd373b84d7e..f4cea4ad964 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -248,7 +248,6 @@ class AccountsController(TransactionBase): if self.get("is_subcontracted"): args["is_subcontracted"] = self.is_subcontracted ret = get_item_details(args) - for fieldname, value in ret.items(): if item.meta.get_field(fieldname) and value is not None: if (item.get(fieldname) is None or fieldname in force_item_fields): @@ -267,9 +266,10 @@ class AccountsController(TransactionBase): if ret.get("pricing_rule"): # if user changed the discount percentage then set user's discount percentage ? + item.set("pricing_rule", ret.get("pricing_rule")) item.set("discount_percentage", ret.get("discount_percentage")) - if ret.get("pricing_rule_for") == "Price": - item.set("pricing_list_rate", ret.get("pricing_list_rate")) + if ret.get("pricing_rule_for") == "Rate": + item.set("price_list_rate", ret.get("price_list_rate")) if item.price_list_rate: item.rate = flt(item.price_list_rate * From 237a871f17787b6a8842a3f3ac1e3efb3072d89a Mon Sep 17 00:00:00 2001 From: "FinByz Tech Pvt. Ltd" Date: Tue, 22 Jan 2019 20:49:06 +0530 Subject: [PATCH 105/112] fix: NoneType object has no attribute "gstin" (#16458) * fix: NoneType object has no attribute "gstin" * fix: handle NoneType values --- 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 fd0eb34abc9..d7a025805c2 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -5,7 +5,7 @@ from erpnext.regional.india import states, state_numbers from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount def validate_gstin_for_india(doc, method): - if not hasattr(doc, 'gstin'): + if not hasattr(doc, 'gstin') or not doc.gstin: return doc.gstin = doc.gstin.upper().strip() From 4b4265f8a7521a55d0c250c660412f3b8afe03b0 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Tue, 22 Jan 2019 20:54:21 +0530 Subject: [PATCH 106/112] fix(dead-code): Remove .py files (#16457) --- erpnext/crm/doctype/lead/.py | 9 --------- erpnext/manufacturing/doctype/work_order/.py | 8 -------- 2 files changed, 17 deletions(-) delete mode 100644 erpnext/crm/doctype/lead/.py delete mode 100644 erpnext/manufacturing/doctype/work_order/.py diff --git a/erpnext/crm/doctype/lead/.py b/erpnext/crm/doctype/lead/.py deleted file mode 100644 index 70a6b22a99f..00000000000 --- a/erpnext/crm/doctype/lead/.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class Lead(Document): - pass diff --git a/erpnext/manufacturing/doctype/work_order/.py b/erpnext/manufacturing/doctype/work_order/.py deleted file mode 100644 index 4476b16dbc9..00000000000 --- a/erpnext/manufacturing/doctype/work_order/.py +++ /dev/null @@ -1,8 +0,0 @@ -import frappe - -def set_required_items(production_order): - pass - -def reserve_for_production(production_order): - '''Reserve pending raw materials for production''' - pass \ No newline at end of file From 55a08dee785da2ba6239d20e2a2d7a1f8634530e Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 23 Jan 2019 00:28:02 +0530 Subject: [PATCH 107/112] style: Remove unusued local variable --- erpnext/stock/stock_balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 09d4e438406..045bee52d92 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -257,7 +257,7 @@ def repost_all_stock_vouchers(): doc.update_stock_ledger() doc.make_gl_entries(repost_future_gle=False) frappe.db.commit() - except Exception as e: + except Exception: print(frappe.get_traceback()) rejected.append([voucher_type, voucher_no]) frappe.db.rollback() From f79937d64b7ae9a61fd138440e1b33585118398f Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 23 Jan 2019 00:28:37 +0530 Subject: [PATCH 108/112] style: Remove unused imports --- erpnext/accounts/party.py | 2 +- erpnext/controllers/item_variant.py | 2 -- erpnext/controllers/selling_controller.py | 2 +- erpnext/controllers/stock_controller.py | 2 +- erpnext/hub_node/api.py | 4 ---- erpnext/shopping_cart/product_info.py | 2 +- erpnext/startup/__init__.py | 1 - erpnext/templates/utils.py | 5 ++--- erpnext/tests/test_woocommerce.py | 2 +- erpnext/www/payment_setup_certification.py | 1 - 10 files changed, 7 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index a69a1722845..1d1eb21eaca 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -14,7 +14,7 @@ from frappe.contacts.doctype.address.address import (get_address_display, from frappe.contacts.doctype.contact.contact import get_contact_details, get_default_contact from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrency from erpnext.accounts.utils import get_fiscal_year -from erpnext import get_default_currency, get_company_currency +from erpnext import get_company_currency from six import iteritems diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 24726ad03b1..646f23879a9 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -260,8 +260,6 @@ def generate_keyed_value_combinations(args): return results def copy_attributes_to_variant(item, variant): - from frappe.model import no_value_fields - # copy non no-copy fields exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 684a2cdbcbe..a9883017cfa 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -8,7 +8,7 @@ from frappe import _, throw from erpnext.stock.get_item_details import get_bin_details from erpnext.stock.utils import get_incoming_rate from erpnext.stock.get_item_details import get_conversion_factor -from erpnext.stock.doctype.item.item import get_item_defaults, set_item_default +from erpnext.stock.doctype.item.item import set_item_default from frappe.contacts.doctype.address.address import get_address_display from erpnext.controllers.stock_controller import StockController diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 0b19b7aaf9a..0a3cd3499b1 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe.utils import cint, flt, cstr -from frappe import msgprint, _ +from frappe import _ import frappe.defaults from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index c236822490d..0c94df31596 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -2,10 +2,6 @@ from __future__ import unicode_literals import frappe import json -import io -import base64 -import os -import requests from frappe import _ from frappe.frappeclient import FrappeClient diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py index b5f129b4aa2..3af5afa0442 100644 --- a/erpnext/shopping_cart/product_info.py +++ b/erpnext/shopping_cart/product_info.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from erpnext.shopping_cart.cart import _get_cart_quotation from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings \ - import is_cart_enabled, get_shopping_cart_settings, show_quantity_in_website + import get_shopping_cart_settings, show_quantity_in_website from erpnext.utilities.product import get_price, get_qty_in_stock @frappe.whitelist(allow_guest=True) diff --git a/erpnext/startup/__init__.py b/erpnext/startup/__init__.py index b65fc0f10e4..deef4ba4d68 100644 --- a/erpnext/startup/__init__.py +++ b/erpnext/startup/__init__.py @@ -19,7 +19,6 @@ # default settings that can be made for a user. from __future__ import unicode_literals -import frappe product_name = "ERPNext" user_defaults = { diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py index eb84bcc8d85..03060201bbe 100644 --- a/erpnext/templates/utils.py +++ b/erpnext/templates/utils.py @@ -3,9 +3,8 @@ from __future__ import unicode_literals -import frappe, json -from frappe import _ -from frappe.utils import cint, formatdate +import frappe + @frappe.whitelist(allow_guest=True) def send_message(subject="Website Query", message="", sender="", status="Open"): diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py index 0347e953f49..a48d48cde13 100644 --- a/erpnext/tests/test_woocommerce.py +++ b/erpnext/tests/test_woocommerce.py @@ -1,4 +1,4 @@ -import unittest, frappe, requests, os, time, erpnext +import unittest, frappe, requests, os, time from erpnext.erpnext_integrations.connectors.woocommerce_connection import order class TestWoocommerce(unittest.TestCase): diff --git a/erpnext/www/payment_setup_certification.py b/erpnext/www/payment_setup_certification.py index 185c2209422..c65cddb5cac 100644 --- a/erpnext/www/payment_setup_certification.py +++ b/erpnext/www/payment_setup_certification.py @@ -1,5 +1,4 @@ import frappe -import foundation no_cache = 1 From 819a16d0f56a011e216c869ebf9107f8cb0e7571 Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Wed, 23 Jan 2019 07:57:08 +0530 Subject: [PATCH 109/112] fix(dead-code): Remove .py files (#16462) --- erpnext/crm/doctype/lead/.py | 9 --------- erpnext/manufacturing/doctype/production_order/.py | 8 -------- 2 files changed, 17 deletions(-) delete mode 100644 erpnext/crm/doctype/lead/.py delete mode 100644 erpnext/manufacturing/doctype/production_order/.py diff --git a/erpnext/crm/doctype/lead/.py b/erpnext/crm/doctype/lead/.py deleted file mode 100644 index 70a6b22a99f..00000000000 --- a/erpnext/crm/doctype/lead/.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class Lead(Document): - pass diff --git a/erpnext/manufacturing/doctype/production_order/.py b/erpnext/manufacturing/doctype/production_order/.py deleted file mode 100644 index 4476b16dbc9..00000000000 --- a/erpnext/manufacturing/doctype/production_order/.py +++ /dev/null @@ -1,8 +0,0 @@ -import frappe - -def set_required_items(production_order): - pass - -def reserve_for_production(production_order): - '''Reserve pending raw materials for production''' - pass \ No newline at end of file From 49e09624d675c13538cc571169766569f5d95bd9 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 23 Jan 2019 10:43:00 +0530 Subject: [PATCH 110/112] fix(projects): None type handling while updating tasks --- erpnext/projects/doctype/project/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 9654ca345eb..dcf485a8030 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -162,7 +162,7 @@ class Project(Document): def is_row_updated(self, row, existing_task_data, fields): if self.get("__islocal") or not existing_task_data: return True - d = existing_task_data.get(row.task_id) + d = existing_task_data.get(row.task_id, {}) for field in fields: if row.get(field) != d.get(field): From 342854f6f2388599f0ccaf1b8ea4ba72bee08880 Mon Sep 17 00:00:00 2001 From: Frappe Bot Date: Wed, 23 Jan 2019 08:08:53 +0000 Subject: [PATCH 111/112] bumped to version 10.1.80 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index ff8ca747900..847618232c7 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.79' +__version__ = '10.1.80' def get_default_company(user=None): '''Get default company for user''' From 2d5bf69dc445a26f59b9cd4516b5b31f3be2c17f Mon Sep 17 00:00:00 2001 From: Frappe Bot Date: Wed, 23 Jan 2019 08:12:19 +0000 Subject: [PATCH 112/112] bumped to version 11.0.3-beta.37 --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index cd352de3940..3c3750a4fbb 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" develop_version = '12.x.x-develop' -staging_version = '11.0.3-beta.36' +staging_version = '11.0.3-beta.37' error_report_email = "support@erpnext.com"