diff --git a/erpnext/manufacturing/doctype/job_card/mapper.py b/erpnext/manufacturing/doctype/job_card/mapper.py
index 42cf4c91e5b..1753be76bbd 100644
--- a/erpnext/manufacturing/doctype/job_card/mapper.py
+++ b/erpnext/manufacturing/doctype/job_card/mapper.py
@@ -102,6 +102,9 @@ def make_stock_entry(source_name: str, target_doc: str | dict | Document | None
target.qty = pending_rm_qty
def set_missing_values(source, target):
+ if not source.items:
+ frappe.throw(_("This Job Card has no raw materials to transfer."))
+
if source.finished_good and not source.target_warehouse:
frappe.throw(_("Please set the Target Warehouse in the Job Card"))
diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py
index 4c8e740e71e..4ca34c9ab85 100644
--- a/erpnext/manufacturing/doctype/workstation/test_workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py
@@ -3,18 +3,95 @@
import frappe
from frappe import _
+from erpnext.manufacturing.doctype.job_card.mapper import make_stock_entry
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
from erpnext.manufacturing.doctype.routing.test_routing import create_routing, setup_bom
from erpnext.manufacturing.doctype.workstation.workstation import (
NotInWorkingHoursError,
WorkstationHolidayError,
check_if_within_operating_hours,
+ get_raw_materials,
update_job_card,
)
from erpnext.tests.utils import ERPNextTestSuite
class TestWorkstation(ERPNextTestSuite):
+ def test_get_raw_materials_without_items(self):
+ for skip_transfer, backflush_from_wip in ((0, 0), (1, 0), (1, 1)):
+ with self.subTest(skip_transfer=skip_transfer, backflush_from_wip=backflush_from_wip):
+ job_card = frappe.get_doc(
+ {
+ "doctype": "Job Card",
+ "company": "_Test Company",
+ "skip_material_transfer": skip_transfer,
+ "backflush_from_wip_warehouse": backflush_from_wip,
+ "wip_warehouse": "_Test Warehouse 1 - _TC",
+ }
+ ).insert(ignore_mandatory=True)
+
+ for method in (get_raw_materials, make_stock_entry):
+ with self.subTest(method=method.__name__):
+ with self.assertRaisesRegex(
+ frappe.ValidationError, "This Job Card has no raw materials to transfer"
+ ):
+ method(job_card.name)
+
+ job_card.reload()
+ self.assertFalse(job_card.items)
+ self.assertFalse(frappe.db.exists("Stock Entry", {"job_card": job_card.name}))
+
+ def test_get_raw_materials_availability(self):
+ for skip_transfer, backflush_from_wip, transferred_qty in (
+ (0, 0, 2),
+ (0, 0, 5),
+ (1, 0, 0),
+ (1, 1, 0),
+ ):
+ with self.subTest(
+ skip_transfer=skip_transfer,
+ backflush_from_wip=backflush_from_wip,
+ transferred_qty=transferred_qty,
+ ):
+ job_card = frappe.get_doc(
+ {
+ "doctype": "Job Card",
+ "company": "_Test Company",
+ "skip_material_transfer": skip_transfer,
+ "backflush_from_wip_warehouse": backflush_from_wip,
+ "wip_warehouse": "_Test Warehouse 1 - _TC",
+ "items": [
+ {
+ "item_code": "_Test Item",
+ "source_warehouse": "_Test Warehouse - _TC",
+ "required_qty": 5,
+ "transferred_qty": transferred_qty,
+ },
+ ],
+ }
+ ).insert(ignore_mandatory=True)
+
+ materials = get_raw_materials(job_card.name)
+
+ self.assertEqual(len(materials), 1)
+ material = materials[0]
+ warehouse = "_Test Warehouse 1 - _TC" if backflush_from_wip else "_Test Warehouse - _TC"
+ stock_qty = (
+ frappe.db.get_value(
+ "Bin", {"item_code": "_Test Item", "warehouse": warehouse}, "actual_qty"
+ )
+ or 0
+ )
+ self.assertEqual(material.item_code, "_Test Item")
+ self.assertEqual(material.required_qty, 5)
+ self.assertEqual(material.transferred_qty, transferred_qty)
+ self.assertEqual(material.warehouse, warehouse)
+ self.assertEqual(material.stock_qty, stock_qty)
+ self.assertEqual(
+ material.material_availability_status,
+ int(stock_qty >= 5) if skip_transfer else int(transferred_qty >= 5),
+ )
+
def test_update_job_card_rejects_disallowed_method(self):
# The whitelisted update_job_card endpoint must only run an allowlisted set of Job Card
# methods. An arbitrary method name must be rejected (PermissionError) before the document
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py
index efd11a18d05..88e8ac179b8 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation.py
@@ -287,8 +287,8 @@ def get_raw_materials(job_card: str):
filters={"name": job_card},
)
- if not raw_materials:
- return []
+ if not raw_materials or not raw_materials[0].item_code:
+ frappe.throw(_("This Job Card has no raw materials to transfer."))
for row in raw_materials:
warehouse = row.source_warehouse
diff --git a/erpnext/public/js/templates/shop_floor_template.html b/erpnext/public/js/templates/shop_floor_template.html
index 0adc8432629..09360ee873a 100644
--- a/erpnext/public/js/templates/shop_floor_template.html
+++ b/erpnext/public/js/templates/shop_floor_template.html
@@ -727,7 +727,7 @@
- {% } else { %}
+ {% } else if (slot.materials && slot.materials.length) { %}
@@ -990,7 +990,7 @@
- {% } else { %}
+ {% } else if (jc.materials && jc.materials.length) { %}