Merge branch 'develop' into jc-excess-transfer-config

This commit is contained in:
Ankush Menat
2022-05-21 14:32:44 +05:30
27 changed files with 944 additions and 159 deletions

View File

@@ -499,15 +499,11 @@ cur_frm.cscript.qty = function(doc) {
cur_frm.cscript.rate = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
var scrap_items = false;
if(cdt == 'BOM Scrap Item') {
scrap_items = true;
}
const is_scrap_item = cdt == "BOM Scrap Item";
if (d.bom_no) {
frappe.msgprint(__("You cannot change the rate if BOM is mentioned against any Item."));
get_bom_material_detail(doc, cdt, cdn, scrap_items);
get_bom_material_detail(doc, cdt, cdn, is_scrap_item);
} else {
erpnext.bom.calculate_rm_cost(doc);
erpnext.bom.calculate_scrap_materials_cost(doc);

View File

@@ -33,7 +33,6 @@
"amount",
"base_amount",
"section_break_18",
"scrap",
"qty_consumed_per_unit",
"section_break_27",
"has_variants",
@@ -223,15 +222,6 @@
"fieldname": "section_break_18",
"fieldtype": "Section Break"
},
{
"columns": 1,
"fieldname": "scrap",
"fieldtype": "Float",
"label": "Scrap %",
"oldfieldname": "scrap",
"oldfieldtype": "Currency",
"print_hide": 1
},
{
"fieldname": "qty_consumed_per_unit",
"fieldtype": "Float",
@@ -298,7 +288,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-01-24 16:57:57.020232",
"modified": "2022-05-19 02:32:43.785470",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",

View File

@@ -897,6 +897,7 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta
target.set("time_logs", [])
target.set("employee", [])
target.set("items", [])
target.set("sub_operations", [])
target.set_sub_operations()
target.get_required_items()
target.validate_time_logs()

View File

@@ -1,19 +1,25 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from typing import Literal
import frappe
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import random_string
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,
)
from erpnext.manufacturing.doctype.job_card.job_card import (
make_stock_entry as make_stock_entry_from_jc,
)
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.doctype.work_order.work_order import WorkOrder
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -21,35 +27,36 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class TestJobCard(FrappeTestCase):
def setUp(self):
make_bom_for_jc_tests()
self.transfer_material_against: Literal["Work Order", "Job Card"] = "Work Order"
self.source_warehouse = None
self._work_order = None
transfer_material_against, source_warehouse = None, None
@property
def work_order(self) -> WorkOrder:
"""Work Order lazily created for tests."""
if not self._work_order:
self._work_order = make_wo_order_test_record(
item="_Test FG Item 2",
qty=2,
transfer_material_against=self.transfer_material_against,
source_warehouse=self.source_warehouse,
)
return self._work_order
tests_that_skip_setup = ("test_job_card_material_transfer_correctness",)
tests_that_transfer_against_jc = (
"test_job_card_multiple_materials_transfer",
"test_job_card_excess_material_transfer",
"test_job_card_partial_material_transfer",
"test_job_card_excess_material_transfer_block",
)
if self._testMethodName in tests_that_skip_setup:
return
if self._testMethodName in tests_that_transfer_against_jc:
transfer_material_against = "Job Card"
source_warehouse = "Stores - _TC"
self.work_order = make_wo_order_test_record(
item="_Test FG Item 2",
qty=2,
transfer_material_against=transfer_material_against,
source_warehouse=source_warehouse,
)
def generate_required_stock(self, work_order: WorkOrder) -> None:
"""Create twice the stock for all required items in work order."""
for item in work_order.required_items:
make_stock_entry(
item_code=item.item_code,
target=item.source_warehouse or self.source_warehouse,
qty=item.required_qty * 2,
basic_rate=100,
)
def tearDown(self):
frappe.db.rollback()
def test_job_card(self):
def test_job_card_operations(self):
job_cards = frappe.get_all(
"Job Card", filters={"work_order": self.work_order.name}, fields=["operation_id", "name"]
@@ -63,9 +70,6 @@ class TestJobCard(FrappeTestCase):
doc.operation_id = "Test Data"
self.assertRaises(OperationMismatchError, doc.save)
for d in job_cards:
frappe.delete_doc("Job Card", d.name)
def test_job_card_with_different_work_station(self):
job_cards = frappe.get_all(
"Job Card",
@@ -101,19 +105,11 @@ class TestJobCard(FrappeTestCase):
)
self.assertEqual(completed_qty, job_card.for_quantity)
doc.cancel()
for d in job_cards:
frappe.delete_doc("Job Card", d.name)
def test_job_card_overlap(self):
wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2)
jc1_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
jc2_name = frappe.db.get_value("Job Card", {"work_order": wo2.name})
jc1 = frappe.get_doc("Job Card", jc1_name)
jc2 = frappe.get_doc("Job Card", jc2_name)
jc1 = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
jc2 = frappe.get_last_doc("Job Card", {"work_order": wo2.name})
employee = "_T-Employee-00001" # from test records
@@ -142,10 +138,10 @@ class TestJobCard(FrappeTestCase):
def test_job_card_multiple_materials_transfer(self):
"Test transferring RMs separately against Job Card with multiple RMs."
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=100)
make_stock_entry(
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=6, basic_rate=100
)
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})
job_card = frappe.get_doc("Job Card", job_card_name)
@@ -173,22 +169,21 @@ class TestJobCard(FrappeTestCase):
@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."
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
make_stock_entry(
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
)
self.transfer_material_against = "Job Card"
self.source_warehouse = "Stores - _TC"
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
job_card = frappe.get_doc("Job Card", job_card_name)
self.generate_required_stock(self.work_order)
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
self.assertEqual(job_card.status, "Open")
# fully transfer both RMs
transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
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)
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
@@ -201,7 +196,7 @@ class TestJobCard(FrappeTestCase):
# Check if 'For Quantity' is negative
# as 'transferred_qty' > Qty to Manufacture
transfer_entry_3 = make_stock_entry_from_jc(job_card_name)
transfer_entry_3 = make_stock_entry_from_jc(job_card.name)
self.assertEqual(transfer_entry_3.fg_completed_qty, 0)
job_card.append(
@@ -216,10 +211,11 @@ class TestJobCard(FrappeTestCase):
@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 0})
def test_job_card_excess_material_transfer_block(self):
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
make_stock_entry(
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
)
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})
@@ -239,17 +235,15 @@ class TestJobCard(FrappeTestCase):
def test_job_card_partial_material_transfer(self):
"Test partial material transfer against Job Card"
self.transfer_material_against = "Job Card"
self.source_warehouse = "Stores - _TC"
make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=25, basic_rate=100)
make_stock_entry(
item_code="_Test Item Home Desktop Manufactured", target="Stores - _TC", qty=15, basic_rate=100
)
self.generate_required_stock(self.work_order)
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
job_card = frappe.get_doc("Job Card", job_card_name)
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
# partially transfer
transfer_entry = make_stock_entry_from_jc(job_card_name)
transfer_entry = make_stock_entry_from_jc(job_card.name)
transfer_entry.fg_completed_qty = 1
transfer_entry.get_items()
transfer_entry.insert()
@@ -261,7 +255,7 @@ class TestJobCard(FrappeTestCase):
self.assertEqual(transfer_entry.items[1].qty, 3)
# transfer remaining
transfer_entry_2 = make_stock_entry_from_jc(job_card_name)
transfer_entry_2 = make_stock_entry_from_jc(job_card.name)
self.assertEqual(transfer_entry_2.fg_completed_qty, 1)
self.assertEqual(transfer_entry_2.items[0].qty, 5)
@@ -306,7 +300,49 @@ class TestJobCard(FrappeTestCase):
self.assertEqual(transfer_entry.items[0].item_code, "_Test Item")
self.assertEqual(transfer_entry.items[0].qty, 2)
# rollback via tearDown method
@change_settings(
"Manufacturing Settings", {"add_corrective_operation_cost_in_finished_good_valuation": 1}
)
def test_corrective_costing(self):
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
job_card.append(
"time_logs",
{"from_time": now(), "to_time": add_to_date(now(), hours=1), "completed_qty": 2},
)
job_card.submit()
self.work_order.reload()
original_cost = self.work_order.total_operating_cost
# Create a corrective operation against it
corrective_action = frappe.get_doc(
doctype="Operation", is_corrective_operation=1, name=frappe.generate_hash()
).insert()
corrective_job_card = make_corrective_job_card(
job_card.name, operation=corrective_action.name, for_operation=job_card.operation
)
corrective_job_card.hour_rate = 100
corrective_job_card.insert()
corrective_job_card.append(
"time_logs",
{
"from_time": add_to_date(now(), hours=2),
"to_time": add_to_date(now(), hours=2, minutes=30),
"completed_qty": 2,
},
)
corrective_job_card.submit()
self.work_order.reload()
cost_after_correction = self.work_order.total_operating_cost
self.assertGreater(cost_after_correction, original_cost)
corrective_job_card.cancel()
self.work_order.reload()
cost_after_cancel = self.work_order.total_operating_cost
self.assertEqual(cost_after_cancel, original_cost)
def create_bom_with_multiple_operations():

View File

@@ -21,7 +21,7 @@ def get_exploded_items(bom, data, indent=0, qty=1):
exploded_items = frappe.get_all(
"BOM Item",
filters={"parent": bom},
fields=["qty", "bom_no", "qty", "scrap", "item_code", "item_name", "description", "uom"],
fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom"],
)
for item in exploded_items:
@@ -37,7 +37,6 @@ def get_exploded_items(bom, data, indent=0, qty=1):
"qty": item.qty * qty,
"uom": item.uom,
"description": item.description,
"scrap": item.scrap,
}
)
if item.bom_no:
@@ -64,5 +63,4 @@ def get_columns():
"fieldname": "description",
"width": 150,
},
{"label": _("Scrap"), "fieldtype": "data", "fieldname": "scrap", "width": 100},
]