Merge branch 'develop' into jc-excess-transfer-config
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index b13e4e0..d16281a 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -42,6 +42,10 @@
pass
+class JobCardOverTransferError(frappe.ValidationError):
+ pass
+
+
class JobCard(Document):
def onload(self):
excess_transfer = frappe.db.get_single_value(
@@ -522,23 +526,50 @@
},
)
- def set_transferred_qty_in_job_card(self, ste_doc):
+ def set_transferred_qty_in_job_card_item(self, ste_doc):
+ from frappe.query_builder.functions import Sum
+
+ def _validate_over_transfer(row, transferred_qty):
+ "Block over transfer of items if not allowed in settings."
+ allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
+ required_qty = frappe.db.get_value("Job Card Item", row.job_card_item, "required_qty")
+ is_excess = flt(transferred_qty) > flt(required_qty)
+
+ if is_excess and not allow_excess:
+ frappe.throw(
+ _(
+ "Row #{0}: Cannot transfer more than Required Qty {1} for Item {2} against Job Card {3}"
+ ).format(
+ row.idx, frappe.bold(required_qty), frappe.bold(row.item_code), ste_doc.job_card
+ ),
+ title=_("Excess Transfer"),
+ exc=JobCardOverTransferError,
+ )
+
for row in ste_doc.items:
if not row.job_card_item:
continue
- qty = frappe.db.sql(
- """ SELECT SUM(qty) from `tabStock Entry Detail` sed, `tabStock Entry` se
- WHERE sed.job_card_item = %s and se.docstatus = 1 and sed.parent = se.name and
- se.purpose = 'Material Transfer for Manufacture'
- """,
- (row.job_card_item),
- )[0][0]
+ sed = frappe.qb.DocType("Stock Entry Detail")
+ se = frappe.qb.DocType("Stock Entry")
+ transferred_qty = (
+ frappe.qb.from_(sed)
+ .join(se)
+ .on(sed.parent == se.name)
+ .select(Sum(sed.qty))
+ .where(
+ (sed.job_card_item == row.job_card_item)
+ & (se.docstatus == 1)
+ & (se.purpose == "Material Transfer for Manufacture")
+ )
+ ).run()[0][0]
- frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(qty))
+ _validate_over_transfer(row, transferred_qty)
+
+ frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty))
def set_transferred_qty(self, update_status=False):
- "Set total FG Qty for which RM was transferred."
+ "Set total FG Qty in Job Card for which RM was transferred."
if not self.items:
self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index 25a03ea..7f3c7fe 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -10,6 +10,7 @@
from frappe.utils.data import add_to_date, now
from erpnext.manufacturing.doctype.job_card.job_card import (
+ JobCardOverTransferError,
OperationMismatchError,
OverlapError,
make_corrective_job_card,
@@ -165,6 +166,7 @@
# transfer was made for 2 fg qty in first transfer Stock Entry
self.assertEqual(transfer_entry_2.fg_completed_qty, 0)
+ @change_settings("Manufacturing Settings", {"job_card_excess_transfer": 1})
def test_job_card_excess_material_transfer(self):
"Test transferring more than required RM against Job Card."
self.transfer_material_against = "Job Card"
@@ -207,6 +209,30 @@
# JC is Completed with excess transfer
self.assertEqual(job_card.status, "Completed")
+ @change_settings("Manufacturing Settings", {"job_card_excess_transfer": 0})
+ def test_job_card_excess_material_transfer_block(self):
+
+ self.transfer_material_against = "Job Card"
+ self.source_warehouse = "Stores - _TC"
+
+ self.generate_required_stock(self.work_order)
+
+ job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
+
+ # fully transfer both RMs
+ transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
+ transfer_entry_1.insert()
+ transfer_entry_1.submit()
+
+ # transfer extra qty of both RM due to previously damaged RM
+ transfer_entry_2 = make_stock_entry_from_jc(job_card_name)
+ # deliberately change 'For Quantity'
+ transfer_entry_2.fg_completed_qty = 1
+ transfer_entry_2.items[0].qty = 5
+ transfer_entry_2.items[1].qty = 3
+ transfer_entry_2.insert()
+ self.assertRaises(JobCardOverTransferError, transfer_entry_2.submit)
+
def test_job_card_partial_material_transfer(self):
"Test partial material transfer against Job Card"
self.transfer_material_against = "Job Card"
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 5c35ed6..f1df54d 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1139,7 +1139,7 @@
if self.job_card:
job_doc = frappe.get_doc("Job Card", self.job_card)
job_doc.set_transferred_qty(update_status=True)
- job_doc.set_transferred_qty_in_job_card(self)
+ job_doc.set_transferred_qty_in_job_card_item(self)
if self.work_order:
pro_doc = frappe.get_doc("Work Order", self.work_order)