mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-13 17:53:49 +00:00
Merge pull request #51305 from mihir-kandoi/gh45824
This commit is contained in:
@@ -1401,6 +1401,7 @@ def make_rm_stock_entry(
|
||||
|
||||
stock_entry.set_stock_entry_type()
|
||||
|
||||
over_transfer_allowance = frappe.get_single_value("Buying Settings", "over_transfer_allowance")
|
||||
for fg_item_code in fg_item_code_list:
|
||||
for rm_item in rm_items:
|
||||
if (
|
||||
@@ -1408,14 +1409,27 @@ def make_rm_stock_entry(
|
||||
or rm_item.get("item_code") == fg_item_code
|
||||
):
|
||||
rm_item_code = rm_item.get("rm_item_code")
|
||||
qty = rm_item.get("qty") or max(
|
||||
rm_item.get("required_qty") - rm_item.get("total_supplied_qty"), 0
|
||||
)
|
||||
if qty <= 0 and rm_item.get("total_supplied_qty"):
|
||||
per_transferred = (
|
||||
flt(
|
||||
rm_item.get("total_supplied_qty") / rm_item.get("required_qty"),
|
||||
frappe.db.get_default("float_precision"),
|
||||
)
|
||||
* 100
|
||||
)
|
||||
if per_transferred >= 100 + over_transfer_allowance:
|
||||
continue
|
||||
|
||||
items_dict = {
|
||||
rm_item_code: {
|
||||
rm_detail_field: rm_item.get("name"),
|
||||
"item_name": rm_item.get("item_name")
|
||||
or item_wh.get(rm_item_code, {}).get("item_name", ""),
|
||||
"description": item_wh.get(rm_item_code, {}).get("description", ""),
|
||||
"qty": rm_item.get("qty")
|
||||
or max(rm_item.get("required_qty") - rm_item.get("total_supplied_qty"), 0),
|
||||
"qty": qty,
|
||||
"from_warehouse": rm_item.get("warehouse")
|
||||
or rm_item.get("reserve_warehouse"),
|
||||
"to_warehouse": subcontract_order.supplier_warehouse,
|
||||
|
||||
@@ -1454,9 +1454,11 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
)
|
||||
).run()[0][0] or 0
|
||||
|
||||
if flt(total_supplied - total_returned, precision) > flt(total_allowed, precision):
|
||||
if flt(total_supplied + se_item.transfer_qty - total_returned, precision) > flt(
|
||||
total_allowed, precision
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row {0}# Item {1} cannot be transferred more than {2} against {3} {4}").format(
|
||||
_("Row #{0}: Item {1} cannot be transferred more than {2} against {3} {4}").format(
|
||||
se_item.idx,
|
||||
se_item.item_code,
|
||||
total_allowed,
|
||||
@@ -3080,7 +3082,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
|
||||
child_qty = flt(item_row["qty"], precision)
|
||||
if not self.is_return and child_qty <= 0 and not item_row.get("is_scrap_item"):
|
||||
if self.purpose != "Receive from Customer":
|
||||
if self.purpose not in ["Receive from Customer", "Send to Subcontractor"]:
|
||||
continue
|
||||
|
||||
se_child = self.append("items")
|
||||
|
||||
@@ -460,6 +460,119 @@ frappe.ui.form.on("Subcontracting Order", {
|
||||
});
|
||||
},
|
||||
|
||||
make_subcontracting_receipt(this_obj) {
|
||||
const doc = this_obj.frm.doc;
|
||||
const has_overtransferred_items = doc.supplied_items.some((item) => {
|
||||
return item.supplied_qty > item.required_qty;
|
||||
});
|
||||
const backflush_based_on = doc.__onload.backflush_based_on;
|
||||
if (has_overtransferred_items && backflush_based_on === "BOM") {
|
||||
const raw_data = doc.supplied_items.map((item) => {
|
||||
const row = doc.items.find((i) => i.name === item.reference_name);
|
||||
const qty = flt(row.qty) - flt(row.received_qty);
|
||||
return {
|
||||
__checked: 1,
|
||||
item_code: row.item_code,
|
||||
warehouse: row.warehouse,
|
||||
bom_no: row.bom,
|
||||
required_by: row.schedule_date,
|
||||
qty: qty > 0 ? qty : null,
|
||||
subcontracting_order_item: row.name,
|
||||
};
|
||||
});
|
||||
const item_names_list = [];
|
||||
const data = [];
|
||||
raw_data.forEach((d) => {
|
||||
if (!item_names_list.includes(d.subcontracting_order_item)) {
|
||||
item_names_list.push(d.subcontracting_order_item);
|
||||
data.push(d);
|
||||
}
|
||||
});
|
||||
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Select Items"),
|
||||
size: "extra-large",
|
||||
fields: [
|
||||
{
|
||||
fieldname: "items",
|
||||
fieldtype: "Table",
|
||||
reqd: 1,
|
||||
label: __("Select Items to Receive"),
|
||||
cannot_add_rows: true,
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "item_code",
|
||||
reqd: 1,
|
||||
options: "Item",
|
||||
label: __("Item Code"),
|
||||
in_list_view: 1,
|
||||
read_only: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "warehouse",
|
||||
options: "Warehouse",
|
||||
label: __("Warehouse"),
|
||||
in_list_view: 1,
|
||||
read_only: 1,
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "bom_no",
|
||||
options: "BOM",
|
||||
label: __("BOM"),
|
||||
in_list_view: 1,
|
||||
read_only: 1,
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Date",
|
||||
fieldname: "required_by",
|
||||
label: __("Required By"),
|
||||
in_list_view: 1,
|
||||
read_only: 1,
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Float",
|
||||
fieldname: "qty",
|
||||
reqd: 1,
|
||||
label: __("Qty to Receive"),
|
||||
in_list_view: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "subcontracting_order_item",
|
||||
reqd: 1,
|
||||
label: __("Subcontracting Order Item"),
|
||||
hidden: 1,
|
||||
read_only: 1,
|
||||
in_list_view: 0,
|
||||
},
|
||||
],
|
||||
data: data,
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Proceed"),
|
||||
primary_action: () => {
|
||||
const values = dialog.fields_dict["items"].grid
|
||||
.get_selected_children()
|
||||
.map((i) => ({ name: i.subcontracting_order_item, qty: i.qty }));
|
||||
if (values.some((i) => !i.qty || i.qty == 0)) {
|
||||
frappe.throw(__("Quantity is mandatory for the selected items."));
|
||||
} else {
|
||||
this_obj.make_subcontracting_receipt(values);
|
||||
}
|
||||
},
|
||||
});
|
||||
dialog.show();
|
||||
} else {
|
||||
this_obj.make_subcontracting_receipt();
|
||||
}
|
||||
},
|
||||
|
||||
company: function (frm) {
|
||||
erpnext.utils.set_letter_head(frm);
|
||||
},
|
||||
@@ -524,11 +637,11 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
|
||||
var me = this;
|
||||
|
||||
if (doc.docstatus == 1) {
|
||||
if (!["Closed", "Completed"].includes(doc.status)) {
|
||||
if (flt(doc.per_received) < 100) {
|
||||
if (doc.status != "Closed") {
|
||||
if (flt(doc.per_received) < 100 + doc.__onload.over_delivery_receipt_allowance) {
|
||||
this.frm.add_custom_button(
|
||||
__("Subcontracting Receipt"),
|
||||
this.make_subcontracting_receipt,
|
||||
() => this.frm.events.make_subcontracting_receipt(this),
|
||||
__("Create")
|
||||
);
|
||||
if (me.has_unsupplied_items()) {
|
||||
@@ -576,10 +689,12 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
|
||||
});
|
||||
}
|
||||
|
||||
make_subcontracting_receipt() {
|
||||
make_subcontracting_receipt(items) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt",
|
||||
frm: cur_frm,
|
||||
args: { items: items || [] },
|
||||
freeze: true,
|
||||
freeze_message: __("Creating Subcontracting Receipt ..."),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -110,6 +110,14 @@ class SubcontractingOrder(SubcontractingController):
|
||||
"over_transfer_allowance",
|
||||
frappe.db.get_single_value("Buying Settings", "over_transfer_allowance"),
|
||||
)
|
||||
self.set_onload(
|
||||
"over_delivery_receipt_allowance",
|
||||
frappe.get_single_value("Stock Settings", "over_delivery_receipt_allowance"),
|
||||
)
|
||||
self.set_onload(
|
||||
"backflush_based_on",
|
||||
frappe.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on"),
|
||||
)
|
||||
|
||||
if self.reserve_stock:
|
||||
if self.has_unreserved_stock():
|
||||
@@ -464,16 +472,18 @@ class SubcontractingOrder(SubcontractingController):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_subcontracting_receipt(source_name, target_doc=None):
|
||||
return get_mapped_subcontracting_receipt(source_name, target_doc)
|
||||
items = frappe.flags.args.get("items") if frappe.flags.args else None
|
||||
return get_mapped_subcontracting_receipt(source_name, target_doc, items=items)
|
||||
|
||||
|
||||
def get_mapped_subcontracting_receipt(source_name, target_doc=None):
|
||||
def get_mapped_subcontracting_receipt(source_name, target_doc=None, items=None):
|
||||
def update_item(source, target, source_parent):
|
||||
target.purchase_order = source_parent.purchase_order
|
||||
target.purchase_order_item = source.purchase_order_item
|
||||
target.qty = flt(source.qty) - flt(source.received_qty)
|
||||
target.qty = items.get(source.name) or (flt(source.qty) - flt(source.received_qty))
|
||||
target.amount = (flt(source.qty) - flt(source.received_qty)) * flt(source.rate)
|
||||
|
||||
items = {item["name"]: item["qty"] for item in items} if items else {}
|
||||
target_doc = get_mapped_doc(
|
||||
"Subcontracting Order",
|
||||
source_name,
|
||||
@@ -496,7 +506,9 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None):
|
||||
"bom": "bom",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
||||
if not items
|
||||
else doc.name in items,
|
||||
},
|
||||
},
|
||||
target_doc,
|
||||
|
||||
@@ -1890,6 +1890,36 @@ class TestSubcontractingReceipt(IntegrationTestCase):
|
||||
|
||||
self.assertRaises(BOMQuantityError, scr.submit)
|
||||
|
||||
@IntegrationTestCase.change_settings("Buying Settings", {"over_transfer_allowance": 20})
|
||||
@IntegrationTestCase.change_settings("Stock Settings", {"over_delivery_receipt_allowance": 20})
|
||||
def test_over_receipt(self):
|
||||
from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
|
||||
|
||||
set_backflush_based_on("BOM")
|
||||
|
||||
sco = get_subcontracting_order()
|
||||
rm_items = get_rm_items(sco.supplied_items)
|
||||
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||
make_stock_transfer_entry(
|
||||
sco_no=sco.name,
|
||||
rm_items=rm_items,
|
||||
itemwise_details=copy.deepcopy(itemwise_details),
|
||||
)
|
||||
|
||||
rm_items[0]["qty"] = 2
|
||||
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||
ste_dict = make_rm_stock_entry(sco.name)
|
||||
doc = frappe.get_doc(ste_dict)
|
||||
self.assertEqual(doc.items[0].qty, 0)
|
||||
doc.items[0].qty = 2
|
||||
doc.submit()
|
||||
|
||||
frappe.flags["args"] = {"items": [{"name": sco.items[0].name, "qty": 2}]}
|
||||
scr = make_subcontracting_receipt(sco.name)
|
||||
self.assertEqual(scr.items[0].qty, 2)
|
||||
scr.submit()
|
||||
frappe.flags["args"].pop("items", None)
|
||||
|
||||
|
||||
def make_return_subcontracting_receipt(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
Reference in New Issue
Block a user