From 5ed768af1975de4a5b535e2e1a5ebb3d6ff63b42 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 15 Sep 2021 16:45:57 +0530 Subject: [PATCH] feat: provision to add scrap item in job card (#27483) (cherry picked from commit c5a77f60ed362ece3dd7ecd4568c82809f15bf28) # Conflicts: # erpnext/manufacturing/doctype/job_card/job_card.json # erpnext/manufacturing/doctype/production_plan/test_production_plan.py # erpnext/stock/doctype/stock_entry/stock_entry.py --- .../doctype/job_card/job_card.json | 10 +++ .../production_plan/test_production_plan.py | 13 ++++ .../doctype/work_order/test_work_order.py | 54 +++++++++++++++ .../stock/doctype/stock_entry/stock_entry.py | 68 +++++++++++++++++++ 4 files changed, 145 insertions(+) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 43dc5c0b7be..bb38c5f49d3 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -397,7 +397,10 @@ "options": "Batch" }, { +<<<<<<< HEAD "collapsible": 1, +======= +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) "fieldname": "scrap_items_section", "fieldtype": "Section Break", "label": "Scrap Items" @@ -409,6 +412,7 @@ "no_copy": 1, "options": "Job Card Scrap Item", "print_hide": 1 +<<<<<<< HEAD }, { "fetch_from": "operation.quality_inspection_template", @@ -416,11 +420,17 @@ "fieldtype": "Link", "label": "Quality Inspection Template", "options": "Quality Inspection Template" +======= +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) } ], "is_submittable": 1, "links": [], +<<<<<<< HEAD "modified": "2023-05-22 23:26:57.589331", +======= + "modified": "2021-09-14 00:38:46.873105", +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 8cd79202dd8..23742c22a8d 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -844,6 +844,7 @@ def make_bom(**args): ) for item in args.raw_materials: +<<<<<<< HEAD item_doc = frappe.get_doc("Item", item) bom.append( "items", @@ -856,6 +857,18 @@ def make_bom(**args): "source_warehouse": args.source_warehouse, }, ) +======= + item_doc = frappe.get_doc('Item', item) + + bom.append('items', { + 'item_code': item, + 'qty': args.rm_qty or 1.0, + 'uom': item_doc.stock_uom, + 'stock_uom': item_doc.stock_uom, + 'rate': item_doc.valuation_rate or args.rate, + 'source_warehouse': args.source_warehouse + }) +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) if not args.do_not_save: bom.insert(ignore_permissions=True) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index c5c9b5f0d31..07d3f81ffa8 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1285,6 +1285,60 @@ def update_job_card(job_card, jc_qty=None): job_card_doc.submit() + def test_job_card_scrap_item(self): + items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test', + 'Test RM Item 2 for Scrap Item Test'] + + company = '_Test Company with perpetual inventory' + for item_code in items: + create_item(item_code = item_code, is_stock_item = 1, + is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1') + + item = 'Test FG Item for Scrap Item Test' + raw_materials = ['Test RM Item 1 for Scrap Item Test', 'Test RM Item 2 for Scrap Item Test'] + if not frappe.db.get_value('BOM', {'item': item}): + bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True) + bom.with_operations = 1 + bom.append('operations', { + 'operation': '_Test Operation 1', + 'workstation': '_Test Workstation 1', + 'hour_rate': 20, + 'time_in_mins': 60 + }) + + bom.submit() + + wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) + job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') + update_job_card(job_card) + + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) + for row in stock_entry.items: + if row.is_scrap_item: + self.assertEqual(row.qty, 1) + +def update_job_card(job_card): + job_card_doc = frappe.get_doc('Job Card', job_card) + job_card_doc.set('scrap_items', [ + { + 'item_code': 'Test RM Item 1 for Scrap Item Test', + 'stock_qty': 2 + }, + { + 'item_code': 'Test RM Item 2 for Scrap Item Test', + 'stock_qty': 2 + }, + ]) + + job_card_doc.append('time_logs', { + 'from_time': now(), + 'time_in_mins': 60, + 'completed_qty': job_card_doc.for_quantity + }) + + job_card_doc.submit() + + def get_scrap_item_details(bom_no): scrap_items = {} for item in frappe.db.sql( diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8d6e93fa8c5..80adc24b597 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -921,7 +921,11 @@ class StockEntry(StockController): row.db_set("po_detail", po_detail) def validate_bom(self): +<<<<<<< HEAD for d in self.get("items"): +======= + for d in self.get('items'): +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) if d.bom_no and d.is_finished_item: item_code = d.original_item or d.item_code validate_bom_no(item_code, d.bom_no) @@ -1548,10 +1552,15 @@ class StockEntry(StockController): from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict # item dict = { item_code: {qty, description, stock_uom} } +<<<<<<< HEAD item_dict = ( get_bom_items_as_dict(self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1) or {} ) +======= + item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty, + fetch_exploded = 0, fetch_scrap_items = 1) or {} +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) for item in itervalues(item_dict): item.from_warehouse = "" @@ -1565,6 +1574,7 @@ class StockEntry(StockController): if not item_row: item_row = frappe._dict({}) +<<<<<<< HEAD item_row.update( { "uom": row.stock_uom, @@ -1577,6 +1587,18 @@ class StockEntry(StockController): "allow_zero_valuation_rate": 1, } ) +======= + item_row.update({ + 'uom': row.stock_uom, + 'from_warehouse': '', + 'qty': row.stock_qty + flt(item_row.stock_qty), + 'converison_factor': 1, + 'is_scrap_item': 1, + 'item_name': row.item_name, + 'description': row.description, + 'allow_zero_valuation_rate': 1 + }) +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) item_dict[row.item_code] = item_row @@ -1586,6 +1608,7 @@ class StockEntry(StockController): if not self.pro_doc: self.set_work_order_details() +<<<<<<< HEAD if not self.pro_doc.operations: return [] @@ -1613,6 +1636,24 @@ class StockEntry(StockController): pending_qty = flt(self.get_completed_job_card_qty()) - flt(self.pro_doc.produced_qty) +======= + scrap_items = frappe.db.sql(''' + SELECT + JCSI.item_code, JCSI.item_name, SUM(JCSI.stock_qty) as stock_qty, JCSI.stock_uom, JCSI.description + FROM + `tabJob Card` JC, `tabJob Card Scrap Item` JCSI + WHERE + JCSI.parent = JC.name AND JC.docstatus = 1 + AND JCSI.item_code IS NOT NULL AND JC.work_order = %s + GROUP BY + JCSI.item_code + ''', self.work_order, as_dict=1) + + pending_qty = flt(self.pro_doc.qty) - flt(self.pro_doc.produced_qty) + if pending_qty <=0: + return [] + +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) used_scrap_items = self.get_used_scrap_items() for row in scrap_items: row.stock_qty -= flt(used_scrap_items.get(row.item_code)) @@ -1621,11 +1662,16 @@ class StockEntry(StockController): if used_scrap_items.get(row.item_code): used_scrap_items[row.item_code] -= row.stock_qty +<<<<<<< HEAD if cint(frappe.get_cached_value("UOM", row.stock_uom, "must_be_whole_number")): +======= + if cint(frappe.get_cached_value('UOM', row.stock_uom, 'must_be_whole_number')): +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) row.stock_qty = frappe.utils.ceil(row.stock_qty) return scrap_items +<<<<<<< HEAD def get_completed_job_card_qty(self): return flt(min([d.completed_qty for d in self.pro_doc.operations])) @@ -1640,6 +1686,21 @@ class StockEntry(StockController): ["Stock Entry", "docstatus", "=", 1], ["Stock Entry", "purpose", "in", ["Repack", "Manufacture"]], ], +======= + def get_used_scrap_items(self): + used_scrap_items = defaultdict(float) + data = frappe.get_all( + 'Stock Entry', + fields = [ + '`tabStock Entry Detail`.`item_code`', '`tabStock Entry Detail`.`qty`' + ], + filters = [ + ['Stock Entry', 'work_order', '=', self.work_order], + ['Stock Entry Detail', 'is_scrap_item', '=', 1], + ['Stock Entry', 'docstatus', '=', 1], + ['Stock Entry', 'purpose', 'in', ['Repack', 'Manufacture']] + ] +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) ) for row in data: @@ -1929,6 +1990,7 @@ class StockEntry(StockController): se_child.is_scrap_item = item_row.get("is_scrap_item", 0) se_child.is_process_loss = item_row.get("is_process_loss", 0) +<<<<<<< HEAD for field in [ "po_detail", "original_item", @@ -1941,6 +2003,12 @@ class StockEntry(StockController): ]: if item_row.get(field): se_child.set(field, item_row.get(field)) +======= + for field in ["idx", "po_detail", "original_item", "expense_account", + "description", "item_name", "serial_no", "batch_no", "allow_zero_valuation_rate"]: + if item_dict[d].get(field): + se_child.set(field, item_dict[d].get(field)) +>>>>>>> c5a77f60ed (feat: provision to add scrap item in job card (#27483)) if se_child.s_warehouse == None: se_child.s_warehouse = self.from_warehouse