mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-22 08:08:29 +00:00
* fix: better integration of Pick List with Delivery Note (#47831)
Co-authored-by: priyanshshah2442 <priyanshshah2442@gmail.com>
(cherry picked from commit 527cfe9c7d)
# Conflicts:
# erpnext/patches.txt
# erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
# erpnext/stock/doctype/pick_list/pick_list.py
# erpnext/stock/doctype/pick_list_item/pick_list_item.json
* chore: resolve conflicts
* fix: setting status correctly as per v15 utility
* fix: get items from Pick List to DN even if not linked to Sales Order
---------
Co-authored-by: Smit Vora <smitvora203@gmail.com>
Co-authored-by: Priyansh Shah <108476017+priyanshshah2442@users.noreply.github.com>
This commit is contained in:
@@ -156,6 +156,17 @@ status_map = {
|
|||||||
["Draft", None],
|
["Draft", None],
|
||||||
["Completed", "eval:self.docstatus == 1"],
|
["Completed", "eval:self.docstatus == 1"],
|
||||||
],
|
],
|
||||||
|
"Pick List": [
|
||||||
|
["Draft", None],
|
||||||
|
["Open", "eval:self.docstatus == 1"],
|
||||||
|
["Completed", "stock_entry_exists"],
|
||||||
|
[
|
||||||
|
"Partly Delivered",
|
||||||
|
"eval:self.purpose == 'Delivery' and self.delivery_status == 'Partly Delivered'",
|
||||||
|
],
|
||||||
|
["Completed", "eval:self.purpose == 'Delivery' and self.delivery_status == 'Fully Delivered'"],
|
||||||
|
["Cancelled", "eval:self.docstatus == 2"],
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -409,4 +409,5 @@ erpnext.patches.v15_0.set_cancelled_status_to_cancelled_pos_invoice
|
|||||||
erpnext.patches.v15_0.rename_group_by_to_categorize_by_in_custom_reports
|
erpnext.patches.v15_0.rename_group_by_to_categorize_by_in_custom_reports
|
||||||
erpnext.patches.v14_0.update_full_name_in_contract
|
erpnext.patches.v14_0.update_full_name_in_contract
|
||||||
erpnext.patches.v15_0.drop_sle_indexes
|
erpnext.patches.v15_0.drop_sle_indexes
|
||||||
|
erpnext.patches.v15_0.update_pick_list_fields
|
||||||
erpnext.patches.v15_0.update_pegged_currencies
|
erpnext.patches.v15_0.update_pegged_currencies
|
||||||
|
|||||||
28
erpnext/patches/v15_0/update_pick_list_fields.py
Normal file
28
erpnext/patches/v15_0/update_pick_list_fields.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.query_builder.functions import IfNull
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
update_delivery_note()
|
||||||
|
update_pick_list_items()
|
||||||
|
|
||||||
|
|
||||||
|
def update_delivery_note():
|
||||||
|
DN = frappe.qb.DocType("Delivery Note")
|
||||||
|
DNI = frappe.qb.DocType("Delivery Note Item")
|
||||||
|
|
||||||
|
frappe.qb.update(DNI).join(DN).on(DN.name == DNI.parent).set(DNI.against_pick_list, DN.pick_list).where(
|
||||||
|
IfNull(DN.pick_list, "") != ""
|
||||||
|
).run()
|
||||||
|
|
||||||
|
|
||||||
|
def update_pick_list_items():
|
||||||
|
PL = frappe.qb.DocType("Pick List")
|
||||||
|
PLI = frappe.qb.DocType("Pick List Item")
|
||||||
|
|
||||||
|
pick_lists = frappe.qb.from_(PL).select(PL.name).where(PL.status == "Completed").run(pluck="name")
|
||||||
|
|
||||||
|
if not pick_lists:
|
||||||
|
return
|
||||||
|
|
||||||
|
frappe.qb.update(PLI).set(PLI.delivered_qty, PLI.picked_qty).where(PLI.parent.isin(pick_lists)).run()
|
||||||
@@ -1004,7 +1004,7 @@ erpnext.utils.map_current_doc = function (opts) {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
opts.allow_child_item_selection ||
|
opts.allow_child_item_selection ||
|
||||||
["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype)
|
["Purchase Receipt", "Delivery Note", "Pick List"].includes(opts.source_doctype)
|
||||||
) {
|
) {
|
||||||
// args contains filtered child docnames
|
// args contains filtered child docnames
|
||||||
opts.args = args;
|
opts.args = args;
|
||||||
|
|||||||
@@ -1741,8 +1741,8 @@ def create_pick_list(source_name, target_doc=None):
|
|||||||
"doctype": "Pick List Item",
|
"doctype": "Pick List Item",
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"parent": "sales_order",
|
"parent": "sales_order",
|
||||||
"name": "sales_order_item",
|
"parent_detail_docname": "sales_order_item",
|
||||||
"parent_detail_docname": "product_bundle_item",
|
"name": "product_bundle_item",
|
||||||
},
|
},
|
||||||
"field_no_map": ["picked_qty"],
|
"field_no_map": ["picked_qty"],
|
||||||
"postprocess": update_packed_item_qty,
|
"postprocess": update_packed_item_qty,
|
||||||
|
|||||||
@@ -188,6 +188,55 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!doc.is_return &&
|
||||||
|
doc.status != "Closed" &&
|
||||||
|
this.frm.has_perm("write") &&
|
||||||
|
frappe.model.can_read("Pick List") &&
|
||||||
|
this.frm.doc.docstatus === 0
|
||||||
|
) {
|
||||||
|
this.frm.add_custom_button(
|
||||||
|
__("Pick List"),
|
||||||
|
function () {
|
||||||
|
if (!me.frm.doc.customer) {
|
||||||
|
frappe.throw({
|
||||||
|
title: __("Mandatory"),
|
||||||
|
message: __("Please Select a Customer"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
erpnext.utils.map_current_doc({
|
||||||
|
method: "erpnext.stock.doctype.pick_list.pick_list.create_dn_for_pick_lists",
|
||||||
|
source_doctype: "Pick List",
|
||||||
|
target: me.frm,
|
||||||
|
setters: [
|
||||||
|
{
|
||||||
|
fieldname: "customer",
|
||||||
|
default: me.frm.doc.customer,
|
||||||
|
label: __("Customer"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Customer",
|
||||||
|
reqd: 1,
|
||||||
|
read_only: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "sales_order",
|
||||||
|
label: __("Sales Order"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Sales Order",
|
||||||
|
link_filters: `[["Sales Order","customer","=","${me.frm.doc.customer}"],["Sales Order","docstatus","=","1"],["Sales Order","delivery_status","not in",["Closed","Fully Delivered"]]]`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
get_query_filters: {
|
||||||
|
company: me.frm.doc.company,
|
||||||
|
},
|
||||||
|
get_query_method: "erpnext.stock.doctype.pick_list.pick_list.get_pick_list_query",
|
||||||
|
size: "extra-large",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
__("Get Items From")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!doc.is_return && doc.status != "Closed") {
|
if (!doc.is_return && doc.status != "Closed") {
|
||||||
if (doc.docstatus == 1 && frappe.model.can_create("Shipment")) {
|
if (doc.docstatus == 1 && frappe.model.can_create("Shipment")) {
|
||||||
this.frm.add_custom_button(
|
this.frm.add_custom_button(
|
||||||
|
|||||||
@@ -38,7 +38,6 @@
|
|||||||
"ignore_pricing_rule",
|
"ignore_pricing_rule",
|
||||||
"items_section",
|
"items_section",
|
||||||
"scan_barcode",
|
"scan_barcode",
|
||||||
"pick_list",
|
|
||||||
"col_break_warehouse",
|
"col_break_warehouse",
|
||||||
"set_warehouse",
|
"set_warehouse",
|
||||||
"set_target_warehouse",
|
"set_target_warehouse",
|
||||||
@@ -1218,15 +1217,6 @@
|
|||||||
"options": "Sales Team",
|
"options": "Sales Team",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "pick_list",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"hidden": 1,
|
|
||||||
"label": "Pick List",
|
|
||||||
"options": "Pick List",
|
|
||||||
"read_only": 1,
|
|
||||||
"search_index": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fetch_from": "customer.is_internal_customer",
|
"fetch_from": "customer.is_internal_customer",
|
||||||
|
|||||||
@@ -174,6 +174,19 @@ class DeliveryNote(SellingController):
|
|||||||
"overflow_type": "delivery",
|
"overflow_type": "delivery",
|
||||||
"no_allowance": 1,
|
"no_allowance": 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"source_dt": "Delivery Note Item",
|
||||||
|
"target_dt": "Pick List Item",
|
||||||
|
"join_field": "pick_list_item",
|
||||||
|
"target_field": "delivered_qty",
|
||||||
|
"target_parent_dt": "Pick List",
|
||||||
|
"target_parent_field": "per_delivered",
|
||||||
|
"target_ref_field": "picked_qty",
|
||||||
|
"source_field": "stock_qty",
|
||||||
|
"percent_join_field": "against_pick_list",
|
||||||
|
"status_field": "delivery_status",
|
||||||
|
"keyword": "Delivered",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
if cint(self.is_return):
|
if cint(self.is_return):
|
||||||
self.status_updater.extend(
|
self.status_updater.extend(
|
||||||
@@ -326,18 +339,15 @@ class DeliveryNote(SellingController):
|
|||||||
def set_serial_and_batch_bundle_from_pick_list(self):
|
def set_serial_and_batch_bundle_from_pick_list(self):
|
||||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||||
|
|
||||||
if not self.pick_list:
|
|
||||||
return
|
|
||||||
|
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if item.use_serial_batch_fields:
|
if item.use_serial_batch_fields or not item.against_pick_list:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if item.pick_list_item and not item.serial_and_batch_bundle:
|
if item.pick_list_item and not item.serial_and_batch_bundle:
|
||||||
filters = {
|
filters = {
|
||||||
"item_code": item.item_code,
|
"item_code": item.item_code,
|
||||||
"voucher_type": "Pick List",
|
"voucher_type": "Pick List",
|
||||||
"voucher_no": self.pick_list,
|
"voucher_no": item.against_pick_list,
|
||||||
"voucher_detail_no": item.pick_list_item,
|
"voucher_detail_no": item.pick_list_item,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,7 +596,9 @@ class DeliveryNote(SellingController):
|
|||||||
def update_pick_list_status(self):
|
def update_pick_list_status(self):
|
||||||
from erpnext.stock.doctype.pick_list.pick_list import update_pick_list_status
|
from erpnext.stock.doctype.pick_list.pick_list import update_pick_list_status
|
||||||
|
|
||||||
update_pick_list_status(self.pick_list)
|
pick_lists = {row.against_pick_list for row in self.items if row.against_pick_list}
|
||||||
|
for pick_list in pick_lists:
|
||||||
|
update_pick_list_status(pick_list)
|
||||||
|
|
||||||
def check_next_docstatus(self):
|
def check_next_docstatus(self):
|
||||||
submit_rv = frappe.db.sql(
|
submit_rv = frappe.db.sql(
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
"against_sales_invoice",
|
"against_sales_invoice",
|
||||||
"si_detail",
|
"si_detail",
|
||||||
"dn_detail",
|
"dn_detail",
|
||||||
|
"against_pick_list",
|
||||||
"pick_list_item",
|
"pick_list_item",
|
||||||
"section_break_40",
|
"section_break_40",
|
||||||
"pick_serial_and_batch",
|
"pick_serial_and_batch",
|
||||||
@@ -935,13 +936,23 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_fguf",
|
"fieldname": "column_break_fguf",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_pick_list",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Against Pick List",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Pick List",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-02-05 14:28:33.322181",
|
"modified": "2025-05-31 18:51:32.651562",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note Item",
|
"name": "Delivery Note Item",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class DeliveryNoteItem(Document):
|
|||||||
|
|
||||||
actual_batch_qty: DF.Float
|
actual_batch_qty: DF.Float
|
||||||
actual_qty: DF.Float
|
actual_qty: DF.Float
|
||||||
|
against_pick_list: DF.Link | None
|
||||||
against_sales_invoice: DF.Link | None
|
against_sales_invoice: DF.Link | None
|
||||||
against_sales_order: DF.Link | None
|
against_sales_order: DF.Link | None
|
||||||
allow_zero_valuation_rate: DF.Check
|
allow_zero_valuation_rate: DF.Check
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ def make_packing_list(doc):
|
|||||||
update_packed_item_basic_data(item_row, pi_row, bundle_item, item_data)
|
update_packed_item_basic_data(item_row, pi_row, bundle_item, item_data)
|
||||||
update_packed_item_stock_data(item_row, pi_row, bundle_item, item_data, doc)
|
update_packed_item_stock_data(item_row, pi_row, bundle_item, item_data, doc)
|
||||||
update_packed_item_price_data(pi_row, item_data, doc)
|
update_packed_item_price_data(pi_row, item_data, doc)
|
||||||
|
|
||||||
|
if item_row.get("against_pick_list"):
|
||||||
|
update_packed_item_with_pick_list_info(item_row, pi_row)
|
||||||
|
|
||||||
update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc)
|
update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc)
|
||||||
|
|
||||||
if set_price_from_children: # create/update bundle item wise price dict
|
if set_price_from_children: # create/update bundle item wise price dict
|
||||||
@@ -228,6 +232,28 @@ def update_packed_item_stock_data(main_item_row, pi_row, packing_item, item_data
|
|||||||
pi_row.use_serial_batch_fields = frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields")
|
pi_row.use_serial_batch_fields = frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields")
|
||||||
|
|
||||||
|
|
||||||
|
def update_packed_item_with_pick_list_info(main_item_row, pi_row):
|
||||||
|
pl_row = frappe.db.get_value(
|
||||||
|
"Pick List Item",
|
||||||
|
{
|
||||||
|
"item_code": pi_row.item_code,
|
||||||
|
"sales_order": main_item_row.get("against_sales_order"),
|
||||||
|
"sales_order_item": main_item_row.get("so_detail"),
|
||||||
|
"parent": main_item_row.against_pick_list,
|
||||||
|
},
|
||||||
|
["warehouse", "batch_no", "serial_no"],
|
||||||
|
as_dict=True,
|
||||||
|
order_by="qty desc",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not pl_row:
|
||||||
|
return
|
||||||
|
|
||||||
|
pi_row.warehouse = pl_row.warehouse
|
||||||
|
pi_row.batch_no = pl_row.batch_no
|
||||||
|
pi_row.serial_no = pl_row.serial_no
|
||||||
|
|
||||||
|
|
||||||
def update_packed_item_price_data(pi_row, item_data, doc):
|
def update_packed_item_price_data(pi_row, item_data, doc):
|
||||||
"Set price as per price list or from the Item master."
|
"Set price as per price list or from the Item master."
|
||||||
if pi_row.rate:
|
if pi_row.rate:
|
||||||
|
|||||||
@@ -98,34 +98,28 @@ frappe.ui.form.on("Pick List", {
|
|||||||
refresh: (frm) => {
|
refresh: (frm) => {
|
||||||
frm.trigger("add_get_items_button");
|
frm.trigger("add_get_items_button");
|
||||||
if (frm.doc.docstatus === 1) {
|
if (frm.doc.docstatus === 1) {
|
||||||
frappe
|
const status_completed = frm.doc.status === "Completed";
|
||||||
.xcall("erpnext.stock.doctype.pick_list.pick_list.target_document_exists", {
|
frm.set_df_property("locations", "allow_on_submit", status_completed ? 0 : 1);
|
||||||
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 (!status_completed) {
|
||||||
|
frm.add_custom_button(__("Update Current Stock"), () =>
|
||||||
|
frm.trigger("update_pick_list_stock")
|
||||||
|
);
|
||||||
|
|
||||||
frm.add_custom_button(__("Update Current Stock"), () =>
|
if (frm.doc.purpose === "Delivery") {
|
||||||
frm.trigger("update_pick_list_stock")
|
frm.add_custom_button(
|
||||||
|
__("Create Delivery Note"),
|
||||||
|
() => frm.trigger("create_delivery_note"),
|
||||||
|
__("Create")
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
if (frm.doc.purpose === "Delivery") {
|
frm.add_custom_button(
|
||||||
frm.add_custom_button(
|
__("Create Stock Entry"),
|
||||||
__("Delivery Note"),
|
() => frm.trigger("create_stock_entry"),
|
||||||
() => frm.trigger("create_delivery_note"),
|
__("Create")
|
||||||
__("Create")
|
);
|
||||||
);
|
}
|
||||||
} else {
|
}
|
||||||
frm.add_custom_button(
|
|
||||||
__("Stock Entry"),
|
|
||||||
() => frm.trigger("create_stock_entry"),
|
|
||||||
__("Create")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (frm.doc.purpose === "Delivery" && frm.doc.status === "Open") {
|
if (frm.doc.purpose === "Delivery" && frm.doc.status === "Open") {
|
||||||
if (frm.doc.__onload && frm.doc.__onload.has_unreserved_stock) {
|
if (frm.doc.__onload && frm.doc.__onload.has_unreserved_stock) {
|
||||||
|
|||||||
@@ -30,7 +30,11 @@
|
|||||||
"amended_from",
|
"amended_from",
|
||||||
"print_settings_section",
|
"print_settings_section",
|
||||||
"group_same_items",
|
"group_same_items",
|
||||||
"status"
|
"status_section",
|
||||||
|
"status",
|
||||||
|
"column_break_qyam",
|
||||||
|
"delivery_status",
|
||||||
|
"per_delivered"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -181,7 +185,7 @@
|
|||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Draft\nOpen\nCompleted\nCancelled",
|
"options": "Draft\nOpen\nPartly Delivered\nCompleted\nCancelled",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 1,
|
"report_hide": 1,
|
||||||
@@ -208,11 +212,42 @@
|
|||||||
"fieldname": "ignore_pricing_rule",
|
"fieldname": "ignore_pricing_rule",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Ignore Pricing Rule"
|
"label": "Ignore Pricing Rule"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "status_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Status",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "delivery_status",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"hidden": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Delivery Status",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Not Delivered\nFully Delivered\nPartly Delivered",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:!doc.__islocal && doc.purpose === \"Delivery\"",
|
||||||
|
"description": "% of materials delivered against this Pick List",
|
||||||
|
"fieldname": "per_delivered",
|
||||||
|
"fieldtype": "Percent",
|
||||||
|
"label": "% Delivered",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_qyam",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-08-14 13:20:42.168827",
|
"modified": "2025-05-31 19:18:30.860044",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Pick List",
|
"name": "Pick List",
|
||||||
@@ -280,6 +315,7 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ from itertools import groupby
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
from frappe.model.document import Document
|
from frappe.model.mapper import get_mapped_doc, map_child_doc
|
||||||
from frappe.model.mapper import map_child_doc
|
|
||||||
from frappe.query_builder import Case
|
from frappe.query_builder import Case
|
||||||
from frappe.query_builder.custom import GROUP_CONCAT
|
from frappe.query_builder.custom import GROUP_CONCAT
|
||||||
from frappe.query_builder.functions import Coalesce, Locate, Replace, Sum
|
from frappe.query_builder.functions import Coalesce, Locate, Replace, Sum
|
||||||
@@ -28,11 +27,12 @@ from erpnext.stock.serial_batch_bundle import (
|
|||||||
get_batches_from_bundle,
|
get_batches_from_bundle,
|
||||||
get_serial_nos_from_bundle,
|
get_serial_nos_from_bundle,
|
||||||
)
|
)
|
||||||
|
from erpnext.utilities.transaction_base import TransactionBase
|
||||||
|
|
||||||
# TODO: Prioritize SO or WO group warehouse
|
# TODO: Prioritize SO or WO group warehouse
|
||||||
|
|
||||||
|
|
||||||
class PickList(Document):
|
class PickList(TransactionBase):
|
||||||
# begin: auto-generated types
|
# begin: auto-generated types
|
||||||
# This code is auto-generated. Do not modify anything in this block.
|
# This code is auto-generated. Do not modify anything in this block.
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ class PickList(Document):
|
|||||||
consider_rejected_warehouses: DF.Check
|
consider_rejected_warehouses: DF.Check
|
||||||
customer: DF.Link | None
|
customer: DF.Link | None
|
||||||
customer_name: DF.Data | None
|
customer_name: DF.Data | None
|
||||||
|
delivery_status: DF.Literal["Not Delivered", "Fully Delivered", "Partly Delivered"]
|
||||||
for_qty: DF.Float
|
for_qty: DF.Float
|
||||||
group_same_items: DF.Check
|
group_same_items: DF.Check
|
||||||
ignore_pricing_rule: DF.Check
|
ignore_pricing_rule: DF.Check
|
||||||
@@ -55,12 +56,13 @@ class PickList(Document):
|
|||||||
material_request: DF.Link | None
|
material_request: DF.Link | None
|
||||||
naming_series: DF.Literal["STO-PICK-.YYYY.-"]
|
naming_series: DF.Literal["STO-PICK-.YYYY.-"]
|
||||||
parent_warehouse: DF.Link | None
|
parent_warehouse: DF.Link | None
|
||||||
|
per_delivered: DF.Percent
|
||||||
pick_manually: DF.Check
|
pick_manually: DF.Check
|
||||||
prompt_qty: DF.Check
|
prompt_qty: DF.Check
|
||||||
purpose: DF.Literal["Material Transfer for Manufacture", "Material Transfer", "Delivery"]
|
purpose: DF.Literal["Material Transfer for Manufacture", "Material Transfer", "Delivery"]
|
||||||
scan_barcode: DF.Data | None
|
scan_barcode: DF.Data | None
|
||||||
scan_mode: DF.Check
|
scan_mode: DF.Check
|
||||||
status: DF.Literal["Draft", "Open", "Completed", "Cancelled"]
|
status: DF.Literal["Draft", "Open", "Partly Delivered", "Completed", "Cancelled"]
|
||||||
work_order: DF.Link | None
|
work_order: DF.Link | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
@@ -77,6 +79,7 @@ class PickList(Document):
|
|||||||
self.validate_for_qty()
|
self.validate_for_qty()
|
||||||
self.validate_stock_qty()
|
self.validate_stock_qty()
|
||||||
self.check_serial_no_status()
|
self.check_serial_no_status()
|
||||||
|
self.validate_with_previous_doc()
|
||||||
|
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
self.update_status()
|
self.update_status()
|
||||||
@@ -150,6 +153,18 @@ class PickList(Document):
|
|||||||
title=_("Incorrect Warehouse"),
|
title=_("Incorrect Warehouse"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_with_previous_doc(self):
|
||||||
|
super().validate_with_previous_doc(
|
||||||
|
{
|
||||||
|
"Sales Order": {
|
||||||
|
"ref_dn_field": "sales_order",
|
||||||
|
"compare_fields": [
|
||||||
|
["company", "="],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def validate_sales_order_percentage(self):
|
def validate_sales_order_percentage(self):
|
||||||
# set percentage picked in SO
|
# set percentage picked in SO
|
||||||
for location in self.get("locations"):
|
for location in self.get("locations"):
|
||||||
@@ -326,19 +341,19 @@ class PickList(Document):
|
|||||||
doc.submit()
|
doc.submit()
|
||||||
|
|
||||||
def update_status(self, status=None, update_modified=True):
|
def update_status(self, status=None, update_modified=True):
|
||||||
if not status:
|
|
||||||
if self.docstatus == 0:
|
|
||||||
status = "Draft"
|
|
||||||
elif self.docstatus == 1:
|
|
||||||
if target_document_exists(self.name, self.purpose):
|
|
||||||
status = "Completed"
|
|
||||||
else:
|
|
||||||
status = "Open"
|
|
||||||
elif self.docstatus == 2:
|
|
||||||
status = "Cancelled"
|
|
||||||
|
|
||||||
if status:
|
if status:
|
||||||
self.db_set("status", status)
|
self.db_set("status", status, update_modified=update_modified)
|
||||||
|
else:
|
||||||
|
self.set_status(update=True)
|
||||||
|
|
||||||
|
def stock_entry_exists(self):
|
||||||
|
if self.docstatus != 1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.purpose == "Delivery":
|
||||||
|
return False
|
||||||
|
|
||||||
|
return stock_entry_exists(self.name)
|
||||||
|
|
||||||
def update_reference_qty(self):
|
def update_reference_qty(self):
|
||||||
packed_items = []
|
packed_items = []
|
||||||
@@ -346,7 +361,7 @@ class PickList(Document):
|
|||||||
|
|
||||||
for item in self.locations:
|
for item in self.locations:
|
||||||
if item.product_bundle_item:
|
if item.product_bundle_item:
|
||||||
packed_items.append(item.sales_order_item)
|
packed_items.append(item.product_bundle_item)
|
||||||
elif item.sales_order_item:
|
elif item.sales_order_item:
|
||||||
so_items.append(item.sales_order_item)
|
so_items.append(item.sales_order_item)
|
||||||
|
|
||||||
@@ -357,12 +372,12 @@ class PickList(Document):
|
|||||||
self.update_sales_order_item_qty(so_items)
|
self.update_sales_order_item_qty(so_items)
|
||||||
|
|
||||||
def update_packed_items_qty(self, packed_items):
|
def update_packed_items_qty(self, packed_items):
|
||||||
picked_items = get_picked_items_qty(packed_items)
|
picked_items = get_picked_items_qty(packed_items, contains_packed_items=True)
|
||||||
self.validate_picked_qty(picked_items)
|
self.validate_picked_qty(picked_items)
|
||||||
|
|
||||||
picked_qty = frappe._dict()
|
picked_qty = frappe._dict()
|
||||||
for d in picked_items:
|
for d in picked_items:
|
||||||
picked_qty[d.sales_order_item] = d.picked_qty
|
picked_qty[d.product_bundle_item] = d.picked_qty
|
||||||
|
|
||||||
for packed_item in packed_items:
|
for packed_item in packed_items:
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
@@ -575,7 +590,6 @@ class PickList(Document):
|
|||||||
# maintain count of each item (useful to limit get query)
|
# maintain count of each item (useful to limit get query)
|
||||||
self.item_count_map.setdefault(item_code, 0)
|
self.item_count_map.setdefault(item_code, 0)
|
||||||
self.item_count_map[item_code] += flt(item.stock_qty, item.precision("stock_qty"))
|
self.item_count_map[item_code] += flt(item.stock_qty, item.precision("stock_qty"))
|
||||||
|
|
||||||
return item_map.values()
|
return item_map.values()
|
||||||
|
|
||||||
def validate_for_qty(self):
|
def validate_for_qty(self):
|
||||||
@@ -739,9 +753,10 @@ class PickList(Document):
|
|||||||
for item in self.locations:
|
for item in self.locations:
|
||||||
if not item.product_bundle_item:
|
if not item.product_bundle_item:
|
||||||
continue
|
continue
|
||||||
product_bundles[item.product_bundle_item] = frappe.db.get_value(
|
|
||||||
|
product_bundles[item.sales_order_item] = frappe.db.get_value(
|
||||||
"Sales Order Item",
|
"Sales Order Item",
|
||||||
item.product_bundle_item,
|
item.sales_order_item,
|
||||||
"item_code",
|
"item_code",
|
||||||
)
|
)
|
||||||
return product_bundles
|
return product_bundles
|
||||||
@@ -757,17 +772,16 @@ class PickList(Document):
|
|||||||
def _compute_picked_qty_for_bundle(self, bundle_row, bundle_items) -> int:
|
def _compute_picked_qty_for_bundle(self, bundle_row, bundle_items) -> int:
|
||||||
"""Compute how many full bundles can be created from picked items."""
|
"""Compute how many full bundles can be created from picked items."""
|
||||||
precision = frappe.get_precision("Stock Ledger Entry", "qty_after_transaction")
|
precision = frappe.get_precision("Stock Ledger Entry", "qty_after_transaction")
|
||||||
|
possible_bundles = {}
|
||||||
possible_bundles = []
|
|
||||||
for item in self.locations:
|
for item in self.locations:
|
||||||
if item.product_bundle_item != bundle_row:
|
if item.sales_order_item != bundle_row:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if qty_in_bundle := bundle_items.get(item.item_code):
|
if qty_in_bundle := bundle_items.get(item.item_code):
|
||||||
possible_bundles.append(item.picked_qty / qty_in_bundle)
|
possible_bundles.setdefault(item.product_bundle_item, 0)
|
||||||
else:
|
possible_bundles[item.product_bundle_item] += item.picked_qty / qty_in_bundle
|
||||||
possible_bundles.append(0)
|
|
||||||
return int(flt(min(possible_bundles), precision or 6))
|
return int(flt(min(possible_bundles.values()), precision or 6)) if possible_bundles else 0
|
||||||
|
|
||||||
def has_unreserved_stock(self):
|
def has_unreserved_stock(self):
|
||||||
if self.purpose == "Delivery":
|
if self.purpose == "Delivery":
|
||||||
@@ -800,24 +814,35 @@ def update_pick_list_status(pick_list):
|
|||||||
doc.run_method("update_status")
|
doc.run_method("update_status")
|
||||||
|
|
||||||
|
|
||||||
def get_picked_items_qty(items) -> list[dict]:
|
def get_picked_items_qty(items, contains_packed_items=False) -> list[dict]:
|
||||||
pi_item = frappe.qb.DocType("Pick List Item")
|
pi_item = frappe.qb.DocType("Pick List Item")
|
||||||
return (
|
|
||||||
|
query = (
|
||||||
frappe.qb.from_(pi_item)
|
frappe.qb.from_(pi_item)
|
||||||
.select(
|
.select(
|
||||||
pi_item.sales_order_item,
|
pi_item.sales_order_item,
|
||||||
|
pi_item.product_bundle_item,
|
||||||
pi_item.item_code,
|
pi_item.item_code,
|
||||||
pi_item.sales_order,
|
pi_item.sales_order,
|
||||||
Sum(pi_item.stock_qty).as_("stock_qty"),
|
Sum(pi_item.stock_qty).as_("stock_qty"),
|
||||||
Sum(pi_item.picked_qty).as_("picked_qty"),
|
Sum(pi_item.picked_qty).as_("picked_qty"),
|
||||||
)
|
)
|
||||||
.where((pi_item.docstatus == 1) & (pi_item.sales_order_item.isin(items)))
|
.where(pi_item.docstatus == 1)
|
||||||
.groupby(
|
.for_update()
|
||||||
|
)
|
||||||
|
|
||||||
|
if contains_packed_items:
|
||||||
|
query = query.groupby(
|
||||||
|
pi_item.product_bundle_item,
|
||||||
|
pi_item.sales_order,
|
||||||
|
).where(pi_item.product_bundle_item.isin(items))
|
||||||
|
else:
|
||||||
|
query = query.groupby(
|
||||||
pi_item.sales_order_item,
|
pi_item.sales_order_item,
|
||||||
pi_item.sales_order,
|
pi_item.sales_order,
|
||||||
)
|
).where(pi_item.sales_order_item.isin(items))
|
||||||
.for_update()
|
|
||||||
).run(as_dict=True)
|
return query.run(as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
def validate_item_locations(pick_list):
|
def validate_item_locations(pick_list):
|
||||||
@@ -1188,13 +1213,17 @@ def create_delivery_note(source_name, target_doc=None):
|
|||||||
|
|
||||||
if not all(item.sales_order for item in pick_list.locations):
|
if not all(item.sales_order for item in pick_list.locations):
|
||||||
delivery_note = create_dn_wo_so(pick_list)
|
delivery_note = create_dn_wo_so(pick_list)
|
||||||
|
delivery_note.flags.ignore_mandatory = True
|
||||||
|
delivery_note.save()
|
||||||
|
|
||||||
frappe.msgprint(_("Delivery Note(s) created for the Pick List"))
|
frappe.msgprint(_("Delivery Note(s) created for the Pick List"))
|
||||||
return delivery_note
|
return delivery_note
|
||||||
|
|
||||||
|
|
||||||
def create_dn_wo_so(pick_list):
|
def create_dn_wo_so(pick_list, delivery_note=None):
|
||||||
delivery_note = frappe.new_doc("Delivery Note")
|
if not delivery_note:
|
||||||
|
delivery_note = frappe.new_doc("Delivery Note")
|
||||||
|
|
||||||
delivery_note.company = pick_list.company
|
delivery_note.company = pick_list.company
|
||||||
|
|
||||||
item_table_mapper_without_so = {
|
item_table_mapper_without_so = {
|
||||||
@@ -1206,14 +1235,61 @@ def create_dn_wo_so(pick_list):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
map_pl_locations(pick_list, item_table_mapper_without_so, delivery_note)
|
map_pl_locations(pick_list, item_table_mapper_without_so, delivery_note)
|
||||||
delivery_note.insert(ignore_mandatory=True)
|
|
||||||
|
return delivery_note
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_dn_for_pick_lists(source_name, target_doc=None, kwargs=None):
|
||||||
|
"""Get Items from Multiple Pick Lists and create a Delivery Note for filtered customer"""
|
||||||
|
pick_list = frappe.get_doc("Pick List", source_name)
|
||||||
|
validate_item_locations(pick_list)
|
||||||
|
|
||||||
|
sales_order_arg = kwargs.get("sales_order") if kwargs else None
|
||||||
|
customer_arg = kwargs.get("customer") if kwargs else None
|
||||||
|
|
||||||
|
if sales_order_arg:
|
||||||
|
sales_orders = {sales_order_arg}
|
||||||
|
else:
|
||||||
|
sales_orders = {row.sales_order for row in pick_list.locations if row.sales_order}
|
||||||
|
|
||||||
|
if customer_arg:
|
||||||
|
sales_orders = frappe.get_all(
|
||||||
|
"Sales Order",
|
||||||
|
filters={"customer": customer_arg, "name": ["in", list(sales_orders)]},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
|
||||||
|
delivery_note = create_dn_from_so(pick_list, sales_orders, delivery_note=target_doc)
|
||||||
|
|
||||||
|
if not sales_order_arg and not all(item.sales_order for item in pick_list.locations):
|
||||||
|
if isinstance(delivery_note, str):
|
||||||
|
delivery_note = frappe.get_doc(frappe.parse_json(delivery_note))
|
||||||
|
|
||||||
|
delivery_note = create_dn_wo_so(pick_list, delivery_note)
|
||||||
|
|
||||||
return delivery_note
|
return delivery_note
|
||||||
|
|
||||||
|
|
||||||
def create_dn_with_so(sales_dict, pick_list):
|
def create_dn_with_so(sales_dict, pick_list):
|
||||||
|
"""Create Delivery Note for each customer (based on SO) in a Pick List."""
|
||||||
delivery_note = None
|
delivery_note = None
|
||||||
|
|
||||||
|
for customer in sales_dict:
|
||||||
|
delivery_note = create_dn_from_so(pick_list, sales_dict[customer], None)
|
||||||
|
if delivery_note:
|
||||||
|
delivery_note.flags.ignore_mandatory = True
|
||||||
|
# updates packed_items on save
|
||||||
|
# save as multiple customers are possible
|
||||||
|
delivery_note.save()
|
||||||
|
|
||||||
|
return delivery_note
|
||||||
|
|
||||||
|
|
||||||
|
def create_dn_from_so(pick_list, sales_order_list, delivery_note=None):
|
||||||
|
if not sales_order_list:
|
||||||
|
return delivery_note
|
||||||
|
|
||||||
item_table_mapper = {
|
item_table_mapper = {
|
||||||
"doctype": "Delivery Note Item",
|
"doctype": "Delivery Note Item",
|
||||||
"field_map": {
|
"field_map": {
|
||||||
@@ -1224,20 +1300,17 @@ def create_dn_with_so(sales_dict, pick_list):
|
|||||||
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier != 1,
|
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier != 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
for customer in sales_dict:
|
kwargs = {"skip_item_mapping": True, "ignore_pricing_rule": pick_list.ignore_pricing_rule}
|
||||||
for so in sales_dict[customer]:
|
|
||||||
delivery_note = None
|
delivery_note = create_delivery_note_from_sales_order(
|
||||||
kwargs = {"skip_item_mapping": True, "ignore_pricing_rule": pick_list.ignore_pricing_rule}
|
next(iter(sales_order_list)), delivery_note, kwargs=kwargs
|
||||||
delivery_note = create_delivery_note_from_sales_order(so, delivery_note, kwargs=kwargs)
|
)
|
||||||
break
|
|
||||||
if delivery_note:
|
if not delivery_note:
|
||||||
# map all items of all sales orders of that customer
|
return
|
||||||
for so in sales_dict[customer]:
|
|
||||||
map_pl_locations(pick_list, item_table_mapper, delivery_note, so)
|
for so in sales_order_list:
|
||||||
delivery_note.flags.ignore_mandatory = True
|
map_pl_locations(pick_list, item_table_mapper, delivery_note, so)
|
||||||
delivery_note.insert()
|
|
||||||
update_packed_item_details(pick_list, delivery_note)
|
|
||||||
delivery_note.save()
|
|
||||||
|
|
||||||
return delivery_note
|
return delivery_note
|
||||||
|
|
||||||
@@ -1257,24 +1330,29 @@ def map_pl_locations(pick_list, item_mapper, delivery_note, sales_order=None):
|
|||||||
dn_item = map_child_doc(source_doc, delivery_note, item_mapper)
|
dn_item = map_child_doc(source_doc, delivery_note, item_mapper)
|
||||||
|
|
||||||
if dn_item:
|
if dn_item:
|
||||||
|
dn_item.against_pick_list = pick_list.name
|
||||||
dn_item.pick_list_item = location.name
|
dn_item.pick_list_item = location.name
|
||||||
dn_item.warehouse = location.warehouse
|
dn_item.warehouse = location.warehouse
|
||||||
dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1)
|
dn_item.qty = flt(location.picked_qty - location.delivered_qty) / (
|
||||||
|
flt(dn_item.conversion_factor) or 1
|
||||||
|
)
|
||||||
dn_item.batch_no = location.batch_no
|
dn_item.batch_no = location.batch_no
|
||||||
dn_item.serial_no = location.serial_no
|
dn_item.serial_no = location.serial_no
|
||||||
dn_item.use_serial_batch_fields = location.use_serial_batch_fields
|
dn_item.use_serial_batch_fields = location.use_serial_batch_fields
|
||||||
|
|
||||||
update_delivery_note_item(source_doc, dn_item, delivery_note)
|
update_delivery_note_item(source_doc, dn_item, delivery_note)
|
||||||
|
|
||||||
add_product_bundles_to_delivery_note(pick_list, delivery_note, item_mapper)
|
add_product_bundles_to_delivery_note(pick_list, delivery_note, item_mapper, sales_order)
|
||||||
set_delivery_note_missing_values(delivery_note)
|
set_delivery_note_missing_values(delivery_note)
|
||||||
|
|
||||||
delivery_note.pick_list = pick_list.name
|
|
||||||
delivery_note.company = pick_list.company
|
delivery_note.company = pick_list.company
|
||||||
delivery_note.customer = frappe.get_value("Sales Order", sales_order, "customer")
|
if sales_order:
|
||||||
|
delivery_note.customer = frappe.get_value("Sales Order", sales_order, "customer")
|
||||||
|
|
||||||
|
|
||||||
def add_product_bundles_to_delivery_note(pick_list: "PickList", delivery_note, item_mapper) -> None:
|
def add_product_bundles_to_delivery_note(
|
||||||
|
pick_list: "PickList", delivery_note, item_mapper, sales_order=None
|
||||||
|
) -> None:
|
||||||
"""Add product bundles found in pick list to delivery note.
|
"""Add product bundles found in pick list to delivery note.
|
||||||
|
|
||||||
When mapping pick list items, the bundle item itself isn't part of the
|
When mapping pick list items, the bundle item itself isn't part of the
|
||||||
@@ -1284,38 +1362,17 @@ def add_product_bundles_to_delivery_note(pick_list: "PickList", delivery_note, i
|
|||||||
|
|
||||||
for so_row, item_code in product_bundles.items():
|
for so_row, item_code in product_bundles.items():
|
||||||
sales_order_item = frappe.get_doc("Sales Order Item", so_row)
|
sales_order_item = frappe.get_doc("Sales Order Item", so_row)
|
||||||
|
if sales_order and sales_order_item.parent != sales_order:
|
||||||
|
continue
|
||||||
|
|
||||||
dn_bundle_item = map_child_doc(sales_order_item, delivery_note, item_mapper)
|
dn_bundle_item = map_child_doc(sales_order_item, delivery_note, item_mapper)
|
||||||
dn_bundle_item.qty = pick_list._compute_picked_qty_for_bundle(
|
dn_bundle_item.qty = pick_list._compute_picked_qty_for_bundle(
|
||||||
so_row, product_bundle_qty_map[item_code]
|
so_row, product_bundle_qty_map[item_code]
|
||||||
)
|
)
|
||||||
|
dn_bundle_item.against_pick_list = pick_list.name
|
||||||
update_delivery_note_item(sales_order_item, dn_bundle_item, delivery_note)
|
update_delivery_note_item(sales_order_item, dn_bundle_item, delivery_note)
|
||||||
|
|
||||||
|
|
||||||
def update_packed_item_details(pick_list: "PickList", delivery_note) -> None:
|
|
||||||
"""Update stock details on packed items table of delivery note."""
|
|
||||||
|
|
||||||
def _find_so_row(packed_item):
|
|
||||||
for item in delivery_note.items:
|
|
||||||
if packed_item.parent_detail_docname == item.name:
|
|
||||||
return item.so_detail
|
|
||||||
|
|
||||||
def _find_pick_list_location(bundle_row, packed_item):
|
|
||||||
if not bundle_row:
|
|
||||||
return
|
|
||||||
for loc in pick_list.locations:
|
|
||||||
if loc.product_bundle_item == bundle_row and loc.item_code == packed_item.item_code:
|
|
||||||
return loc
|
|
||||||
|
|
||||||
for packed_item in delivery_note.packed_items:
|
|
||||||
so_row = _find_so_row(packed_item)
|
|
||||||
location = _find_pick_list_location(so_row, packed_item)
|
|
||||||
if not location:
|
|
||||||
continue
|
|
||||||
packed_item.warehouse = location.warehouse
|
|
||||||
packed_item.batch_no = location.batch_no
|
|
||||||
packed_item.serial_no = location.serial_no
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_stock_entry(pick_list):
|
def create_stock_entry(pick_list):
|
||||||
pick_list = frappe.get_doc(json.loads(pick_list))
|
pick_list = frappe.get_doc(json.loads(pick_list))
|
||||||
@@ -1362,14 +1419,6 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
|
|||||||
).run(as_dict=as_dict)
|
).run(as_dict=as_dict)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def target_document_exists(pick_list_name, purpose):
|
|
||||||
if purpose == "Delivery":
|
|
||||||
return frappe.db.exists("Delivery Note", {"pick_list": pick_list_name, "docstatus": 1})
|
|
||||||
|
|
||||||
return stock_entry_exists(pick_list_name)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_item_details(item_code, uom=None):
|
def get_item_details(item_code, uom=None):
|
||||||
details = frappe.db.get_value("Item", item_code, ["stock_uom", "name"], as_dict=1)
|
details = frappe.db.get_value("Item", item_code, ["stock_uom", "name"], as_dict=1)
|
||||||
@@ -1490,3 +1539,50 @@ def get_rejected_warehouses():
|
|||||||
)
|
)
|
||||||
|
|
||||||
return frappe.local.rejected_warehouses
|
return frappe.local.rejected_warehouses
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_pick_list_query(doctype, txt, searchfield, start, page_len, filters):
|
||||||
|
frappe.has_permission("Pick List", throw=True)
|
||||||
|
|
||||||
|
if not filters.get("company"):
|
||||||
|
frappe.throw(_("Please select a Company"))
|
||||||
|
|
||||||
|
PICK_LIST = frappe.qb.DocType("Pick List")
|
||||||
|
PICK_LIST_ITEM = frappe.qb.DocType("Pick List Item")
|
||||||
|
SALES_ORDER = frappe.qb.DocType("Sales Order")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(PICK_LIST)
|
||||||
|
.join(PICK_LIST_ITEM)
|
||||||
|
.on(PICK_LIST.name == PICK_LIST_ITEM.parent)
|
||||||
|
.join(SALES_ORDER)
|
||||||
|
.on(PICK_LIST_ITEM.sales_order == SALES_ORDER.name)
|
||||||
|
.select(
|
||||||
|
PICK_LIST.name,
|
||||||
|
SALES_ORDER.customer,
|
||||||
|
Replace(GROUP_CONCAT(PICK_LIST_ITEM.sales_order).distinct(), ",", "<br>").as_("sales_order"),
|
||||||
|
)
|
||||||
|
.where(PICK_LIST.docstatus == 1)
|
||||||
|
.where(PICK_LIST.status.isin(["Open", "Partly Delivered"]))
|
||||||
|
.where(PICK_LIST.company == filters.get("company"))
|
||||||
|
.where(SALES_ORDER.customer == filters.get("customer"))
|
||||||
|
.groupby(PICK_LIST.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
if filters.get("sales_order"):
|
||||||
|
query = query.where(PICK_LIST_ITEM.sales_order == filters.get("sales_order"))
|
||||||
|
|
||||||
|
if txt:
|
||||||
|
meta = frappe.get_meta("Pick List")
|
||||||
|
search_fields = meta.get_search_fields()
|
||||||
|
|
||||||
|
txt = f"%{txt}%"
|
||||||
|
txt_condition = PICK_LIST[search_fields[-1]].like(txt)
|
||||||
|
|
||||||
|
for field in search_fields[:-1]:
|
||||||
|
txt_condition |= PICK_LIST[field].like(txt)
|
||||||
|
|
||||||
|
query = query.where(txt_condition)
|
||||||
|
|
||||||
|
return query.run(as_dict=True)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ def get_data():
|
|||||||
"fieldname": "pick_list",
|
"fieldname": "pick_list",
|
||||||
"non_standard_fieldnames": {
|
"non_standard_fieldnames": {
|
||||||
"Stock Reservation Entry": "from_voucher_no",
|
"Stock Reservation Entry": "from_voucher_no",
|
||||||
|
"Delivery Note": "against_pick_list",
|
||||||
},
|
},
|
||||||
"internal_links": {
|
"internal_links": {
|
||||||
"Sales Order": ["locations", "sales_order"],
|
"Sales Order": ["locations", "sales_order"],
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ frappe.listview_settings["Pick List"] = {
|
|||||||
const status_colors = {
|
const status_colors = {
|
||||||
Draft: "red",
|
Draft: "red",
|
||||||
Open: "orange",
|
Open: "orange",
|
||||||
|
"Partly Delivered": "orange",
|
||||||
Completed: "green",
|
Completed: "green",
|
||||||
Cancelled: "red",
|
Cancelled: "red",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import frappe
|
|||||||
from frappe import _dict
|
from frappe import _dict
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||||
from erpnext.selling.doctype.sales_order.sales_order import create_pick_list
|
from erpnext.selling.doctype.sales_order.sales_order import create_pick_list
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||||
from erpnext.stock.doctype.packed_item.test_packed_item import create_product_bundle
|
from erpnext.stock.doctype.packed_item.test_packed_item import create_product_bundle
|
||||||
from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note
|
from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note, create_dn_for_pick_lists
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||||
get_batch_from_bundle,
|
get_batch_from_bundle,
|
||||||
@@ -398,7 +399,13 @@ class TestPickList(FrappeTestCase):
|
|||||||
self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
|
self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
|
||||||
|
|
||||||
def test_pick_list_for_items_with_multiple_UOM(self):
|
def test_pick_list_for_items_with_multiple_UOM(self):
|
||||||
item_code = make_item().name
|
item_code = make_item(
|
||||||
|
uoms=[
|
||||||
|
{"uom": "Nos", "conversion_factor": 1},
|
||||||
|
{"uom": "Hand", "conversion_factor": 5},
|
||||||
|
{"uom": "Unit", "conversion_factor": 0.5},
|
||||||
|
]
|
||||||
|
).name
|
||||||
purchase_receipt = make_purchase_receipt(item_code=item_code, qty=10)
|
purchase_receipt = make_purchase_receipt(item_code=item_code, qty=10)
|
||||||
purchase_receipt.submit()
|
purchase_receipt.submit()
|
||||||
|
|
||||||
@@ -411,8 +418,7 @@ class TestPickList(FrappeTestCase):
|
|||||||
{
|
{
|
||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
"qty": 1,
|
"qty": 1,
|
||||||
"conversion_factor": 5,
|
"uom": "Hand",
|
||||||
"stock_qty": 5,
|
|
||||||
"delivery_date": frappe.utils.today(),
|
"delivery_date": frappe.utils.today(),
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
},
|
},
|
||||||
@@ -426,6 +432,7 @@ class TestPickList(FrappeTestCase):
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
).insert()
|
).insert()
|
||||||
|
|
||||||
sales_order.submit()
|
sales_order.submit()
|
||||||
|
|
||||||
pick_list = frappe.get_doc(
|
pick_list = frappe.get_doc(
|
||||||
@@ -440,6 +447,7 @@ class TestPickList(FrappeTestCase):
|
|||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
"qty": 2,
|
"qty": 2,
|
||||||
"stock_qty": 1,
|
"stock_qty": 1,
|
||||||
|
"uom": "Unit",
|
||||||
"conversion_factor": 0.5,
|
"conversion_factor": 0.5,
|
||||||
"sales_order": sales_order.name,
|
"sales_order": sales_order.name,
|
||||||
"sales_order_item": sales_order.items[0].name,
|
"sales_order_item": sales_order.items[0].name,
|
||||||
@@ -461,7 +469,11 @@ class TestPickList(FrappeTestCase):
|
|||||||
delivery_note = create_delivery_note(pick_list.name)
|
delivery_note = create_delivery_note(pick_list.name)
|
||||||
pick_list.load_from_db()
|
pick_list.load_from_db()
|
||||||
|
|
||||||
self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty)
|
# pick list stk_qty / dn conversion_factor = dn qty (1/5 = 0.2)
|
||||||
|
self.assertEqual(
|
||||||
|
pick_list.locations[0].picked_qty,
|
||||||
|
delivery_note.items[0].qty * delivery_note.items[0].conversion_factor,
|
||||||
|
)
|
||||||
self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty)
|
self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty)
|
||||||
self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor)
|
self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor)
|
||||||
|
|
||||||
@@ -554,10 +566,10 @@ class TestPickList(FrappeTestCase):
|
|||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"items_based_on": "Sales Order",
|
"items_based_on": "Sales Order",
|
||||||
"purpose": "Delivery",
|
"purpose": "Delivery",
|
||||||
"picker": "P001",
|
"customer": "_Test Customer",
|
||||||
"locations": [
|
"locations": [
|
||||||
{
|
{
|
||||||
"item_code": "_Test Item ",
|
"item_code": "_Test Item",
|
||||||
"qty": 1,
|
"qty": 1,
|
||||||
"stock_qty": 1,
|
"stock_qty": 1,
|
||||||
"conversion_factor": 1,
|
"conversion_factor": 1,
|
||||||
@@ -580,32 +592,34 @@ class TestPickList(FrappeTestCase):
|
|||||||
create_delivery_note(pick_list.name)
|
create_delivery_note(pick_list.name)
|
||||||
for dn in frappe.get_all(
|
for dn in frappe.get_all(
|
||||||
"Delivery Note",
|
"Delivery Note",
|
||||||
filters={"pick_list": pick_list.name, "customer": "_Test Customer"},
|
filters={"against_pick_list": pick_list.name, "customer": "_Test Customer"},
|
||||||
fields={"name"},
|
fields={"name"},
|
||||||
):
|
):
|
||||||
for dn_item in frappe.get_doc("Delivery Note", dn.name).get("items"):
|
for dn_item in frappe.get_doc("Delivery Note", dn.name).get("items"):
|
||||||
self.assertEqual(dn_item.item_code, "_Test Item")
|
self.assertEqual(dn_item.item_code, "_Test Item")
|
||||||
self.assertEqual(dn_item.against_sales_order, sales_order_1.name)
|
self.assertEqual(dn_item.against_sales_order, sales_order_1.name)
|
||||||
self.assertEqual(dn_item.pick_list_item, pick_list.locations[dn_item.idx - 1].name)
|
self.assertEqual(dn_item.against_pick_list, pick_list.name)
|
||||||
|
self.assertEqual(dn_item.pick_list_item, pick_list.locations[0].name)
|
||||||
|
|
||||||
for dn in frappe.get_all(
|
for dn in frappe.get_all(
|
||||||
"Delivery Note",
|
"Delivery Note",
|
||||||
filters={"pick_list": pick_list.name, "customer": "_Test Customer 1"},
|
filters={"against_pick_list": pick_list.name, "customer": "_Test Customer 1"},
|
||||||
fields={"name"},
|
fields={"name"},
|
||||||
):
|
):
|
||||||
for dn_item in frappe.get_doc("Delivery Note", dn.name).get("items"):
|
for dn_item in frappe.get_doc("Delivery Note", dn.name).get("items"):
|
||||||
self.assertEqual(dn_item.item_code, "_Test Item 2")
|
self.assertEqual(dn_item.item_code, "_Test Item 2")
|
||||||
self.assertEqual(dn_item.against_sales_order, sales_order_2.name)
|
self.assertEqual(dn_item.against_sales_order, sales_order_2.name)
|
||||||
|
self.assertEqual(dn_item.against_pick_list, pick_list.name)
|
||||||
|
self.assertEqual(dn_item.pick_list_item, pick_list.locations[1].name)
|
||||||
# test DN creation without so
|
# test DN creation without so
|
||||||
pick_list_1 = frappe.get_doc(
|
pick_list_1 = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Pick List",
|
"doctype": "Pick List",
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"purpose": "Delivery",
|
"purpose": "Delivery",
|
||||||
"picker": "P001",
|
|
||||||
"locations": [
|
"locations": [
|
||||||
{
|
{
|
||||||
"item_code": "_Test Item ",
|
"item_code": "_Test Item",
|
||||||
"qty": 1,
|
"qty": 1,
|
||||||
"stock_qty": 1,
|
"stock_qty": 1,
|
||||||
"conversion_factor": 1,
|
"conversion_factor": 1,
|
||||||
@@ -622,7 +636,9 @@ class TestPickList(FrappeTestCase):
|
|||||||
pick_list_1.set_item_locations()
|
pick_list_1.set_item_locations()
|
||||||
pick_list_1.submit()
|
pick_list_1.submit()
|
||||||
create_delivery_note(pick_list_1.name)
|
create_delivery_note(pick_list_1.name)
|
||||||
for dn in frappe.get_all("Delivery Note", filters={"pick_list": pick_list_1.name}, fields={"name"}):
|
for dn in frappe.get_all(
|
||||||
|
"Delivery Note", filters={"against_pick_list": pick_list_1.name}, fields={"name"}
|
||||||
|
):
|
||||||
for dn_item in frappe.get_doc("Delivery Note", dn.name).get("items"):
|
for dn_item in frappe.get_doc("Delivery Note", dn.name).get("items"):
|
||||||
if dn_item.item_code == "_Test Item":
|
if dn_item.item_code == "_Test Item":
|
||||||
self.assertEqual(dn_item.qty, 1)
|
self.assertEqual(dn_item.qty, 1)
|
||||||
@@ -759,7 +775,6 @@ class TestPickList(FrappeTestCase):
|
|||||||
quantities = [5, 2]
|
quantities = [5, 2]
|
||||||
bundle, components = create_product_bundle(quantities, warehouse=warehouse)
|
bundle, components = create_product_bundle(quantities, warehouse=warehouse)
|
||||||
bundle_items = dict(zip(components, quantities, strict=False))
|
bundle_items = dict(zip(components, quantities, strict=False))
|
||||||
|
|
||||||
so = make_sales_order(item_code=bundle, qty=3, rate=42)
|
so = make_sales_order(item_code=bundle, qty=3, rate=42)
|
||||||
|
|
||||||
pl = create_pick_list(so.name)
|
pl = create_pick_list(so.name)
|
||||||
@@ -1307,3 +1322,166 @@ class TestPickList(FrappeTestCase):
|
|||||||
|
|
||||||
for loc in pl.locations:
|
for loc in pl.locations:
|
||||||
self.assertEqual(loc.batch_no, batch2)
|
self.assertEqual(loc.batch_no, batch2)
|
||||||
|
|
||||||
|
def test_multiple_pick_lists_delivery_note(self):
|
||||||
|
from erpnext.stock.doctype.pick_list.pick_list import create_dn_for_pick_lists
|
||||||
|
|
||||||
|
item_code = make_item().name
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
stock_entry = make_stock_entry(item=item_code, to_warehouse=warehouse, qty=500, basic_rate=100)
|
||||||
|
|
||||||
|
def create_pick_list(qty):
|
||||||
|
pick_list = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Pick List",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"customer": "_Test Customer",
|
||||||
|
"purpose": "Delivery",
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"item_code": item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"qty": qty,
|
||||||
|
"stock_qty": qty,
|
||||||
|
"picked_qty": 0,
|
||||||
|
"sales_order": sales_order.name,
|
||||||
|
"sales_order_item": sales_order.items[0].name,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
pick_list.submit()
|
||||||
|
return pick_list
|
||||||
|
|
||||||
|
sales_order = make_sales_order(item_code=item_code, qty=50, rate=100)
|
||||||
|
pick_list_1 = create_pick_list(10)
|
||||||
|
pick_list_2 = create_pick_list(20)
|
||||||
|
|
||||||
|
delivery_note = create_dn_for_pick_lists(pick_list_1.name)
|
||||||
|
delivery_note = create_dn_for_pick_lists(pick_list_2.name, delivery_note)
|
||||||
|
delivery_note.items[0].qty = 5
|
||||||
|
delivery_note.submit()
|
||||||
|
|
||||||
|
sales_order.reload()
|
||||||
|
pick_list_1.reload()
|
||||||
|
pick_list_2.reload()
|
||||||
|
|
||||||
|
self.assertEqual(sales_order.items[0].picked_qty, 30)
|
||||||
|
self.assertEqual(pick_list_1.locations[0].delivered_qty, delivery_note.items[0].qty)
|
||||||
|
self.assertEqual(pick_list_1.status, "Partly Delivered")
|
||||||
|
self.assertEqual(pick_list_2.status, "Completed")
|
||||||
|
|
||||||
|
pick_list_1.cancel()
|
||||||
|
pick_list_2.cancel()
|
||||||
|
delivery_note.cancel()
|
||||||
|
sales_order.reload()
|
||||||
|
sales_order.cancel()
|
||||||
|
stock_entry.cancel()
|
||||||
|
|
||||||
|
def test_packed_item_in_pick_list(self):
|
||||||
|
warehouse_1 = "RJ Warehouse - _TC"
|
||||||
|
warehouse_2 = "_Test Warehouse 2 - _TC"
|
||||||
|
item_1 = make_item(properties={"is_stock_item": 0}).name
|
||||||
|
item_2 = make_item().name
|
||||||
|
item_3 = make_item().name
|
||||||
|
|
||||||
|
make_product_bundle(item_1, items=[item_2, item_3])
|
||||||
|
|
||||||
|
stock_entry_1 = make_stock_entry(item=item_2, to_warehouse=warehouse_1, qty=10, basic_rate=100)
|
||||||
|
stock_entry_2 = make_stock_entry(item=item_3, to_warehouse=warehouse_1, qty=4, basic_rate=100)
|
||||||
|
stock_entry_3 = make_stock_entry(item=item_3, to_warehouse=warehouse_2, qty=6, basic_rate=100)
|
||||||
|
|
||||||
|
sales_order = make_sales_order(item_code=item_1, qty=10, rate=100)
|
||||||
|
|
||||||
|
pick_list = create_pick_list(sales_order.name)
|
||||||
|
pick_list.submit()
|
||||||
|
self.assertEqual(len(pick_list.locations), 3)
|
||||||
|
delivery_note = create_delivery_note(pick_list.name)
|
||||||
|
|
||||||
|
self.assertEqual(delivery_note.items[0].qty, 10)
|
||||||
|
self.assertEqual(delivery_note.packed_items[0].warehouse, warehouse_1)
|
||||||
|
self.assertEqual(delivery_note.packed_items[1].warehouse, warehouse_2)
|
||||||
|
|
||||||
|
pick_list.cancel()
|
||||||
|
sales_order.cancel()
|
||||||
|
stock_entry_1.cancel()
|
||||||
|
stock_entry_2.cancel()
|
||||||
|
stock_entry_3.cancel()
|
||||||
|
|
||||||
|
def test_packed_item_multiple_times_in_so(self):
|
||||||
|
frappe.db.delete("Item Price")
|
||||||
|
warehouse_1 = "RJ Warehouse - _TC"
|
||||||
|
warehouse_2 = "_Test Warehouse 2 - _TC"
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
item_1 = make_item(properties={"is_stock_item": 0}).name
|
||||||
|
item_2 = make_item().name
|
||||||
|
item_3 = make_item().name
|
||||||
|
|
||||||
|
make_product_bundle(item_1, items=[item_2, item_3])
|
||||||
|
|
||||||
|
stock_entry_1 = make_stock_entry(item=item_2, to_warehouse=warehouse_1, qty=20, basic_rate=100)
|
||||||
|
stock_entry_2 = make_stock_entry(item=item_3, to_warehouse=warehouse_1, qty=8, basic_rate=100)
|
||||||
|
stock_entry_3 = make_stock_entry(item=item_3, to_warehouse=warehouse_2, qty=12, basic_rate=100)
|
||||||
|
|
||||||
|
sales_order = make_sales_order(
|
||||||
|
item_list=[
|
||||||
|
{"item_code": item_1, "qty": 8, "rate": 100, "warehouse": warehouse},
|
||||||
|
{"item_code": item_1, "qty": 12, "rate": 100, "warehouse": warehouse},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
pick_list = create_pick_list(sales_order.name)
|
||||||
|
pick_list.submit()
|
||||||
|
self.assertEqual(len(pick_list.locations), 4)
|
||||||
|
delivery_note = create_delivery_note(pick_list.name)
|
||||||
|
|
||||||
|
self.assertEqual(delivery_note.items[0].qty, 8)
|
||||||
|
self.assertEqual(delivery_note.items[1].qty, 12)
|
||||||
|
|
||||||
|
self.assertEqual(delivery_note.packed_items[0].qty, 8)
|
||||||
|
self.assertEqual(delivery_note.packed_items[2].qty, 12)
|
||||||
|
|
||||||
|
self.assertEqual(delivery_note.packed_items[0].warehouse, warehouse_1)
|
||||||
|
self.assertEqual(delivery_note.packed_items[1].warehouse, warehouse_1)
|
||||||
|
self.assertEqual(delivery_note.packed_items[2].warehouse, warehouse_1)
|
||||||
|
self.assertEqual(delivery_note.packed_items[3].warehouse, warehouse_2)
|
||||||
|
|
||||||
|
pick_list.cancel()
|
||||||
|
sales_order.cancel()
|
||||||
|
stock_entry_1.cancel()
|
||||||
|
stock_entry_2.cancel()
|
||||||
|
stock_entry_3.cancel()
|
||||||
|
|
||||||
|
def test_pick_list_with_and_without_so(self):
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
item = make_item().name
|
||||||
|
|
||||||
|
sales_order = make_sales_order(item_code=item, qty=20, rate=100)
|
||||||
|
stock_entry = make_stock_entry(item=item, to_warehouse=warehouse, qty=500, basic_rate=100)
|
||||||
|
|
||||||
|
pick_list = create_pick_list(sales_order.name)
|
||||||
|
pick_list.append(
|
||||||
|
"locations",
|
||||||
|
{
|
||||||
|
"item_code": item,
|
||||||
|
"qty": 10,
|
||||||
|
"stock_qty": 10,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"picked_qty": 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pick_list.submit()
|
||||||
|
|
||||||
|
delivery_note = create_dn_for_pick_lists(pick_list.name)
|
||||||
|
|
||||||
|
self.assertEqual(delivery_note.items[0].against_pick_list, pick_list.name)
|
||||||
|
self.assertEqual(delivery_note.items[0].against_sales_order, sales_order.name)
|
||||||
|
self.assertEqual(delivery_note.items[0].qty, 20)
|
||||||
|
|
||||||
|
self.assertEqual(delivery_note.items[1].against_pick_list, pick_list.name)
|
||||||
|
self.assertEqual(delivery_note.items[1].qty, 10)
|
||||||
|
|
||||||
|
pick_list.cancel()
|
||||||
|
sales_order.cancel()
|
||||||
|
stock_entry.cancel()
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"uom",
|
"uom",
|
||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
|
"delivered_qty",
|
||||||
"serial_no_and_batch_section",
|
"serial_no_and_batch_section",
|
||||||
"pick_serial_and_batch",
|
"pick_serial_and_batch",
|
||||||
"serial_and_batch_bundle",
|
"serial_and_batch_bundle",
|
||||||
@@ -237,17 +238,28 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_belw",
|
"fieldname": "column_break_belw",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "delivered_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Delivered Qty (in Stock UOM)",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1,
|
||||||
|
"report_hide": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-05-07 15:32:42.905446",
|
"modified": "2025-05-31 19:57:43.531298",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Pick List Item",
|
"name": "Pick List Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class PickListItem(Document):
|
|||||||
|
|
||||||
batch_no: DF.Link | None
|
batch_no: DF.Link | None
|
||||||
conversion_factor: DF.Float
|
conversion_factor: DF.Float
|
||||||
|
delivered_qty: DF.Float
|
||||||
description: DF.Text | None
|
description: DF.Text | None
|
||||||
item_code: DF.Link
|
item_code: DF.Link
|
||||||
item_group: DF.Data | None
|
item_group: DF.Data | None
|
||||||
|
|||||||
Reference in New Issue
Block a user