Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: provision to add scrap item in job card #27512

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion erpnext/manufacturing/doctype/job_card/job_card.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"total_time_in_mins",
"section_break_8",
"items",
"scrap_items_section",
"scrap_items",
"corrective_operation_section",
"for_job_card",
"is_corrective_job_card",
Expand Down Expand Up @@ -392,11 +394,24 @@
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch"
},
{
"fieldname": "scrap_items_section",
"fieldtype": "Section Break",
"label": "Scrap Items"
},
{
"fieldname": "scrap_items",
"fieldtype": "Table",
"label": "Scrap Items",
"no_copy": 1,
"options": "Job Card Scrap Item",
"print_hide": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2021-09-13 21:34:15.177928",
"modified": "2021-09-14 00:38:46.873105",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"actions": [],
"creation": "2021-09-14 00:30:28.533884",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
"column_break_3",
"description",
"quantity_and_rate",
"stock_qty",
"column_break_6",
"stock_uom"
],
"fields": [
{
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Scrap Item Code",
"options": "Item",
"reqd": 1
},
{
"fetch_from": "item_code.item_name",
"fieldname": "item_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Scrap Item Name"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fetch_from": "item_code.description",
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description",
"read_only": 1
},
{
"fieldname": "quantity_and_rate",
"fieldtype": "Section Break",
"label": "Quantity and Rate"
},
{
"fieldname": "stock_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Qty",
"reqd": 1
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"fetch_from": "item_code.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"options": "UOM",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-09-14 01:20:48.588052",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card Scrap Item",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

from frappe.model.document import Document


class JobCardScrapItem(Document):
pass
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ def make_bom(**args):
'uom': item_doc.stock_uom,
'stock_uom': item_doc.stock_uom,
'rate': item_doc.valuation_rate or args.rate,
'source_warehouse': args.source_warehouse
})

if not args.do_not_save:
Expand Down
56 changes: 55 additions & 1 deletion erpnext/manufacturing/doctype/work_order/test_work_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
stop_unstop,
)
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.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_bin
Expand Down Expand Up @@ -768,6 +768,60 @@ def test_wo_completion_with_pl_bom(self):
total_pl_qty
)

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("""select item_code, stock_qty from `tabBOM Scrap Item`
Expand Down
84 changes: 80 additions & 4 deletions erpnext/stock/doctype/stock_entry/stock_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import unicode_literals

import json
from collections import defaultdict

import frappe
from frappe import _
Expand Down Expand Up @@ -684,7 +685,7 @@ def validate_purchase_order(self):

def validate_bom(self):
for d in self.get('items'):
if d.bom_no and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse):
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)

Expand Down Expand Up @@ -1191,13 +1192,88 @@ def get_bom_scrap_material(self, qty):

# item dict = { item_code: {qty, description, stock_uom} }
item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty,
fetch_exploded = 0, fetch_scrap_items = 1)
fetch_exploded = 0, fetch_scrap_items = 1) or {}

for item in itervalues(item_dict):
item.from_warehouse = ""
item.is_scrap_item = 1

for row in self.get_scrap_items_from_job_card():
if row.stock_qty <= 0:
continue

item_row = item_dict.get(row.item_code)
if not item_row:
item_row = frappe._dict({})

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
})

item_dict[row.item_code] = item_row

return item_dict

def get_scrap_items_from_job_card(self):
if not self.pro_doc:
self.set_work_order_details()

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 []

used_scrap_items = self.get_used_scrap_items()
for row in scrap_items:
row.stock_qty -= flt(used_scrap_items.get(row.item_code))
row.stock_qty = (row.stock_qty) * flt(self.fg_completed_qty) / flt(pending_qty)

if used_scrap_items.get(row.item_code):
used_scrap_items[row.item_code] -= row.stock_qty

if cint(frappe.get_cached_value('UOM', row.stock_uom, 'must_be_whole_number')):
row.stock_qty = frappe.utils.ceil(row.stock_qty)

return scrap_items

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']]
]
)

for row in data:
used_scrap_items[row.item_code] += row.qty

return used_scrap_items

def get_unconsumed_raw_materials(self):
wo = frappe.get_doc("Work Order", self.work_order)
wo_items = frappe.get_all('Work Order Item',
Expand Down Expand Up @@ -1417,8 +1493,8 @@ def add_to_stock_entry_detail(self, item_dict, bom_no=None):
se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0)
se_child.is_process_loss = item_dict[d].get("is_process_loss", 0)

for field in ["idx", "po_detail", "original_item",
"expense_account", "description", "item_name", "serial_no", "batch_no"]:
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))

Expand Down