[item-variants] added more validations, tests #2224
This commit is contained in:
parent
ef276045c4
commit
1a7637459c
@ -207,6 +207,12 @@ class BOM(Document):
|
|||||||
|
|
||||||
def validate_bom_no(self, item, bom_no, idx):
|
def validate_bom_no(self, item, bom_no, idx):
|
||||||
"""Validate BOM No of sub-contracted items"""
|
"""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
|
bom = frappe.db.sql("""select name from `tabBOM` where name = %s and item = %s
|
||||||
and is_active=1 and docstatus=1""",
|
and is_active=1 and docstatus=1""",
|
||||||
(bom_no, item), as_dict =1)
|
(bom_no, item), as_dict =1)
|
||||||
|
|||||||
@ -9,6 +9,14 @@ cur_frm.cscript.refresh = function(doc) {
|
|||||||
|
|
||||||
cur_frm.cscript.make_dashboard();
|
cur_frm.cscript.make_dashboard();
|
||||||
|
|
||||||
|
cur_frm.set_intro();
|
||||||
|
if (cur_frm.doc.has_variants) {
|
||||||
|
cur_frm.set_intro(__("This Item is a Template and cannot be used in transactions. Item attributes will be copied over into the variants unless 'No Copy' is set"));
|
||||||
|
}
|
||||||
|
if (cur_frm.doc.variant_of) {
|
||||||
|
cur_frm.set_intro(__("This Item is a Variant of {0} (Template). Attributes will be copied over from the template unless 'No Copy' is set", [cur_frm.doc.variant_of]));
|
||||||
|
}
|
||||||
|
|
||||||
if (frappe.defaults.get_default("item_naming_by")!="Naming Series") {
|
if (frappe.defaults.get_default("item_naming_by")!="Naming Series") {
|
||||||
cur_frm.toggle_display("naming_series", false);
|
cur_frm.toggle_display("naming_series", false);
|
||||||
} else {
|
} else {
|
||||||
@ -154,7 +162,9 @@ cur_frm.cscript.add_image = function(doc, dt, dn) {
|
|||||||
doc.description_html = repl('<table style="width: 100%; table-layout: fixed;">' +
|
doc.description_html = repl('<table style="width: 100%; table-layout: fixed;">' +
|
||||||
'<tr><td style="width:110px"><img src="%(imgurl)s" width="100px"></td>' +
|
'<tr><td style="width:110px"><img src="%(imgurl)s" width="100px"></td>' +
|
||||||
'<td>%(desc)s</td></tr>' +
|
'<td>%(desc)s</td></tr>' +
|
||||||
'</table>', {imgurl: frappe.utils.get_file_link(doc.image), desc:doc.description});
|
'</table>', {
|
||||||
|
imgurl: frappe.utils.get_file_link(doc.image),
|
||||||
|
desc: doc.description.replace(/\n/g, "<br>")});
|
||||||
|
|
||||||
refresh_field('description_html');
|
refresh_field('description_html');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,13 +42,15 @@
|
|||||||
"search_index": 0
|
"search_index": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "variant_of",
|
||||||
"description": "If item is a variant of another item then description, image, pricing, taxes etc will be set from the template unless explicitly specified",
|
"description": "If item is a variant of another item then description, image, pricing, taxes etc will be set from the template unless explicitly specified",
|
||||||
"fieldname": "variant_of",
|
"fieldname": "variant_of",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Variant Of",
|
"label": "Variant Of",
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"precision": ""
|
"precision": "",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "item_name",
|
"fieldname": "item_name",
|
||||||
@ -860,7 +862,7 @@
|
|||||||
"icon": "icon-tag",
|
"icon": "icon-tag",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"max_attachments": 1,
|
"max_attachments": 1,
|
||||||
"modified": "2014-09-30 14:34:50.101879",
|
"modified": "2014-10-03 04:58:39.278047",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
|
|||||||
@ -9,9 +9,11 @@ from frappe.website.website_generator import WebsiteGenerator
|
|||||||
from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for, get_parent_item_groups
|
from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for, get_parent_item_groups
|
||||||
from frappe.website.render import clear_cache
|
from frappe.website.render import clear_cache
|
||||||
from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
|
from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
|
||||||
|
import copy
|
||||||
|
|
||||||
class WarehouseNotSet(frappe.ValidationError): pass
|
class WarehouseNotSet(frappe.ValidationError): pass
|
||||||
class DuplicateVariant(frappe.ValidationError): pass
|
class DuplicateVariant(frappe.ValidationError): pass
|
||||||
|
class ItemTemplateCannotHaveStock(frappe.ValidationError): pass
|
||||||
|
|
||||||
class Item(WebsiteGenerator):
|
class Item(WebsiteGenerator):
|
||||||
page_title_field = "item_name"
|
page_title_field = "item_name"
|
||||||
@ -24,7 +26,7 @@ class Item(WebsiteGenerator):
|
|||||||
self.get("__onload").sle_exists = self.check_if_sle_exists()
|
self.get("__onload").sle_exists = self.check_if_sle_exists()
|
||||||
|
|
||||||
def autoname(self):
|
def autoname(self):
|
||||||
if frappe.db.get_default("item_naming_by")=="Naming Series":
|
if frappe.db.get_default("item_naming_by")=="Naming Series" and not self.variant_of:
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
self.item_code = make_autoname(self.naming_series+'.#####')
|
self.item_code = make_autoname(self.naming_series+'.#####')
|
||||||
elif not self.item_code:
|
elif not self.item_code:
|
||||||
@ -51,7 +53,7 @@ class Item(WebsiteGenerator):
|
|||||||
self.validate_barcode()
|
self.validate_barcode()
|
||||||
self.cant_change()
|
self.cant_change()
|
||||||
self.validate_item_type_for_reorder()
|
self.validate_item_type_for_reorder()
|
||||||
self.validate_variants_are_unique()
|
self.validate_variants()
|
||||||
|
|
||||||
if not self.get("__islocal"):
|
if not self.get("__islocal"):
|
||||||
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
|
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
|
||||||
@ -117,6 +119,18 @@ class Item(WebsiteGenerator):
|
|||||||
if not matched:
|
if not matched:
|
||||||
frappe.throw(_("Default Unit of Measure can not be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module."))
|
frappe.throw(_("Default Unit of Measure can not be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module."))
|
||||||
|
|
||||||
|
def validate_variants(self):
|
||||||
|
self.validate_variants_are_unique()
|
||||||
|
self.validate_stock_for_template_must_be_zero()
|
||||||
|
|
||||||
|
def validate_stock_for_template_must_be_zero(self):
|
||||||
|
if self.has_variants:
|
||||||
|
stock_in = frappe.db.sql_list("""select warehouse from tabBin
|
||||||
|
where item_code=%s and ifnull(actual_qty, 0) > 0""", self.name)
|
||||||
|
if stock_in:
|
||||||
|
frappe.throw(_("Item Template cannot have stock and varaiants. Please remove stock from warehouses {0}").format(", ".join(stock_in)),
|
||||||
|
ItemTemplateCannotHaveStock)
|
||||||
|
|
||||||
def validate_variants_are_unique(self):
|
def validate_variants_are_unique(self):
|
||||||
if not self.has_variants:
|
if not self.has_variants:
|
||||||
self.item_variants = []
|
self.item_variants = []
|
||||||
@ -167,6 +181,7 @@ class Item(WebsiteGenerator):
|
|||||||
if not self.item_variants:
|
if not self.item_variants:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
self.variant_attributes = {}
|
||||||
variant_dict = {}
|
variant_dict = {}
|
||||||
variant_item_codes = []
|
variant_item_codes = []
|
||||||
|
|
||||||
@ -178,31 +193,34 @@ class Item(WebsiteGenerator):
|
|||||||
# sort attributes by their priority
|
# sort attributes by their priority
|
||||||
attributes = filter(None, map(lambda d: d if d in variant_dict else None, all_attributes))
|
attributes = filter(None, map(lambda d: d if d in variant_dict else None, all_attributes))
|
||||||
|
|
||||||
def add_attribute_suffixes(item_code, attributes):
|
def add_attribute_suffixes(item_code, my_attributes, attributes):
|
||||||
attr = frappe.get_doc("Item Attribute", attributes[0])
|
attr = frappe.get_doc("Item Attribute", attributes[0])
|
||||||
for value in attr.item_attribute_values:
|
for value in attr.item_attribute_values:
|
||||||
if value.attribute_value in variant_dict[attr.name]:
|
if value.attribute_value in variant_dict[attr.name]:
|
||||||
|
_my_attributes = copy.deepcopy(my_attributes)
|
||||||
|
_my_attributes.append([attr.name, value.attribute_value])
|
||||||
if len(attributes) > 1:
|
if len(attributes) > 1:
|
||||||
add_attribute_suffixes(item_code + "-" + value.abbr, attributes[1:])
|
add_attribute_suffixes(item_code + "-" + value.abbr, _my_attributes, attributes[1:])
|
||||||
else:
|
else:
|
||||||
variant_item_codes.append(item_code + "-" + value.abbr)
|
variant_item_codes.append(item_code + "-" + value.abbr)
|
||||||
|
self.variant_attributes[item_code + "-" + value.abbr] = _my_attributes
|
||||||
|
|
||||||
add_attribute_suffixes(self.name, attributes)
|
add_attribute_suffixes(self.name, [], attributes)
|
||||||
|
|
||||||
return variant_item_codes
|
return variant_item_codes
|
||||||
|
|
||||||
def make_variant(self, item_code):
|
def make_variant(self, item_code):
|
||||||
item = frappe.new_doc("Item")
|
item = frappe.new_doc("Item")
|
||||||
self.copy_attributes_to_variant(item, insert=True)
|
self.copy_attributes_to_variant(item, item_code, insert=True)
|
||||||
item.item_code = item_code
|
item.item_code = item_code
|
||||||
item.insert()
|
item.insert()
|
||||||
|
|
||||||
def update_variant(self, item_code):
|
def update_variant(self, item_code):
|
||||||
item = frappe.get_doc("Item", item_code)
|
item = frappe.get_doc("Item", item_code)
|
||||||
self.copy_attributes_to_variant(item)
|
self.copy_attributes_to_variant(item, item_code)
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
def copy_attributes_to_variant(self, variant, insert=False):
|
def copy_attributes_to_variant(self, variant, item_code, insert=False):
|
||||||
from frappe.model import no_value_fields
|
from frappe.model import no_value_fields
|
||||||
for field in self.meta.fields:
|
for field in self.meta.fields:
|
||||||
if field.fieldtype not in no_value_fields and (insert or not field.no_copy):
|
if field.fieldtype not in no_value_fields and (insert or not field.no_copy):
|
||||||
@ -210,6 +228,11 @@ class Item(WebsiteGenerator):
|
|||||||
variant.set(field.fieldname, self.get(field.fieldname))
|
variant.set(field.fieldname, self.get(field.fieldname))
|
||||||
variant.__dirty = True
|
variant.__dirty = True
|
||||||
|
|
||||||
|
variant.description += "\n"
|
||||||
|
for attr in self.variant_attributes[item_code]:
|
||||||
|
variant.description += "\n" + attr[0] + ": " + attr[1]
|
||||||
|
if variant.description_html:
|
||||||
|
variant.description_html += "<div style='margin-top: 4px; font-size: 80%'>" + attr[0] + ": " + attr[1] + "</div>"
|
||||||
variant.variant_of = self.name
|
variant.variant_of = self.name
|
||||||
variant.has_variants = 0
|
variant.has_variants = 0
|
||||||
variant.show_in_website = 0
|
variant.show_in_website = 0
|
||||||
|
|||||||
@ -6,21 +6,47 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
from frappe.test_runner import make_test_records
|
from frappe.test_runner import make_test_records
|
||||||
from erpnext.stock.doctype.item.item import WarehouseNotSet, DuplicateVariant
|
from erpnext.stock.doctype.item.item import WarehouseNotSet, DuplicateVariant, ItemTemplateCannotHaveStock
|
||||||
|
|
||||||
test_ignore = ["BOM"]
|
test_ignore = ["BOM"]
|
||||||
test_dependencies = ["Warehouse"]
|
test_dependencies = ["Warehouse"]
|
||||||
|
|
||||||
class TestItem(unittest.TestCase):
|
class TestItem(unittest.TestCase):
|
||||||
|
def get_item(self, idx):
|
||||||
|
item_code = test_records[idx].get("item_code")
|
||||||
|
if not frappe.db.exists("Item", item_code):
|
||||||
|
item = frappe.copy_doc(test_records[idx])
|
||||||
|
item.insert()
|
||||||
|
else:
|
||||||
|
item = frappe.get_doc("Item", item_code)
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
def test_duplicate_variant(self):
|
def test_duplicate_variant(self):
|
||||||
item = frappe.copy_doc(test_records[11])
|
item = frappe.copy_doc(test_records[11])
|
||||||
item.append("item_variants", {"item_attribute": "Test Size", "item_attribute_value": "Small"})
|
item.append("item_variants", {"item_attribute": "Test Size", "item_attribute_value": "Small"})
|
||||||
self.assertRaises(DuplicateVariant, item.insert)
|
self.assertRaises(DuplicateVariant, item.insert)
|
||||||
|
|
||||||
|
def test_template_cannot_have_stock(self):
|
||||||
|
item = self.get_item(10)
|
||||||
|
|
||||||
|
se = frappe.new_doc("Stock Entry")
|
||||||
|
se.purpose = "Material Receipt"
|
||||||
|
se.append("mtn_details", {
|
||||||
|
"item_code": item.name,
|
||||||
|
"t_warehouse": "Stores - WP",
|
||||||
|
"qty": 1,
|
||||||
|
"incoming_rate": 1
|
||||||
|
})
|
||||||
|
se.insert()
|
||||||
|
se.submit()
|
||||||
|
|
||||||
|
item.has_variants = 1
|
||||||
|
self.assertRaises(ItemTemplateCannotHaveStock, item.save)
|
||||||
|
|
||||||
def test_variant_item_codes(self):
|
def test_variant_item_codes(self):
|
||||||
frappe.delete_doc("Item", test_records[11].get("item_code"))
|
item = self.get_item(11)
|
||||||
item = frappe.copy_doc(test_records[11])
|
|
||||||
item.insert()
|
|
||||||
variants = ['_Test Variant Item-S', '_Test Variant Item-M', '_Test Variant Item-L']
|
variants = ['_Test Variant Item-S', '_Test Variant Item-M', '_Test Variant Item-L']
|
||||||
self.assertEqual(item.get_variant_item_codes(), variants)
|
self.assertEqual(item.get_variant_item_codes(), variants)
|
||||||
for v in variants:
|
for v in variants:
|
||||||
@ -36,10 +62,23 @@ class TestItem(unittest.TestCase):
|
|||||||
'_Test Variant Item-M-B', '_Test Variant Item-L-R',
|
'_Test Variant Item-M-B', '_Test Variant Item-L-R',
|
||||||
'_Test Variant Item-L-G', '_Test Variant Item-L-B'])
|
'_Test Variant Item-L-G', '_Test Variant Item-L-B'])
|
||||||
|
|
||||||
def test_item_creation(self):
|
self.assertEqual(item.variant_attributes['_Test Variant Item-L-R'], [['Test Size', 'Large'], ['Test Colour', 'Red']])
|
||||||
frappe.delete_doc("Item", test_records[11].get("item_code"))
|
self.assertEqual(item.variant_attributes['_Test Variant Item-S-G'], [['Test Size', 'Small'], ['Test Colour', 'Green']])
|
||||||
item = frappe.copy_doc(test_records[11])
|
|
||||||
item.insert()
|
# check stock entry cannot be made
|
||||||
|
def test_stock_entry_cannot_be_made_for_template(self):
|
||||||
|
item = self.get_item(11)
|
||||||
|
|
||||||
|
se = frappe.new_doc("Stock Entry")
|
||||||
|
se.purpose = "Material Receipt"
|
||||||
|
se.append("mtn_details", {
|
||||||
|
"item_code": item.name,
|
||||||
|
"t_warehouse": "Stores - WP",
|
||||||
|
"qty": 1,
|
||||||
|
"incoming_rate": 1
|
||||||
|
})
|
||||||
|
se.insert()
|
||||||
|
self.assertRaises(ItemTemplateCannotHaveStock, se.submit)
|
||||||
|
|
||||||
def test_default_warehouse(self):
|
def test_default_warehouse(self):
|
||||||
item = frappe.copy_doc(test_records[0])
|
item = frappe.copy_doc(test_records[0])
|
||||||
@ -57,7 +96,7 @@ class TestItem(unittest.TestCase):
|
|||||||
"income_account": "Sales - _TC",
|
"income_account": "Sales - _TC",
|
||||||
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
"expense_account": "_Test Account Cost for Goods Sold - _TC",
|
||||||
"cost_center": "_Test Cost Center 2 - _TC",
|
"cost_center": "_Test Cost Center 2 - _TC",
|
||||||
"qty": 1.0,
|
"qty": 0.0,
|
||||||
"price_list_rate": 100.0,
|
"price_list_rate": 100.0,
|
||||||
"base_price_list_rate": 0.0,
|
"base_price_list_rate": 0.0,
|
||||||
"discount_percentage": 0.0,
|
"discount_percentage": 0.0,
|
||||||
|
|||||||
@ -11,7 +11,7 @@ from frappe import _
|
|||||||
from erpnext.stock.utils import get_incoming_rate
|
from erpnext.stock.utils import get_incoming_rate
|
||||||
from erpnext.stock.stock_ledger import get_previous_sle
|
from erpnext.stock.stock_ledger import get_previous_sle
|
||||||
from erpnext.controllers.queries import get_match_cond
|
from erpnext.controllers.queries import get_match_cond
|
||||||
from erpnext.stock.get_item_details import get_available_qty
|
from erpnext.stock.get_item_details import get_available_qty, get_default_cost_center
|
||||||
|
|
||||||
class NotUpdateStockError(frappe.ValidationError): pass
|
class NotUpdateStockError(frappe.ValidationError): pass
|
||||||
class StockOverReturnError(frappe.ValidationError): pass
|
class StockOverReturnError(frappe.ValidationError): pass
|
||||||
@ -42,8 +42,8 @@ class StockEntry(StockController):
|
|||||||
pro_obj = self.production_order and \
|
pro_obj = self.production_order and \
|
||||||
frappe.get_doc('Production Order', self.production_order) or None
|
frappe.get_doc('Production Order', self.production_order) or None
|
||||||
|
|
||||||
self.set_transfer_qty()
|
|
||||||
self.validate_item()
|
self.validate_item()
|
||||||
|
self.set_transfer_qty()
|
||||||
self.validate_uom_is_integer("uom", "qty")
|
self.validate_uom_is_integer("uom", "qty")
|
||||||
self.validate_uom_is_integer("stock_uom", "transfer_qty")
|
self.validate_uom_is_integer("stock_uom", "transfer_qty")
|
||||||
self.validate_warehouse(pro_obj)
|
self.validate_warehouse(pro_obj)
|
||||||
@ -96,21 +96,23 @@ class StockEntry(StockController):
|
|||||||
for item in self.get("mtn_details"):
|
for item in self.get("mtn_details"):
|
||||||
if item.item_code not in stock_items:
|
if item.item_code not in stock_items:
|
||||||
frappe.throw(_("{0} is not a stock Item").format(item.item_code))
|
frappe.throw(_("{0} is not a stock Item").format(item.item_code))
|
||||||
if not item.stock_uom:
|
|
||||||
item.stock_uom = frappe.db.get_value("Item", item.item_code, "stock_uom")
|
item_details = self.get_item_details(frappe._dict({"item_code": item.item_code,
|
||||||
if not item.uom:
|
"company": self.company, "project_name": self.project_name}))
|
||||||
item.uom = item.stock_uom
|
|
||||||
if not item.conversion_factor:
|
for f in ("uom", "stock_uom", "description", "item_name", "expense_account",
|
||||||
item.conversion_factor = 1
|
"cost_center", "conversion_factor"):
|
||||||
|
item.set(f, item_details.get(f))
|
||||||
|
|
||||||
if not item.transfer_qty:
|
if not item.transfer_qty:
|
||||||
item.transfer_qty = item.qty * item.conversion_factor
|
item.transfer_qty = item.qty * item.conversion_factor
|
||||||
|
|
||||||
if (self.purpose in ("Material Transfer", "Sales Return", "Purchase Return")
|
if (self.purpose in ("Material Transfer", "Sales Return", "Purchase Return")
|
||||||
and not item.serial_no
|
and not item.serial_no
|
||||||
and item.item_code in serialized_items):
|
and item.item_code in serialized_items):
|
||||||
frappe.throw(_("Row #{0}: Please specify Serial No for Item {1}").format(item.idx, item.item_code),
|
frappe.throw(_("Row #{0}: Please specify Serial No for Item {1}").format(item.idx, item.item_code),
|
||||||
frappe.MandatoryError)
|
frappe.MandatoryError)
|
||||||
|
|
||||||
|
|
||||||
def validate_warehouse(self, pro_obj):
|
def validate_warehouse(self, pro_obj):
|
||||||
"""perform various (sometimes conditional) validations on warehouse"""
|
"""perform various (sometimes conditional) validations on warehouse"""
|
||||||
|
|
||||||
@ -408,20 +410,21 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
def get_item_details(self, args):
|
def get_item_details(self, args):
|
||||||
item = frappe.db.sql("""select stock_uom, description, item_name,
|
item = frappe.db.sql("""select stock_uom, description, item_name,
|
||||||
expense_account, buying_cost_center from `tabItem`
|
expense_account, buying_cost_center, item_group from `tabItem`
|
||||||
where name = %s and (ifnull(end_of_life,'0000-00-00')='0000-00-00' or end_of_life > now())""",
|
where name = %s and (ifnull(end_of_life,'0000-00-00')='0000-00-00' or end_of_life > now())""",
|
||||||
(args.get('item_code')), as_dict = 1)
|
(args.get('item_code')), as_dict = 1)
|
||||||
if not item:
|
if not item:
|
||||||
frappe.throw(_("Item {0} is not active or end of life has been reached").format(args.get("item_code")))
|
frappe.throw(_("Item {0} is not active or end of life has been reached").format(args.get("item_code")))
|
||||||
|
item = item[0]
|
||||||
|
|
||||||
ret = {
|
ret = {
|
||||||
'uom' : item and item[0]['stock_uom'] or '',
|
'uom' : item.stock_uom,
|
||||||
'stock_uom' : item and item[0]['stock_uom'] or '',
|
'stock_uom' : item.stock_uom,
|
||||||
'description' : item and item[0]['description'] or '',
|
'description' : item.description,
|
||||||
'item_name' : item and item[0]['item_name'] or '',
|
'item_name' : item.item_name,
|
||||||
'expense_account' : args.get("expense_account") \
|
'expense_account' : args.get("expense_account") \
|
||||||
or frappe.db.get_value("Company", args.get("company"), "stock_adjustment_account"),
|
or frappe.db.get_value("Company", args.get("company"), "stock_adjustment_account"),
|
||||||
'cost_center' : item and item[0]['buying_cost_center'] or args.get("cost_center"),
|
'cost_center' : get_default_cost_center(args, item),
|
||||||
'qty' : 0,
|
'qty' : 0,
|
||||||
'transfer_qty' : 0,
|
'transfer_qty' : 0,
|
||||||
'conversion_factor' : 1,
|
'conversion_factor' : 1,
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from frappe import _
|
|||||||
from frappe.utils import flt, getdate, add_days, formatdate
|
from frappe.utils import flt, getdate, add_days, formatdate
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
from erpnext.stock.doctype.item.item import ItemTemplateCannotHaveStock
|
||||||
|
|
||||||
class StockFreezeError(frappe.ValidationError): pass
|
class StockFreezeError(frappe.ValidationError): pass
|
||||||
|
|
||||||
@ -50,7 +51,8 @@ class StockLedgerEntry(Document):
|
|||||||
frappe.throw(_("{0} is required").format(self.meta.get_label(k)))
|
frappe.throw(_("{0} is required").format(self.meta.get_label(k)))
|
||||||
|
|
||||||
def validate_item(self):
|
def validate_item(self):
|
||||||
item_det = frappe.db.sql("""select name, has_batch_no, docstatus, is_stock_item
|
item_det = frappe.db.sql("""select name, has_batch_no, docstatus,
|
||||||
|
is_stock_item, has_variants
|
||||||
from tabItem where name=%s""", self.item_code, as_dict=True)[0]
|
from tabItem where name=%s""", self.item_code, as_dict=True)[0]
|
||||||
|
|
||||||
if item_det.is_stock_item != 'Yes':
|
if item_det.is_stock_item != 'Yes':
|
||||||
@ -66,6 +68,10 @@ class StockLedgerEntry(Document):
|
|||||||
{"item": self.item_code, "name": self.batch_no}):
|
{"item": self.item_code, "name": self.batch_no}):
|
||||||
frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, self.item_code))
|
frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, self.item_code))
|
||||||
|
|
||||||
|
if item_det.has_variants:
|
||||||
|
frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code),
|
||||||
|
ItemTemplateCannotHaveStock)
|
||||||
|
|
||||||
if not self.stock_uom:
|
if not self.stock_uom:
|
||||||
self.stock_uom = item_det.stock_uom
|
self.stock_uom = item_det.stock_uom
|
||||||
|
|
||||||
|
|||||||
@ -40,7 +40,7 @@ def get_item_details(args):
|
|||||||
|
|
||||||
validate_item_details(args, item)
|
validate_item_details(args, item)
|
||||||
|
|
||||||
out = get_basic_details(args, item_doc)
|
out = get_basic_details(args, item)
|
||||||
|
|
||||||
get_party_item_code(args, item_doc, out)
|
get_party_item_code(args, item_doc, out)
|
||||||
|
|
||||||
@ -129,8 +129,9 @@ def validate_item_details(args, item):
|
|||||||
if args.get("is_subcontracted") == "Yes" and item.is_sub_contracted_item != "Yes":
|
if args.get("is_subcontracted") == "Yes" and item.is_sub_contracted_item != "Yes":
|
||||||
throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
|
throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
|
||||||
|
|
||||||
def get_basic_details(args, item_doc):
|
def get_basic_details(args, item):
|
||||||
item = item_doc
|
if not item:
|
||||||
|
item = frappe.get_doc("Item", args.get("item_code"))
|
||||||
|
|
||||||
from frappe.defaults import get_user_default_as_list
|
from frappe.defaults import get_user_default_as_list
|
||||||
user_default_warehouse_list = get_user_default_as_list('warehouse')
|
user_default_warehouse_list = get_user_default_as_list('warehouse')
|
||||||
@ -143,26 +144,17 @@ def get_basic_details(args, item_doc):
|
|||||||
"item_name": item.item_name,
|
"item_name": item.item_name,
|
||||||
"description": item.description_html or item.description,
|
"description": item.description_html or item.description,
|
||||||
"warehouse": user_default_warehouse or args.warehouse or item.default_warehouse,
|
"warehouse": user_default_warehouse or args.warehouse or item.default_warehouse,
|
||||||
"income_account": (item.income_account
|
"income_account": get_default_income_account(args, item),
|
||||||
or args.income_account
|
"expense_account": get_default_expense_account(args, item),
|
||||||
or frappe.db.get_value("Item Group", item.item_group, "default_income_account")
|
"cost_center": get_default_cost_center(args, item),
|
||||||
or frappe.db.get_value("Company", args.company, "default_income_account")),
|
|
||||||
"expense_account": (item.expense_account
|
|
||||||
or args.expense_account
|
|
||||||
or frappe.db.get_value("Item Group", item.item_group, "default_expense_account")
|
|
||||||
or frappe.db.get_value("Company", args.company, "default_expense_account")),
|
|
||||||
"cost_center": (frappe.db.get_value("Project", args.project_name, "cost_center")
|
|
||||||
or (item.selling_cost_center if args.transaction_type == "selling" else item.buying_cost_center)
|
|
||||||
or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
|
|
||||||
or frappe.db.get_value("Company", args.company, "cost_center")),
|
|
||||||
"batch_no": None,
|
"batch_no": None,
|
||||||
"item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in
|
"item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in
|
||||||
item_doc.get("item_tax")))),
|
item.get("item_tax")))),
|
||||||
"uom": item.stock_uom,
|
"uom": item.stock_uom,
|
||||||
"min_order_qty": flt(item.min_order_qty) if args.parenttype == "Material Request" else "",
|
"min_order_qty": flt(item.min_order_qty) if args.parenttype == "Material Request" else "",
|
||||||
"conversion_factor": 1.0,
|
"conversion_factor": 1.0,
|
||||||
"qty": 1.0,
|
"qty": 0.0,
|
||||||
"stock_qty": 1.0,
|
"stock_qty": 0.0,
|
||||||
"price_list_rate": 0.0,
|
"price_list_rate": 0.0,
|
||||||
"base_price_list_rate": 0.0,
|
"base_price_list_rate": 0.0,
|
||||||
"rate": 0.0,
|
"rate": 0.0,
|
||||||
@ -177,6 +169,24 @@ def get_basic_details(args, item_doc):
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def get_default_income_account(args, item):
|
||||||
|
return (item.income_account
|
||||||
|
or args.income_account
|
||||||
|
or frappe.db.get_value("Item Group", item.item_group, "default_income_account")
|
||||||
|
or frappe.db.get_value("Company", args.company, "default_income_account"))
|
||||||
|
|
||||||
|
def get_default_expense_account(args, item):
|
||||||
|
return (item.expense_account
|
||||||
|
or args.expense_account
|
||||||
|
or frappe.db.get_value("Item Group", item.item_group, "default_expense_account")
|
||||||
|
or frappe.db.get_value("Company", args.company, "default_expense_account"))
|
||||||
|
|
||||||
|
def get_default_cost_center(args, item):
|
||||||
|
return (frappe.db.get_value("Project", args.project_name, "cost_center")
|
||||||
|
or (item.selling_cost_center if args.transaction_type == "selling" else item.buying_cost_center)
|
||||||
|
or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
|
||||||
|
or frappe.db.get_value("Company", args.company, "cost_center"))
|
||||||
|
|
||||||
def get_price_list_rate(args, item_doc, out):
|
def get_price_list_rate(args, item_doc, out):
|
||||||
meta = frappe.get_meta(args.parenttype)
|
meta = frappe.get_meta(args.parenttype)
|
||||||
|
|
||||||
@ -185,10 +195,12 @@ def get_price_list_rate(args, item_doc, out):
|
|||||||
validate_conversion_rate(args, meta)
|
validate_conversion_rate(args, meta)
|
||||||
|
|
||||||
|
|
||||||
price_list_rate = frappe.db.get_value("Item Price",
|
price_list_rate = get_price_list_rate_for(args, item_doc.name)
|
||||||
{"price_list": args.price_list, "item_code": args.item_code}, "price_list_rate")
|
if not price_list_rate and item_doc.variant_of:
|
||||||
|
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
|
||||||
|
|
||||||
if not price_list_rate: return {}
|
if not price_list_rate:
|
||||||
|
return {}
|
||||||
|
|
||||||
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
|
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
|
||||||
/ flt(args.conversion_rate)
|
/ flt(args.conversion_rate)
|
||||||
@ -198,6 +210,10 @@ def get_price_list_rate(args, item_doc, out):
|
|||||||
out.update(get_last_purchase_details(item_doc.name,
|
out.update(get_last_purchase_details(item_doc.name,
|
||||||
args.parent, args.conversion_rate))
|
args.parent, args.conversion_rate))
|
||||||
|
|
||||||
|
def get_price_list_rate_for(args, item_code):
|
||||||
|
return frappe.db.get_value("Item Price",
|
||||||
|
{"price_list": args.price_list, "item_code": item_code}, "price_list_rate")
|
||||||
|
|
||||||
def validate_price_list(args):
|
def validate_price_list(args):
|
||||||
if args.get("price_list"):
|
if args.get("price_list"):
|
||||||
if not frappe.db.get_value("Price List",
|
if not frappe.db.get_value("Price List",
|
||||||
@ -236,7 +252,6 @@ def get_party_item_code(args, item_doc, out):
|
|||||||
item_supplier = item_doc.get("item_supplier_details", {"supplier": args.supplier})
|
item_supplier = item_doc.get("item_supplier_details", {"supplier": args.supplier})
|
||||||
out.supplier_part_no = item_supplier[0].supplier_part_no if item_supplier else None
|
out.supplier_part_no = item_supplier[0].supplier_part_no if item_supplier else None
|
||||||
|
|
||||||
|
|
||||||
def get_pos_settings_item_details(company, args, pos_settings=None):
|
def get_pos_settings_item_details(company, args, pos_settings=None):
|
||||||
res = frappe._dict()
|
res = frappe._dict()
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user