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 <nabinhait@gmail.com>
This commit is contained in:
Suraj Shetty 2019-08-21 09:03:26 +05:30
parent dd63b656c7
commit d79a16c9f3
5 changed files with 126 additions and 82 deletions

View File

@ -710,7 +710,7 @@ def get_work_order_operation_data(work_order, operation, workstation):
return d return d
@frappe.whitelist() @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): def update_item_quantity(source, target, source_parent):
qty = source.required_qty - source.transferred_qty qty = source.required_qty - source.transferred_qty
target.qty = qty target.qty = qty

View File

@ -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) => { 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.add_custom_button(__('Get Item Locations'), () => {
frm.call('set_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) => { items_based_on: (frm) => {
frm.trigger('add_get_items_button'); frm.trigger('add_get_items_button');
@ -33,40 +49,29 @@ frappe.ui.form.on('Pick List', {
}); });
}, },
add_get_items_button(frm) { add_get_items_button(frm) {
frm.remove_custom_button(__("Get items"));
let source_doctype = frm.doc.items_based_on; let source_doctype = frm.doc.items_based_on;
let date_field = 'transaction_date'; if (source_doctype != 'Sales Order') return;
let get_query_method = null;
let get_query_filters = { let get_query_filters = {
docstatus: 1, docstatus: 1,
per_delivered: ['<', 100], per_delivered: ['<', 100],
status: ['!=', ''] status: ['!=', ''],
customer: frm.doc.customer
}; };
let method = 'erpnext.selling.doctype.sales_order.sales_order.make_pick_list'; frm.get_items_btn = frm.add_custom_button(__('Get Items'), () => {
if (source_doctype === 'Work Order') { if (!frm.doc.customer) {
method = 'erpnext.manufacturing.doctype.work_order.work_order.make_pick_list'; frappe.msgprint(__('Please select Customer first'));
date_field = 'planned_start_date'; return;
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"), () => {
erpnext.utils.map_current_doc({ erpnext.utils.map_current_doc({
method: method, method: 'erpnext.selling.doctype.sales_order.sales_order.make_pick_list',
source_doctype: source_doctype, source_doctype: 'Sales Order',
target: frm, target: frm,
setters: { setters: {
company: frm.doc.company, company: frm.doc.company,
customer: frm.doc.customer
}, },
date_field: date_field, date_field: 'transaction_date',
get_query: get_query get_query_filters: get_query_filters
}); });
}); });
} }

View File

@ -5,21 +5,25 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"company",
"column_break_4",
"items_based_on", "items_based_on",
"customer",
"work_order",
"column_break_4",
"parent_warehouse", "parent_warehouse",
"company",
"section_break_4", "section_break_4",
"reference_items", "items",
"section_break_6", "section_break_6",
"item_locations" "locations"
], ],
"fields": [ "fields": [
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Company", "label": "Company",
"options": "Company" "options": "Company",
"reqd": 1
}, },
{ {
"fieldname": "column_break_4", "fieldname": "column_break_4",
@ -41,26 +45,45 @@
"options": "Warehouse" "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", "fieldtype": "Table",
"label": "Item Locations", "label": "Item Locations",
"options": "Pick List Item" "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", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Pick List", "name": "Pick List",

View File

@ -14,25 +14,29 @@ from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note a
class PickList(Document): class PickList(Document):
def set_item_locations(self): def set_item_locations(self):
reference_items = self.reference_items items = self.items
self.item_location_map = frappe._dict()
from_warehouses = None from_warehouses = None
if self.parent_warehouse: if self.parent_warehouse:
from_warehouses = frappe.db.get_descendants('Warehouse', self.parent_warehouse) from_warehouses = frappe.db.get_descendants('Warehouse', self.parent_warehouse)
# Reset # Reset
self.delete_key('item_locations') self.delete_key('locations')
for item_doc in reference_items: for item_doc in items:
if frappe.get_cached_value('Item', item_doc.item_code, 'has_serial_no'): item_code = item_doc.item_code
item_locations = get_item_locations_based_on_serial_nos(item_doc) if frappe.get_cached_value('Item', item_code, 'has_serial_no'):
elif frappe.get_cached_value('Item', item_doc.item_code, 'has_batch_no'): locations = get_item_locations_based_on_serial_nos(item_doc)
item_locations = get_item_locations_based_on_batch_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: 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({ row.update({
'item_code': item_doc.item_code, 'item_code': item_code,
'sales_order': item_doc.sales_order, 'sales_order': item_doc.sales_order,
'sales_order_item': item_doc.sales_order_item, 'sales_order_item': item_doc.sales_order_item,
'uom': item_doc.uom, 'uom': item_doc.uom,
@ -41,14 +45,15 @@ class PickList(Document):
'stock_qty': row.get("qty", 0) * item_doc.conversion_factor, 'stock_qty': row.get("qty", 0) * item_doc.conversion_factor,
'picked_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): def get_items_with_warehouse_and_quantity(item_doc, from_warehouses, item_location_map):
item_locations = [] available_locations = item_location_map.get(item_doc.item_code)
item_location_map = get_available_items(item_doc.item_code, from_warehouses)
locations = []
remaining_stock_qty = item_doc.stock_qty remaining_stock_qty = item_doc.stock_qty
while remaining_stock_qty > 0 and item_location_map: while remaining_stock_qty > 0 and available_locations:
item_location = item_location_map.pop(0) item_location = available_locations.pop(0)
stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty 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) 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) qty = floor(qty)
stock_qty = qty * item_doc.conversion_factor stock_qty = qty * item_doc.conversion_factor
item_locations.append({ locations.append({
'qty': qty, 'qty': qty,
'warehouse': item_location.warehouse 'warehouse': item_location.warehouse
}) })
remaining_stock_qty -= stock_qty 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: if remaining_stock_qty:
frappe.msgprint('{0} {1} of {2} is not available.' frappe.msgprint('{0} {1} of {2} is not available.'
.format(remaining_stock_qty / item_doc.conversion_factor, item_doc.uom, item_doc.item_code)) .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): def get_available_items(item_code, from_warehouses):
# gets all items available in different 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: for serial_no, warehouse in serial_nos:
warehouse_serial_nos_map.setdefault(warehouse, []).append(serial_no) warehouse_serial_nos_map.setdefault(warehouse, []).append(serial_no)
item_locations = [] locations = []
for warehouse, serial_nos in iteritems(warehouse_serial_nos_map): for warehouse, serial_nos in iteritems(warehouse_serial_nos_map):
item_locations.append({ locations.append({
'qty': len(serial_nos), 'qty': len(serial_nos),
'warehouse': warehouse, 'warehouse': warehouse,
'serial_no': '\n'.join(serial_nos) 'serial_no': '\n'.join(serial_nos)
}) })
return item_locations return locations
def get_item_locations_based_on_batch_nos(item_doc): def get_item_locations_based_on_batch_nos(item_doc):
batch_qty = frappe.db.sql(""" batch_qty = frappe.db.sql("""
@ -143,7 +157,7 @@ def get_item_locations_based_on_batch_nos(item_doc):
'today': today() 'today': today()
}, as_dict=1) }, as_dict=1)
item_locations = [] locations = []
required_qty = item_doc.qty required_qty = item_doc.qty
for d in batch_qty: for d in batch_qty:
if d.qty > required_qty: if d.qty > required_qty:
@ -151,7 +165,7 @@ def get_item_locations_based_on_batch_nos(item_doc):
else: else:
required_qty -= d.qty required_qty -= d.qty
item_locations.append(d) locations.append(d)
if required_qty <= 0: if required_qty <= 0:
break break
@ -159,12 +173,12 @@ def get_item_locations_based_on_batch_nos(item_doc):
if required_qty: if required_qty:
frappe.msgprint('No batches found for {} qty of {}.'.format(required_qty, item_doc.item_code)) frappe.msgprint('No batches found for {} qty of {}.'.format(required_qty, item_doc.item_code))
return item_locations return locations
@frappe.whitelist() @frappe.whitelist()
def make_delivery_note(source_name, target_doc=None): def make_delivery_note(source_name, target_doc=None):
pick_list = frappe.get_doc('Pick List', source_name) 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) sales_orders = set(sales_orders)
delivery_note = None 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 = make_delivery_note_from_sales_order(sales_order,
delivery_note, skip_item_mapping=True) 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) sales_order_item = frappe.get_cached_doc('Sales Order Item', location.sales_order_item)
item_table_mapper = { item_table_mapper = {
"doctype": "Delivery Note Item", "doctype": "Delivery Note Item",
@ -232,10 +246,11 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
FROM FROM
`tabWork Order` `tabWork Order`
WHERE WHERE
`qty` > `produced_qty` `status` not in ('Completed', 'Stopped')
AND `status` not in ('Completed', 'Stopped') AND `qty` > `produced_qty`
AND name like %(txt)s AND `docstatus` = 1
AND docstatus = 1 AND `company` = %(company)s
AND `name` like %(txt)s
ORDER BY ORDER BY
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name
LIMIT LIMIT
@ -245,4 +260,5 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
'_txt': txt.replace('%', ''), '_txt': txt.replace('%', ''),
'start': start, 'start': start,
'page_length': frappe.utils.cint(page_length), 'page_length': frappe.utils.cint(page_length),
'company': filters.get('company')
}, as_dict=as_dict) }, as_dict=as_dict)

View File

@ -14,7 +14,7 @@ class TestPickList(unittest.TestCase):
pick_list = frappe.get_doc({ pick_list = frappe.get_doc({
'doctype': 'Pick List', 'doctype': 'Pick List',
'company': '_Test Company', 'company': '_Test Company',
'reference_items': [{ 'items': [{
'item': '_Test Item Home Desktop 100', 'item': '_Test Item Home Desktop 100',
'reference_doctype': 'Sales Order', 'reference_doctype': 'Sales Order',
'qty': 5, 'qty': 5,
@ -32,7 +32,7 @@ class TestPickList(unittest.TestCase):
pick_list = frappe.get_doc({ pick_list = frappe.get_doc({
'doctype': 'Pick List', 'doctype': 'Pick List',
'company': '_Test Company', 'company': '_Test Company',
'reference_items': [{ 'items': [{
'item': '_Test Item Warehouse Group Wise Reorder', 'item': '_Test Item Warehouse Group Wise Reorder',
'reference_doctype': 'Sales Order', 'reference_doctype': 'Sales Order',
'qty': 1000, 'qty': 1000,
@ -68,7 +68,7 @@ class TestPickList(unittest.TestCase):
pick_list = frappe.get_doc({ pick_list = frappe.get_doc({
'doctype': 'Pick List', 'doctype': 'Pick List',
'company': '_Test Company', 'company': '_Test Company',
'reference_items': [{ 'items': [{
'item': '_Test Serialized Item', 'item': '_Test Serialized Item',
'reference_doctype': 'Sales Order', 'reference_doctype': 'Sales Order',
'qty': 1000, 'qty': 1000,