mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-15 03:01:22 +00:00
* feat: partial delivery in dropshipping (#54787)
(cherry picked from commit db74360396)
# Conflicts:
# erpnext/buying/doctype/purchase_order/purchase_order.py
# erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
* chore: resolve conflicts
* chore: resolve conflicts
---------
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
This commit is contained in:
@@ -354,9 +354,9 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_drop_ship && doc.status != "Delivered") {
|
if (is_drop_ship && !["Completed", "Delivered"].includes(doc.status)) {
|
||||||
this.frm.add_custom_button(
|
this.frm.add_custom_button(
|
||||||
__("Delivered"),
|
__("Deliver (Dropship)"),
|
||||||
this.delivered_by_supplier.bind(this),
|
this.delivered_by_supplier.bind(this),
|
||||||
__("Status")
|
__("Status")
|
||||||
);
|
);
|
||||||
@@ -374,7 +374,12 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
|||||||
}
|
}
|
||||||
if (doc.status != "Closed") {
|
if (doc.status != "Closed") {
|
||||||
if (doc.status != "On Hold") {
|
if (doc.status != "On Hold") {
|
||||||
if (flt(doc.per_received) < 100 && allow_receipt) {
|
if (
|
||||||
|
doc.items
|
||||||
|
.filter((item) => !item.delivered_by_supplier)
|
||||||
|
.some((item) => item.received_qty < item.qty) &&
|
||||||
|
allow_receipt
|
||||||
|
) {
|
||||||
this.frm.add_custom_button(
|
this.frm.add_custom_button(
|
||||||
__("Purchase Receipt"),
|
__("Purchase Receipt"),
|
||||||
() => {
|
() => {
|
||||||
@@ -730,7 +735,143 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
|||||||
}
|
}
|
||||||
|
|
||||||
delivered_by_supplier() {
|
delivered_by_supplier() {
|
||||||
this.frm.cscript.update_status("Deliver", "Delivered");
|
const data = this.frm.doc.items
|
||||||
|
.filter((item) => item.delivered_by_supplier == 1)
|
||||||
|
.map((item) => {
|
||||||
|
return {
|
||||||
|
__checked: item.qty > item.received_qty,
|
||||||
|
name: item.name,
|
||||||
|
item_code: item.item_code,
|
||||||
|
item_name: item.item_name,
|
||||||
|
qty: item.qty,
|
||||||
|
uom: item.uom,
|
||||||
|
delivered_qty: item.received_qty || 0,
|
||||||
|
qty_change: item.qty - item.received_qty,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const dialog = new frappe.ui.Dialog({
|
||||||
|
title: __("Set Dropship Items Delivered Quantity"),
|
||||||
|
size: "extra-large",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldname: "items",
|
||||||
|
fieldtype: "Table",
|
||||||
|
data: data,
|
||||||
|
cannot_add_rows: true,
|
||||||
|
cannot_delete_rows: true,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldname: "name",
|
||||||
|
fieldtype: "Data",
|
||||||
|
read_only: true,
|
||||||
|
hidden: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "item_code",
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Item",
|
||||||
|
label: __("Item Code"),
|
||||||
|
in_list_view: 1,
|
||||||
|
read_only: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "item_name",
|
||||||
|
fieldtype: "Data",
|
||||||
|
label: __("Item Name"),
|
||||||
|
in_list_view: 1,
|
||||||
|
read_only: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "qty",
|
||||||
|
fieldtype: "Float",
|
||||||
|
label: __("Quantity"),
|
||||||
|
in_list_view: 1,
|
||||||
|
read_only: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "uom",
|
||||||
|
fieldtype: "Data",
|
||||||
|
label: __("UOM"),
|
||||||
|
in_list_view: 1,
|
||||||
|
read_only: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "delivered_qty",
|
||||||
|
fieldtype: "Float",
|
||||||
|
label: __("Delivered Qty"),
|
||||||
|
read_only: true,
|
||||||
|
in_list_view: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "qty_change",
|
||||||
|
fieldtype: "Float",
|
||||||
|
label: __("Qty Change"),
|
||||||
|
in_list_view: 1,
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primary_action: (values) => {
|
||||||
|
const data = values.items.filter((item) => item.__checked);
|
||||||
|
if (!data.length) {
|
||||||
|
frappe.throw(__("Please select at least one item to update delivered quantity."));
|
||||||
|
}
|
||||||
|
|
||||||
|
data.forEach((item) => {
|
||||||
|
if (!item.qty_change) {
|
||||||
|
frappe.throw(
|
||||||
|
__(
|
||||||
|
"Item {0} has no changes in delivered quantity. Please unselect the row if you do not wish to update its quantity.",
|
||||||
|
[item.item_code.bold()]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (item.qty_change < 0 && Math.abs(item.qty_change) > item.delivered_qty) {
|
||||||
|
frappe.throw(
|
||||||
|
__("Delivered Qty cannot be reduced by more than {0} for item {1}", [
|
||||||
|
item.delivered_qty,
|
||||||
|
item.item_code.bold(),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (item.qty_change > 0 && item.delivered_qty + item.qty_change > item.qty) {
|
||||||
|
frappe.throw(
|
||||||
|
__("Delivered Qty cannot be increased by more than {0} for item {1}", [
|
||||||
|
item.qty - item.delivered_qty,
|
||||||
|
item.item_code.bold(),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
data.forEach((item) => {
|
||||||
|
frappe.model.set_value(
|
||||||
|
"Purchase Order Item",
|
||||||
|
item.name,
|
||||||
|
"received_qty",
|
||||||
|
item.delivered_qty + item.qty_change
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const frm = this.frm;
|
||||||
|
frm.dirty();
|
||||||
|
frm.save("Update", () => {
|
||||||
|
frappe.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
method: "update_receiving_percentage",
|
||||||
|
callback: function (r) {
|
||||||
|
if (!r.exc) {
|
||||||
|
dialog.hide();
|
||||||
|
frappe.toast(__("Quantities updated successfully."));
|
||||||
|
frm.reload_doc();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
items_on_form_rendered() {
|
items_on_form_rendered() {
|
||||||
|
|||||||
@@ -218,7 +218,6 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.create_raw_materials_supplied()
|
self.create_raw_materials_supplied()
|
||||||
|
|
||||||
self.validate_fg_item_for_subcontracting()
|
self.validate_fg_item_for_subcontracting()
|
||||||
self.set_received_qty_for_drop_ship_items()
|
|
||||||
|
|
||||||
if not self.advance_payment_status:
|
if not self.advance_payment_status:
|
||||||
self.advance_payment_status = "Not Initiated"
|
self.advance_payment_status = "Not Initiated"
|
||||||
@@ -493,6 +492,8 @@ class PurchaseOrder(BuyingController):
|
|||||||
|
|
||||||
if self.has_drop_ship_item():
|
if self.has_drop_ship_item():
|
||||||
self.update_delivered_qty_in_sales_order()
|
self.update_delivered_qty_in_sales_order()
|
||||||
|
self.set_received_qty_to_zero_for_drop_ship_items()
|
||||||
|
self.update_receiving_percentage()
|
||||||
|
|
||||||
self.update_reserved_qty_for_subcontract()
|
self.update_reserved_qty_for_subcontract()
|
||||||
self.check_on_hold_or_closed_status()
|
self.check_on_hold_or_closed_status()
|
||||||
@@ -566,6 +567,11 @@ class PurchaseOrder(BuyingController):
|
|||||||
so.set_status(update=True)
|
so.set_status(update=True)
|
||||||
so.notify_update()
|
so.notify_update()
|
||||||
|
|
||||||
|
def set_received_qty_to_zero_for_drop_ship_items(self):
|
||||||
|
for item in self.items:
|
||||||
|
if item.delivered_by_supplier:
|
||||||
|
item.db_set("received_qty", 0)
|
||||||
|
|
||||||
def has_drop_ship_item(self):
|
def has_drop_ship_item(self):
|
||||||
return any(d.delivered_by_supplier for d in self.items)
|
return any(d.delivered_by_supplier for d in self.items)
|
||||||
|
|
||||||
@@ -575,11 +581,6 @@ class PurchaseOrder(BuyingController):
|
|||||||
def is_against_pp(self):
|
def is_against_pp(self):
|
||||||
return any(d.production_plan for d in self.items if d.production_plan)
|
return any(d.production_plan for d in self.items if d.production_plan)
|
||||||
|
|
||||||
def set_received_qty_for_drop_ship_items(self):
|
|
||||||
for item in self.items:
|
|
||||||
if item.delivered_by_supplier == 1:
|
|
||||||
item.received_qty = item.qty
|
|
||||||
|
|
||||||
def update_reserved_qty_for_subcontract(self):
|
def update_reserved_qty_for_subcontract(self):
|
||||||
if self.is_old_subcontracting_flow:
|
if self.is_old_subcontracting_flow:
|
||||||
for d in self.supplied_items:
|
for d in self.supplied_items:
|
||||||
@@ -587,12 +588,13 @@ class PurchaseOrder(BuyingController):
|
|||||||
stock_bin = get_bin(d.rm_item_code, d.reserve_warehouse)
|
stock_bin = get_bin(d.rm_item_code, d.reserve_warehouse)
|
||||||
stock_bin.update_reserved_qty_for_sub_contracting(subcontract_doctype="Purchase Order")
|
stock_bin.update_reserved_qty_for_sub_contracting(subcontract_doctype="Purchase Order")
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def update_receiving_percentage(self):
|
def update_receiving_percentage(self):
|
||||||
total_qty, received_qty = 0.0, 0.0
|
total_qty, received_qty = 0.0, 0.0
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
received_qty += min(item.received_qty, item.qty)
|
received_qty += min(item.received_qty, item.qty)
|
||||||
total_qty += item.qty
|
total_qty += item.qty
|
||||||
if total_qty:
|
if total_qty and received_qty:
|
||||||
self.db_set("per_received", flt(received_qty / total_qty) * 100, update_modified=False)
|
self.db_set("per_received", flt(received_qty / total_qty) * 100, update_modified=False)
|
||||||
else:
|
else:
|
||||||
self.db_set("per_received", 0, update_modified=False)
|
self.db_set("per_received", 0, update_modified=False)
|
||||||
|
|||||||
@@ -622,11 +622,13 @@
|
|||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
"depends_on": "received_qty",
|
"depends_on": "received_qty",
|
||||||
"fieldname": "received_qty",
|
"fieldname": "received_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Received Qty",
|
"label": "Received Qty",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
|
"non_negative": 1,
|
||||||
"oldfieldname": "received_qty",
|
"oldfieldname": "received_qty",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
@@ -950,7 +952,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-11-30 16:51:57.761673",
|
"modified": "2026-05-08 20:40:10.683023",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
|
|||||||
@@ -3908,8 +3908,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
)
|
)
|
||||||
|
|
||||||
qty_limits = {
|
qty_limits = {
|
||||||
"Sales Order": ("delivered_qty", _("Cannot set quantity less than delivered quantity")),
|
"Sales Order": ("delivered_qty", _("Cannot set quantity less than delivered quantity.")),
|
||||||
"Purchase Order": ("received_qty", _("Cannot set quantity less than received quantity")),
|
"Purchase Order": ("received_qty", _("Cannot set quantity less than received quantity.")),
|
||||||
}
|
}
|
||||||
|
|
||||||
if parent_doctype in qty_limits:
|
if parent_doctype in qty_limits:
|
||||||
|
|||||||
@@ -1628,7 +1628,7 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
|
|||||||
if default_payment_terms:
|
if default_payment_terms:
|
||||||
target.payment_terms_template = default_payment_terms
|
target.payment_terms_template = default_payment_terms
|
||||||
|
|
||||||
if any(item.delivered_by_supplier == 1 for item in source.items):
|
if any(item.delivered_by_supplier for item in target.items):
|
||||||
if source.shipping_address_name:
|
if source.shipping_address_name:
|
||||||
target.shipping_address = source.shipping_address_name
|
target.shipping_address = source.shipping_address_name
|
||||||
target.shipping_address_display = source.shipping_address
|
target.shipping_address_display = source.shipping_address
|
||||||
|
|||||||
Reference in New Issue
Block a user