Merge pull request #20962 from marination/pick-list-enhance
fix: Pick List Enhancements
This commit is contained in:
commit
47c8ddb29b
@ -664,3 +664,4 @@ erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22
|
||||
erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom
|
||||
erpnext.patches.v12_0.recalculate_requested_qty_in_bin
|
||||
erpnext.patches.v12_0.set_total_batch_quantity
|
||||
erpnext.patches.v12_0.set_updated_purpose_in_pick_list
|
||||
|
11
erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py
Normal file
11
erpnext/patches/v12_0/set_updated_purpose_in_pick_list.py
Normal file
@ -0,0 +1,11 @@
|
||||
# Copyright (c) 2019, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("stock", "doctype", "pick_list")
|
||||
frappe.db.sql("""UPDATE `tabPick List` set purpose = 'Delivery'
|
||||
WHERE docstatus = 1 and purpose = 'Delivery against Sales Order' """)
|
@ -1036,7 +1036,7 @@ def create_pick_list(source_name, target_doc=None):
|
||||
},
|
||||
}, target_doc)
|
||||
|
||||
doc.purpose = 'Delivery against Sales Order'
|
||||
doc.purpose = 'Delivery'
|
||||
|
||||
doc.set_item_locations()
|
||||
|
||||
|
@ -38,13 +38,17 @@ frappe.ui.form.on('Pick List', {
|
||||
};
|
||||
});
|
||||
},
|
||||
get_item_locations: (frm) => {
|
||||
if (!frm.doc.locations || !frm.doc.locations.length) {
|
||||
frappe.msgprint(__('First add items in the Item Locations table'));
|
||||
set_item_locations:(frm, save) => {
|
||||
if (!(frm.doc.locations && frm.doc.locations.length)) {
|
||||
frappe.msgprint(__('Add items in the Item Locations table'));
|
||||
} else {
|
||||
frm.call('set_item_locations');
|
||||
frm.call('set_item_locations', {save: save});
|
||||
}
|
||||
},
|
||||
get_item_locations: (frm) => {
|
||||
// Button on the form
|
||||
frm.events.set_item_locations(frm, false);
|
||||
},
|
||||
refresh: (frm) => {
|
||||
frm.trigger('add_get_items_button');
|
||||
if (frm.doc.docstatus === 1) {
|
||||
@ -52,8 +56,13 @@ frappe.ui.form.on('Pick List', {
|
||||
'pick_list_name': frm.doc.name,
|
||||
'purpose': frm.doc.purpose
|
||||
}).then(target_document_exists => {
|
||||
frm.set_df_property("locations", "allow_on_submit", target_document_exists ? 0 : 1);
|
||||
|
||||
if (target_document_exists) return;
|
||||
if (frm.doc.purpose === 'Delivery against Sales Order') {
|
||||
|
||||
frm.add_custom_button(__('Update Current Stock'), () => frm.trigger('update_pick_list_stock'));
|
||||
|
||||
if (frm.doc.purpose === 'Delivery') {
|
||||
frm.add_custom_button(__('Delivery Note'), () => frm.trigger('create_delivery_note'), __('Create'));
|
||||
} else {
|
||||
frm.add_custom_button(__('Stock Entry'), () => frm.trigger('create_stock_entry'), __('Create'));
|
||||
@ -105,6 +114,7 @@ frappe.ui.form.on('Pick List', {
|
||||
method: 'erpnext.stock.doctype.pick_list.pick_list.create_delivery_note',
|
||||
frm: frm
|
||||
});
|
||||
|
||||
},
|
||||
create_stock_entry: (frm) => {
|
||||
frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.create_stock_entry', {
|
||||
@ -114,9 +124,12 @@ frappe.ui.form.on('Pick List', {
|
||||
frappe.set_route("Form", 'Stock Entry', stock_entry.name);
|
||||
});
|
||||
},
|
||||
update_pick_list_stock: (frm) => {
|
||||
frm.events.set_item_locations(frm, true);
|
||||
},
|
||||
add_get_items_button: (frm) => {
|
||||
let purpose = frm.doc.purpose;
|
||||
if (purpose != 'Delivery against Sales Order' || frm.doc.docstatus !== 0) return;
|
||||
if (purpose != 'Delivery' || frm.doc.docstatus !== 0) return;
|
||||
let get_query_filters = {
|
||||
docstatus: 1,
|
||||
per_delivered: ['<', 100],
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2019-07-11 16:03:13.681045",
|
||||
"doctype": "DocType",
|
||||
@ -44,7 +45,7 @@
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.purpose==='Delivery against Sales Order'",
|
||||
"depends_on": "eval:doc.purpose==='Delivery'",
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@ -59,6 +60,7 @@
|
||||
"options": "Work Order"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "locations",
|
||||
"fieldtype": "Table",
|
||||
"label": "Item Locations",
|
||||
@ -86,7 +88,7 @@
|
||||
"fieldname": "purpose",
|
||||
"fieldtype": "Select",
|
||||
"label": "Purpose",
|
||||
"options": "Material Transfer for Manufacture\nMaterial Transfer\nDelivery against Sales Order"
|
||||
"options": "Material Transfer for Manufacture\nMaterial Transfer\nDelivery"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:['Material Transfer', 'Material Issue'].includes(doc.purpose)",
|
||||
@ -111,7 +113,8 @@
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-08-29 21:10:11.572387",
|
||||
"links": [],
|
||||
"modified": "2020-03-17 11:38:41.932875",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List",
|
||||
|
@ -29,7 +29,7 @@ class PickList(Document):
|
||||
frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
|
||||
.format(frappe.bold(item.item_code), frappe.bold(item.idx)))
|
||||
|
||||
def set_item_locations(self):
|
||||
def set_item_locations(self, save=False):
|
||||
items = self.aggregate_item_qty()
|
||||
self.item_location_map = frappe._dict()
|
||||
|
||||
@ -43,7 +43,7 @@ class PickList(Document):
|
||||
item_code = item_doc.item_code
|
||||
|
||||
self.item_location_map.setdefault(item_code,
|
||||
get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code)))
|
||||
get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code), self.company))
|
||||
|
||||
locations = get_items_with_location_and_quantity(item_doc, self.item_location_map)
|
||||
|
||||
@ -59,12 +59,17 @@ class PickList(Document):
|
||||
location.update(row)
|
||||
self.append('locations', location)
|
||||
|
||||
if save:
|
||||
self.save()
|
||||
|
||||
def aggregate_item_qty(self):
|
||||
locations = self.get('locations')
|
||||
self.item_count_map = {}
|
||||
# aggregate qty for same item
|
||||
item_map = OrderedDict()
|
||||
for item in locations:
|
||||
if not item.item_code:
|
||||
frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
|
||||
item_code = item.item_code
|
||||
reference = item.sales_order_item or item.material_request_item
|
||||
key = (item_code, item.uom, reference)
|
||||
@ -85,6 +90,10 @@ class PickList(Document):
|
||||
return item_map.values()
|
||||
|
||||
|
||||
def validate_item_locations(pick_list):
|
||||
if not pick_list.locations:
|
||||
frappe.throw(_("Add items in the Item Locations table"))
|
||||
|
||||
def get_items_with_location_and_quantity(item_doc, item_location_map):
|
||||
available_locations = item_location_map.get(item_doc.item_code)
|
||||
locations = []
|
||||
@ -130,14 +139,14 @@ def get_items_with_location_and_quantity(item_doc, item_location_map):
|
||||
item_location_map[item_doc.item_code] = available_locations
|
||||
return locations
|
||||
|
||||
def get_available_item_locations(item_code, from_warehouses, required_qty):
|
||||
def get_available_item_locations(item_code, from_warehouses, required_qty, company):
|
||||
locations = []
|
||||
if frappe.get_cached_value('Item', item_code, 'has_serial_no'):
|
||||
locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty)
|
||||
locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty, company)
|
||||
elif frappe.get_cached_value('Item', item_code, 'has_batch_no'):
|
||||
locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty)
|
||||
locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company)
|
||||
else:
|
||||
locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty)
|
||||
locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company)
|
||||
|
||||
total_qty_available = sum(location.get('qty') for location in locations)
|
||||
|
||||
@ -150,9 +159,10 @@ def get_available_item_locations(item_code, from_warehouses, required_qty):
|
||||
return locations
|
||||
|
||||
|
||||
def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty):
|
||||
def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty, company):
|
||||
filters = frappe._dict({
|
||||
'item_code': item_code,
|
||||
'company': company,
|
||||
'warehouse': ['!=', '']
|
||||
})
|
||||
|
||||
@ -180,7 +190,7 @@ def get_available_item_locations_for_serialized_item(item_code, from_warehouses,
|
||||
|
||||
return locations
|
||||
|
||||
def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty):
|
||||
def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company):
|
||||
warehouse_condition = 'and warehouse in %(warehouses)s' if from_warehouses else ''
|
||||
batch_locations = frappe.db.sql("""
|
||||
SELECT
|
||||
@ -192,6 +202,7 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re
|
||||
WHERE
|
||||
sle.batch_no = batch.name
|
||||
and sle.`item_code`=%(item_code)s
|
||||
and sle.`company` = %(company)s
|
||||
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
|
||||
{warehouse_condition}
|
||||
GROUP BY
|
||||
@ -202,16 +213,20 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re
|
||||
ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`
|
||||
""".format(warehouse_condition=warehouse_condition), { #nosec
|
||||
'item_code': item_code,
|
||||
'company': company,
|
||||
'today': today(),
|
||||
'warehouses': from_warehouses
|
||||
}, as_dict=1)
|
||||
|
||||
return batch_locations
|
||||
|
||||
def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty):
|
||||
def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company):
|
||||
# gets all items available in different warehouses
|
||||
warehouses = [x.get('name') for x in frappe.get_list("Warehouse", {'company': company}, "name")]
|
||||
|
||||
filters = frappe._dict({
|
||||
'item_code': item_code,
|
||||
'warehouse': ['in', warehouses],
|
||||
'actual_qty': ['>', 0]
|
||||
})
|
||||
|
||||
@ -230,7 +245,9 @@ def get_available_item_locations_for_other_item(item_code, from_warehouses, requ
|
||||
@frappe.whitelist()
|
||||
def create_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.locations]
|
||||
validate_item_locations(pick_list)
|
||||
|
||||
sales_orders = [d.sales_order for d in pick_list.locations if d.sales_order]
|
||||
sales_orders = set(sales_orders)
|
||||
|
||||
delivery_note = None
|
||||
@ -238,6 +255,10 @@ def create_delivery_note(source_name, target_doc=None):
|
||||
delivery_note = create_delivery_note_from_sales_order(sales_order,
|
||||
delivery_note, skip_item_mapping=True)
|
||||
|
||||
# map rows without sales orders as well
|
||||
if not delivery_note:
|
||||
delivery_note = frappe.new_doc("Delivery Note")
|
||||
|
||||
item_table_mapper = {
|
||||
'doctype': 'Delivery Note Item',
|
||||
'field_map': {
|
||||
@ -248,9 +269,25 @@ def create_delivery_note(source_name, target_doc=None):
|
||||
'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
|
||||
}
|
||||
|
||||
item_table_mapper_without_so = {
|
||||
'doctype': 'Delivery Note Item',
|
||||
'field_map': {
|
||||
'rate': 'rate',
|
||||
'name': 'name',
|
||||
'parent': '',
|
||||
}
|
||||
}
|
||||
|
||||
for location in pick_list.locations:
|
||||
sales_order_item = frappe.get_cached_doc('Sales Order Item', location.sales_order_item)
|
||||
dn_item = map_child_doc(sales_order_item, delivery_note, item_table_mapper)
|
||||
if location.sales_order_item:
|
||||
sales_order_item = frappe.get_cached_doc('Sales Order Item', {'name':location.sales_order_item})
|
||||
else:
|
||||
sales_order_item = None
|
||||
|
||||
source_doc, table_mapper = [sales_order_item, item_table_mapper] if sales_order_item \
|
||||
else [location, item_table_mapper_without_so]
|
||||
|
||||
dn_item = map_child_doc(source_doc, delivery_note, table_mapper)
|
||||
|
||||
if dn_item:
|
||||
dn_item.warehouse = location.warehouse
|
||||
@ -258,7 +295,7 @@ def create_delivery_note(source_name, target_doc=None):
|
||||
dn_item.batch_no = location.batch_no
|
||||
dn_item.serial_no = location.serial_no
|
||||
|
||||
update_delivery_note_item(sales_order_item, dn_item, delivery_note)
|
||||
update_delivery_note_item(source_doc, dn_item, delivery_note)
|
||||
|
||||
set_delivery_note_missing_values(delivery_note)
|
||||
|
||||
@ -269,6 +306,7 @@ def create_delivery_note(source_name, target_doc=None):
|
||||
@frappe.whitelist()
|
||||
def create_stock_entry(pick_list):
|
||||
pick_list = frappe.get_doc(json.loads(pick_list))
|
||||
validate_item_locations(pick_list)
|
||||
|
||||
if stock_entry_exists(pick_list.get('name')):
|
||||
return frappe.msgprint(_('Stock Entry has been already created against this Pick List'))
|
||||
@ -318,7 +356,7 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
|
||||
|
||||
@frappe.whitelist()
|
||||
def target_document_exists(pick_list_name, purpose):
|
||||
if purpose == 'Delivery against Sales Order':
|
||||
if purpose == 'Delivery':
|
||||
return frappe.db.exists('Delivery Note', {
|
||||
'pick_list': pick_list_name
|
||||
})
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2019-07-11 16:01:22.832885",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@ -8,6 +9,7 @@
|
||||
"item_name",
|
||||
"column_break_2",
|
||||
"description",
|
||||
"item_group",
|
||||
"section_break_5",
|
||||
"warehouse",
|
||||
"quantity_section",
|
||||
@ -120,7 +122,8 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item",
|
||||
"options": "Item"
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity_section",
|
||||
@ -166,10 +169,18 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Material Request Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_group",
|
||||
"fieldname": "item_group",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Group",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-08-29 21:28:39.539007",
|
||||
"links": [],
|
||||
"modified": "2020-03-13 19:08:21.995986",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List Item",
|
||||
|
Loading…
Reference in New Issue
Block a user