From d79a16c9f3c33faeb14ea35aefcf71db5f1f9ae9 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 21 Aug 2019 09:03:26 +0530 Subject: [PATCH] fix: Warehouse selection login - Rename item_loactions -> locations, reference_items -> items - Add customer and work_order field - fix "Get Items" button UX - fix get_pending_work_orders query Co-authored-by: Nabin Hait --- .../doctype/work_order/work_order.py | 2 +- erpnext/stock/doctype/pick_list/pick_list.js | 61 ++++++++------- .../stock/doctype/pick_list/pick_list.json | 63 ++++++++++----- erpnext/stock/doctype/pick_list/pick_list.py | 76 +++++++++++-------- .../stock/doctype/pick_list/test_pick_list.py | 6 +- 5 files changed, 126 insertions(+), 82 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index c653deece3..988cd223f2 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -710,7 +710,7 @@ def get_work_order_operation_data(work_order, operation, workstation): return d @frappe.whitelist() -def make_pick_list(source_name, target_doc=None): +def create_pick_list(source_name, target_doc=None): def update_item_quantity(source, target, source_parent): qty = source.required_qty - source.transferred_qty target.qty = qty diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js index b9b317b732..bd893aafd0 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.js +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -11,17 +11,33 @@ frappe.ui.form.on('Pick List', { } }; }); + frm.set_query('work_order', () => { + return { + query: 'erpnext.stock.doctype.pick_list.pick_list.get_pending_work_orders', + filters: { + 'company': frm.doc.company + } + }; + }); }, refresh: (frm) => { - frm.add_custom_button(__('Delivery Note'), () => frm.trigger('make_delivery_note'), __('Create')); + frm.trigger('add_get_items_button'); - if (frm.doc.reference_items && frm.doc.reference_items.length) { + if (frm.doc.items && (frm.doc.items.length > 1 || frm.doc.items[0].item_code)) { frm.add_custom_button(__('Get Item Locations'), () => { frm.call('set_item_locations'); - }); + }).addClass('btn-primary'); } - frm.trigger('add_get_items_button'); + frm.add_custom_button(__('Delivery Note'), () => frm.trigger('make_delivery_note'), __('Create')); + }, + work_order: (frm) => { + frm.clear_table('items'); + erpnext.utils.map_current_doc({ + method: 'erpnext.manufacturing.doctype.work_order.work_order.create_pick_list', + target: frm, + source_name: frm.doc.work_order + }); }, items_based_on: (frm) => { frm.trigger('add_get_items_button'); @@ -33,40 +49,29 @@ frappe.ui.form.on('Pick List', { }); }, add_get_items_button(frm) { - frm.remove_custom_button(__("Get items")); let source_doctype = frm.doc.items_based_on; - let date_field = 'transaction_date'; - let get_query_method = null; + if (source_doctype != 'Sales Order') return; let get_query_filters = { docstatus: 1, per_delivered: ['<', 100], - status: ['!=', ''] + status: ['!=', ''], + customer: frm.doc.customer }; - let method = 'erpnext.selling.doctype.sales_order.sales_order.make_pick_list'; - if (source_doctype === 'Work Order') { - method = 'erpnext.manufacturing.doctype.work_order.work_order.make_pick_list'; - date_field = 'planned_start_date'; - get_query_method = 'erpnext.stock.doctype.pick_list.pick_list.get_pending_work_orders'; - get_query_filters = null; - } - - let get_query = () => { - return { - 'query': get_query_method, - 'filters': get_query_filters - }; - }; - - frm.add_custom_button(__("Get items"), () => { + frm.get_items_btn = frm.add_custom_button(__('Get Items'), () => { + if (!frm.doc.customer) { + frappe.msgprint(__('Please select Customer first')); + return; + } erpnext.utils.map_current_doc({ - method: method, - source_doctype: source_doctype, + method: 'erpnext.selling.doctype.sales_order.sales_order.make_pick_list', + source_doctype: 'Sales Order', target: frm, setters: { company: frm.doc.company, + customer: frm.doc.customer }, - date_field: date_field, - get_query: get_query + date_field: 'transaction_date', + get_query_filters: get_query_filters }); }); } diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index 70272bf584..1a33622167 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -5,21 +5,25 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "company", - "column_break_4", "items_based_on", + "customer", + "work_order", + "column_break_4", "parent_warehouse", + "company", "section_break_4", - "reference_items", + "items", "section_break_6", - "item_locations" + "locations" ], "fields": [ { "fieldname": "company", "fieldtype": "Link", + "in_list_view": 1, "label": "Company", - "options": "Company" + "options": "Company", + "reqd": 1 }, { "fieldname": "column_break_4", @@ -41,26 +45,45 @@ "options": "Warehouse" }, { - "fieldname": "item_locations", + "default": "Work Order", + "fieldname": "items_based_on", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Items Based On", + "options": "Sales Order\nWork Order", + "reqd": 1 + }, + { + "depends_on": "eval:doc.items_based_on===\"Sales Order\"", + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer", + "options": "Customer", + "reqd": 1 + }, + { + "depends_on": "eval:doc.items_based_on===\"Work Order\"", + "fieldname": "work_order", + "fieldtype": "Link", + "label": "Work Order", + "options": "Work Order" + }, + { + "fieldname": "items", + "fieldtype": "Table", + "label": "Items To Be Picked", + "options": "Pick List Reference Item", + "reqd": 1 + }, + { + "fieldname": "locations", "fieldtype": "Table", "label": "Item Locations", "options": "Pick List Item" - }, - { - "fieldname": "reference_items", - "fieldtype": "Table", - "label": "Items To Be Picked", - "options": "Pick List Reference Item" - }, - { - "default": "Sales Order", - "fieldname": "items_based_on", - "fieldtype": "Select", - "label": "Items Based On", - "options": "Sales Order\nWork Order" } ], - "modified": "2019-08-19 12:31:54.023456", + "modified": "2019-08-20 16:57:11.006221", "modified_by": "Administrator", "module": "Stock", "name": "Pick List", diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 91bdcb33ee..8699ee6e19 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -14,25 +14,29 @@ from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note a class PickList(Document): def set_item_locations(self): - reference_items = self.reference_items + items = self.items + self.item_location_map = frappe._dict() from_warehouses = None if self.parent_warehouse: from_warehouses = frappe.db.get_descendants('Warehouse', self.parent_warehouse) # Reset - self.delete_key('item_locations') - for item_doc in reference_items: - if frappe.get_cached_value('Item', item_doc.item_code, 'has_serial_no'): - item_locations = get_item_locations_based_on_serial_nos(item_doc) - elif frappe.get_cached_value('Item', item_doc.item_code, 'has_batch_no'): - item_locations = get_item_locations_based_on_batch_nos(item_doc) + self.delete_key('locations') + for item_doc in items: + item_code = item_doc.item_code + if frappe.get_cached_value('Item', item_code, 'has_serial_no'): + locations = get_item_locations_based_on_serial_nos(item_doc) + elif frappe.get_cached_value('Item', item_code, 'has_batch_no'): + locations = get_item_locations_based_on_batch_nos(item_doc) else: - item_locations = get_items_with_warehouse_and_quantity(item_doc, from_warehouses) + if item_code not in self.item_location_map: + self.item_location_map[item_code] = get_available_items(item_code, from_warehouses) + locations = get_items_with_warehouse_and_quantity(item_doc, from_warehouses, self.item_location_map) - for row in item_locations: + for row in locations: row.update({ - 'item_code': item_doc.item_code, + 'item_code': item_code, 'sales_order': item_doc.sales_order, 'sales_order_item': item_doc.sales_order_item, 'uom': item_doc.uom, @@ -41,14 +45,15 @@ class PickList(Document): 'stock_qty': row.get("qty", 0) * item_doc.conversion_factor, 'picked_qty': row.get("qty", 0) * item_doc.conversion_factor }) - self.append('item_locations', row) + self.append('locations', row) -def get_items_with_warehouse_and_quantity(item_doc, from_warehouses): - item_locations = [] - item_location_map = get_available_items(item_doc.item_code, from_warehouses) +def get_items_with_warehouse_and_quantity(item_doc, from_warehouses, item_location_map): + available_locations = item_location_map.get(item_doc.item_code) + + locations = [] remaining_stock_qty = item_doc.stock_qty - while remaining_stock_qty > 0 and item_location_map: - item_location = item_location_map.pop(0) + while remaining_stock_qty > 0 and available_locations: + item_location = available_locations.pop(0) stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty qty = stock_qty / (item_doc.conversion_factor or 1) @@ -57,16 +62,25 @@ def get_items_with_warehouse_and_quantity(item_doc, from_warehouses): qty = floor(qty) stock_qty = qty * item_doc.conversion_factor - item_locations.append({ + locations.append({ 'qty': qty, 'warehouse': item_location.warehouse }) remaining_stock_qty -= stock_qty + qty_diff = item_location.qty - stock_qty + # if extra quantity is available push current warehouse to available locations + if qty_diff: + item_location.qty = qty_diff + available_locations = [item_location] + available_locations + if remaining_stock_qty: frappe.msgprint('{0} {1} of {2} is not available.' .format(remaining_stock_qty / item_doc.conversion_factor, item_doc.uom, item_doc.item_code)) - return item_locations + + # update available locations for the item + item_location_map[item_doc.item_code] = available_locations + return locations def get_available_items(item_code, from_warehouses): # gets all items available in different warehouses @@ -110,15 +124,15 @@ def get_item_locations_based_on_serial_nos(item_doc): for serial_no, warehouse in serial_nos: warehouse_serial_nos_map.setdefault(warehouse, []).append(serial_no) - item_locations = [] + locations = [] for warehouse, serial_nos in iteritems(warehouse_serial_nos_map): - item_locations.append({ + locations.append({ 'qty': len(serial_nos), 'warehouse': warehouse, 'serial_no': '\n'.join(serial_nos) }) - return item_locations + return locations def get_item_locations_based_on_batch_nos(item_doc): batch_qty = frappe.db.sql(""" @@ -143,7 +157,7 @@ def get_item_locations_based_on_batch_nos(item_doc): 'today': today() }, as_dict=1) - item_locations = [] + locations = [] required_qty = item_doc.qty for d in batch_qty: if d.qty > required_qty: @@ -151,7 +165,7 @@ def get_item_locations_based_on_batch_nos(item_doc): else: required_qty -= d.qty - item_locations.append(d) + locations.append(d) if required_qty <= 0: break @@ -159,12 +173,12 @@ def get_item_locations_based_on_batch_nos(item_doc): if required_qty: frappe.msgprint('No batches found for {} qty of {}.'.format(required_qty, item_doc.item_code)) - return item_locations + return locations @frappe.whitelist() def make_delivery_note(source_name, target_doc=None): pick_list = frappe.get_doc('Pick List', source_name) - sales_orders = [d.sales_order for d in pick_list.item_locations] + sales_orders = [d.sales_order for d in pick_list.locations] sales_orders = set(sales_orders) delivery_note = None @@ -172,7 +186,7 @@ def make_delivery_note(source_name, target_doc=None): delivery_note = make_delivery_note_from_sales_order(sales_order, delivery_note, skip_item_mapping=True) - for location in pick_list.item_locations: + for location in pick_list.locations: sales_order_item = frappe.get_cached_doc('Sales Order Item', location.sales_order_item) item_table_mapper = { "doctype": "Delivery Note Item", @@ -232,10 +246,11 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte FROM `tabWork Order` WHERE - `qty` > `produced_qty` - AND `status` not in ('Completed', 'Stopped') - AND name like %(txt)s - AND docstatus = 1 + `status` not in ('Completed', 'Stopped') + AND `qty` > `produced_qty` + AND `docstatus` = 1 + AND `company` = %(company)s + AND `name` like %(txt)s ORDER BY if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name LIMIT @@ -245,4 +260,5 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte '_txt': txt.replace('%', ''), 'start': start, 'page_length': frappe.utils.cint(page_length), + 'company': filters.get('company') }, as_dict=as_dict) diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index ed4b6b58cc..4048e5d0b0 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -14,7 +14,7 @@ class TestPickList(unittest.TestCase): pick_list = frappe.get_doc({ 'doctype': 'Pick List', 'company': '_Test Company', - 'reference_items': [{ + 'items': [{ 'item': '_Test Item Home Desktop 100', 'reference_doctype': 'Sales Order', 'qty': 5, @@ -32,7 +32,7 @@ class TestPickList(unittest.TestCase): pick_list = frappe.get_doc({ 'doctype': 'Pick List', 'company': '_Test Company', - 'reference_items': [{ + 'items': [{ 'item': '_Test Item Warehouse Group Wise Reorder', 'reference_doctype': 'Sales Order', 'qty': 1000, @@ -68,7 +68,7 @@ class TestPickList(unittest.TestCase): pick_list = frappe.get_doc({ 'doctype': 'Pick List', 'company': '_Test Company', - 'reference_items': [{ + 'items': [{ 'item': '_Test Serialized Item', 'reference_doctype': 'Sales Order', 'qty': 1000,