From 0f0b1216699e7ea279f1eb220e506172d95bc50c Mon Sep 17 00:00:00 2001 From: Ben Cornwell-Mott Date: Tue, 23 May 2017 15:15:17 -0700 Subject: [PATCH 01/27] Added BOM UOM selection for items Added patch for BOM Item UOM Fixed scrap qty issue Added Scrap Qty update to patch Reverted test record for production order --- erpnext/controllers/buying_controller.py | 2 +- erpnext/manufacturing/doctype/bom/bom.js | 17 ++ erpnext/manufacturing/doctype/bom/bom.py | 39 ++- .../doctype/bom/test_records.json | 16 +- .../doctype/bom_item/bom_item.json | 260 +++++++++++++----- .../bom_replace_tool/bom_replace_tool.py | 2 +- .../bom_scrap_item/bom_scrap_item.json | 49 +++- .../production_planning_tool.py | 2 +- .../test_production_planning_tool.py | 26 +- .../production_order_stock_report.py | 2 +- erpnext/patches.txt | 3 +- .../update_stock_qty_value_in_bom_item.py | 10 + 12 files changed, 325 insertions(+), 103 deletions(-) create mode 100644 erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 5bc8bb38f86..3e35cdd4717 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -252,7 +252,7 @@ class BuyingController(StockController): def get_items_from_bom(self, item_code, bom): bom_items = frappe.db.sql("""select t2.item_code, - t2.qty / ifnull(t1.quantity, 1) as qty_consumed_per_unit, + t2.stock_qty / ifnull(t1.quantity, 1) as qty_consumed_per_unit, t2.rate, t2.stock_uom, t2.name, t2.description from `tabBOM` t1, `tabBOM Item` t2, tabItem t3 where t2.parent = t1.name and t1.item = %s diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 576e46df507..42970803aff 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -70,6 +70,15 @@ erpnext.bom.BomController = erpnext.TransactionController.extend({ get_bom_material_detail(doc, cdt, cdn, scrap_items); }, + conversion_factor: function(doc, cdt, cdn, dont_fetch_price_list_rate) { + if(frappe.meta.get_docfield(cdt, "stock_qty", cdn)) { + var item = frappe.get_doc(cdt, cdn); + frappe.model.round_floats_in(item, ["qty", "conversion_factor"]); + item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item)); + refresh_field("stock_qty", item.name, item.parentfield); + this.toggle_conversion_factor(item); + } + }, }) $.extend(cur_frm.cscript, new erpnext.bom.BomController({frm: cur_frm})); @@ -296,6 +305,14 @@ frappe.ui.form.on("BOM Operation", "workstation", function(frm, cdt, cdn) { }) }); +frappe.ui.form.on("BOM Item", "qty", function(frm, cdt, cdn) { + var d = locals[cdt][cdn]; + + d.stock_qty = d.qty * d.conversion_factor + refresh_field("items"); + }); + + frappe.ui.form.on("BOM Operation", "operations_remove", function(frm) { erpnext.bom.calculate_op_cost(frm.doc); erpnext.bom.calculate_total(frm.doc); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index b8a8ae8aea3..5c45e5d5b62 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -7,6 +7,7 @@ from frappe.utils import cint, cstr, flt 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 operator import itemgetter @@ -48,7 +49,7 @@ class BOM(WebsiteGenerator): self.set_conversion_rate() from erpnext.utilities.transaction_base import validate_uom_is_integer - validate_uom_is_integer(self, "stock_uom", "qty", "BOM Item") + validate_uom_is_integer(self, "stock_uom", "stock_qty", "BOM Item") self.validate_materials() self.set_bom_material_details() @@ -60,6 +61,7 @@ class BOM(WebsiteGenerator): def on_update(self): self.check_recursion() + self.update_stock_qty() self.update_exploded_items() def on_submit(self): @@ -94,7 +96,7 @@ class BOM(WebsiteGenerator): def set_bom_material_details(self): for item in self.get("items"): ret = self.get_bom_material_detail({"item_code": item.item_code, "item_name": item.item_name, "bom_no": item.bom_no, - "qty": item.qty}) + "stock_qty": item.stock_qty}) for r in ret: if not item.get(r): item.set(r, ret[r]) @@ -122,6 +124,8 @@ class BOM(WebsiteGenerator): 'description' : item and args['description'] or '', 'image' : item and args['image'] or '', 'stock_uom' : item and args['stock_uom'] or '', + 'uom' : item and args['stock_uom'] or '', + 'conversion_factor' : 1, 'bom_no' : args['bom_no'], 'rate' : rate, 'base_rate' : rate if self.company_currency() == self.currency else rate * self.conversion_rate @@ -159,7 +163,7 @@ class BOM(WebsiteGenerator): for d in self.get("items"): rate = self.get_bom_material_detail({'item_code': d.item_code, 'bom_no': d.bom_no, - 'qty': d.qty})["rate"] + 'stock_qty': d.stock_qty})["rate"] if rate: d.rate = rate @@ -239,6 +243,19 @@ class BOM(WebsiteGenerator): frappe.db.get_value('Price List', self.buying_price_list, 'currency') != self.currency: frappe.throw(_("Currency of the price list {0} is not similar with the selected currency {1}").format(self.buying_price_list, self.currency)) + + def update_stock_qty(self): + for m in self.get('items'): + + if not m.conversion_factor: + m.conversion_factor = flt(get_conversion_factor(m.item_code, m.uom)['conversion_factor']) + if m.uom and m.qty: + m.stock_qty = flt(m.conversion_factor)*flt(m.qty) + if not m.uom and m.stock_uom: + m.uom = m.stock_uom + m.qty = m.stock_qty + + def set_conversion_rate(self): self.conversion_rate = get_exchange_rate(self.currency, self.company_currency()) @@ -250,7 +267,7 @@ class BOM(WebsiteGenerator): for m in self.get('items'): if m.bom_no: validate_bom_no(m.item_code, m.bom_no) - if flt(m.qty) <= 0: + if flt(m.stock_qty) <= 0: frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx)) check_list.append(cstr(m.item_code)) unique_chk_list = set(check_list) @@ -336,9 +353,9 @@ class BOM(WebsiteGenerator): d.rate = self.get_bom_unitcost(d.bom_no) d.base_rate = flt(d.rate) * flt(self.conversion_rate) - d.amount = flt(d.rate, self.precision("rate", d)) * flt(d.qty, self.precision("qty", d)) + d.amount = flt(d.rate, self.precision("rate", d)) * flt(d.stock_qty, self.precision("stock_qty", d)) d.base_amount = d.amount * flt(self.conversion_rate) - d.qty_consumed_per_unit = flt(d.qty, self.precision("qty", d)) / flt(self.quantity, self.precision("quantity")) + d.qty_consumed_per_unit = flt(d.stock_qty, self.precision("stock_qty", d)) / flt(self.quantity, self.precision("quantity")) total_rm_cost += d.amount base_total_rm_cost += d.base_amount @@ -352,7 +369,7 @@ class BOM(WebsiteGenerator): for d in self.get('scrap_items'): d.base_rate = d.rate * self.conversion_rate - d.amount = flt(d.rate, self.precision("rate", d)) * flt(d.qty, self.precision("qty", d)) + d.amount = flt(d.rate, self.precision("rate", d)) * flt(d.stock_qty, self.precision("stock_qty", d)) d.base_amount = d.amount * self.conversion_rate total_sm_cost += d.amount base_total_sm_cost += d.base_amount @@ -370,7 +387,7 @@ class BOM(WebsiteGenerator): self.cur_exploded_items = {} for d in self.get('items'): if d.bom_no: - self.get_child_exploded_items(d.bom_no, d.qty) + self.get_child_exploded_items(d.bom_no, d.stock_qty) else: self.add_to_cur_exploded_items(frappe._dict({ 'item_code' : d.item_code, @@ -378,7 +395,7 @@ class BOM(WebsiteGenerator): 'description' : d.description, 'image' : d.image, 'stock_uom' : d.stock_uom, - 'qty' : flt(d.qty), + 'qty' : flt(d.stock_qty), 'rate' : d.base_rate, })) @@ -453,7 +470,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite query = """select bom_item.item_code, item.item_name, - sum(bom_item.qty/ifnull(bom.quantity, 1)) * %(qty)s as qty, + sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(qty)s as qty, item.description, item.image, item.stock_uom, @@ -521,7 +538,7 @@ def get_children(): return frappe.db.sql("""select bom_item.item_code, bom_item.bom_no as value, - bom_item.qty, + bom_item.stock_qty, if(ifnull(bom_item.bom_no, "")!="", 1, 0) as expandable, item.image, item.description diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json index 5baa0cbf1d2..0f1143ef7d3 100644 --- a/erpnext/manufacturing/doctype/bom/test_records.json +++ b/erpnext/manufacturing/doctype/bom/test_records.json @@ -6,7 +6,7 @@ "doctype": "BOM Item", "item_code": "_Test Serialized Item With Series", "parentfield": "items", - "qty": 1.0, + "stock_qty": 1.0, "rate": 5000.0, "stock_uom": "_Test UOM" }, @@ -15,7 +15,7 @@ "doctype": "BOM Item", "item_code": "_Test Item 2", "parentfield": "items", - "qty": 2.0, + "stock_qty": 2.0, "rate": 1000.0, "stock_uom": "_Test UOM" } @@ -35,7 +35,7 @@ "doctype": "BOM Item", "item_code": "_Test Item Home Desktop 100", "parentfield": "items", - "qty": 1.0, + "stock_qty": 1.0, "rate": 2000.0, "stock_uom": "_Test UOM" } @@ -46,7 +46,7 @@ "doctype": "BOM Item", "item_code": "_Test Item", "parentfield": "items", - "qty": 1.0, + "stock_qty": 1.0, "rate": 5000.0, "stock_uom": "_Test UOM" }, @@ -55,7 +55,7 @@ "doctype": "BOM Item", "item_code": "_Test Item Home Desktop 100", "parentfield": "items", - "qty": 2.0, + "stock_qty": 2.0, "rate": 1000.0, "stock_uom": "_Test UOM" } @@ -84,7 +84,7 @@ "doctype": "BOM Item", "item_code": "_Test Item", "parentfield": "items", - "qty": 1.0, + "stock_qty": 1.0, "rate": 5000.0, "stock_uom": "_Test UOM" }, @@ -94,7 +94,7 @@ "doctype": "BOM Item", "item_code": "_Test Item Home Desktop Manufactured", "parentfield": "items", - "qty": 3.0, + "stock_qty": 3.0, "rate": 1000.0, "stock_uom": "_Test UOM" } @@ -124,7 +124,7 @@ "doctype": "BOM Item", "item_code": "_Test Item", "parentfield": "items", - "qty": 2.0, + "stock_qty": 2.0, "rate": 3000.0, "stock_uom": "_Test UOM" } diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index 2fc29a883b4..56af7a10d37 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, "beta": 0, @@ -11,6 +12,7 @@ "editable_grid": 1, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -20,7 +22,8 @@ "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, - "in_filter": 1, + "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, "label": "Item Code", @@ -41,6 +44,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -51,6 +55,7 @@ "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 Name", @@ -69,6 +74,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -79,6 +85,7 @@ "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, @@ -96,6 +103,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -105,7 +113,8 @@ "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, - "in_filter": 1, + "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "BOM No", @@ -128,6 +137,7 @@ "width": "150px" }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -138,6 +148,7 @@ "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, @@ -155,6 +166,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -165,6 +177,7 @@ "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 Description", @@ -186,6 +199,7 @@ "width": "250px" }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -196,6 +210,7 @@ "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, @@ -212,6 +227,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -222,6 +238,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Image", @@ -240,6 +257,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -250,6 +268,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Image View", @@ -269,6 +288,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -279,6 +299,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Quantity and Rate", @@ -296,6 +317,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -306,6 +328,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, "label": "Qty", @@ -319,16 +342,168 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 1, + "fieldname": "uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "UOM", + "length": 0, + "no_copy": 0, + "options": "UOM", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "col_break2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "stock_qty", + "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": "Stock Qty", + "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": 1, "search_index": 0, "set_only_once": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 2, + "columns": 0, + "fieldname": "conversion_factor", + "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": "Conversion Factor", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "stock_uom", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Stock UOM", + "length": 0, + "no_copy": 0, + "oldfieldname": "stock_uom", + "oldfieldtype": "Data", + "options": "UOM", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "description": "See \"Rate Of Materials Based On\" in Costing Section", "fieldname": "rate", "fieldtype": "Currency", @@ -336,7 +511,8 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 1, + "in_global_search": 0, + "in_list_view": 0, "in_standard_filter": 0, "label": "Rate", "length": 0, @@ -354,6 +530,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -364,6 +541,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, "label": "Amount", @@ -386,62 +564,7 @@ "width": "150px" }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Stock UOM", - "length": 0, - "no_copy": 0, - "oldfieldname": "stock_uom", - "oldfieldtype": "Data", - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -452,6 +575,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Basic Rate (Company Currency)", @@ -471,6 +595,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -481,6 +606,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Amount (Company Currency)", @@ -500,6 +626,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -510,6 +637,7 @@ "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, @@ -527,6 +655,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -537,6 +666,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, "label": "Scrap %", @@ -556,6 +686,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -566,6 +697,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Qty Consumed Per Unit", @@ -585,18 +717,18 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 1, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-12-20 12:54:34.859076", - "modified_by": "rmehta@gmail.com", + "modified": "2017-05-23 15:59:37.946963", + "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", "owner": "Administrator", @@ -604,7 +736,9 @@ "quick_entry": 0, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", + "track_changes": 0, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py b/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py index d4d5329acea..f0a834c37b3 100644 --- a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py +++ b/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py @@ -33,7 +33,7 @@ class BOMReplaceTool(Document): from `tabBOM` where name = %s""", self.current_bom) current_bom_unitcost = current_bom_unitcost and flt(current_bom_unitcost[0][0]) or 0 frappe.db.sql("""update `tabBOM Item` set bom_no=%s, - rate=%s, amount=qty*%s where bom_no = %s and docstatus < 2""", + rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2""", (self.new_bom, current_bom_unitcost, current_bom_unitcost, self.current_bom)) def get_parent_boms(self): diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json index fe815927699..e9aebfe39cf 100644 --- a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json +++ b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, "beta": 0, @@ -11,6 +12,7 @@ "editable_grid": 1, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -21,7 +23,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Item Code", "length": 0, "no_copy": 0, @@ -31,6 +35,7 @@ "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, @@ -38,6 +43,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -48,7 +54,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Item Name", "length": 0, "no_copy": 0, @@ -57,6 +65,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -64,6 +73,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -74,7 +84,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Quantity and Rate", "length": 0, "no_copy": 0, @@ -83,6 +95,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -90,17 +103,20 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "qty", + "fieldname": "stock_qty", "fieldtype": "Float", "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": "Qty", "length": 0, "no_copy": 0, @@ -109,6 +125,7 @@ "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, @@ -116,6 +133,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -126,7 +144,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Rate", "length": 0, "no_copy": 0, @@ -136,6 +156,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -143,6 +164,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -153,7 +175,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Amount", "length": 0, "no_copy": 0, @@ -163,6 +187,7 @@ "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, @@ -170,6 +195,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -180,7 +206,9 @@ "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, @@ -188,6 +216,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -195,6 +224,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -205,7 +235,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Stock UOM", "length": 0, "no_copy": 0, @@ -215,6 +247,7 @@ "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, @@ -222,6 +255,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -232,7 +266,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Basic Rate (Company Currency)", "length": 0, "no_copy": 0, @@ -242,6 +278,7 @@ "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -249,6 +286,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -259,7 +297,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Basic Amount (Company Currency)", "length": 0, "no_copy": 0, @@ -269,6 +309,7 @@ "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -276,17 +317,17 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-10-25 00:27:53.712140", + "modified": "2017-05-23 16:04:32.442287", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Scrap Item", @@ -296,7 +337,9 @@ "quick_entry": 1, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", + "track_changes": 1, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py index dd4b2b69ab8..798e17486c7 100644 --- a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py +++ b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py @@ -348,7 +348,7 @@ class ProductionPlanningTool(Document): SELECT bom_item.item_code, default_material_request_type, - ifnull(%(parent_qty)s * sum(bom_item.qty/ifnull(bom.quantity, 1)), 0) as qty, + 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, diff --git a/erpnext/manufacturing/doctype/production_planning_tool/test_production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/test_production_planning_tool.py index ea4da0cb942..f656b2c9056 100644 --- a/erpnext/manufacturing/doctype/production_planning_tool/test_production_planning_tool.py +++ b/erpnext/manufacturing/doctype/production_planning_tool/test_production_planning_tool.py @@ -235,9 +235,9 @@ def create_test_records(): "is_active": 1, "is_default": 1, "docstatus": 1, - "with_operations": 0}, [{"item_code": "_Test PPT Item Raw B", "doctype":"BOM Item", "qty":1, + "with_operations": 0}, [{"item_code": "_Test PPT Item Raw B", "doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100, "stock_uom": "_Test UOM"}, - {"item_code": "_Test PPT Item Raw C", "doctype":"BOM Item", "qty":4, "rate":100, + {"item_code": "_Test PPT Item Raw C", "doctype":"BOM Item", "stock_qty":4, "rate":100, "amount": 400,"stock_uom": "_Test UOM"}]) bom_subC = make_bom("BOM-_Test PPT Item Sub C-001",{"quantity":1, @@ -247,9 +247,9 @@ def create_test_records(): "docstatus": 1, "with_operations": 0}, [ {"item_code": "_Test PPT Item Raw A","item_name": "_Test PPT Item Raw A", - "doctype":"BOM Item", "qty":6, "rate":100, "amount": 600}, + "doctype":"BOM Item", "stock_qty":6, "rate":100, "amount": 600}, {"item_code": "_Test PPT Item Sub B","item_name": "_Test PPT Item Sub B", - "bom_no":"BOM-_Test PPT Item Sub B-001", "doctype":"BOM Item", "qty":2, + "bom_no":"BOM-_Test PPT Item Sub B-001", "doctype":"BOM Item", "stock_qty":2, "rate":100, "amount": 200}]) bom_sCA = make_bom("BOM-_Test PPT Item SC A-001",{"quantity":1, @@ -259,7 +259,7 @@ def create_test_records(): "docstatus": 1, "with_operations": 0}, [ {"item_code": "_Test PPT Item Raw D","item_name": "_Test PPT Item Raw D", - "doctype":"BOM Item", "qty":1, "rate":100, "amount": 100}]) + "doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100}]) bom_sCB = make_bom("BOM-_Test PPT Item SC B-001",{"quantity":1, "item": "_Test PPT Item SC B", @@ -268,9 +268,9 @@ def create_test_records(): "docstatus": 1, "with_operations": 0}, [ {"item_code": "_Test PPT Item Raw B","item_name": "_Test PPT Item Raw B", - "doctype":"BOM Item", "qty":1, "rate":100, "amount": 100}, + "doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100}, {"item_code": "_Test PPT Item Raw C","item_name": "_Test PPT Item Raw C", - "doctype":"BOM Item", "qty":4, "rate":100, "amount": 400}]) + "doctype":"BOM Item", "stock_qty":4, "rate":100, "amount": 400}]) bom_subA = make_bom("BOM-_Test PPT Item Sub A-001",{"quantity":1, "item": "_Test PPT Item Sub A", @@ -280,9 +280,9 @@ def create_test_records(): "with_operations": 0}, [ {"item_code": "_Test PPT Item Sub C","item_name": "_Test PPT Item Sub C", "bom_no":"BOM-_Test PPT Item Sub C-001", "doctype":"BOM Item", - "qty":1, "rate":100, "amount": 100}, + "stock_qty":1, "rate":100, "amount": 100}, {"item_code": "_Test PPT Item SC B","item_name": "_Test PPT Item SC B", - "bom_no":"BOM-_Test PPT Item SC B-001", "doctype":"BOM Item", "qty":2, + "bom_no":"BOM-_Test PPT Item SC B-001", "doctype":"BOM Item", "stock_qty":2, "rate":100, "amount": 200}]) bom_master = make_bom("BOM-_Test PPT Item Master-001",{"quantity":1, @@ -293,16 +293,16 @@ def create_test_records(): "with_operations": 0}, [ {"item_code": "_Test PPT Item Sub A","item_name": "_Test PPT Item Sub A", "bom_no":"BOM-_Test PPT Item Sub A-001", - "doctype":"BOM Item", "qty":2, "rate":100, "amount": 200}, + "doctype":"BOM Item", "stock_qty":2, "rate":100, "amount": 200}, {"item_code": "_Test PPT Item Sub B","item_name": "_Test PPT Item Sub B", "bom_no":"BOM-_Test PPT Item Sub B-001", - "doctype":"BOM Item", "qty":1, "rate":100, "amount": 100}, + "doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100}, {"item_code": "_Test PPT Item Raw A","item_name": "_Test PPT Item Raw A", - "doctype":"BOM Item", "qty":2, "rate":100, + "doctype":"BOM Item", "stock_qty":2, "rate":100, "amount": 200}, {"item_code": "_Test PPT Item SC A","item_name": "_Test PPT Item SC A", "bom_no":"BOM-_Test PPT Item SC A-001", - "doctype":"BOM Item", "qty":1, "rate":100, "amount": 100} + "doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100} ]) diff --git a/erpnext/manufacturing/report/production_order_stock_report/production_order_stock_report.py b/erpnext/manufacturing/report/production_order_stock_report/production_order_stock_report.py index 6d586ddb223..bb79a491597 100644 --- a/erpnext/manufacturing/report/production_order_stock_report/production_order_stock_report.py +++ b/erpnext/manufacturing/report/production_order_stock_report/production_order_stock_report.py @@ -23,7 +23,7 @@ def get_item_list(prod_list, filters): item_list = frappe.db.sql("""SELECT bom_item.item_code as item_code, - ifnull(ledger.actual_qty*bom.quantity/bom_item.qty,0) as build_qty + ifnull(ledger.actual_qty*bom.quantity/bom_item.stock_qty,0) as build_qty FROM `tabBOM` as bom, `tabBOM Item` AS bom_item LEFT JOIN `tabBin` AS ledger diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0faf98e688d..5d2d2f05171 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -396,4 +396,5 @@ erpnext.patches.v8_0.merge_student_batch_and_student_group erpnext.patches.v8_0.rename_total_margin_to_rate_with_margin # 11-05-2017 erpnext.patches.v8_0.fix_status_for_invoices_with_negative_outstanding erpnext.patches.v8_0.make_payments_table_blank_for_non_pos_invoice -erpnext.patches.v8_0.set_sales_invoice_serial_number_from_delivery_note \ No newline at end of file +erpnext.patches.v8_0.set_sales_invoice_serial_number_from_delivery_note +erpnext.patches.v8_0.update_stock_qty_value_in_bom_item \ No newline at end of file diff --git a/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py b/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py new file mode 100644 index 00000000000..bc69815fa26 --- /dev/null +++ b/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py @@ -0,0 +1,10 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('manufacturing', 'doctype', 'bom_item') + frappe.db.sql("update `tabBOM Item` set stock_qty = qty, uom = stock_uom") + frappe.db.sql("update `tabBOM Scrap Item` set stock_qty = qty") \ No newline at end of file From a3aa6a4449b6f2a2e9ae4bcc01e41301f7521cb2 Mon Sep 17 00:00:00 2001 From: Ben Cornwell-Mott Date: Fri, 2 Jun 2017 16:54:03 -0700 Subject: [PATCH 02/27] Changed Explosion Item as well --- erpnext/manufacturing/doctype/bom/bom.py | 24 +++++++++---------- .../bom_explosion_item.json | 24 +++++++++++++++---- .../doctype/bom_item/bom_item.json | 2 ++ .../production_planning_tool.py | 2 +- .../update_stock_qty_value_in_bom_item.py | 1 + 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 5c45e5d5b62..ca4a9bc125d 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -395,7 +395,7 @@ class BOM(WebsiteGenerator): 'description' : d.description, 'image' : d.image, 'stock_uom' : d.stock_uom, - 'qty' : flt(d.stock_qty), + 'stock_qty' : flt(d.stock_qty), 'rate' : d.base_rate, })) @@ -404,16 +404,16 @@ class BOM(WebsiteGenerator): def add_to_cur_exploded_items(self, args): if self.cur_exploded_items.get(args.item_code): - self.cur_exploded_items[args.item_code]["qty"] += args.qty + self.cur_exploded_items[args.item_code]["stock_qty"] += args.stock_qty else: self.cur_exploded_items[args.item_code] = args - def get_child_exploded_items(self, bom_no, qty): + def get_child_exploded_items(self, bom_no, stock_qty): """ Add all items from Flat BOM of child BOM""" # Did not use qty_consumed_per_unit in the query, as it leads to rounding loss child_fb_items = frappe.db.sql("""select bom_item.item_code, bom_item.item_name, bom_item.description, - bom_item.stock_uom, bom_item.qty, bom_item.rate, - bom_item.qty / ifnull(bom.quantity, 1) as qty_consumed_per_unit + bom_item.stock_uom, bom_item.stock_qty, bom_item.rate, + bom_item.stock_qty / ifnull(bom.quantity, 1) as qty_consumed_per_unit from `tabBOM Explosion Item` bom_item, tabBOM bom where bom_item.parent = bom.name and bom.name = %s and bom.docstatus = 1""", bom_no, as_dict = 1) @@ -423,7 +423,7 @@ class BOM(WebsiteGenerator): 'item_name' : d['item_name'], 'description' : d['description'], 'stock_uom' : d['stock_uom'], - 'qty' : d['qty_consumed_per_unit']*qty, + 'stock_qty' : d['qty_consumed_per_unit']*stock_qty, 'rate' : flt(d['rate']), })) @@ -435,8 +435,8 @@ class BOM(WebsiteGenerator): ch = self.append('exploded_items', {}) for i in self.cur_exploded_items[d].keys(): ch.set(i, self.cur_exploded_items[d][i]) - ch.amount = flt(ch.qty) * flt(ch.rate) - ch.qty_consumed_per_unit = flt(ch.qty) / flt(self.quantity) + ch.amount = flt(ch.stock_qty) * flt(ch.rate) + ch.qty_consumed_per_unit = flt(ch.stock_qty) / flt(self.quantity) ch.docstatus = self.docstatus ch.db_insert() @@ -480,13 +480,13 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite from `tab{table}` bom_item, `tabBOM` bom, `tabItem` item where - bom_item.parent = bom.name - and bom_item.docstatus < 2 - and bom_item.parent = %(bom)s + bom_item.docstatus < 2 + and bom.name = %(bom)s + and bom_item.parent = bom.name and item.name = bom_item.item_code and is_stock_item = 1 {conditions} - group by item_code, stock_uom""" + group by item_code, stock_uom""" if fetch_exploded: query = query.format(table="BOM Explosion Item", diff --git a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json index f0758408060..e1a3d4da53a 100644 --- a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json +++ b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, "autoname": "hash", @@ -13,6 +14,7 @@ "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -44,6 +46,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -72,6 +75,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -101,6 +105,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -129,6 +134,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -161,6 +167,7 @@ "width": "300px" }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -189,6 +196,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -218,6 +226,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -248,6 +257,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -276,11 +286,12 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "qty", + "fieldname": "stock_qty", "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, @@ -289,7 +300,7 @@ "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Qty", + "label": "Stock Qty", "length": 0, "no_copy": 0, "oldfieldname": "qty", @@ -306,6 +317,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -337,6 +349,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -365,6 +378,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -393,6 +407,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -424,6 +439,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -455,17 +471,17 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 1, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-02-17 17:27:43.757983", + "modified": "2017-06-02 19:29:34.498719", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Explosion Item", diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index 56af7a10d37..966b89bd4cc 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -424,6 +424,8 @@ "label": "Stock Qty", "length": 0, "no_copy": 0, + "oldfieldname": "stock_qty", + "oldfieldtype": "Currency", "permlevel": 0, "precision": "", "print_hide": 0, diff --git a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py index 798e17486c7..050c3c1c338 100644 --- a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py +++ b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py @@ -321,7 +321,7 @@ class ProductionPlanningTool(Document): # 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.qty/ifnull(bom.quantity, 1)), 0) as qty, + 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 diff --git a/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py b/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py index bc69815fa26..9d227877ce2 100644 --- a/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py +++ b/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py @@ -7,4 +7,5 @@ import frappe def execute(): frappe.reload_doc('manufacturing', 'doctype', 'bom_item') frappe.db.sql("update `tabBOM Item` set stock_qty = qty, uom = stock_uom") + frappe.db.sql("update `tabBOM Explosion Item` set stock_qty = qty") frappe.db.sql("update `tabBOM Scrap Item` set stock_qty = qty") \ No newline at end of file From 70fe968f02f9d9c482862d921ff452142115f9ba Mon Sep 17 00:00:00 2001 From: Ben Cornwell-Mott Date: Sun, 4 Jun 2017 20:19:59 -0700 Subject: [PATCH 03/27] Fixed some errors --- .../doctype/production_order/test_production_order.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.py b/erpnext/manufacturing/doctype/production_order/test_production_order.py index 40e839323c2..c736ae25554 100644 --- a/erpnext/manufacturing/doctype/production_order/test_production_order.py +++ b/erpnext/manufacturing/doctype/production_order/test_production_order.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import unittest import frappe +from pprint import pprint from frappe.utils import flt, time_diff_in_hours, now, add_days, cint from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.manufacturing.doctype.production_order.production_order \ @@ -266,9 +267,9 @@ class TestProductionOrder(unittest.TestCase): def get_scrap_item_details(bom_no): scrap_items = {} - for item in frappe.db.sql("""select item_code, qty from `tabBOM Scrap Item` + for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` where parent = %s""", bom_no, as_dict=1): - scrap_items[item.item_code] = item.qty + scrap_items[item.item_code] = item.stock_qty return scrap_items @@ -287,8 +288,7 @@ def make_prod_order_test_record(**args): pro_order.stock_uom = args.stock_uom or "_Test UOM" pro_order.use_multi_level_bom=0 pro_order.set_production_order_operations() - - + if args.source_warehouse: pro_order.source_warehouse = args.source_warehouse @@ -297,6 +297,7 @@ def make_prod_order_test_record(**args): if not args.do_not_save: pro_order.insert() + if not args.do_not_submit: pro_order.submit() return pro_order From a35839aa4798d04cfcb0105cdcc59279c916de90 Mon Sep 17 00:00:00 2001 From: bcornwellmott Date: Fri, 9 Jun 2017 07:36:15 -0700 Subject: [PATCH 04/27] Removing unused pprint --- .../doctype/production_order/test_production_order.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.py b/erpnext/manufacturing/doctype/production_order/test_production_order.py index c736ae25554..cdadba48252 100644 --- a/erpnext/manufacturing/doctype/production_order/test_production_order.py +++ b/erpnext/manufacturing/doctype/production_order/test_production_order.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import unittest import frappe -from pprint import pprint from frappe.utils import flt, time_diff_in_hours, now, add_days, cint from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.manufacturing.doctype.production_order.production_order \ From fed9816213f2237590e04ee6f22ff6c0beb1bedb Mon Sep 17 00:00:00 2001 From: bcornwellmott Date: Fri, 9 Jun 2017 07:37:27 -0700 Subject: [PATCH 05/27] Removed trailing whitespace --- .../test_production_planning_tool.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_planning_tool/test_production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/test_production_planning_tool.py index f656b2c9056..4f80b6a626d 100644 --- a/erpnext/manufacturing/doctype/production_planning_tool/test_production_planning_tool.py +++ b/erpnext/manufacturing/doctype/production_planning_tool/test_production_planning_tool.py @@ -235,9 +235,9 @@ def create_test_records(): "is_active": 1, "is_default": 1, "docstatus": 1, - "with_operations": 0}, [{"item_code": "_Test PPT Item Raw B", "doctype":"BOM Item", "stock_qty":1, + "with_operations": 0}, [{"item_code": "_Test PPT Item Raw B", "doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100, "stock_uom": "_Test UOM"}, - {"item_code": "_Test PPT Item Raw C", "doctype":"BOM Item", "stock_qty":4, "rate":100, + {"item_code": "_Test PPT Item Raw C", "doctype":"BOM Item", "stock_qty":4, "rate":100, "amount": 400,"stock_uom": "_Test UOM"}]) bom_subC = make_bom("BOM-_Test PPT Item Sub C-001",{"quantity":1, @@ -249,7 +249,7 @@ def create_test_records(): {"item_code": "_Test PPT Item Raw A","item_name": "_Test PPT Item Raw A", "doctype":"BOM Item", "stock_qty":6, "rate":100, "amount": 600}, {"item_code": "_Test PPT Item Sub B","item_name": "_Test PPT Item Sub B", - "bom_no":"BOM-_Test PPT Item Sub B-001", "doctype":"BOM Item", "stock_qty":2, + "bom_no":"BOM-_Test PPT Item Sub B-001", "doctype":"BOM Item", "stock_qty":2, "rate":100, "amount": 200}]) bom_sCA = make_bom("BOM-_Test PPT Item SC A-001",{"quantity":1, @@ -278,11 +278,11 @@ def create_test_records(): "is_default": 1, "docstatus": 1, "with_operations": 0}, [ - {"item_code": "_Test PPT Item Sub C","item_name": "_Test PPT Item Sub C", + {"item_code": "_Test PPT Item Sub C","item_name": "_Test PPT Item Sub C", "bom_no":"BOM-_Test PPT Item Sub C-001", "doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100}, {"item_code": "_Test PPT Item SC B","item_name": "_Test PPT Item SC B", - "bom_no":"BOM-_Test PPT Item SC B-001", "doctype":"BOM Item", "stock_qty":2, + "bom_no":"BOM-_Test PPT Item SC B-001", "doctype":"BOM Item", "stock_qty":2, "rate":100, "amount": 200}]) bom_master = make_bom("BOM-_Test PPT Item Master-001",{"quantity":1, @@ -298,7 +298,7 @@ def create_test_records(): "bom_no":"BOM-_Test PPT Item Sub B-001", "doctype":"BOM Item", "stock_qty":1, "rate":100, "amount": 100}, {"item_code": "_Test PPT Item Raw A","item_name": "_Test PPT Item Raw A", - "doctype":"BOM Item", "stock_qty":2, "rate":100, + "doctype":"BOM Item", "stock_qty":2, "rate":100, "amount": 200}, {"item_code": "_Test PPT Item SC A","item_name": "_Test PPT Item SC A", "bom_no":"BOM-_Test PPT Item SC A-001", @@ -388,4 +388,4 @@ def get_requested_types(item_code): where item.item_code = %(item_code)s and item.parent = mat_req.name""", {"item_code":item_code}, as_dict=1): types.append(d.type) return types - \ No newline at end of file + From 70554465089dd6cbd904062f4264de7d33a662b8 Mon Sep 17 00:00:00 2001 From: Ben Cornwell-Mott Date: Thu, 15 Jun 2017 08:52:55 -0700 Subject: [PATCH 06/27] Reload explosion and scrap items docs --- erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py b/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py index 9d227877ce2..c872177e1f4 100644 --- a/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py +++ b/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py @@ -6,6 +6,8 @@ import frappe def execute(): frappe.reload_doc('manufacturing', 'doctype', 'bom_item') + frappe.reload_doc('manufacturing', 'doctype', 'bom_explosion_item') + frappe.reload_doc('manufacturing', 'doctype', 'bom_scrap_item') frappe.db.sql("update `tabBOM Item` set stock_qty = qty, uom = stock_uom") frappe.db.sql("update `tabBOM Explosion Item` set stock_qty = qty") frappe.db.sql("update `tabBOM Scrap Item` set stock_qty = qty") \ No newline at end of file From 2c77165fc61a885b15bbff37756fbcaf1e9c2426 Mon Sep 17 00:00:00 2001 From: Ben Cornwell-Mott Date: Fri, 16 Jun 2017 13:47:40 -0700 Subject: [PATCH 07/27] Fixed small code issues (codecy) --- erpnext/manufacturing/doctype/bom/bom.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index a6703907b29..d3c33798503 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -5,7 +5,7 @@ frappe.provide("erpnext.bom"); frappe.ui.form.on("BOM", { setup: function(frm) { - frm.add_fetch('buying_price_list', 'currency', 'currency') + frm.add_fetch('buying_price_list', 'currency', 'currency'); frm.fields_dict["items"].grid.get_field("bom_no").get_query = function(doc, cdt, cdn){ return { filters: {'currency': frm.doc.currency} @@ -306,11 +306,10 @@ frappe.ui.form.on("BOM Operation", "workstation", function(frm, cdt, cdn) { }); frappe.ui.form.on("BOM Item", "qty", function(frm, cdt, cdn) { - var d = locals[cdt][cdn]; - - d.stock_qty = d.qty * d.conversion_factor - refresh_field("items"); - }); + var d = locals[cdt][cdn]; + d.stock_qty = d.qty * d.conversion_factor; + refresh_field("items"); +}); frappe.ui.form.on("BOM Operation", "operations_remove", function(frm) { From 881491cd2b2e0200ae915ae7e8a16dd9152818aa Mon Sep 17 00:00:00 2001 From: nick9822 Date: Wed, 21 Jun 2017 19:51:08 +0530 Subject: [PATCH 08/27] Removed "Asset" filter on payment account --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index cada95f2f6c..e883f257805 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -224,7 +224,6 @@ cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { return { filters: [ ["Account", "account_type", "in", ["Cash", "Bank"]], - ["Account", "root_type", "=", "Asset"], ["Account", "is_group", "=",0], ["Account", "company", "=", doc.company] ] From 1e96b7bbe55579dcd5973eaf63490c13139c974e Mon Sep 17 00:00:00 2001 From: Ben Cornwell-Mott Date: Wed, 21 Jun 2017 10:16:50 -0700 Subject: [PATCH 09/27] Added conversion_factor update in patch --- erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py b/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py index c872177e1f4..9e95eb0c23c 100644 --- a/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py +++ b/erpnext/patches/v8_0/update_stock_qty_value_in_bom_item.py @@ -8,6 +8,6 @@ def execute(): frappe.reload_doc('manufacturing', 'doctype', 'bom_item') frappe.reload_doc('manufacturing', 'doctype', 'bom_explosion_item') frappe.reload_doc('manufacturing', 'doctype', 'bom_scrap_item') - frappe.db.sql("update `tabBOM Item` set stock_qty = qty, uom = stock_uom") + frappe.db.sql("update `tabBOM Item` set stock_qty = qty, uom = stock_uom, conversion_factor = 1") frappe.db.sql("update `tabBOM Explosion Item` set stock_qty = qty") frappe.db.sql("update `tabBOM Scrap Item` set stock_qty = qty") \ No newline at end of file From cb650f836ef1e5b170c59da4806b8806272d81c9 Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Mon, 26 Jun 2017 14:11:12 +0530 Subject: [PATCH 10/27] validation of student in student attendance --- .../school_settings/school_settings.json | 39 +++++++++++++++++-- .../school_settings/school_settings.py | 3 +- .../student_attendance/student_attendance.py | 4 +- .../doctype/student_group/student_group.py | 12 +++--- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/erpnext/schools/doctype/school_settings/school_settings.json b/erpnext/schools/doctype/school_settings/school_settings.json index 8607a765690..8d4d4f530d3 100644 --- a/erpnext/schools/doctype/school_settings/school_settings.json +++ b/erpnext/schools/doctype/school_settings/school_settings.json @@ -139,8 +139,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "1", - "fieldname": "validation_from_pe", + "description": "For Batch based Student Group, the Student Batch will be validated for every Student from the Program Enrollment.", + "fieldname": "validate_batch", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, @@ -149,7 +149,38 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Validate the Student Group from Program Enrollment", + "label": "Validate Batch for Students in Student Group", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "For Course based Student Group, the Course will be validated for every Student from the enrolled Courses in Program Enrollment.", + "fieldname": "validate_course", + "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": "Validate Enrolled Course for Students in Student Group", "length": 0, "no_copy": 0, "permlevel": 0, @@ -175,7 +206,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-04-27 15:37:00.159072", + "modified": "2017-06-26 14:07:36.542314", "modified_by": "Administrator", "module": "Schools", "name": "School Settings", diff --git a/erpnext/schools/doctype/school_settings/school_settings.py b/erpnext/schools/doctype/school_settings/school_settings.py index 6d8efb42798..999014ad80b 100644 --- a/erpnext/schools/doctype/school_settings/school_settings.py +++ b/erpnext/schools/doctype/school_settings/school_settings.py @@ -11,7 +11,8 @@ school_keydict = { # "key in defaults": "key in Global Defaults" "academic_year": "current_academic_year", "academic_term": "current_academic_term", - "student_validation_setting": "validation_from_pe", + "validate_batch": "validate_batch", + "validate_course": "validate_course" } class SchoolSettings(Document): diff --git a/erpnext/schools/doctype/student_attendance/student_attendance.py b/erpnext/schools/doctype/student_attendance/student_attendance.py index 2688123f97f..696029680fd 100644 --- a/erpnext/schools/doctype/student_attendance/student_attendance.py +++ b/erpnext/schools/doctype/student_attendance/student_attendance.py @@ -35,9 +35,7 @@ class StudentAttendance(Document): student_group = frappe.db.get_value("Course Schedule", self.course_schedule, "student_group") else: student_group = self.student_group - student_group_students = [] - for d in get_student_group_students(student_group): - student_group_students.append(d.student) + student_group_students = [d.student for d in get_student_group_students(student_group)] if student_group and self.student not in student_group_students: frappe.throw(_('''Student {0}: {1} does not belong to Student Group {2}'''.format(self.student, self.student_name, student_group))) diff --git a/erpnext/schools/doctype/student_group/student_group.py b/erpnext/schools/doctype/student_group/student_group.py index 9cdf9c7f016..f2d52a2be8e 100644 --- a/erpnext/schools/doctype/student_group/student_group.py +++ b/erpnext/schools/doctype/student_group/student_group.py @@ -12,8 +12,7 @@ class StudentGroup(Document): def validate(self): self.validate_mandatory_fields() self.validate_strength() - if frappe.defaults.get_defaults().student_validation_setting: - self.validate_students() + self.validate_students() self.validate_and_set_child_table_fields() validate_duplicate_student(self.students) @@ -31,12 +30,15 @@ class StudentGroup(Document): def validate_students(self): program_enrollment = get_program_enrollment(self.academic_year, self.academic_term, self.program, self.batch, self.course) - students = [d.student for d in program_enrollment] if program_enrollment else None + print program_enrollment + students = [d.student for d in program_enrollment] if program_enrollment else [] for d in self.students: - if self.group_based_on != "Activity" and students and d.student not in students and d.active == 1: - frappe.throw(_("{0} - {1} is not enrolled in the given {2}".format(d.group_roll_number, d.student_name, self.group_based_on))) if not frappe.db.get_value("Student", d.student, "enabled") and d.active: frappe.throw(_("{0} - {1} is inactive student".format(d.group_roll_number, d.student_name))) + if self.group_based_on == "Batch" and d.student not in students and frappe.defaults.get_defaults().validate_batch: + frappe.throw(_("{0} - {1} is not enrolled in the Batch {2}".format(d.group_roll_number, d.student_name, self.batch))) + if self.group_based_on == "Course" and d.student not in students and frappe.defaults.get_defaults().validate_course: + frappe.throw(_("{0} - {1} is not enrolled in the Course {2}".format(d.group_roll_number, d.student_name, self.course))) def validate_and_set_child_table_fields(self): roll_numbers = [d.group_roll_number for d in self.students if d.group_roll_number] From 82e816054e094090fa0a557747bf3b0b17f41e1b Mon Sep 17 00:00:00 2001 From: nick9822 Date: Mon, 26 Jun 2017 15:29:44 +0530 Subject: [PATCH 11/27] Added report_type to payment account filter --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index e883f257805..b64e6b2faae 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -225,7 +225,8 @@ cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { filters: [ ["Account", "account_type", "in", ["Cash", "Bank"]], ["Account", "is_group", "=",0], - ["Account", "company", "=", doc.company] + ["Account", "company", "=", doc.company], + ["Account", "report_type", "=", "Balance Sheet"] ] } } From 67526f244ec66f9557c6ff387c43c7939136f9db Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 23 Jun 2017 20:20:01 +0530 Subject: [PATCH 12/27] [enhance] Report for analysis of support hours count --- erpnext/config/support.py | 6 ++ .../docs/assets/img/support/support_hours.png | Bin 0 -> 48805 bytes .../user/manual/en/support/support_reports.md | 8 ++ .../support/report/support_hours/__init__.py | 0 .../report/support_hours/support_hours.js | 39 ++++++++++ .../report/support_hours/support_hours.json | 27 +++++++ .../report/support_hours/support_hours.py | 73 ++++++++++++++++++ 7 files changed, 153 insertions(+) create mode 100644 erpnext/docs/assets/img/support/support_hours.png create mode 100644 erpnext/docs/user/manual/en/support/support_reports.md create mode 100644 erpnext/support/report/support_hours/__init__.py create mode 100644 erpnext/support/report/support_hours/support_hours.js create mode 100644 erpnext/support/report/support_hours/support_hours.json create mode 100644 erpnext/support/report/support_hours/support_hours.py diff --git a/erpnext/config/support.py b/erpnext/config/support.py index c1f56f05163..b85c4308999 100644 --- a/erpnext/config/support.py +++ b/erpnext/config/support.py @@ -49,6 +49,12 @@ def get_data(): "doctype": "Issue", "is_query_report": True }, + { + "type": "report", + "name": "Support Hours", + "doctype": "Issue", + "is_query_report": True + }, ] }, ] diff --git a/erpnext/docs/assets/img/support/support_hours.png b/erpnext/docs/assets/img/support/support_hours.png new file mode 100644 index 0000000000000000000000000000000000000000..44cfbbbdaabf9bcd061c46c89043293a74b5cd64 GIT binary patch literal 48805 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 literal 0 HcmV?d00001 diff --git a/erpnext/docs/user/manual/en/support/support_reports.md b/erpnext/docs/user/manual/en/support/support_reports.md new file mode 100644 index 00000000000..2be72e10eff --- /dev/null +++ b/erpnext/docs/user/manual/en/support/support_reports.md @@ -0,0 +1,8 @@ + + +### Support Hours +This report provide the information about the time slot along with the count of issues has been reported during the slot daywise. + +> Support > Reports > Support Hours + +Maintenance Visit diff --git a/erpnext/support/report/support_hours/__init__.py b/erpnext/support/report/support_hours/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/support/report/support_hours/support_hours.js b/erpnext/support/report/support_hours/support_hours.js new file mode 100644 index 00000000000..439b7678eed --- /dev/null +++ b/erpnext/support/report/support_hours/support_hours.js @@ -0,0 +1,39 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Support Hours"] = { + "filters": [ + { + 'lable': __("From Date"), + 'fieldname': 'from_date', + 'fieldtype': 'Date', + 'default': frappe.datetime.nowdate(), + 'reqd': 1 + }, + { + 'lable': __("To Date"), + 'fieldname': 'to_date', + 'fieldtype': 'Date', + 'default': frappe.datetime.nowdate(), + 'reqd': 1 + } + ], + get_chart_data: function(columns, result) { + return { + data: { + x: 'Date', + columns: [ + ['Date'].concat($.map(result, function(d) { return d.date; })), + [columns[3].label].concat($.map(result, function(d) { return d[columns[3].label]; })), + [columns[4].label].concat($.map(result, function(d) { return d[columns[4].label]; })), + [columns[5].label].concat($.map(result, function(d) { return d[columns[5].label]; })), + [columns[6].label].concat($.map(result, function(d) { return d[columns[6].label]; })), + [columns[7].label].concat($.map(result, function(d) { return d[columns[7].label]; })) + ] + }, + chart_type: 'bar', + + } + } +} diff --git a/erpnext/support/report/support_hours/support_hours.json b/erpnext/support/report/support_hours/support_hours.json new file mode 100644 index 00000000000..01e4bb44cb3 --- /dev/null +++ b/erpnext/support/report/support_hours/support_hours.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 1, + "creation": "2017-06-23 14:21:37.558691", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "", + "modified": "2017-06-23 16:33:31.211390", + "modified_by": "Administrator", + "module": "Support", + "name": "Support Hours", + "owner": "Administrator", + "ref_doctype": "Issue", + "report_name": "Support Hours", + "report_type": "Script Report", + "roles": [ + { + "role": "Support Team" + }, + { + "role": "System Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/support/report/support_hours/support_hours.py b/erpnext/support/report/support_hours/support_hours.py new file mode 100644 index 00000000000..f1606cd646a --- /dev/null +++ b/erpnext/support/report/support_hours/support_hours.py @@ -0,0 +1,73 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import add_to_date, getdate, get_datetime + +time_slots = { + '12AM - 3AM': '00:00:00-03:00:00', + '3AM - 6AM': '03:00:00-06:00:00', + '6AM - 9AM': '06:00:00-09:00:00', + '9AM - 12PM': '09:00:00-12:00:00', + '12PM - 3PM': '12:00:00-15:00:00', + '3PM - 6PM': '15:00:00-18:00:00', + '6PM - 9PM': '18:00:00-21:00:00', + '9PM - 12AM': '21:00:00-23:00:00' +} + +def execute(filters=None): + columns, data = [], [] + if not filters.get('periodicity'): + filters['periodicity'] = 'Daily' + + columns = get_columns() + data = get_data(filters) + return columns, data + +def get_data(filters): + start_date = getdate(filters.from_date) + data = [] + while(start_date <= getdate(filters.to_date)): + hours_count = {'date': start_date} + for key, value in time_slots.items(): + start_time, end_time = value.split('-') + start_time = get_datetime("{0} {1}".format(start_date.strftime("%Y-%m-%d"), start_time)) + end_time = get_datetime("{0} {1}".format(start_date.strftime("%Y-%m-%d"), end_time)) + hours_count[key] = get_hours_count(start_time, end_time) + + if hours_count: + data.append(hours_count) + + start_date = add_to_date(start_date, days=1) + + return data + +def get_hours_count(start_time, end_time): + data = frappe.db.sql(""" select count(*) from `tabIssue` where creation + between %(start_time)s and %(end_time)s""", { + 'start_time': start_time, + 'end_time': end_time + }, as_list=1) or [] + + return data[0][0] if len(data) > 0 else 0 + +def get_columns(): + columns = [{ + "fieldname": "date", + "label": _("Date"), + "fieldtype": "Date", + "width": 100 + }] + + for label in ['12AM - 3AM', '3AM - 6AM', '6AM - 9AM', + '9AM - 12PM', '12PM - 3PM', '3PM - 6PM', '6PM - 9PM', '9PM - 12AM']: + columns.append({ + "fieldname": label, + "label": _(label), + "fieldtype": "Data", + "width": 120 + }) + + return columns \ No newline at end of file From 3959c7c3c1d2682371f698b8237ecb45e3eca22f Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Mon, 26 Jun 2017 17:29:09 +0530 Subject: [PATCH 13/27] filter out students which are already enrolled in program enrollment --- .../program_enrollment/program_enrollment.js | 10 ++++++++++ .../program_enrollment/program_enrollment.py | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/erpnext/schools/doctype/program_enrollment/program_enrollment.js b/erpnext/schools/doctype/program_enrollment/program_enrollment.js index dcdaabc83d2..d1b703b8ca6 100644 --- a/erpnext/schools/doctype/program_enrollment/program_enrollment.js +++ b/erpnext/schools/doctype/program_enrollment/program_enrollment.js @@ -30,6 +30,16 @@ frappe.ui.form.on("Program Enrollment", { } }); } + + frm.set_query("student", function() { + return{ + query: "erpnext.schools.doctype.program_enrollment.program_enrollment.get_students", + filters: { + 'academic_year': frm.doc.academic_year, + 'academic_term': frm.doc.academic_term + } + } + }); }, program: function(frm) { diff --git a/erpnext/schools/doctype/program_enrollment/program_enrollment.py b/erpnext/schools/doctype/program_enrollment/program_enrollment.py index feb4c2f60ed..1346ef651e2 100644 --- a/erpnext/schools/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/schools/doctype/program_enrollment/program_enrollment.py @@ -77,3 +77,21 @@ def get_program_courses(doctype, txt, searchfield, start, page_len, filters): "_txt": txt.replace('%', ''), "program": filters['program'] }) + + +@frappe.whitelist() +def get_students(doctype, txt, searchfield, start, page_len, filters): + if not filters.get("academic_term"): + filters["academic_term"] = frappe.defaults.get_defaults().academic_term + if not filters.get("academic_year"): + filters["academic_year"] = frappe.defaults.get_defaults().academic_year + enrolled_students = frappe.get_list("Program Enrollment", fields=["student"], filters= + {"academic_term": filters.get('academic_term'), "academic_year": filters.get('academic_year')}) + students = [d.student for d in enrolled_students] if enrolled_students else [""] + return frappe.db.sql("""select name, title from tabStudent + where name not in (%s) + and `%s` LIKE %s + order by idx desc, name + limit %s, %s""" % + (", ".join(['%s']*len(students)), searchfield, "%s", "%s", "%s"), + tuple(students + ["%%%s%%" % txt, start, page_len])) From d652221071ac9c05998ba1d2d0736a56b9a841a9 Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Tue, 27 Jun 2017 12:45:46 +0530 Subject: [PATCH 14/27] fetch queries for the students in the student group --- .../doctype/student_group/student_group.js | 16 ++++++++++++++ .../doctype/student_group/student_group.py | 22 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/erpnext/schools/doctype/student_group/student_group.js b/erpnext/schools/doctype/student_group/student_group.js index 83fe0945cc3..80355a8b947 100644 --- a/erpnext/schools/doctype/student_group/student_group.js +++ b/erpnext/schools/doctype/student_group/student_group.js @@ -9,6 +9,22 @@ frappe.ui.form.on("Student Group", { } }; }); + if (!frm.__islocal) { + frm.set_query("student", "students", function() { + return{ + query: "erpnext.schools.doctype.student_group.student_group.fetch_students", + filters: { + 'academic_year': frm.doc.academic_year, + 'group_based_on': frm.doc.group_based_on, + 'academic_term': frm.doc.academic_term, + 'program': frm.doc.program, + 'batch': frm.doc.batch, + 'course': frm.doc.course, + 'student_group': frm.doc.name + } + } + }); + } }, refresh: function(frm) { diff --git a/erpnext/schools/doctype/student_group/student_group.py b/erpnext/schools/doctype/student_group/student_group.py index 9cdf9c7f016..9822d90b6e3 100644 --- a/erpnext/schools/doctype/student_group/student_group.py +++ b/erpnext/schools/doctype/student_group/student_group.py @@ -95,3 +95,25 @@ def get_program_enrollment(academic_year, academic_term=None, program=None, batc '''.format(condition1=condition1, condition2=condition2), ({"academic_year": academic_year, "academic_term":academic_term, "program": program, "batch": batch, "course": course}), as_dict=1) + +@frappe.whitelist() +def fetch_students(doctype, txt, searchfield, start, page_len, filters): + if filters.get("group_based_on") != "Activity": + enrolled_students = get_program_enrollment(filters.get('academic_year'), filters.get('academic_term'), + filters.get('program'), filters.get('batch')) + student_group_student = frappe.db.sql_list('''select student from `tabStudent Group Student` where parent=%s''', + (filters.get('student_group'))) + students = ([d.student for d in enrolled_students if d.student not in student_group_student] + if enrolled_students else [""]) or [""] + return frappe.db.sql("""select name, title from tabStudent + where name in ({0}) and `{1}` LIKE %s + order by idx desc, name + limit %s, %s""".format(", ".join(['%s']*len(students)), searchfield), + tuple(students + ["%%%s%%" % txt, start, page_len])) + else: + return frappe.db.sql("""select name, title from tabStudent + where `{}` LIKE %s + order by idx desc, name + limit %s, %s""".format(searchfield), + tuple(["%%%s%%" % txt, start, page_len])) + From 544de60d369d060a39020bb6b0d51370dbaff911 Mon Sep 17 00:00:00 2001 From: Ashwini Save Date: Wed, 28 Jun 2017 11:44:09 +0530 Subject: [PATCH 15/27] Adding ERPNext logo for email tempates. --- erpnext/public/images/erpnext-logo.jpg | Bin 0 -> 5608 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 erpnext/public/images/erpnext-logo.jpg diff --git a/erpnext/public/images/erpnext-logo.jpg b/erpnext/public/images/erpnext-logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..811eca833efc7b2168cbb96d127aec0832709a6c GIT binary patch literal 5608 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 CVps_P literal 0 HcmV?d00001 From 5b7028c7bb167e4cf83ae3054ea28af9ef1e0187 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 28 Jun 2017 15:35:53 +0530 Subject: [PATCH 16/27] stock qty not changed if item change --- erpnext/manufacturing/doctype/bom/bom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 0f9fe8b8ade..41eec8d933e 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -128,6 +128,7 @@ class BOM(WebsiteGenerator): 'conversion_factor' : 1, 'bom_no' : args['bom_no'], 'rate' : rate, + 'stock_qty' : args.get("qty") or args.get("stock_qty") or 1, 'base_rate' : rate if self.company_currency() == self.currency else rate * self.conversion_rate } return ret_item From 2e4b4454b336ef740c12e4f9cb55c109a4a6c405 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 28 Jun 2017 17:31:44 +0530 Subject: [PATCH 17/27] [Fix] Module error during patch execution --- erpnext/patches/v7_2/contact_address_links.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v7_2/contact_address_links.py b/erpnext/patches/v7_2/contact_address_links.py index 07d9341f247..cf23e88798c 100644 --- a/erpnext/patches/v7_2/contact_address_links.py +++ b/erpnext/patches/v7_2/contact_address_links.py @@ -4,8 +4,8 @@ from frappe.utils import update_progress_bar def execute(): frappe.reload_doc('core', 'doctype', 'dynamic_link') - frappe.reload_doc('email', 'doctype', 'contact') - frappe.reload_doc('contact', 'doctype', 'address') + frappe.reload_doc('contacts', 'doctype', 'contact') + frappe.reload_doc('contacts', 'doctype', 'address') map_fields = ( ('Customer', 'customer'), ('Supplier', 'supplier'), From 1ef50c89dbf9236bc89c5d354caef7c487fc137e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 28 Jun 2017 23:52:22 +0530 Subject: [PATCH 18/27] [minor] use party_name instead of party and save GSTIN in uppercase --- .../templates/pages/regional/india/update_gstin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/templates/pages/regional/india/update_gstin.py b/erpnext/templates/pages/regional/india/update_gstin.py index 0d4ec37ef7f..5dc4316573d 100644 --- a/erpnext/templates/pages/regional/india/update_gstin.py +++ b/erpnext/templates/pages/regional/india/update_gstin.py @@ -11,16 +11,16 @@ def get_context(context): context.invalid_gstin = 1 party_type = 'Customer' - party = frappe.db.get_value('Customer', party) + party_name = frappe.db.get_value('Customer', party) - if not party: + if not party_name: party_type = 'Supplier' - party = frappe.db.get_value('Supplier', party) + party_name = frappe.db.get_value('Supplier', party) - if not party: + if not party_name: frappe.throw(_("Not Found"), frappe.DoesNotExistError) - context.party = frappe.get_doc(party_type, party) + context.party = frappe.get_doc(party_type, party_name) context.party.onload() @@ -31,7 +31,7 @@ def update_gstin(context): address_name = frappe.get_value('Address', key) if address_name: address = frappe.get_doc('Address', address_name) - address.gstin = value + address.gstin = value.upper() address.save(ignore_permissions=True) dirty = True From 435032f5bc125290811356c79367a13da293cd98 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 28 Jun 2017 23:59:47 +0530 Subject: [PATCH 19/27] [minor] fix typo --- erpnext/regional/doctype/gst_settings/gst_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.py b/erpnext/regional/doctype/gst_settings/gst_settings.py index 5edb1723368..45c565d193d 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.py +++ b/erpnext/regional/doctype/gst_settings/gst_settings.py @@ -83,7 +83,7 @@ def _send_gstin_reminder(party_type, party, default_email_id=None, sent_to=None)

Please help us send you GST Ready Invoices.

- Click on the here to update your GSTIN Number in our system + Click here to update your GSTIN Number in our system

From d6e8bb54527765da2b4cd4f86a9307a396fc71c5 Mon Sep 17 00:00:00 2001 From: mbauskar Date: Wed, 28 Jun 2017 18:01:58 +0530 Subject: [PATCH 20/27] [fixes] delete auto email report of deprecated reports and other minor fixes --- .../patches/v8_1/delete_deprecated_reports.py | 35 +++++++++++++++---- erpnext/patches/v8_1/setup_gst_india.py | 2 +- erpnext/setup/setup_wizard/setup_wizard.py | 2 +- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/erpnext/patches/v8_1/delete_deprecated_reports.py b/erpnext/patches/v8_1/delete_deprecated_reports.py index 7e861319bf8..9047d84fc86 100644 --- a/erpnext/patches/v8_1/delete_deprecated_reports.py +++ b/erpnext/patches/v8_1/delete_deprecated_reports.py @@ -15,17 +15,40 @@ def execute(): for report in reports: if frappe.db.exists("Report", report): check_and_update_desktop_icon_for_report(report) + check_and_update_auto_email_report(report) + frappe.db.commit() + frappe.delete_doc("Report", report, ignore_permissions=True) def check_and_update_desktop_icon_for_report(report): - """ delete desktop icon for deprecated desktop icon and update the _report for Addresses And Contacts""" + """ delete or update desktop icon""" + desktop_icons = frappe.db.sql_list("""select name from `tabDesktop Icon` + where _report='{0}'""".format(report)) + + if not desktop_icons: + return if report == "Monthly Salary Register": - frappe.delete_doc("Desktop Icon", report) + for icon in desktop_icons: + frappe.delete_doc("Desktop Icon", icon) elif report in ["Customer Addresses And Contacts", "Supplier Addresses And Contacts"]: - name = frappe.db.get_value("Desktop Icon", {"_report": report}) - if name: - frappe.db.set_value("Desktop Icon", name, "_report", "Addresses And Contacts") + frappe.db.sql("""update `tabDesktop Icon` set _report='{value}' + where name in ({docnames})""".format( + value=report, + docnames=",".join(["'%s'"%icon for icon in desktop_icons]) + ) + ) - frappe.db.commit() +def check_and_update_auto_email_report(report): + """ delete or update auto email report for deprecated report """ + + auto_email_report = frappe.db.get_value("Auto Email Report", {"report": report}) + if not auto_email_report: + return + + if report == "Monthly Salary Register": + frappe.delete_doc("Auto Email Report", auto_email_report) + + elif report in ["Customer Addresses And Contacts", "Supplier Addresses And Contacts"]: + frapppe.db.set_value("Auto Email Report", auto_email_report, "report", report) \ No newline at end of file diff --git a/erpnext/patches/v8_1/setup_gst_india.py b/erpnext/patches/v8_1/setup_gst_india.py index 374c738f13a..1b319f96805 100644 --- a/erpnext/patches/v8_1/setup_gst_india.py +++ b/erpnext/patches/v8_1/setup_gst_india.py @@ -18,7 +18,7 @@ def execute(): def delete_custom_field_tax_id_if_exists(): for field in frappe.db.sql_list("""select name from `tabCustom Field` where fieldname='tax_id' - and dt in ('Sales Order', 'Salse Invoice', 'Delivery Note')"""): + and dt in ('Sales Order', 'Sales Invoice', 'Delivery Note')"""): frappe.delete_doc("Custom Field", field, ignore_permissions=True) frappe.db.commit() diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index 47e64d76a32..5c7697ef975 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -196,7 +196,7 @@ def set_defaults(args): hr_settings.save() domain_settings = frappe.get_doc("Domain Settings") - domain_settings.append('active_domains', dict(domain=args.domain)) + domain_settings.append('active_domains', dict(domain=_(args.domain))) domain_settings.save() def create_feed_and_todo(): From 5c7545da0c3edfbff0c679e2a75d33d36971f601 Mon Sep 17 00:00:00 2001 From: Makarand Bauskar Date: Thu, 29 Jun 2017 09:07:59 +0530 Subject: [PATCH 21/27] [minor] passed index for string format --- erpnext/schools/doctype/student_group/student_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/schools/doctype/student_group/student_group.py b/erpnext/schools/doctype/student_group/student_group.py index 9822d90b6e3..26bd3113829 100644 --- a/erpnext/schools/doctype/student_group/student_group.py +++ b/erpnext/schools/doctype/student_group/student_group.py @@ -112,7 +112,7 @@ def fetch_students(doctype, txt, searchfield, start, page_len, filters): tuple(students + ["%%%s%%" % txt, start, page_len])) else: return frappe.db.sql("""select name, title from tabStudent - where `{}` LIKE %s + where `{0}` LIKE %s order by idx desc, name limit %s, %s""".format(searchfield), tuple(["%%%s%%" % txt, start, page_len])) From af00c9f70bcf9a8154b57fc52c043455daacdf5e Mon Sep 17 00:00:00 2001 From: Makarand Bauskar Date: Thu, 29 Jun 2017 09:25:19 +0530 Subject: [PATCH 22/27] [minor] removed whitespaces from get_students method --- .../program_enrollment/program_enrollment.py | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/erpnext/schools/doctype/program_enrollment/program_enrollment.py b/erpnext/schools/doctype/program_enrollment/program_enrollment.py index 1346ef651e2..32cfe6b2b31 100644 --- a/erpnext/schools/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/schools/doctype/program_enrollment/program_enrollment.py @@ -83,15 +83,28 @@ def get_program_courses(doctype, txt, searchfield, start, page_len, filters): def get_students(doctype, txt, searchfield, start, page_len, filters): if not filters.get("academic_term"): filters["academic_term"] = frappe.defaults.get_defaults().academic_term + if not filters.get("academic_year"): filters["academic_year"] = frappe.defaults.get_defaults().academic_year - enrolled_students = frappe.get_list("Program Enrollment", fields=["student"], filters= - {"academic_term": filters.get('academic_term'), "academic_year": filters.get('academic_year')}) + + enrolled_students = frappe.get_list("Program Enrollment", filters={ + "academic_term": filters.get('academic_term'), + "academic_year": filters.get('academic_year') + }, fields=["student"]) + students = [d.student for d in enrolled_students] if enrolled_students else [""] - return frappe.db.sql("""select name, title from tabStudent - where name not in (%s) - and `%s` LIKE %s - order by idx desc, name - limit %s, %s""" % - (", ".join(['%s']*len(students)), searchfield, "%s", "%s", "%s"), - tuple(students + ["%%%s%%" % txt, start, page_len])) + + return frappe.db.sql("""select + name, title from tabStudent + where + name not in (%s) + and + `%s` LIKE %s + order by + idx desc, name + limit %s, %s"""%( + ", ".join(['%s']*len(students)), searchfield, "%s", "%s", "%s"), + tuple(students + ["%%%s%%" % txt, start, page_len] + ) + ) + From e38eb8335859807a75115195ea7c7c3987754ed6 Mon Sep 17 00:00:00 2001 From: Makarand Bauskar Date: Thu, 29 Jun 2017 09:35:11 +0530 Subject: [PATCH 23/27] [minor] removed print statement --- erpnext/schools/doctype/student_group/student_group.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/schools/doctype/student_group/student_group.py b/erpnext/schools/doctype/student_group/student_group.py index f2d52a2be8e..27d073a212a 100644 --- a/erpnext/schools/doctype/student_group/student_group.py +++ b/erpnext/schools/doctype/student_group/student_group.py @@ -30,7 +30,6 @@ class StudentGroup(Document): def validate_students(self): program_enrollment = get_program_enrollment(self.academic_year, self.academic_term, self.program, self.batch, self.course) - print program_enrollment students = [d.student for d in program_enrollment] if program_enrollment else [] for d in self.students: if not frappe.db.get_value("Student", d.student, "enabled") and d.active: From 7f9af46da5ecc15495193b36a28a657e487cd64c Mon Sep 17 00:00:00 2001 From: Makarand Bauskar Date: Thu, 29 Jun 2017 09:38:46 +0530 Subject: [PATCH 24/27] [minor] removed whitespace --- .../schools/doctype/program_enrollment/program_enrollment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/schools/doctype/program_enrollment/program_enrollment.py b/erpnext/schools/doctype/program_enrollment/program_enrollment.py index 32cfe6b2b31..4e679082c67 100644 --- a/erpnext/schools/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/schools/doctype/program_enrollment/program_enrollment.py @@ -94,8 +94,8 @@ def get_students(doctype, txt, searchfield, start, page_len, filters): students = [d.student for d in enrolled_students] if enrolled_students else [""] - return frappe.db.sql("""select - name, title from tabStudent + return frappe.db.sql("""select + name, title from tabStudent where name not in (%s) and From 76f93d05b7fb5ce18d2c2c182315442ee4b4e5cc Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Thu, 29 Jun 2017 11:23:13 +0530 Subject: [PATCH 25/27] display image in supplier's list view (#9500) * [fix]display supplier image in desk * Supplier image displayed in desk * Show image in supplier desk page * [minor] removed whitespaces --- erpnext/buying/doctype/supplier/supplier_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/supplier/supplier_list.js b/erpnext/buying/doctype/supplier/supplier_list.js index dd98c434f1e..ab25d2c8fcf 100644 --- a/erpnext/buying/doctype/supplier/supplier_list.js +++ b/erpnext/buying/doctype/supplier/supplier_list.js @@ -1,3 +1,3 @@ frappe.listview_settings['Supplier'] = { - add_fields: ["supplier_name", "supplier_type"], + add_fields: ["supplier_name", "supplier_type", "image"], }; From f1bd39c937abe08e92fcba1cd41ce8ab5dd27310 Mon Sep 17 00:00:00 2001 From: Aditya Duggal Date: Thu, 29 Jun 2017 14:25:19 +0530 Subject: [PATCH 26/27] Allow NA values in GSTIN number (#9503) -Reason for allowing NA value is that if a user wants to make GSTIN number mandatory then they can do the same easily --- erpnext/regional/india/utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 26c579448dc..4d37498114f 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -7,9 +7,10 @@ def validate_gstin_for_india(doc, method): return if doc.gstin: - 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}") - if not p.match(doc.gstin): - frappe.throw(_("Invalid GSTIN")) + if doc.gstin != "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}") + if not p.match(doc.gstin): + frappe.throw(_("Invalid GSTIN or Enter NA for Unregistered")) if not doc.gst_state: if doc.state in states: From 8f1f93603d230b680e75307fe132babb3f67470b Mon Sep 17 00:00:00 2001 From: mbauskar Date: Thu, 29 Jun 2017 15:37:26 +0600 Subject: [PATCH 27/27] bumped to version 8.2.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 8c387dd887a..32c971969b5 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe -__version__ = '8.1.7' +__version__ = '8.2.0' def get_default_company(user=None):