Merge pull request #18524 from surajshetty3416/feature-pick-list
feat: Pick List
This commit is contained in:
commit
ae587dbd83
@ -30,6 +30,12 @@ def get_data():
|
||||
"onboard": 1,
|
||||
"dependencies": ["Item"],
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Pick List",
|
||||
"onboard": 1,
|
||||
"dependencies": ["Item"],
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Delivery Trip"
|
||||
@ -329,5 +335,5 @@ def get_data():
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
]
|
||||
|
@ -263,7 +263,7 @@ class AccountsController(TransactionBase):
|
||||
if self.get("is_subcontracted"):
|
||||
args["is_subcontracted"] = self.is_subcontracted
|
||||
|
||||
ret = get_item_details(args, self)
|
||||
ret = get_item_details(args, self, overwrite_warehouse=False)
|
||||
|
||||
for fieldname, value in ret.items():
|
||||
if item.meta.get_field(fieldname) and value is not None:
|
||||
|
@ -4,16 +4,17 @@
|
||||
frappe.ui.form.on("Work Order", {
|
||||
setup: function(frm) {
|
||||
frm.custom_make_buttons = {
|
||||
'Stock Entry': 'Make Stock Entry',
|
||||
}
|
||||
'Stock Entry': 'Start',
|
||||
'Pick List': 'Create Pick List',
|
||||
};
|
||||
|
||||
// Set query for warehouses
|
||||
frm.set_query("wip_warehouse", function(doc) {
|
||||
frm.set_query("wip_warehouse", function() {
|
||||
return {
|
||||
filters: {
|
||||
'company': frm.doc.company,
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("source_warehouse", function() {
|
||||
@ -21,7 +22,7 @@ frappe.ui.form.on("Work Order", {
|
||||
filters: {
|
||||
'company': frm.doc.company,
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("source_warehouse", "required_items", function() {
|
||||
@ -29,7 +30,7 @@ frappe.ui.form.on("Work Order", {
|
||||
filters: {
|
||||
'company': frm.doc.company,
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("sales_order", function() {
|
||||
@ -37,7 +38,7 @@ frappe.ui.form.on("Work Order", {
|
||||
filters: {
|
||||
"status": ["not in", ["Closed", "On Hold"]]
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("fg_warehouse", function() {
|
||||
@ -46,7 +47,7 @@ frappe.ui.form.on("Work Order", {
|
||||
'company': frm.doc.company,
|
||||
'is_group': 0
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("scrap_warehouse", function() {
|
||||
@ -55,17 +56,19 @@ frappe.ui.form.on("Work Order", {
|
||||
'company': frm.doc.company,
|
||||
'is_group': 0
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Set query for BOM
|
||||
frm.set_query("bom_no", function() {
|
||||
if (frm.doc.production_item) {
|
||||
return{
|
||||
return {
|
||||
query: "erpnext.controllers.queries.bom",
|
||||
filters: {item: cstr(frm.doc.production_item)}
|
||||
}
|
||||
} else msgprint(__("Please enter Production Item first"));
|
||||
};
|
||||
} else {
|
||||
frappe.msgprint(__("Please enter Production Item first"));
|
||||
}
|
||||
});
|
||||
|
||||
// Set query for FG Item
|
||||
@ -76,7 +79,7 @@ frappe.ui.form.on("Work Order", {
|
||||
['is_stock_item', '=',1],
|
||||
['default_bom', '!=', '']
|
||||
]
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Set query for FG Item
|
||||
@ -85,12 +88,12 @@ frappe.ui.form.on("Work Order", {
|
||||
filters:[
|
||||
['Project', 'status', 'not in', 'Completed, Cancelled']
|
||||
]
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// formatter for work order operation
|
||||
frm.set_indicator_formatter('operation',
|
||||
function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" });
|
||||
function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange"; });
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
@ -133,7 +136,7 @@ frappe.ui.form.on("Work Order", {
|
||||
|
||||
if(not_completed && not_completed.length) {
|
||||
frm.add_custom_button(__('Create Job Card'), () => {
|
||||
frm.trigger("make_job_card")
|
||||
frm.trigger("make_job_card");
|
||||
}).addClass('btn-primary');
|
||||
}
|
||||
}
|
||||
@ -151,7 +154,7 @@ frappe.ui.form.on("Work Order", {
|
||||
condition: (d) => {
|
||||
if (d.allow_alternative_item) {return true;}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -285,13 +288,13 @@ frappe.ui.form.on("Work Order", {
|
||||
if(!frm.doc.skip_transfer){
|
||||
var pending_complete = frm.doc.material_transferred_for_manufacturing - frm.doc.produced_qty;
|
||||
if(pending_complete) {
|
||||
var title = __('{0} items in progress', [pending_complete]);
|
||||
var width = ((pending_complete / frm.doc.qty * 100) - added_min);
|
||||
title = __('{0} items in progress', [pending_complete]);
|
||||
bars.push({
|
||||
'title': title,
|
||||
'width': (width > 100 ? "99.5" : width) + '%',
|
||||
'progress_class': 'progress-bar-warning'
|
||||
})
|
||||
});
|
||||
message = message + '. ' + title;
|
||||
}
|
||||
}
|
||||
@ -377,7 +380,7 @@ frappe.ui.form.on("Work Order", {
|
||||
filters: [
|
||||
["Sales Order","name", "in", r.message]
|
||||
]
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -401,10 +404,10 @@ frappe.ui.form.on("Work Order Item", {
|
||||
frappe.model.set_value(row.doctype, row.name,
|
||||
"available_qty_at_source_warehouse", r.message);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Work Order Operation", {
|
||||
workstation: function(frm, cdt, cdn) {
|
||||
@ -421,7 +424,7 @@ frappe.ui.form.on("Work Order Operation", {
|
||||
erpnext.work_order.calculate_cost(frm.doc);
|
||||
erpnext.work_order.calculate_total_cost(frm);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
time_in_mins: function(frm, cdt, cdn) {
|
||||
@ -447,10 +450,13 @@ erpnext.work_order = {
|
||||
const show_start_btn = (frm.doc.skip_transfer
|
||||
|| frm.doc.transfer_material_against == 'Job Card') ? 0 : 1;
|
||||
|
||||
if (show_start_btn){
|
||||
if (show_start_btn) {
|
||||
if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty))
|
||||
&& frm.doc.status != 'Stopped') {
|
||||
frm.has_start_btn = true;
|
||||
frm.add_custom_button(__('Create Pick List'), function() {
|
||||
erpnext.work_order.create_pick_list(frm);
|
||||
});
|
||||
var start_btn = frm.add_custom_button(__('Start'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Material Transfer for Manufacture');
|
||||
});
|
||||
@ -519,8 +525,8 @@ erpnext.work_order = {
|
||||
|
||||
calculate_total_cost: function(frm) {
|
||||
var variable_cost = frm.doc.actual_operating_cost ?
|
||||
flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost)
|
||||
frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost))
|
||||
flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost);
|
||||
frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost));
|
||||
},
|
||||
|
||||
set_default_warehouse: function(frm) {
|
||||
@ -528,45 +534,72 @@ erpnext.work_order = {
|
||||
frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.work_order.work_order.get_default_warehouse",
|
||||
callback: function(r) {
|
||||
if(!r.exe) {
|
||||
if (!r.exe) {
|
||||
frm.set_value("wip_warehouse", r.message.wip_warehouse);
|
||||
frm.set_value("fg_warehouse", r.message.fg_warehouse)
|
||||
frm.set_value("fg_warehouse", r.message.fg_warehouse);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
make_se: function(frm, purpose) {
|
||||
if(!frm.doc.skip_transfer){
|
||||
var max = (purpose === "Manufacture") ?
|
||||
flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty) :
|
||||
flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing);
|
||||
get_max_transferable_qty: (frm, purpose) => {
|
||||
let max = 0;
|
||||
if (frm.doc.skip_transfer) return max;
|
||||
if (purpose === 'Manufacture') {
|
||||
max = flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty);
|
||||
} else {
|
||||
var max = flt(frm.doc.qty) - flt(frm.doc.produced_qty);
|
||||
max = flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing);
|
||||
}
|
||||
return flt(max, precision('qty'));
|
||||
},
|
||||
|
||||
max = flt(max, precision("qty"));
|
||||
frappe.prompt({fieldtype:"Float", label: __("Qty for {0}", [purpose]), fieldname:"qty",
|
||||
description: __("Max: {0}", [max]), 'default': max }, function(data)
|
||||
{
|
||||
if(data.qty > max) {
|
||||
frappe.msgprint(__("Quantity must not be more than {0}", [max]));
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
method:"erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry",
|
||||
args: {
|
||||
"work_order_id": frm.doc.name,
|
||||
"purpose": purpose,
|
||||
"qty": data.qty
|
||||
},
|
||||
callback: function(r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
show_prompt_for_qty_input: function(frm, purpose) {
|
||||
let max = this.get_max_transferable_qty(frm, purpose);
|
||||
return new Promise((resolve, reject) => {
|
||||
frappe.prompt({
|
||||
fieldtype: 'Float',
|
||||
label: __('Qty for {0}', [purpose]),
|
||||
fieldname: 'qty',
|
||||
description: __('Max: {0}', [max]),
|
||||
default: max
|
||||
}, data => {
|
||||
if (data.qty > max) {
|
||||
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
|
||||
reject();
|
||||
}
|
||||
data.purpose = purpose;
|
||||
resolve(data);
|
||||
}, __('Select Quantity'), __('Create'));
|
||||
});
|
||||
},
|
||||
|
||||
make_se: function(frm, purpose) {
|
||||
this.show_prompt_for_qty_input(frm, purpose)
|
||||
.then(data => {
|
||||
return frappe.xcall('erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry', {
|
||||
'work_order_id': frm.doc.name,
|
||||
'purpose': purpose,
|
||||
'qty': data.qty
|
||||
});
|
||||
}).then(stock_entry => {
|
||||
frappe.model.sync(stock_entry);
|
||||
frappe.set_route('Form', stock_entry.doctype, stock_entry.name);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
create_pick_list: function(frm, purpose='Material Transfer for Manufacture') {
|
||||
this.show_prompt_for_qty_input(frm, purpose)
|
||||
.then(data => {
|
||||
return frappe.xcall('erpnext.manufacturing.doctype.work_order.work_order.create_pick_list', {
|
||||
'source_name': frm.doc.name,
|
||||
'for_qty': data.qty
|
||||
});
|
||||
}).then(pick_list => {
|
||||
frappe.model.sync(pick_list);
|
||||
frappe.set_route('Form', pick_list.doctype, pick_list.name);
|
||||
});
|
||||
}, __("Select Quantity"), __('Create'));
|
||||
},
|
||||
|
||||
make_consumption_se: function(frm, backflush_raw_materials_based_on) {
|
||||
@ -606,6 +639,6 @@ erpnext.work_order = {
|
||||
frm.reload_doc();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -72,6 +72,7 @@
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Series",
|
||||
"no_copy": 1,
|
||||
"options": "MFG-WO-.YYYY.-",
|
||||
"print_hide": 1,
|
||||
"reqd": 1,
|
||||
@ -467,7 +468,7 @@
|
||||
"idx": 1,
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-07-31 00:13:38.218277",
|
||||
"modified": "2019-08-28 12:29:35.315239",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Work Order",
|
||||
|
@ -19,6 +19,7 @@ from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
|
||||
from frappe.utils.csvutils import getlink
|
||||
from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty
|
||||
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
class OverProductionError(frappe.ValidationError): pass
|
||||
class StockOverProductionError(frappe.ValidationError): pass
|
||||
@ -707,3 +708,46 @@ def get_work_order_operation_data(work_order, operation, workstation):
|
||||
for d in work_order.operations:
|
||||
if d.operation == operation and d.workstation == workstation:
|
||||
return d
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_pick_list(source_name, target_doc=None, for_qty=None):
|
||||
for_qty = for_qty or json.loads(target_doc).get('for_qty')
|
||||
max_finished_goods_qty = frappe.db.get_value('Work Order', source_name, 'qty')
|
||||
def update_item_quantity(source, target, source_parent):
|
||||
pending_to_issue = flt(source.required_qty) - flt(source.transferred_qty)
|
||||
desire_to_transfer = flt(source.required_qty) / max_finished_goods_qty * flt(for_qty)
|
||||
|
||||
qty = 0
|
||||
if desire_to_transfer <= pending_to_issue:
|
||||
qty = desire_to_transfer
|
||||
elif pending_to_issue > 0:
|
||||
qty = pending_to_issue
|
||||
|
||||
if qty:
|
||||
target.qty = qty
|
||||
target.stock_qty = qty
|
||||
target.uom = frappe.get_value('Item', source.item_code, 'stock_uom')
|
||||
target.stock_uom = target.uom
|
||||
target.conversion_factor = 1
|
||||
else:
|
||||
target.delete()
|
||||
|
||||
doc = get_mapped_doc('Work Order', source_name, {
|
||||
'Work Order': {
|
||||
'doctype': 'Pick List',
|
||||
'validation': {
|
||||
'docstatus': ['=', 1]
|
||||
}
|
||||
},
|
||||
'Work Order Item': {
|
||||
'doctype': 'Pick List Item',
|
||||
'postprocess': update_item_quantity,
|
||||
'condition': lambda doc: abs(doc.transferred_qty) < abs(doc.required_qty)
|
||||
},
|
||||
}, target_doc)
|
||||
|
||||
doc.for_qty = for_qty
|
||||
|
||||
doc.set_item_locations()
|
||||
|
||||
return doc
|
@ -6,7 +6,7 @@ def get_data():
|
||||
'fieldname': 'work_order',
|
||||
'transactions': [
|
||||
{
|
||||
'items': ['Stock Entry', 'Job Card']
|
||||
'items': ['Pick List', 'Stock Entry', 'Job Card']
|
||||
}
|
||||
]
|
||||
}
|
@ -7,6 +7,7 @@ frappe.ui.form.on("Sales Order", {
|
||||
setup: function(frm) {
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'Pick List': 'Pick List',
|
||||
'Sales Invoice': 'Invoice',
|
||||
'Material Request': 'Material Request',
|
||||
'Purchase Order': 'Purchase Order',
|
||||
@ -109,7 +110,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
this._super();
|
||||
let allow_delivery = false;
|
||||
|
||||
if(doc.docstatus==1) {
|
||||
if (doc.docstatus==1) {
|
||||
this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
|
||||
|
||||
if(this.frm.has_perm("submit")) {
|
||||
if(doc.status === 'On Hold') {
|
||||
// un-hold
|
||||
@ -233,6 +236,13 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
this.order_type(doc);
|
||||
},
|
||||
|
||||
create_pick_list() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.selling.doctype.sales_order.sales_order.create_pick_list",
|
||||
frm: this.frm
|
||||
})
|
||||
},
|
||||
|
||||
make_work_order() {
|
||||
var me = this;
|
||||
this.frm.call({
|
||||
|
@ -559,7 +559,7 @@ def make_project(source_name, target_doc=None):
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_delivery_note(source_name, target_doc=None):
|
||||
def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
|
||||
def set_missing_values(source, target):
|
||||
target.ignore_pricing_rule = 1
|
||||
target.run_method("set_missing_values")
|
||||
@ -584,23 +584,13 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
or item.get("buying_cost_center") \
|
||||
or item_group.get("buying_cost_center")
|
||||
|
||||
target_doc = get_mapped_doc("Sales Order", source_name, {
|
||||
mapper = {
|
||||
"Sales Order": {
|
||||
"doctype": "Delivery Note",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
}
|
||||
},
|
||||
"Sales Order Item": {
|
||||
"doctype": "Delivery Note Item",
|
||||
"field_map": {
|
||||
"rate": "rate",
|
||||
"name": "so_detail",
|
||||
"parent": "against_sales_order",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
|
||||
},
|
||||
"Sales Taxes and Charges": {
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"add_if_empty": True
|
||||
@ -609,7 +599,21 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
"doctype": "Sales Team",
|
||||
"add_if_empty": True
|
||||
}
|
||||
}, target_doc, set_missing_values)
|
||||
}
|
||||
|
||||
if not skip_item_mapping:
|
||||
mapper["Sales Order Item"] = {
|
||||
"doctype": "Delivery Note Item",
|
||||
"field_map": {
|
||||
"rate": "rate",
|
||||
"name": "so_detail",
|
||||
"parent": "against_sales_order",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
|
||||
}
|
||||
|
||||
target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values)
|
||||
|
||||
return target_doc
|
||||
|
||||
@ -987,3 +991,33 @@ def make_raw_material_request(items, company, sales_order, project=None):
|
||||
def make_inter_company_purchase_order(source_name, target_doc=None):
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
|
||||
return make_inter_company_transaction("Sales Order", source_name, target_doc)
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_pick_list(source_name, target_doc=None):
|
||||
def update_item_quantity(source, target, source_parent):
|
||||
target.qty = flt(source.qty) - flt(source.delivered_qty)
|
||||
target.stock_qty = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.conversion_factor)
|
||||
|
||||
doc = get_mapped_doc('Sales Order', source_name, {
|
||||
'Sales Order': {
|
||||
'doctype': 'Pick List',
|
||||
'validation': {
|
||||
'docstatus': ['=', 1]
|
||||
}
|
||||
},
|
||||
'Sales Order Item': {
|
||||
'doctype': 'Pick List Item',
|
||||
'field_map': {
|
||||
'parent': 'sales_order',
|
||||
'name': 'sales_order_item'
|
||||
},
|
||||
'postprocess': update_item_quantity,
|
||||
'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
|
||||
},
|
||||
}, target_doc)
|
||||
|
||||
doc.purpose = 'Delivery against Sales Order'
|
||||
|
||||
doc.set_item_locations()
|
||||
|
||||
return doc
|
||||
|
@ -17,7 +17,7 @@ def get_data():
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Fulfillment'),
|
||||
'items': ['Sales Invoice', 'Delivery Note']
|
||||
'items': ['Sales Invoice', 'Pick List', 'Delivery Note']
|
||||
},
|
||||
{
|
||||
'label': _('Purchasing'),
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,7 @@ frappe.ui.form.on('Material Request', {
|
||||
setup: function(frm) {
|
||||
frm.custom_make_buttons = {
|
||||
'Stock Entry': 'Issue Material',
|
||||
'Pick List': 'Pick List',
|
||||
'Purchase Order': 'Purchase Order',
|
||||
'Request for Quotation': 'Request for Quotation',
|
||||
'Supplier Quotation': 'Supplier Quotation',
|
||||
@ -55,8 +56,13 @@ frappe.ui.form.on('Material Request', {
|
||||
|
||||
if (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') {
|
||||
if (flt(frm.doc.per_ordered, 2) < 100) {
|
||||
// make
|
||||
let add_create_pick_list_button = () => {
|
||||
frm.add_custom_button(__('Pick List'),
|
||||
() => frm.events.create_pick_list(frm), __('Create'));
|
||||
}
|
||||
|
||||
if (frm.doc.material_request_type === "Material Transfer") {
|
||||
add_create_pick_list_button();
|
||||
frm.add_custom_button(__("Transfer Material"),
|
||||
() => frm.events.make_stock_entry(frm), __('Create'));
|
||||
}
|
||||
@ -258,6 +264,13 @@ frappe.ui.form.on('Material Request', {
|
||||
});
|
||||
},
|
||||
|
||||
create_pick_list: (frm) => {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.stock.doctype.material_request.material_request.create_pick_list",
|
||||
frm: frm
|
||||
});
|
||||
},
|
||||
|
||||
raise_work_orders: function(frm) {
|
||||
frappe.call({
|
||||
method:"erpnext.stock.doctype.material_request.material_request.raise_work_orders",
|
||||
|
@ -502,3 +502,28 @@ def raise_work_orders(material_request):
|
||||
frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors))
|
||||
|
||||
return work_orders
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_pick_list(source_name, target_doc=None):
|
||||
doc = get_mapped_doc('Material Request', source_name, {
|
||||
'Material Request': {
|
||||
'doctype': 'Pick List',
|
||||
'field_map': {
|
||||
'material_request_type': 'purpose'
|
||||
},
|
||||
'validation': {
|
||||
'docstatus': ['=', 1]
|
||||
}
|
||||
},
|
||||
'Material Request Item': {
|
||||
'doctype': 'Pick List Item',
|
||||
'field_map': {
|
||||
'name': 'material_request_item',
|
||||
'qty': 'stock_qty'
|
||||
},
|
||||
},
|
||||
}, target_doc)
|
||||
|
||||
doc.set_item_locations()
|
||||
|
||||
return doc
|
@ -8,7 +8,7 @@ def get_data():
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Related'),
|
||||
'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order', "Stock Entry"]
|
||||
'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order', 'Stock Entry', 'Pick List']
|
||||
},
|
||||
{
|
||||
'label': _('Manufacturing'),
|
||||
|
0
erpnext/stock/doctype/pick_list/__init__.py
Normal file
0
erpnext/stock/doctype/pick_list/__init__.py
Normal file
180
erpnext/stock/doctype/pick_list/pick_list.js
Normal file
180
erpnext/stock/doctype/pick_list/pick_list.js
Normal file
@ -0,0 +1,180 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Pick List', {
|
||||
setup: (frm) => {
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery Note',
|
||||
'Stock Entry': 'Stock Entry',
|
||||
};
|
||||
frm.set_query('parent_warehouse', () => {
|
||||
return {
|
||||
filters: {
|
||||
'is_group': 1,
|
||||
'company': frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query('work_order', () => {
|
||||
return {
|
||||
query: 'erpnext.stock.doctype.pick_list.pick_list.get_pending_work_orders',
|
||||
filters: {
|
||||
'company': frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query('material_request', () => {
|
||||
return {
|
||||
filters: {
|
||||
'material_request_type': ['=', frm.doc.purpose]
|
||||
}
|
||||
};
|
||||
});
|
||||
frm.set_query('item_code', 'locations', () => {
|
||||
return {
|
||||
filters: {
|
||||
is_stock_item: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
get_item_locations: (frm) => {
|
||||
if (!frm.doc.locations || !frm.doc.locations.length) {
|
||||
frappe.msgprint(__('First add items in the Item Locations table'));
|
||||
} else {
|
||||
frm.call('set_item_locations');
|
||||
}
|
||||
},
|
||||
refresh: (frm) => {
|
||||
frm.trigger('add_get_items_button');
|
||||
if (frm.doc.docstatus === 1) {
|
||||
frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.target_document_exists', {
|
||||
'pick_list_name': frm.doc.name,
|
||||
'purpose': frm.doc.purpose
|
||||
}).then(target_document_exists => {
|
||||
if (target_document_exists) return;
|
||||
if (frm.doc.purpose === 'Delivery against Sales Order') {
|
||||
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'));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
work_order: (frm) => {
|
||||
frappe.db.get_value('Work Order',
|
||||
frm.doc.work_order,
|
||||
['qty', 'material_transferred_for_manufacturing']
|
||||
).then(data => {
|
||||
let qty_data = data.message;
|
||||
let max = qty_data.qty - qty_data.material_transferred_for_manufacturing;
|
||||
frappe.prompt({
|
||||
fieldtype: 'Float',
|
||||
label: __('Qty of Finished Goods Item'),
|
||||
fieldname: 'qty',
|
||||
description: __('Max: {0}', [max]),
|
||||
default: max
|
||||
}, (data) => {
|
||||
frm.set_value('for_qty', data.qty);
|
||||
if (data.qty > max) {
|
||||
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
|
||||
return;
|
||||
}
|
||||
frm.clear_table('locations');
|
||||
erpnext.utils.map_current_doc({
|
||||
method: 'erpnext.manufacturing.doctype.work_order.work_order.create_pick_list',
|
||||
target: frm,
|
||||
source_name: frm.doc.work_order
|
||||
});
|
||||
}, __('Select Quantity'), __('Get Items'));
|
||||
});
|
||||
},
|
||||
material_request: (frm) => {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: 'erpnext.stock.doctype.material_request.material_request.create_pick_list',
|
||||
target: frm,
|
||||
source_name: frm.doc.material_request
|
||||
});
|
||||
},
|
||||
purpose: (frm) => {
|
||||
frm.clear_table('locations');
|
||||
frm.trigger('add_get_items_button');
|
||||
},
|
||||
create_delivery_note: (frm) => {
|
||||
frappe.model.open_mapped_doc({
|
||||
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', {
|
||||
'pick_list': frm.doc,
|
||||
}).then(stock_entry => {
|
||||
frappe.model.sync(stock_entry);
|
||||
frappe.set_route("Form", 'Stock Entry', stock_entry.name);
|
||||
});
|
||||
},
|
||||
add_get_items_button: (frm) => {
|
||||
let purpose = frm.doc.purpose;
|
||||
if (purpose != 'Delivery against Sales Order' || frm.doc.docstatus !== 0) return;
|
||||
let get_query_filters = {
|
||||
docstatus: 1,
|
||||
per_delivered: ['<', 100],
|
||||
status: ['!=', ''],
|
||||
customer: frm.doc.customer
|
||||
};
|
||||
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: 'erpnext.selling.doctype.sales_order.sales_order.create_pick_list',
|
||||
source_doctype: 'Sales Order',
|
||||
target: frm,
|
||||
setters: {
|
||||
company: frm.doc.company,
|
||||
customer: frm.doc.customer
|
||||
},
|
||||
date_field: 'transaction_date',
|
||||
get_query_filters: get_query_filters
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Pick List Item', {
|
||||
item_code: (frm, cdt, cdn) => {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
if (row.item_code) {
|
||||
get_item_details(row.item_code).then(data => {
|
||||
frappe.model.set_value(cdt, cdn, 'uom', data.stock_uom);
|
||||
frappe.model.set_value(cdt, cdn, 'stock_uom', data.stock_uom);
|
||||
frappe.model.set_value(cdt, cdn, 'conversion_factor', 1);
|
||||
});
|
||||
}
|
||||
},
|
||||
uom: (frm, cdt, cdn) => {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
if (row.uom) {
|
||||
get_item_details(row.item_code, row.uom).then(data => {
|
||||
frappe.model.set_value(cdt, cdn, 'conversion_factor', data.conversion_factor);
|
||||
});
|
||||
}
|
||||
},
|
||||
qty: (frm, cdt, cdn) => {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
|
||||
},
|
||||
conversion_factor: (frm, cdt, cdn) => {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
|
||||
}
|
||||
});
|
||||
|
||||
function get_item_details(item_code, uom=null) {
|
||||
return frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.get_item_details', {
|
||||
item_code,
|
||||
uom
|
||||
});
|
||||
}
|
184
erpnext/stock/doctype/pick_list/pick_list.json
Normal file
184
erpnext/stock/doctype/pick_list/pick_list.json
Normal file
@ -0,0 +1,184 @@
|
||||
{
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2019-07-11 16:03:13.681045",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"naming_series",
|
||||
"company",
|
||||
"purpose",
|
||||
"customer",
|
||||
"work_order",
|
||||
"material_request",
|
||||
"for_qty",
|
||||
"column_break_4",
|
||||
"parent_warehouse",
|
||||
"get_item_locations",
|
||||
"section_break_6",
|
||||
"locations",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "Items under this warehouse will be suggested",
|
||||
"fieldname": "parent_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Parent Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.purpose==='Delivery against Sales Order'",
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Customer",
|
||||
"options": "Customer"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.purpose==='Material Transfer for Manufacture'",
|
||||
"fieldname": "work_order",
|
||||
"fieldtype": "Link",
|
||||
"label": "Work Order",
|
||||
"options": "Work Order"
|
||||
},
|
||||
{
|
||||
"fieldname": "locations",
|
||||
"fieldtype": "Table",
|
||||
"label": "Item Locations",
|
||||
"options": "Pick List Item"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.purpose==='Material Transfer for Manufacture'",
|
||||
"description": "Qty of raw materials will be decided based on the qty of the Finished Goods Item",
|
||||
"fieldname": "for_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty of Finished Goods Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Pick List",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Material Transfer for Manufacture",
|
||||
"fieldname": "purpose",
|
||||
"fieldtype": "Select",
|
||||
"label": "Purpose",
|
||||
"options": "Material Transfer for Manufacture\nMaterial Transfer\nDelivery against Sales Order"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:['Material Transfer', 'Material Issue'].includes(doc.purpose)",
|
||||
"fieldname": "material_request",
|
||||
"fieldtype": "Link",
|
||||
"label": "Material Request",
|
||||
"options": "Material Request"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus===0",
|
||||
"fieldname": "get_item_locations",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Item Locations"
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Series",
|
||||
"options": "STO-PICK-.YYYY.-",
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-08-29 21:10:11.572387",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
432
erpnext/stock/doctype/pick_list/pick_list.py
Normal file
432
erpnext/stock/doctype/pick_list/pick_list.py
Normal file
@ -0,0 +1,432 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from six import iteritems
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from collections import OrderedDict
|
||||
from frappe.utils import floor, flt, today, cint
|
||||
from frappe.model.mapper import get_mapped_doc, map_child_doc
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note as create_delivery_note_from_sales_order
|
||||
|
||||
# TODO: Prioritize SO or WO group warehouse
|
||||
|
||||
class PickList(Document):
|
||||
def before_save(self):
|
||||
self.set_item_locations()
|
||||
|
||||
def before_submit(self):
|
||||
for item in self.locations:
|
||||
if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'):
|
||||
continue
|
||||
if len(item.serial_no.split('\n')) == item.picked_qty:
|
||||
continue
|
||||
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):
|
||||
items = self.aggregate_item_qty()
|
||||
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('locations')
|
||||
for item_doc in items:
|
||||
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)))
|
||||
|
||||
locations = get_items_with_location_and_quantity(item_doc, self.item_location_map)
|
||||
|
||||
item_doc.idx = None
|
||||
item_doc.name = None
|
||||
|
||||
for row in locations:
|
||||
row.update({
|
||||
'picked_qty': row.stock_qty
|
||||
})
|
||||
|
||||
location = item_doc.as_dict()
|
||||
location.update(row)
|
||||
self.append('locations', location)
|
||||
|
||||
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:
|
||||
item_code = item.item_code
|
||||
reference = item.sales_order_item or item.material_request_item
|
||||
key = (item_code, item.uom, reference)
|
||||
|
||||
item.idx = None
|
||||
item.name = None
|
||||
|
||||
if item_map.get(key):
|
||||
item_map[key].qty += item.qty
|
||||
item_map[key].stock_qty += item.stock_qty
|
||||
else:
|
||||
item_map[key] = item
|
||||
|
||||
# maintain count of each item (useful to limit get query)
|
||||
self.item_count_map.setdefault(item_code, 0)
|
||||
self.item_count_map[item_code] += item.stock_qty
|
||||
|
||||
return item_map.values()
|
||||
|
||||
|
||||
def get_items_with_location_and_quantity(item_doc, 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 available_locations:
|
||||
item_location = available_locations.pop(0)
|
||||
item_location = frappe._dict(item_location)
|
||||
|
||||
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)
|
||||
|
||||
uom_must_be_whole_number = frappe.db.get_value('UOM', item_doc.uom, 'must_be_whole_number')
|
||||
if uom_must_be_whole_number:
|
||||
qty = floor(qty)
|
||||
stock_qty = qty * item_doc.conversion_factor
|
||||
if not stock_qty: break
|
||||
|
||||
serial_nos = None
|
||||
if item_location.serial_no:
|
||||
serial_nos = '\n'.join(item_location.serial_no[0: cint(stock_qty)])
|
||||
|
||||
locations.append(frappe._dict({
|
||||
'qty': qty,
|
||||
'stock_qty': stock_qty,
|
||||
'warehouse': item_location.warehouse,
|
||||
'serial_no': serial_nos,
|
||||
'batch_no': item_location.batch_no
|
||||
}))
|
||||
|
||||
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 > 0:
|
||||
item_location.qty = qty_diff
|
||||
if item_location.serial_no:
|
||||
# set remaining serial numbers
|
||||
item_location.serial_no = item_location.serial_no[-qty_diff:]
|
||||
available_locations = [item_location] + available_locations
|
||||
|
||||
# update available locations for the item
|
||||
item_location_map[item_doc.item_code] = available_locations
|
||||
return locations
|
||||
|
||||
def get_available_item_locations(item_code, from_warehouses, required_qty):
|
||||
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)
|
||||
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)
|
||||
else:
|
||||
locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty)
|
||||
|
||||
total_qty_available = sum(location.get('qty') for location in locations)
|
||||
|
||||
remaining_qty = required_qty - total_qty_available
|
||||
|
||||
if remaining_qty > 0:
|
||||
frappe.msgprint(_('{0} units of {1} is not available.')
|
||||
.format(remaining_qty, frappe.get_desk_link('Item', item_code)))
|
||||
|
||||
return locations
|
||||
|
||||
|
||||
def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty):
|
||||
filters = frappe._dict({
|
||||
'item_code': item_code,
|
||||
'warehouse': ['!=', '']
|
||||
})
|
||||
|
||||
if from_warehouses:
|
||||
filters.warehouse = ['in', from_warehouses]
|
||||
|
||||
serial_nos = frappe.get_all('Serial No',
|
||||
fields=['name', 'warehouse'],
|
||||
filters=filters,
|
||||
limit=required_qty,
|
||||
order_by='purchase_date',
|
||||
as_list=1)
|
||||
|
||||
warehouse_serial_nos_map = frappe._dict()
|
||||
for serial_no, warehouse in serial_nos:
|
||||
warehouse_serial_nos_map.setdefault(warehouse, []).append(serial_no)
|
||||
|
||||
locations = []
|
||||
for warehouse, serial_nos in iteritems(warehouse_serial_nos_map):
|
||||
locations.append({
|
||||
'qty': len(serial_nos),
|
||||
'warehouse': warehouse,
|
||||
'serial_no': serial_nos
|
||||
})
|
||||
|
||||
return locations
|
||||
|
||||
def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty):
|
||||
warehouse_condition = 'and warehouse in %(warehouses)s' if from_warehouses else ''
|
||||
batch_locations = frappe.db.sql("""
|
||||
SELECT
|
||||
sle.`warehouse`,
|
||||
sle.`batch_no`,
|
||||
SUM(sle.`actual_qty`) AS `qty`
|
||||
FROM
|
||||
`tabStock Ledger Entry` sle, `tabBatch` batch
|
||||
WHERE
|
||||
sle.batch_no = batch.name
|
||||
and sle.`item_code`=%(item_code)s
|
||||
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
|
||||
{warehouse_condition}
|
||||
GROUP BY
|
||||
`warehouse`,
|
||||
`batch_no`,
|
||||
`item_code`
|
||||
HAVING `qty` > 0
|
||||
ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`
|
||||
""".format(warehouse_condition=warehouse_condition), { #nosec
|
||||
'item_code': item_code,
|
||||
'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):
|
||||
# gets all items available in different warehouses
|
||||
filters = frappe._dict({
|
||||
'item_code': item_code,
|
||||
'actual_qty': ['>', 0]
|
||||
})
|
||||
|
||||
if from_warehouses:
|
||||
filters.warehouse = ['in', from_warehouses]
|
||||
|
||||
item_locations = frappe.get_all('Bin',
|
||||
fields=['warehouse', 'actual_qty as qty'],
|
||||
filters=filters,
|
||||
limit=required_qty,
|
||||
order_by='creation')
|
||||
|
||||
return item_locations
|
||||
|
||||
|
||||
@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]
|
||||
sales_orders = set(sales_orders)
|
||||
|
||||
delivery_note = None
|
||||
for sales_order in sales_orders:
|
||||
delivery_note = create_delivery_note_from_sales_order(sales_order,
|
||||
delivery_note, skip_item_mapping=True)
|
||||
|
||||
item_table_mapper = {
|
||||
'doctype': 'Delivery Note Item',
|
||||
'field_map': {
|
||||
'rate': 'rate',
|
||||
'name': 'so_detail',
|
||||
'parent': 'against_sales_order',
|
||||
},
|
||||
'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
|
||||
}
|
||||
|
||||
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 dn_item:
|
||||
dn_item.warehouse = location.warehouse
|
||||
dn_item.qty = location.picked_qty
|
||||
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)
|
||||
|
||||
set_delivery_note_missing_values(delivery_note)
|
||||
|
||||
delivery_note.pick_list = pick_list.name
|
||||
|
||||
return delivery_note
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_stock_entry(pick_list):
|
||||
pick_list = frappe.get_doc(json.loads(pick_list))
|
||||
|
||||
if stock_entry_exists(pick_list.get('name')):
|
||||
return frappe.msgprint(_('Stock Entry has been already created against this Pick List'))
|
||||
|
||||
stock_entry = frappe.new_doc('Stock Entry')
|
||||
stock_entry.pick_list = pick_list.get('name')
|
||||
stock_entry.purpose = pick_list.get('purpose')
|
||||
stock_entry.set_stock_entry_type()
|
||||
|
||||
if pick_list.get('work_order'):
|
||||
stock_entry = update_stock_entry_based_on_work_order(pick_list, stock_entry)
|
||||
elif pick_list.get('material_request'):
|
||||
stock_entry = update_stock_entry_based_on_material_request(pick_list, stock_entry)
|
||||
else:
|
||||
stock_entry = update_stock_entry_items_with_no_reference(pick_list, stock_entry)
|
||||
|
||||
stock_entry.set_incoming_rate()
|
||||
stock_entry.set_actual_qty()
|
||||
stock_entry.calculate_rate_and_amount(update_finished_item_rate=False)
|
||||
|
||||
return stock_entry.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filters, as_dict):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
`name`, `company`, `planned_start_date`
|
||||
FROM
|
||||
`tabWork Order`
|
||||
WHERE
|
||||
`status` not in ('Completed', 'Stopped')
|
||||
AND `qty` > `material_transferred_for_manufacturing`
|
||||
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
|
||||
%(start)s, %(page_length)s""",
|
||||
{
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace('%', ''),
|
||||
'start': start,
|
||||
'page_length': frappe.utils.cint(page_length),
|
||||
'company': filters.get('company')
|
||||
}, as_dict=as_dict)
|
||||
|
||||
@frappe.whitelist()
|
||||
def target_document_exists(pick_list_name, purpose):
|
||||
if purpose == 'Delivery against Sales Order':
|
||||
return frappe.db.exists('Delivery Note', {
|
||||
'pick_list': pick_list_name
|
||||
})
|
||||
|
||||
return stock_entry_exists(pick_list_name)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_details(item_code, uom=None):
|
||||
details = frappe.db.get_value('Item', item_code, ['stock_uom', 'name'], as_dict=1)
|
||||
details.uom = uom or details.stock_uom
|
||||
if uom:
|
||||
details.update(get_conversion_factor(item_code, uom))
|
||||
|
||||
return details
|
||||
|
||||
|
||||
def update_delivery_note_item(source, target, delivery_note):
|
||||
cost_center = frappe.db.get_value('Project', delivery_note.project, 'cost_center')
|
||||
if not cost_center:
|
||||
cost_center = get_cost_center(source.item_code, 'Item', delivery_note.company)
|
||||
|
||||
if not cost_center:
|
||||
cost_center = get_cost_center(source.item_group, 'Item Group', delivery_note.company)
|
||||
|
||||
target.cost_center = cost_center
|
||||
|
||||
def get_cost_center(for_item, from_doctype, company):
|
||||
'''Returns Cost Center for Item or Item Group'''
|
||||
return frappe.db.get_value('Item Default',
|
||||
fieldname=['buying_cost_center'],
|
||||
filters={
|
||||
'parent': for_item,
|
||||
'parenttype': from_doctype,
|
||||
'company': company
|
||||
})
|
||||
|
||||
def set_delivery_note_missing_values(target):
|
||||
target.run_method('set_missing_values')
|
||||
target.run_method('set_po_nos')
|
||||
target.run_method('calculate_taxes_and_totals')
|
||||
|
||||
def stock_entry_exists(pick_list_name):
|
||||
return frappe.db.exists('Stock Entry', {
|
||||
'pick_list': pick_list_name
|
||||
})
|
||||
|
||||
def update_stock_entry_based_on_work_order(pick_list, stock_entry):
|
||||
work_order = frappe.get_doc("Work Order", pick_list.get('work_order'))
|
||||
|
||||
stock_entry.work_order = work_order.name
|
||||
stock_entry.company = work_order.company
|
||||
stock_entry.from_bom = 1
|
||||
stock_entry.bom_no = work_order.bom_no
|
||||
stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
|
||||
stock_entry.fg_completed_qty = pick_list.for_qty
|
||||
if work_order.bom_no:
|
||||
stock_entry.inspection_required = frappe.db.get_value('BOM',
|
||||
work_order.bom_no, 'inspection_required')
|
||||
|
||||
is_wip_warehouse_group = frappe.db.get_value('Warehouse', work_order.wip_warehouse, 'is_group')
|
||||
if not (is_wip_warehouse_group and work_order.skip_transfer):
|
||||
wip_warehouse = work_order.wip_warehouse
|
||||
else:
|
||||
wip_warehouse = None
|
||||
stock_entry.to_warehouse = wip_warehouse
|
||||
|
||||
stock_entry.project = work_order.project
|
||||
|
||||
for location in pick_list.locations:
|
||||
item = frappe._dict()
|
||||
update_common_item_properties(item, location)
|
||||
item.t_warehouse = wip_warehouse
|
||||
|
||||
stock_entry.append('items', item)
|
||||
|
||||
return stock_entry
|
||||
|
||||
def update_stock_entry_based_on_material_request(pick_list, stock_entry):
|
||||
for location in pick_list.locations:
|
||||
target_warehouse = None
|
||||
if location.material_request_item:
|
||||
target_warehouse = frappe.get_value('Material Request Item',
|
||||
location.material_request_item, 'warehouse')
|
||||
item = frappe._dict()
|
||||
update_common_item_properties(item, location)
|
||||
item.t_warehouse = target_warehouse
|
||||
stock_entry.append('items', item)
|
||||
|
||||
return stock_entry
|
||||
|
||||
def update_stock_entry_items_with_no_reference(pick_list, stock_entry):
|
||||
for location in pick_list.locations:
|
||||
item = frappe._dict()
|
||||
update_common_item_properties(item, location)
|
||||
|
||||
stock_entry.append('items', item)
|
||||
|
||||
return stock_entry
|
||||
|
||||
def update_common_item_properties(item, location):
|
||||
item.item_code = location.item_code
|
||||
item.s_warehouse = location.warehouse
|
||||
item.qty = location.picked_qty * location.conversion_factor
|
||||
item.transfer_qty = location.picked_qty
|
||||
item.uom = location.uom
|
||||
item.conversion_factor = location.conversion_factor
|
||||
item.stock_uom = location.stock_uom
|
||||
item.material_request = location.material_request
|
||||
item.serial_no = location.serial_no
|
||||
item.batch_no = location.batch_no
|
||||
item.material_request_item = location.material_request_item
|
12
erpnext/stock/doctype/pick_list/pick_list_dashboard.py
Normal file
12
erpnext/stock/doctype/pick_list/pick_list_dashboard.py
Normal file
@ -0,0 +1,12 @@
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'pick_list',
|
||||
'transactions': [
|
||||
{
|
||||
'items': ['Stock Entry', 'Delivery Note']
|
||||
},
|
||||
]
|
||||
}
|
220
erpnext/stock/doctype/pick_list/test_pick_list.py
Normal file
220
erpnext/stock/doctype/pick_list/test_pick_list.py
Normal file
@ -0,0 +1,220 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
|
||||
|
||||
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \
|
||||
import EmptyStockReconciliationItemsError
|
||||
|
||||
class TestPickList(unittest.TestCase):
|
||||
|
||||
def test_pick_list_picks_warehouse_for_each_item(self):
|
||||
try:
|
||||
frappe.get_doc({
|
||||
'doctype': 'Stock Reconciliation',
|
||||
'company': '_Test Company',
|
||||
'purpose': 'Opening Stock',
|
||||
'expense_account': 'Temporary Opening - _TC',
|
||||
'items': [{
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'warehouse': '_Test Warehouse - _TC',
|
||||
'valuation_rate': 100,
|
||||
'qty': 5
|
||||
}]
|
||||
}).submit()
|
||||
except EmptyStockReconciliationItemsError:
|
||||
pass
|
||||
|
||||
pick_list = frappe.get_doc({
|
||||
'doctype': 'Pick List',
|
||||
'company': '_Test Company',
|
||||
'customer': '_Test Customer',
|
||||
'items_based_on': 'Sales Order',
|
||||
'locations': [{
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'qty': 5,
|
||||
'stock_qty': 5,
|
||||
'conversion_factor': 1,
|
||||
'sales_order': '_T-Sales Order-1',
|
||||
'sales_order_item': '_T-Sales Order-1_item',
|
||||
}]
|
||||
})
|
||||
pick_list.set_item_locations()
|
||||
|
||||
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
|
||||
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
|
||||
self.assertEqual(pick_list.locations[0].qty, 5)
|
||||
|
||||
def test_pick_list_splits_row_according_to_warhouse_availability(self):
|
||||
try:
|
||||
frappe.get_doc({
|
||||
'doctype': 'Stock Reconciliation',
|
||||
'company': '_Test Company',
|
||||
'purpose': 'Opening Stock',
|
||||
'expense_account': 'Temporary Opening - _TC',
|
||||
'items': [{
|
||||
'item_code': '_Test Item Warehouse Group Wise Reorder',
|
||||
'warehouse': '_Test Warehouse Group-C1 - _TC',
|
||||
'valuation_rate': 100,
|
||||
'qty': 5
|
||||
}]
|
||||
}).submit()
|
||||
except EmptyStockReconciliationItemsError:
|
||||
pass
|
||||
|
||||
try:
|
||||
frappe.get_doc({
|
||||
'doctype': 'Stock Reconciliation',
|
||||
'company': '_Test Company',
|
||||
'purpose': 'Opening Stock',
|
||||
'expense_account': 'Temporary Opening - _TC',
|
||||
'items': [{
|
||||
'item_code': '_Test Item Warehouse Group Wise Reorder',
|
||||
'warehouse': '_Test Warehouse 2 - _TC',
|
||||
'valuation_rate': 400,
|
||||
'qty': 10
|
||||
}]
|
||||
}).submit()
|
||||
except EmptyStockReconciliationItemsError:
|
||||
pass
|
||||
|
||||
pick_list = frappe.get_doc({
|
||||
'doctype': 'Pick List',
|
||||
'company': '_Test Company',
|
||||
'customer': '_Test Customer',
|
||||
'items_based_on': 'Sales Order',
|
||||
'locations': [{
|
||||
'item_code': '_Test Item Warehouse Group Wise Reorder',
|
||||
'qty': 1000,
|
||||
'stock_qty': 1000,
|
||||
'conversion_factor': 1,
|
||||
'sales_order': '_T-Sales Order-1',
|
||||
'sales_order_item': '_T-Sales Order-1_item',
|
||||
}]
|
||||
})
|
||||
|
||||
pick_list.set_item_locations()
|
||||
|
||||
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Warehouse Group Wise Reorder')
|
||||
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse Group-C1 - _TC')
|
||||
self.assertEqual(pick_list.locations[0].qty, 5)
|
||||
|
||||
self.assertEqual(pick_list.locations[1].item_code, '_Test Item Warehouse Group Wise Reorder')
|
||||
self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse 2 - _TC')
|
||||
self.assertEqual(pick_list.locations[1].qty, 10)
|
||||
|
||||
def test_pick_list_shows_serial_no_for_serialized_item(self):
|
||||
|
||||
stock_reconciliation = frappe.get_doc({
|
||||
'doctype': 'Stock Reconciliation',
|
||||
'company': '_Test Company',
|
||||
'items': [{
|
||||
'item_code': '_Test Serialized Item',
|
||||
'warehouse': '_Test Warehouse - _TC',
|
||||
'valuation_rate': 100,
|
||||
'qty': 5,
|
||||
'serial_no': '123450\n123451\n123452\n123453\n123454'
|
||||
}]
|
||||
})
|
||||
|
||||
stock_reconciliation.submit()
|
||||
|
||||
pick_list = frappe.get_doc({
|
||||
'doctype': 'Pick List',
|
||||
'company': '_Test Company',
|
||||
'customer': '_Test Customer',
|
||||
'items_based_on': 'Sales Order',
|
||||
'locations': [{
|
||||
'item_code': '_Test Serialized Item',
|
||||
'qty': 1000,
|
||||
'stock_qty': 1000,
|
||||
'conversion_factor': 1,
|
||||
'sales_order': '_T-Sales Order-1',
|
||||
'sales_order_item': '_T-Sales Order-1_item',
|
||||
}]
|
||||
})
|
||||
|
||||
pick_list.set_item_locations()
|
||||
self.assertEqual(pick_list.locations[0].item_code, '_Test Serialized Item')
|
||||
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
|
||||
self.assertEqual(pick_list.locations[0].qty, 5)
|
||||
self.assertEqual(pick_list.locations[0].serial_no, '123450\n123451\n123452\n123453\n123454')
|
||||
|
||||
def test_pick_list_for_items_from_multiple_sales_orders(self):
|
||||
try:
|
||||
frappe.get_doc({
|
||||
'doctype': 'Stock Reconciliation',
|
||||
'company': '_Test Company',
|
||||
'purpose': 'Opening Stock',
|
||||
'expense_account': 'Temporary Opening - _TC',
|
||||
'items': [{
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'warehouse': '_Test Warehouse - _TC',
|
||||
'valuation_rate': 100,
|
||||
'qty': 10
|
||||
}]
|
||||
}).submit()
|
||||
except EmptyStockReconciliationItemsError:
|
||||
pass
|
||||
|
||||
sales_order = frappe.get_doc({
|
||||
'doctype': "Sales Order",
|
||||
'customer': '_Test Customer',
|
||||
'company': '_Test Company',
|
||||
'items': [{
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'qty': 10,
|
||||
'delivery_date': frappe.utils.today()
|
||||
}],
|
||||
})
|
||||
sales_order.submit()
|
||||
|
||||
pick_list = frappe.get_doc({
|
||||
'doctype': 'Pick List',
|
||||
'company': '_Test Company',
|
||||
'customer': '_Test Customer',
|
||||
'items_based_on': 'Sales Order',
|
||||
'locations': [{
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'qty': 5,
|
||||
'stock_qty': 5,
|
||||
'conversion_factor': 1,
|
||||
'sales_order': '_T-Sales Order-1',
|
||||
'sales_order_item': '_T-Sales Order-1_item',
|
||||
}, {
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'qty': 5,
|
||||
'stock_qty': 5,
|
||||
'conversion_factor': 1,
|
||||
'sales_order': sales_order.name,
|
||||
'sales_order_item': sales_order.items[0].name,
|
||||
}]
|
||||
})
|
||||
pick_list.set_item_locations()
|
||||
|
||||
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
|
||||
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
|
||||
self.assertEqual(pick_list.locations[0].qty, 5)
|
||||
self.assertEqual(pick_list.locations[0].sales_order_item, '_T-Sales Order-1_item')
|
||||
|
||||
self.assertEqual(pick_list.locations[1].item_code, '_Test Item Home Desktop 100')
|
||||
self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse - _TC')
|
||||
self.assertEqual(pick_list.locations[1].qty, 5)
|
||||
self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
|
||||
|
||||
|
||||
# def test_pick_list_skips_items_in_expired_batch(self):
|
||||
# pass
|
||||
|
||||
# def test_pick_list_from_sales_order(self):
|
||||
# pass
|
||||
|
||||
# def test_pick_list_from_work_order(self):
|
||||
# pass
|
||||
|
||||
# def test_pick_list_from_material_request(self):
|
||||
# pass
|
0
erpnext/stock/doctype/pick_list_item/__init__.py
Normal file
0
erpnext/stock/doctype/pick_list_item/__init__.py
Normal file
182
erpnext/stock/doctype/pick_list_item/pick_list_item.json
Normal file
182
erpnext/stock/doctype/pick_list_item/pick_list_item.json
Normal file
@ -0,0 +1,182 @@
|
||||
{
|
||||
"creation": "2019-07-11 16:01:22.832885",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"column_break_2",
|
||||
"description",
|
||||
"section_break_5",
|
||||
"warehouse",
|
||||
"quantity_section",
|
||||
"qty",
|
||||
"stock_qty",
|
||||
"picked_qty",
|
||||
"column_break_11",
|
||||
"uom",
|
||||
"conversion_factor",
|
||||
"stock_uom",
|
||||
"serial_no_and_batch_section",
|
||||
"serial_no",
|
||||
"column_break_20",
|
||||
"batch_no",
|
||||
"column_break_15",
|
||||
"sales_order",
|
||||
"sales_order_item",
|
||||
"material_request",
|
||||
"material_request_item"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Qty"
|
||||
},
|
||||
{
|
||||
"fieldname": "picked_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Picked Qty"
|
||||
},
|
||||
{
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Warehouse",
|
||||
"options": "Warehouse",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Description",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "serial_no",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"depends_on": "batch_no",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock UOM",
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "UOM",
|
||||
"options": "UOM"
|
||||
},
|
||||
{
|
||||
"fieldname": "conversion_factor",
|
||||
"fieldtype": "Float",
|
||||
"label": "UOM Conversion Factor",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Stock Qty",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Quantity"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Reference"
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_order",
|
||||
"fieldtype": "Link",
|
||||
"label": "Sales Order",
|
||||
"options": "Sales Order",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_order_item",
|
||||
"fieldtype": "Data",
|
||||
"label": "Sales Order Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_no_and_batch_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Serial No and Batch"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_20",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "material_request",
|
||||
"fieldtype": "Link",
|
||||
"label": "Material Request",
|
||||
"options": "Material Request",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "material_request_item",
|
||||
"fieldtype": "Data",
|
||||
"label": "Material Request Item",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-08-29 21:28:39.539007",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
10
erpnext/stock/doctype/pick_list_item/pick_list_item.py
Normal file
10
erpnext/stock/doctype/pick_list_item/pick_list_item.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class PickListItem(Document):
|
||||
pass
|
@ -17,6 +17,7 @@
|
||||
"purchase_order",
|
||||
"delivery_note_no",
|
||||
"sales_invoice_no",
|
||||
"pick_list",
|
||||
"purchase_receipt_no",
|
||||
"col2",
|
||||
"posting_date",
|
||||
@ -613,12 +614,19 @@
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "pick_list",
|
||||
"fieldtype": "Link",
|
||||
"label": "Pick List",
|
||||
"options": "Pick List",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-07-14 17:41:39.257508",
|
||||
"modified": "2019-08-22 17:11:42.074154",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry",
|
||||
|
@ -22,7 +22,7 @@ sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']
|
||||
purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_details(args, doc=None):
|
||||
def get_item_details(args, doc=None, overwrite_warehouse=True):
|
||||
"""
|
||||
args = {
|
||||
"item_code": "",
|
||||
@ -44,11 +44,12 @@ def get_item_details(args, doc=None):
|
||||
"set_warehouse": ""
|
||||
}
|
||||
"""
|
||||
|
||||
args = process_args(args)
|
||||
item = frappe.get_cached_doc("Item", args.item_code)
|
||||
validate_item_details(args, item)
|
||||
|
||||
out = get_basic_details(args, item)
|
||||
out = get_basic_details(args, item, overwrite_warehouse)
|
||||
|
||||
get_item_tax_template(args, item, out)
|
||||
out["item_tax_rate"] = get_item_tax_map(args.company, args.get("item_tax_template") if out.get("item_tax_template") is None \
|
||||
@ -178,7 +179,7 @@ def validate_item_details(args, item):
|
||||
throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
|
||||
|
||||
|
||||
def get_basic_details(args, item):
|
||||
def get_basic_details(args, item, overwrite_warehouse=True):
|
||||
"""
|
||||
:param args: {
|
||||
"item_code": "",
|
||||
@ -225,14 +226,26 @@ def get_basic_details(args, item):
|
||||
item_group_defaults = get_item_group_defaults(item.name, args.company)
|
||||
brand_defaults = get_brand_defaults(item.name, args.company)
|
||||
|
||||
warehouse = (args.get("set_warehouse") or item_defaults.get("default_warehouse") or
|
||||
item_group_defaults.get("default_warehouse") or brand_defaults.get("default_warehouse") or args.warehouse)
|
||||
if overwrite_warehouse or not args.warehouse:
|
||||
warehouse = (
|
||||
args.get("set_warehouse") or
|
||||
item_defaults.get("default_warehouse") or
|
||||
item_group_defaults.get("default_warehouse") or
|
||||
brand_defaults.get("default_warehouse") or
|
||||
args.warehouse
|
||||
)
|
||||
|
||||
if not warehouse:
|
||||
defaults = frappe.defaults.get_defaults() or {}
|
||||
if defaults.get("default_warehouse") and frappe.db.exists("Warehouse",
|
||||
{'name': defaults.default_warehouse, 'company': args.company}):
|
||||
warehouse = defaults.default_warehouse
|
||||
if not warehouse:
|
||||
defaults = frappe.defaults.get_defaults() or {}
|
||||
warehouse_exists = frappe.db.exists("Warehouse", {
|
||||
'name': defaults.default_warehouse,
|
||||
'company': args.company
|
||||
})
|
||||
if defaults.get("default_warehouse") and warehouse_exists:
|
||||
warehouse = defaults.default_warehouse
|
||||
|
||||
else:
|
||||
warehouse = args.warehouse
|
||||
|
||||
if args.get('doctype') == "Material Request" and not args.get('material_request_type'):
|
||||
args['material_request_type'] = frappe.db.get_value('Material Request',
|
||||
|
0
erpnext/stock/print_format/__init__.py
Normal file
0
erpnext/stock/print_format/__init__.py
Normal file
0
erpnext/stock/print_format/pick_list/__init__.py
Normal file
0
erpnext/stock/print_format/pick_list/__init__.py
Normal file
23
erpnext/stock/print_format/pick_list/pick_list.json
Normal file
23
erpnext/stock/print_format/pick_list/pick_list.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"align_labels_right": 1,
|
||||
"creation": "2019-08-02 07:27:42.533305",
|
||||
"custom_format": 0,
|
||||
"disabled": 0,
|
||||
"doc_type": "Pick List",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"font": "Default",
|
||||
"format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div class=\\\"print-heading\\\">\\t\\t\\t\\t<h2>Pick List<br><small>{{ doc.name }}</small>\\t\\t\\t\\t</h2></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company\", \"label\": \"Company\"}, {\"print_hide\": 0, \"fieldname\": \"customer\", \"label\": \"Customer\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"purpose\", \"label\": \"Purpose\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"item_name\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"warehouse\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"qty\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"stock_qty\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"serial_no\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"batch_no\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"locations\", \"label\": \"Item Locations\"}]",
|
||||
"idx": 0,
|
||||
"line_breaks": 1,
|
||||
"modified": "2019-08-30 15:58:27.807219",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List",
|
||||
"owner": "Administrator",
|
||||
"print_format_builder": 1,
|
||||
"print_format_type": "Jinja",
|
||||
"raw_printing": 0,
|
||||
"show_section_headings": 1,
|
||||
"standard": "Yes"
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user