mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-26 18:18:30 +00:00
* Merge pull request #50299 from saadchaudharry/feature-subcontract-return-rejected
Add support for subcontract return from rejected warehouse
(cherry picked from commit 8854db51dd)
# Conflicts:
# erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
* chore: resolve conflicts
---------
Co-authored-by: Saad Chaudhary <47004596+saadchaudharry@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
This commit is contained in:
@@ -480,6 +480,13 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
||||
target_doc.subcontracting_order_item = source_doc.subcontracting_order_item
|
||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||
target_doc.subcontracting_receipt_item = source_doc.name
|
||||
if return_against_rejected_qty:
|
||||
target_doc.qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get("qty") or 0))
|
||||
target_doc.rejected_qty = 0.0
|
||||
target_doc.rejected_warehouse = ""
|
||||
target_doc.warehouse = source_doc.rejected_warehouse
|
||||
target_doc.received_qty = target_doc.qty
|
||||
target_doc.return_qty_from_rejected_warehouse = 1
|
||||
else:
|
||||
target_doc.purchase_order = source_doc.purchase_order
|
||||
target_doc.purchase_order_item = source_doc.purchase_order_item
|
||||
|
||||
@@ -82,10 +82,53 @@ frappe.ui.form.on("Subcontracting Receipt", {
|
||||
frm.add_custom_button(
|
||||
__("Subcontract Return"),
|
||||
() => {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return",
|
||||
frm: frm,
|
||||
const make_standard_return = () => {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return",
|
||||
frm: frm,
|
||||
});
|
||||
};
|
||||
|
||||
let has_rejected_items = frm.doc.items.filter((item) => {
|
||||
if (item.rejected_qty > 0) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (has_rejected_items && has_rejected_items.length > 0) {
|
||||
frappe.prompt(
|
||||
[
|
||||
{
|
||||
label: __("Return Qty from Rejected Warehouse"),
|
||||
fieldtype: "Check",
|
||||
fieldname: "return_for_rejected_warehouse",
|
||||
default: 1,
|
||||
},
|
||||
],
|
||||
function (values) {
|
||||
if (values.return_for_rejected_warehouse) {
|
||||
frappe.call({
|
||||
method: "erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return_against_rejected_warehouse",
|
||||
args: {
|
||||
source_name: frm.doc.name,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", r.message.doctype, r.message.name);
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
make_standard_return();
|
||||
}
|
||||
},
|
||||
__("Return Qty"),
|
||||
__("Make Return Entry")
|
||||
);
|
||||
} else {
|
||||
make_standard_return();
|
||||
}
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
|
||||
@@ -774,6 +774,13 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
make_purchase_receipt(self, save=True, notify=True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_subcontract_return_against_rejected_warehouse(source_name):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
return make_return_doc("Subcontracting Receipt", source_name, return_against_rejected_qty=True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_subcontract_return(source_name, target_doc=None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
@@ -1196,6 +1196,136 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
||||
scr.cancel()
|
||||
self.assertTrue(scr.docstatus == 2)
|
||||
|
||||
def test_subcontract_return_from_rejected_warehouse(self):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import (
|
||||
make_subcontract_return_against_rejected_warehouse,
|
||||
)
|
||||
|
||||
# Create subcontracted item
|
||||
fg_item = make_item(
|
||||
"_Test Subcontract Item Return from Rejected Warehouse",
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"is_sub_contracted_item": 1,
|
||||
},
|
||||
).name
|
||||
|
||||
# Create service item
|
||||
service_item = make_item(
|
||||
"_Test Service Item Return from Rejected Warehouse", properties={"is_stock_item": 0}
|
||||
).name
|
||||
|
||||
# Create BOM for the subcontracted item with required raw materials
|
||||
rm_item1 = make_item(
|
||||
"_Test RM Item 1 Return from Rejected Warehouse", properties={"is_stock_item": 1}
|
||||
).name
|
||||
|
||||
rm_item2 = make_item(
|
||||
"_Test RM Item 2 Return from Rejected Warehouse", properties={"is_stock_item": 1}
|
||||
).name
|
||||
|
||||
make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2])
|
||||
|
||||
# Create warehouses
|
||||
rejected_warehouse = create_warehouse("_Test Subcontract Rejected Warehouse Return Qty Warehouse")
|
||||
|
||||
# Create service items for subcontracting order
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": service_item,
|
||||
"qty": 10,
|
||||
"rate": 100,
|
||||
"fg_item": fg_item,
|
||||
"fg_item_qty": 10,
|
||||
},
|
||||
]
|
||||
|
||||
# Create Subcontracting Order
|
||||
sco = get_subcontracting_order(service_items=service_items)
|
||||
|
||||
# Stock raw materials
|
||||
make_stock_entry(item_code=rm_item1, qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100)
|
||||
make_stock_entry(item_code=rm_item2, qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100)
|
||||
|
||||
# Transfer raw materials
|
||||
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),
|
||||
)
|
||||
|
||||
# Step 1: Create Subcontracting Receipt with rejected quantity
|
||||
sr = make_subcontracting_receipt(sco.name)
|
||||
sr.items[0].qty = 8 # Accepted quantity
|
||||
sr.items[0].rejected_qty = 2
|
||||
sr.items[0].rejected_warehouse = rejected_warehouse
|
||||
sr.save()
|
||||
sr.submit()
|
||||
|
||||
# Verify initial state
|
||||
sr.reload()
|
||||
self.assertEqual(sr.items[0].qty, 8)
|
||||
self.assertEqual(sr.items[0].rejected_qty, 2)
|
||||
self.assertEqual(sr.items[0].rejected_warehouse, rejected_warehouse)
|
||||
|
||||
# Step 2: Create Subcontract Return from Rejected Warehouse
|
||||
sr_return = make_subcontract_return_against_rejected_warehouse(sr.name)
|
||||
|
||||
# Verify the return document properties
|
||||
self.assertEqual(sr_return.doctype, "Subcontracting Receipt")
|
||||
self.assertEqual(sr_return.is_return, 1)
|
||||
self.assertEqual(sr_return.return_against, sr.name)
|
||||
|
||||
# Verify item details in return document
|
||||
self.assertEqual(len(sr_return.items), 1)
|
||||
self.assertEqual(sr_return.items[0].item_code, fg_item)
|
||||
self.assertEqual(sr_return.items[0].warehouse, rejected_warehouse)
|
||||
self.assertEqual(sr_return.items[0].qty, -2.0) # Negative for return
|
||||
self.assertEqual(sr_return.items[0].rejected_qty, 0.0)
|
||||
self.assertEqual(sr_return.items[0].rejected_warehouse, "")
|
||||
|
||||
# Check specific fields that should be set for subcontracting returns
|
||||
self.assertEqual(sr_return.items[0].subcontracting_order, sco.name)
|
||||
self.assertEqual(sr_return.items[0].subcontracting_order_item, sr.items[0].subcontracting_order_item)
|
||||
self.assertEqual(sr_return.items[0].return_qty_from_rejected_warehouse, 1)
|
||||
|
||||
# For returns from rejected warehouse, supplied_items might be empty initially
|
||||
# They might get populated when the document is saved/submitted
|
||||
# Or they might not be needed since we're returning finished goods
|
||||
|
||||
# Save and submit the return
|
||||
sr_return.save()
|
||||
sr_return.submit()
|
||||
|
||||
# Verify final state
|
||||
sr_return.reload()
|
||||
self.assertEqual(sr_return.docstatus, 1)
|
||||
self.assertEqual(sr_return.status, "Return")
|
||||
|
||||
# Verify stock ledger entries for the return
|
||||
sle = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={
|
||||
"voucher_type": "Subcontracting Receipt",
|
||||
"voucher_no": sr_return.name,
|
||||
"warehouse": rejected_warehouse,
|
||||
},
|
||||
fields=["item_code", "actual_qty", "warehouse"],
|
||||
)
|
||||
|
||||
self.assertEqual(len(sle), 1)
|
||||
self.assertEqual(sle[0].item_code, fg_item)
|
||||
self.assertEqual(sle[0].actual_qty, -2.0) # Outward entry from rejected warehouse
|
||||
self.assertEqual(sle[0].warehouse, rejected_warehouse)
|
||||
|
||||
# Verify that the original document's rejected quantity is not affected
|
||||
sr.reload()
|
||||
self.assertEqual(sr.items[0].rejected_qty, 2) # Should remain the same
|
||||
|
||||
@change_settings("Buying Settings", {"auto_create_purchase_receipt": 1})
|
||||
def test_auto_create_purchase_receipt(self):
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
|
||||
Reference in New Issue
Block a user