mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 03:09:09 +00:00
Merge pull request #50299 from saadchaudharry/feature-subcontract-return-rejected
Add support for subcontract return from rejected warehouse
This commit is contained in:
@@ -484,6 +484,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.subcontracting_order_item = source_doc.subcontracting_order_item
|
||||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||||
target_doc.subcontracting_receipt_item = source_doc.name
|
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:
|
else:
|
||||||
target_doc.purchase_order = source_doc.purchase_order
|
target_doc.purchase_order = source_doc.purchase_order
|
||||||
target_doc.purchase_order_item = source_doc.purchase_order_item
|
target_doc.purchase_order_item = source_doc.purchase_order_item
|
||||||
|
|||||||
@@ -82,10 +82,53 @@ frappe.ui.form.on("Subcontracting Receipt", {
|
|||||||
frm.add_custom_button(
|
frm.add_custom_button(
|
||||||
__("Subcontract Return"),
|
__("Subcontract Return"),
|
||||||
() => {
|
() => {
|
||||||
frappe.model.open_mapped_doc({
|
const make_standard_return = () => {
|
||||||
method: "erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return",
|
frappe.model.open_mapped_doc({
|
||||||
frm: frm,
|
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")
|
__("Create")
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -854,6 +854,13 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
make_purchase_receipt(self, save=True, notify=True)
|
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()
|
@frappe.whitelist()
|
||||||
def make_subcontract_return(source_name, target_doc=None):
|
def make_subcontract_return(source_name, target_doc=None):
|
||||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|||||||
@@ -1195,6 +1195,136 @@ class TestSubcontractingReceipt(IntegrationTestCase):
|
|||||||
scr.cancel()
|
scr.cancel()
|
||||||
self.assertTrue(scr.docstatus == 2)
|
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
|
||||||
|
|
||||||
@IntegrationTestCase.change_settings("Buying Settings", {"auto_create_purchase_receipt": 1})
|
@IntegrationTestCase.change_settings("Buying Settings", {"auto_create_purchase_receipt": 1})
|
||||||
def test_auto_create_purchase_receipt(self):
|
def test_auto_create_purchase_receipt(self):
|
||||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
|
|||||||
Reference in New Issue
Block a user