Merge pull request #20962 from marination/pick-list-enhance

fix: Pick List Enhancements
This commit is contained in:
rohitwaghchaure 2020-04-08 12:09:10 +05:30 committed by GitHub
commit 47c8ddb29b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 26 deletions

View File

@ -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

View 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' """)

View File

@ -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()

View File

@ -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],

View File

@ -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",

View File

@ -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
})

View File

@ -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",