[item-variants] allow production order of variant #2224

This commit is contained in:
Rushabh Mehta 2014-10-07 12:34:19 +05:30
parent 1a7637459c
commit baef96b5a2
7 changed files with 154 additions and 126 deletions

View File

@ -1,6 +0,0 @@
[
{
"doctype": "Pricing Rule",
"name": "_Test Pricing Rule 1"
}
]

View File

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

View File

@ -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
}
]
]

View File

@ -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`

View File

@ -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",

View File

@ -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()

View File

@ -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"