mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-09 00:01:18 +00:00
feat: partial delivery in dropshipping (#54787)
This commit is contained in:
@@ -330,9 +330,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(
|
||||
__("Delivered"),
|
||||
__("Deliver (Dropship)"),
|
||||
this.delivered_by_supplier.bind(this),
|
||||
__("Status")
|
||||
);
|
||||
@@ -350,7 +350,12 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
if (doc.status != "Closed") {
|
||||
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(
|
||||
__("Purchase Receipt"),
|
||||
() => {
|
||||
@@ -694,7 +699,143 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
@@ -209,7 +209,6 @@ class PurchaseOrder(BuyingController):
|
||||
validate_against_blanket_order(self)
|
||||
|
||||
self.validate_fg_item_for_subcontracting()
|
||||
self.set_received_qty_for_drop_ship_items()
|
||||
|
||||
if not self.advance_payment_status:
|
||||
self.advance_payment_status = "Not Initiated"
|
||||
@@ -471,6 +470,8 @@ class PurchaseOrder(BuyingController):
|
||||
|
||||
if self.has_drop_ship_item():
|
||||
self.update_delivered_qty_in_sales_order()
|
||||
self.set_received_qty_to_zero_for_drop_ship_items()
|
||||
self.update_receiving_percentage()
|
||||
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
@@ -543,6 +544,11 @@ class PurchaseOrder(BuyingController):
|
||||
so.set_status(update=True)
|
||||
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):
|
||||
return any(d.delivered_by_supplier for d in self.items)
|
||||
|
||||
@@ -552,17 +558,13 @@ class PurchaseOrder(BuyingController):
|
||||
def is_against_pp(self):
|
||||
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
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_receiving_percentage(self):
|
||||
total_qty, received_qty = 0.0, 0.0
|
||||
for item in self.items:
|
||||
received_qty += min(item.received_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)
|
||||
else:
|
||||
self.db_set("per_received", 0, update_modified=False)
|
||||
|
||||
@@ -612,11 +612,13 @@
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "received_qty",
|
||||
"fieldname": "received_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Received Qty",
|
||||
"no_copy": 1,
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "received_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"print_hide": 1,
|
||||
@@ -940,7 +942,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-05-04 10:51:05.183490",
|
||||
"modified": "2026-05-08 20:40:10.683023",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -4046,8 +4046,8 @@ def update_child_qty_rate(
|
||||
)
|
||||
|
||||
qty_limits = {
|
||||
"Sales Order": ("delivered_qty", _("Cannot set quantity less than delivered quantity")),
|
||||
"Purchase Order": ("received_qty", _("Cannot set quantity less than received quantity")),
|
||||
"Sales Order": ("delivered_qty", _("Cannot set quantity less than delivered quantity.")),
|
||||
"Purchase Order": ("received_qty", _("Cannot set quantity less than received quantity.")),
|
||||
}
|
||||
|
||||
if parent_doctype in qty_limits:
|
||||
|
||||
@@ -1735,7 +1735,7 @@ def make_purchase_order(
|
||||
if 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:
|
||||
target.shipping_address = source.shipping_address_name
|
||||
target.shipping_address_display = source.shipping_address
|
||||
|
||||
Reference in New Issue
Block a user