[item-variants] allow production order of variant #2224
This commit is contained in:
parent
1a7637459c
commit
baef96b5a2
@ -1,6 +0,0 @@
|
||||
[
|
||||
{
|
||||
"doctype": "Pricing Rule",
|
||||
"name": "_Test Pricing Rule 1"
|
||||
}
|
||||
]
|
@ -197,27 +197,14 @@ class BOM(Document):
|
||||
if self.with_operations and cstr(m.operation_no) not in self.op:
|
||||
frappe.throw(_("Operation {0} not present in Operations Table").format(m.operation_no))
|
||||
|
||||
if m.bom:
|
||||
self.validate_bom_no(m.item_code, m.bom_no, m.idx)
|
||||
if m.bom_no:
|
||||
validate_bom_no(m.item_code, m.bom_no)
|
||||
|
||||
if flt(m.qty) <= 0:
|
||||
frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx))
|
||||
|
||||
self.check_if_item_repeated(m.item_code, m.operation_no, check_list)
|
||||
|
||||
def validate_bom_no(self, item, bom_no, idx):
|
||||
"""Validate BOM No of sub-contracted items"""
|
||||
bom = frappe.get_doc("BOM", bom_no)
|
||||
if not bom.is_active:
|
||||
frappe.throw(_("BOM {0} must be active").format(bom_no))
|
||||
if not bom.docstatus!=1:
|
||||
frappe.throw(_("BOM {0} must be submitted").format(bom_no))
|
||||
|
||||
bom = frappe.db.sql("""select name from `tabBOM` where name = %s and item = %s
|
||||
and is_active=1 and docstatus=1""",
|
||||
(bom_no, item), as_dict =1)
|
||||
if not bom:
|
||||
frappe.throw(_("BOM {0} for Item {1} in row {2} is inactive or not submitted").format(bom_no, item, idx))
|
||||
|
||||
def check_if_item_repeated(self, item, op, check_list):
|
||||
if [cstr(item), cstr(op)] in check_list:
|
||||
@ -425,3 +412,16 @@ def get_bom_items(bom, qty=1, fetch_exploded=1):
|
||||
items = get_bom_items_as_dict(bom, qty, fetch_exploded).values()
|
||||
items.sort(lambda a, b: a.item_code > b.item_code and 1 or -1)
|
||||
return items
|
||||
|
||||
def validate_bom_no(item, bom_no):
|
||||
"""Validate BOM No of sub-contracted items"""
|
||||
bom = frappe.get_doc("BOM", bom_no)
|
||||
if not bom.is_active:
|
||||
frappe.throw(_("BOM {0} must be active").format(bom_no))
|
||||
if not bom.docstatus!=1:
|
||||
if not getattr(frappe.flags, "in_test", False):
|
||||
frappe.throw(_("BOM {0} must be submitted").format(bom_no))
|
||||
if item and not (bom.item == item or \
|
||||
bom.item == frappe.db.get_value("Item", item, "variant_of")):
|
||||
frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
|
||||
|
||||
|
@ -2,98 +2,128 @@
|
||||
{
|
||||
"bom_materials": [
|
||||
{
|
||||
"amount": 5000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Serialized Item With Series",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 1.0,
|
||||
"rate": 5000.0,
|
||||
"amount": 5000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Serialized Item With Series",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 1.0,
|
||||
"rate": 5000.0,
|
||||
"stock_uom": "_Test UOM"
|
||||
},
|
||||
},
|
||||
{
|
||||
"amount": 2000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item 2",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 2.0,
|
||||
"rate": 1000.0,
|
||||
"amount": 2000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item 2",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 2.0,
|
||||
"rate": 1000.0,
|
||||
"stock_uom": "_Test UOM"
|
||||
}
|
||||
],
|
||||
"docstatus": 1,
|
||||
"doctype": "BOM",
|
||||
"is_active": 1,
|
||||
"is_default": 1,
|
||||
"item": "_Test Item Home Desktop Manufactured",
|
||||
],
|
||||
"docstatus": 1,
|
||||
"doctype": "BOM",
|
||||
"is_active": 1,
|
||||
"is_default": 1,
|
||||
"item": "_Test Item Home Desktop Manufactured",
|
||||
"quantity": 1.0
|
||||
},
|
||||
},
|
||||
{
|
||||
"bom_materials": [
|
||||
{
|
||||
"amount": 5000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 1.0,
|
||||
"rate": 5000.0,
|
||||
"amount": 5000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 1.0,
|
||||
"rate": 5000.0,
|
||||
"stock_uom": "_Test UOM"
|
||||
},
|
||||
},
|
||||
{
|
||||
"amount": 2000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item Home Desktop 100",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 2.0,
|
||||
"rate": 1000.0,
|
||||
"amount": 2000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item Home Desktop 100",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 2.0,
|
||||
"rate": 1000.0,
|
||||
"stock_uom": "_Test UOM"
|
||||
}
|
||||
],
|
||||
"docstatus": 1,
|
||||
"doctype": "BOM",
|
||||
"is_active": 1,
|
||||
"is_default": 1,
|
||||
"item": "_Test FG Item",
|
||||
],
|
||||
"docstatus": 1,
|
||||
"doctype": "BOM",
|
||||
"is_active": 1,
|
||||
"is_default": 1,
|
||||
"item": "_Test FG Item",
|
||||
"quantity": 1.0
|
||||
},
|
||||
{
|
||||
"bom_operations": [
|
||||
{
|
||||
"operation_no": "1",
|
||||
"opn_description": "_Test",
|
||||
"workstation": "_Test Workstation 1",
|
||||
"operation_no": "1",
|
||||
"opn_description": "_Test",
|
||||
"workstation": "_Test Workstation 1",
|
||||
"time_in_min": 60,
|
||||
"operating_cost": 100
|
||||
}
|
||||
],
|
||||
],
|
||||
"bom_materials": [
|
||||
{
|
||||
"operation_no": 1,
|
||||
"amount": 5000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 1.0,
|
||||
"rate": 5000.0,
|
||||
"amount": 5000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 1.0,
|
||||
"rate": 5000.0,
|
||||
"stock_uom": "_Test UOM"
|
||||
},
|
||||
},
|
||||
{
|
||||
"operation_no": 1,
|
||||
"amount": 2000.0,
|
||||
"bom_no": "BOM/_Test Item Home Desktop Manufactured/001",
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item Home Desktop Manufactured",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 2.0,
|
||||
"rate": 1000.0,
|
||||
"amount": 2000.0,
|
||||
"bom_no": "BOM/_Test Item Home Desktop Manufactured/001",
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item Home Desktop Manufactured",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 2.0,
|
||||
"rate": 1000.0,
|
||||
"stock_uom": "_Test UOM"
|
||||
}
|
||||
],
|
||||
"docstatus": 1,
|
||||
"doctype": "BOM",
|
||||
"is_active": 1,
|
||||
"is_default": 1,
|
||||
"item": "_Test FG Item 2",
|
||||
],
|
||||
"docstatus": 1,
|
||||
"doctype": "BOM",
|
||||
"is_active": 1,
|
||||
"is_default": 1,
|
||||
"item": "_Test FG Item 2",
|
||||
"quantity": 1.0,
|
||||
"with_operations": 1
|
||||
},
|
||||
{
|
||||
"bom_operations": [
|
||||
{
|
||||
"operation_no": "1",
|
||||
"opn_description": "_Test",
|
||||
"workstation": "_Test Workstation 1",
|
||||
"time_in_min": 60,
|
||||
"operating_cost": 140
|
||||
}
|
||||
],
|
||||
"bom_materials": [
|
||||
{
|
||||
"operation_no": 1,
|
||||
"amount": 5000.0,
|
||||
"doctype": "BOM Item",
|
||||
"item_code": "_Test Item",
|
||||
"parentfield": "bom_materials",
|
||||
"qty": 2.0,
|
||||
"rate": 3000.0,
|
||||
"stock_uom": "_Test UOM"
|
||||
}
|
||||
],
|
||||
"docstatus": 1,
|
||||
"doctype": "BOM",
|
||||
"is_active": 1,
|
||||
"is_default": 1,
|
||||
"item": "_Test Variant Item",
|
||||
"quantity": 1.0,
|
||||
"with_operations": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
|
@ -7,12 +7,12 @@ import frappe
|
||||
from frappe.utils import flt, nowdate
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
|
||||
class OverProductionError(frappe.ValidationError): pass
|
||||
class StockOverProductionError(frappe.ValidationError): pass
|
||||
|
||||
class ProductionOrder(Document):
|
||||
|
||||
def validate(self):
|
||||
if self.docstatus == 0:
|
||||
self.status = "Draft"
|
||||
@ -21,7 +21,9 @@ class ProductionOrder(Document):
|
||||
validate_status(self.status, ["Draft", "Submitted", "Stopped",
|
||||
"In Process", "Completed", "Cancelled"])
|
||||
|
||||
self.validate_bom_no()
|
||||
if self.bom_no:
|
||||
validate_bom_no(self.production_item, self.bom_no)
|
||||
|
||||
self.validate_sales_order()
|
||||
self.validate_warehouse()
|
||||
self.set_fixed_cost()
|
||||
@ -29,14 +31,6 @@ class ProductionOrder(Document):
|
||||
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
||||
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
|
||||
|
||||
def validate_bom_no(self):
|
||||
if self.bom_no:
|
||||
bom = frappe.db.sql("""select name from `tabBOM` where name=%s and docstatus=1
|
||||
and is_active=1 and item=%s"""
|
||||
, (self.bom_no, self.production_item), as_dict =1)
|
||||
if not bom:
|
||||
frappe.throw(_("BOM {0} is not active or not submitted").format(self.bom_no))
|
||||
|
||||
def validate_sales_order(self):
|
||||
if self.sales_order:
|
||||
so = frappe.db.sql("""select name, delivery_date from `tabSales Order`
|
||||
|
@ -264,6 +264,7 @@
|
||||
"is_sales_item": "Yes",
|
||||
"is_service_item": "No",
|
||||
"is_stock_item": "Yes",
|
||||
"is_manufactured_item": "Yes",
|
||||
"is_sub_contracted_item": "Yes",
|
||||
"item_code": "_Test Variant Item",
|
||||
"item_group": "_Test Item Group Desktops",
|
||||
|
@ -12,6 +12,7 @@ from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.stock_ledger import get_previous_sle
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
from erpnext.stock.get_item_details import get_available_qty, get_default_cost_center
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
|
||||
class NotUpdateStockError(frappe.ValidationError): pass
|
||||
class StockOverReturnError(frappe.ValidationError): pass
|
||||
@ -293,10 +294,8 @@ class StockEntry(StockController):
|
||||
|
||||
def validate_bom(self):
|
||||
for d in self.get('mtn_details'):
|
||||
if d.bom_no and not frappe.db.sql("""select name from `tabBOM`
|
||||
where item = %s and name = %s and docstatus = 1 and is_active = 1""",
|
||||
(d.item_code, d.bom_no)):
|
||||
frappe.throw(_("BOM {0} is not submitted or inactive BOM for Item {1}").format(d.bom_no, d.item_code))
|
||||
if d.bom_no:
|
||||
validate_bom_no(d.item_code, d.bom_no)
|
||||
|
||||
def validate_finished_goods(self):
|
||||
"""validation: finished good quantity should be same as manufacturing quantity"""
|
||||
@ -497,13 +496,20 @@ class StockEntry(StockController):
|
||||
# add raw materials to Stock Entry Detail table
|
||||
self.add_to_stock_entry_detail(item_dict)
|
||||
|
||||
# add finished good item to Stock Entry Detail table -- along with bom_no
|
||||
if self.production_order and self.purpose == "Manufacture":
|
||||
item = frappe.db.get_value("Item", pro_obj.production_item, ["item_name",
|
||||
"description", "stock_uom", "expense_account", "buying_cost_center"], as_dict=1)
|
||||
if self.bom_no:
|
||||
if self.production_order:
|
||||
item_code = pro_obj.production_item
|
||||
to_warehouse = pro_obj.fg_warehouse
|
||||
else:
|
||||
item_code = frappe.db.get_value("BOM", self.bom_no, "item")
|
||||
to_warehouse = ""
|
||||
|
||||
item = frappe.db.get_value("Item", item_code, ["item_name",
|
||||
"description", "stock_uom", "expense_account", "buying_cost_center", "name"], as_dict=1)
|
||||
|
||||
self.add_to_stock_entry_detail({
|
||||
cstr(pro_obj.production_item): {
|
||||
"to_warehouse": pro_obj.fg_warehouse,
|
||||
item.name: {
|
||||
"to_warehouse": to_warehouse,
|
||||
"from_warehouse": "",
|
||||
"qty": self.fg_completed_qty,
|
||||
"item_name": item.item_name,
|
||||
@ -512,27 +518,7 @@ class StockEntry(StockController):
|
||||
"expense_account": item.expense_account,
|
||||
"cost_center": item.buying_cost_center,
|
||||
}
|
||||
}, bom_no=pro_obj.bom_no)
|
||||
|
||||
elif self.purpose in ["Material Receipt", "Repack"]:
|
||||
if self.purpose=="Material Receipt":
|
||||
self.from_warehouse = ""
|
||||
|
||||
item = frappe.db.sql("""select name, item_name, description,
|
||||
stock_uom, expense_account, buying_cost_center from `tabItem`
|
||||
where name=(select item from tabBOM where name=%s)""",
|
||||
self.bom_no, as_dict=1)
|
||||
self.add_to_stock_entry_detail({
|
||||
item[0]["name"] : {
|
||||
"qty": self.fg_completed_qty,
|
||||
"item_name": item[0].item_name,
|
||||
"description": item[0]["description"],
|
||||
"stock_uom": item[0]["stock_uom"],
|
||||
"from_warehouse": "",
|
||||
"expense_account": item[0].expense_account,
|
||||
"cost_center": item[0].buying_cost_center,
|
||||
}
|
||||
}, bom_no=self.bom_no)
|
||||
}, bom_no = self.bom_no)
|
||||
|
||||
self.get_stock_and_rate()
|
||||
|
||||
|
@ -858,6 +858,29 @@ class TestStockEntry(unittest.TestCase):
|
||||
fg_rate = [d.amount for d in stock_entry.get("mtn_details") if d.item_code=="_Test Item"][0]
|
||||
self.assertEqual(fg_rate, 100.00)
|
||||
|
||||
def test_variant_production_order(self):
|
||||
bom_no = frappe.db.get_value("BOM", {"item": "_Test Variant Item",
|
||||
"is_default": 1, "docstatus": 1})
|
||||
|
||||
production_order = frappe.new_doc("Production Order")
|
||||
production_order.update({
|
||||
"company": "_Test Company",
|
||||
"fg_warehouse": "_Test Warehouse 1 - _TC",
|
||||
"production_item": "_Test Variant Item-S",
|
||||
"bom_no": bom_no,
|
||||
"qty": 1.0,
|
||||
"stock_uom": "Nos",
|
||||
"wip_warehouse": "_Test Warehouse - _TC"
|
||||
})
|
||||
production_order.insert()
|
||||
production_order.submit()
|
||||
|
||||
from erpnext.manufacturing.doctype.production_order.production_order import make_stock_entry
|
||||
|
||||
stock_entry = frappe.get_doc(make_stock_entry(production_order.name, "Manufacture", 1))
|
||||
stock_entry.insert()
|
||||
self.assertTrue("_Test Variant Item-S" in [d.item_code for d in stock_entry.mtn_details])
|
||||
|
||||
def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
|
||||
se = frappe.copy_doc(test_records[0])
|
||||
se.get("mtn_details")[0].item_code = item_code or "_Test Serialized Item With Series"
|
||||
|
Loading…
x
Reference in New Issue
Block a user