diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 161f277c168..7b924f7025a 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -10,6 +10,7 @@ from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document class OperationMismatchError(frappe.ValidationError): pass +class JobCardCancelError(frappe.ValidationError): pass class JobCard(Document): def validate(self): @@ -110,39 +111,54 @@ class JobCard(Document): for_quantity, time_in_mins = 0, 0 from_time_list, to_time_list = [], [] - field = "operation_id" data = frappe.get_all('Job Card', fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"], - filters = {"docstatus": 1, "work_order": self.work_order, field: self.get(field)}) + filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id}) if data and len(data) > 0: - for_quantity = data[0].completed_qty - time_in_mins = data[0].time_in_mins + for_quantity = flt(data[0].completed_qty) + time_in_mins = flt(data[0].time_in_mins) - if self.get(field): - time_data = frappe.db.sql(""" + wo = frappe.get_doc('Work Order', self.work_order) + if self.operation_id: + self.validate_produced_quantity(for_quantity, wo) + self.update_work_order_data(for_quantity, time_in_mins, wo) + + def validate_produced_quantity(self, for_quantity, wo): + if self.docstatus < 2: return + + if wo.produced_qty > for_quantity: + first_part_msg = (_("The {0} {1} is used to calculate the valuation cost for the finished good {2}.") + .format(frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item))) + + second_part_msg = (_("Kindly cancel the Manufacturing Entries first against the work order {0}.") + .format(frappe.bold(get_link_to_form("Work Order", self.work_order)))) + + frappe.throw(_("{0} {1}").format(first_part_msg, second_part_msg), + JobCardCancelError, title = _("Error")) + + def update_work_order_data(self, for_quantity, time_in_mins, wo): + time_data = frappe.db.sql(""" SELECT min(from_time) as start_time, max(to_time) as end_time FROM `tabJob Card` jc, `tabJob Card Time Log` jctl WHERE jctl.parent = jc.name and jc.work_order = %s - and jc.{0} = %s and jc.docstatus = 1 - """.format(field), (self.work_order, self.get(field)), as_dict=1) + and jc.operation_id = %s and jc.docstatus = 1 + """, (self.work_order, self.operation_id), as_dict=1) - wo = frappe.get_doc('Work Order', self.work_order) + for data in wo.operations: + if data.get("name") == self.operation_id: + data.completed_qty = for_quantity + data.actual_operation_time = time_in_mins + data.actual_start_time = time_data[0].start_time if time_data else None + data.actual_end_time = time_data[0].end_time if time_data else None - for data in wo.operations: - if data.get("name") == self.get(field): - data.completed_qty = for_quantity - data.actual_operation_time = time_in_mins - data.actual_start_time = time_data[0].start_time if time_data else None - data.actual_end_time = time_data[0].end_time if time_data else None - - wo.flags.ignore_validate_update_after_submit = True - wo.update_operation_status() - wo.calculate_operating_cost() - wo.set_actual_dates() - wo.save() + wo.flags.ignore_validate_update_after_submit = True + wo.update_operation_status() + wo.calculate_operating_cost() + wo.set_actual_dates() + wo.save() def set_transferred_qty(self, update_status=False): if not self.items: diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index bdf1a8e854f..b243c574d8a 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -5,16 +5,17 @@ from __future__ import unicode_literals import unittest import frappe -from frappe.utils import flt, time_diff_in_hours, now, add_days, cint +from frappe.utils import flt, now, cint, add_to_date from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory -from erpnext.manufacturing.doctype.work_order.work_order \ - import make_stock_entry, ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError from erpnext.stock.doctype.stock_entry import test_stock_entry from erpnext.stock.utils import get_bin from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import make_item from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse +from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry, + ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError) +from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError class TestWorkOrder(unittest.TestCase): def setUp(self): @@ -397,14 +398,41 @@ class TestWorkOrder(unittest.TestCase): data = frappe.get_cached_value('BOM', {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) - if data: - bom, bom_item = data + bom, bom_item = data - bom_doc = frappe.get_doc('BOM', bom) - work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + bom_doc = frappe.get_doc('BOM', bom) + work_order = make_wo_order_test_record(item=bom_item, qty=1, + bom_no=bom, source_warehouse="_Test Warehouse - _TC") - job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}) - self.assertEqual(len(job_cards), len(bom_doc.operations)) + for row in work_order.required_items: + test_stock_entry.make_stock_entry(item_code=row.item_code, + target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100) + + ste = frappe.get_doc(make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1)) + ste.submit() + + job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}) + self.assertEqual(len(job_cards), len(bom_doc.operations)) + + for i, job_card in enumerate(job_cards): + doc = frappe.get_doc("Job Card", job_card) + doc.append("time_logs", { + "from_time": now(), + "hours": i, + "to_time": add_to_date(now(), i), + "completed_qty": doc.for_quantity + }) + doc.submit() + + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) + ste1.submit() + + for job_card in job_cards: + doc = frappe.get_doc("Job Card", job_card) + self.assertRaises(JobCardCancelError, doc.cancel) + + ste1.cancel() + ste.cancel() def test_work_order_with_non_transfer_item(self): items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0}