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:
parent
dd63b656c7
commit
d79a16c9f3
@ -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
|
||||||
|
@ -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
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user