Merge pull request #32082 from s-aga-r/t3
fix: validate available qty for consumption in SCR
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
index 098242a..f4a943f 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
@@ -187,22 +187,13 @@
self.assertEqual(len(ste.items), len(rm_items))
def test_update_reserved_qty_for_subcontracting(self):
- # Make stock available for raw materials
- make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
+ # Create RM Material Receipt
+ make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=10, basic_rate=100)
make_stock_entry(
target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100
)
- make_stock_entry(
- target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=30, basic_rate=100
- )
- make_stock_entry(
- target="_Test Warehouse 1 - _TC",
- item_code="_Test Item Home Desktop 100",
- qty=30,
- basic_rate=100,
- )
- bin1 = frappe.db.get_value(
+ bin_before_sco = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
@@ -222,102 +213,97 @@
]
sco = get_subcontracting_order(service_items=service_items)
- bin2 = frappe.db.get_value(
+ bin_after_sco = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"],
as_dict=1,
)
- self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
- self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10)
- self.assertNotEqual(bin1.modified, bin2.modified)
+ # reserved_qty_for_sub_contract should be increased by 10
+ self.assertEqual(
+ bin_after_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract + 10
+ )
- # Create stock transfer
+ # projected_qty should be decreased by 10
+ self.assertEqual(bin_after_sco.projected_qty, bin_before_sco.projected_qty - 10)
+
+ self.assertNotEqual(bin_before_sco.modified, bin_after_sco.modified)
+
+ # Create Stock Entry(Send to Subcontractor)
rm_items = [
{
"item_code": "_Test FG Item",
"rm_item_code": "_Test Item",
"item_name": "_Test Item",
- "qty": 6,
+ "qty": 10,
"warehouse": "_Test Warehouse - _TC",
"rate": 100,
- "amount": 600,
+ "amount": 1000,
"stock_uom": "Nos",
- }
+ },
+ {
+ "item_code": "_Test FG Item",
+ "rm_item_code": "_Test Item Home Desktop 100",
+ "item_name": "_Test Item Home Desktop 100",
+ "qty": 20,
+ "warehouse": "_Test Warehouse - _TC",
+ "rate": 100,
+ "amount": 2000,
+ "stock_uom": "Nos",
+ },
]
ste = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items))
ste.to_warehouse = "_Test Warehouse 1 - _TC"
ste.save()
ste.submit()
- bin3 = frappe.db.get_value(
+ bin_after_rm_transfer = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract",
as_dict=1,
)
- self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
-
- make_stock_entry(
- target="_Test Warehouse 1 - _TC", item_code="_Test Item", qty=40, basic_rate=100
- )
- make_stock_entry(
- target="_Test Warehouse 1 - _TC",
- item_code="_Test Item Home Desktop 100",
- qty=40,
- basic_rate=100,
+ # reserved_qty_for_sub_contract should be decreased by 10
+ self.assertEqual(
+ bin_after_rm_transfer.reserved_qty_for_sub_contract,
+ bin_after_sco.reserved_qty_for_sub_contract - 10,
)
- # Make SCR against the SCO
- scr = make_subcontracting_receipt(sco.name)
- scr.save()
- scr.submit()
-
- bin4 = frappe.db.get_value(
- "Bin",
- filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract",
- as_dict=1,
- )
-
- self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
-
- # Cancel SCR
- scr.reload()
- scr.cancel()
- bin5 = frappe.db.get_value(
- "Bin",
- filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
- fieldname="reserved_qty_for_sub_contract",
- as_dict=1,
- )
-
- self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
-
- # Cancel Stock Entry
+ # Cancel Stock Entry(Send to Subcontractor)
ste.cancel()
- bin6 = frappe.db.get_value(
+ bin_after_cancel_ste = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract",
as_dict=1,
)
- self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
+ # reserved_qty_for_sub_contract should be increased by 10
+ self.assertEqual(
+ bin_after_cancel_ste.reserved_qty_for_sub_contract,
+ bin_after_rm_transfer.reserved_qty_for_sub_contract + 10,
+ )
- # Cancel PO
+ # Cancel SCO
sco.reload()
sco.cancel()
- bin7 = frappe.db.get_value(
+ bin_after_cancel_sco = frappe.db.get_value(
"Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract",
as_dict=1,
)
- self.assertEqual(bin7.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
+ # reserved_qty_for_sub_contract should be decreased by 10
+ self.assertEqual(
+ bin_after_cancel_sco.reserved_qty_for_sub_contract,
+ bin_after_cancel_ste.reserved_qty_for_sub_contract - 10,
+ )
+ self.assertEqual(
+ bin_after_cancel_sco.reserved_qty_for_sub_contract, bin_before_sco.reserved_qty_for_sub_contract
+ )
def test_exploded_items(self):
item_code = "_Test Subcontracted FG Item 11"
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 021d9aa..1da7340 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -75,6 +75,7 @@
self.get_current_stock()
def on_submit(self):
+ self.validate_available_qty_for_consumption()
self.update_status_updater_args()
self.update_prevdoc_status()
self.set_subcontracting_order_status()
@@ -107,10 +108,42 @@
self.set_missing_values_in_supplied_items()
self.set_missing_values_in_items()
+ def set_available_qty_for_consumption(self):
+ supplied_items_details = {}
+
+ sco_supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item")
+ for item in self.get("items"):
+ supplied_items = (
+ frappe.qb.from_(sco_supplied_item)
+ .select(
+ sco_supplied_item.rm_item_code,
+ sco_supplied_item.reference_name,
+ (sco_supplied_item.total_supplied_qty - sco_supplied_item.consumed_qty).as_("available_qty"),
+ )
+ .where(
+ (sco_supplied_item.parent == item.subcontracting_order)
+ & (sco_supplied_item.main_item_code == item.item_code)
+ & (sco_supplied_item.reference_name == item.subcontracting_order_item)
+ )
+ ).run(as_dict=True)
+
+ if supplied_items:
+ supplied_items_details[item.name] = {}
+
+ for supplied_item in supplied_items:
+ supplied_items_details[item.name][supplied_item.rm_item_code] = supplied_item.available_qty
+ else:
+ for item in self.get("supplied_items"):
+ item.available_qty_for_consumption = supplied_items_details.get(item.reference_name, {}).get(
+ item.rm_item_code, 0
+ )
+
def set_missing_values_in_supplied_items(self):
for item in self.get("supplied_items") or []:
item.amount = item.rate * item.consumed_qty
+ self.set_available_qty_for_consumption()
+
def set_missing_values_in_items(self):
rm_supp_cost = {}
for item in self.get("supplied_items") or []:
@@ -147,6 +180,17 @@
_("Rejected Warehouse is mandatory against rejected Item {0}").format(item.item_code)
)
+ def validate_available_qty_for_consumption(self):
+ for item in self.get("supplied_items"):
+ if (
+ item.available_qty_for_consumption and item.available_qty_for_consumption < item.consumed_qty
+ ):
+ frappe.throw(
+ _(
+ "Row {0}: Consumed Qty must be less than or equal to Available Qty For Consumption in Consumed Items Table."
+ ).format(item.idx)
+ )
+
def set_items_cost_center(self):
if self.company:
cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
index 763e768..a47af52 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
@@ -70,6 +70,55 @@
rm_supp_cost = sum(item.amount for item in scr.get("supplied_items"))
self.assertEqual(scr.get("items")[0].rm_supp_cost, flt(rm_supp_cost))
+ def test_available_qty_for_consumption(self):
+ make_stock_entry(
+ item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100
+ )
+ make_stock_entry(
+ item_code="_Test Item Home Desktop 100",
+ qty=100,
+ target="_Test Warehouse 1 - _TC",
+ basic_rate=100,
+ )
+ service_items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 1",
+ "qty": 10,
+ "rate": 100,
+ "fg_item": "_Test FG Item",
+ "fg_item_qty": 10,
+ },
+ ]
+ sco = get_subcontracting_order(service_items=service_items)
+ rm_items = [
+ {
+ "main_item_code": "_Test FG Item",
+ "item_code": "_Test Item",
+ "qty": 5.0,
+ "rate": 100.0,
+ "stock_uom": "_Test UOM",
+ "warehouse": "_Test Warehouse - _TC",
+ },
+ {
+ "main_item_code": "_Test FG Item",
+ "item_code": "_Test Item Home Desktop 100",
+ "qty": 10.0,
+ "rate": 100.0,
+ "stock_uom": "_Test UOM",
+ "warehouse": "_Test Warehouse - _TC",
+ },
+ ]
+ 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),
+ )
+ scr = make_subcontracting_receipt(sco.name)
+ scr.save()
+ self.assertRaises(frappe.ValidationError, scr.submit)
+
def test_subcontracting_gle_fg_item_rate_zero(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
index 100a806..ddbb806 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
@@ -19,6 +19,7 @@
"col_break2",
"amount",
"secbreak_2",
+ "available_qty_for_consumption",
"required_qty",
"col_break3",
"consumed_qty",
@@ -75,8 +76,7 @@
{
"fieldname": "required_qty",
"fieldtype": "Float",
- "in_list_view": 1,
- "label": "Available Qty For Consumption",
+ "label": "Required Qty",
"print_hide": 1,
"read_only": 1
},
@@ -85,7 +85,7 @@
"fieldname": "consumed_qty",
"fieldtype": "Float",
"in_list_view": 1,
- "label": "Qty to be Consumed",
+ "label": "Consumed Qty",
"reqd": 1
},
{
@@ -179,12 +179,21 @@
"options": "Subcontracting Order",
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "available_qty_for_consumption",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Available Qty For Consumption",
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-04-18 10:45:16.538479",
+ "modified": "2022-09-02 22:28:53.392381",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Supplied Item",
@@ -193,6 +202,6 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1,
- "states": []
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file