- added barcode validation against Item Barcode

This commit is contained in:
Giovanni
2017-12-10 18:11:48 +01:00
parent 2144e02d3c
commit 9b7e52cafb

View File

@@ -2,23 +2,36 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe
import erpnext
import json
import itertools
from frappe import msgprint, _
from frappe.utils import (cstr, flt, cint, getdate, now_datetime, formatdate,
strip, get_timestamp, random_string)
from frappe.utils.html_utils import clean_html
from frappe.website.website_generator import WebsiteGenerator
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.doctype.website_slideshow.website_slideshow import get_slideshow
from erpnext.controllers.item_variant import (get_variant, copy_attributes_to_variant,
make_variant_item_code, validate_item_variant_attributes, ItemVariantExistsError)
class DuplicateReorderRows(frappe.ValidationError): pass import itertools
class StockExistsForTemplate(frappe.ValidationError): pass import json
import erpnext
import frappe
from erpnext.controllers.item_variant import (ItemVariantExistsError,
copy_attributes_to_variant,
get_variant,
make_variant_item_code,
validate_item_variant_attributes)
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups,
invalidate_cache_for)
from frappe import _, msgprint
from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
now_datetime, random_string, strip)
from frappe.utils.html_utils import clean_html
from frappe.website.doctype.website_slideshow.website_slideshow import \
get_slideshow
from frappe.website.render import clear_cache
from frappe.website.website_generator import WebsiteGenerator
class DuplicateReorderRows(frappe.ValidationError):
pass
class StockExistsForTemplate(frappe.ValidationError):
pass
class Item(WebsiteGenerator): class Item(WebsiteGenerator):
website = frappe._dict( website = frappe._dict(
@@ -85,7 +98,7 @@ class Item(WebsiteGenerator):
self.check_for_active_boms() self.check_for_active_boms()
self.fill_customer_code() self.fill_customer_code()
self.check_item_tax() self.check_item_tax()
# self.validate_barcode() self.validate_barcode()
self.validate_warehouse_for_reorder() self.validate_warehouse_for_reorder()
self.update_bom_item_desc() self.update_bom_item_desc()
self.synced_with_hub = 0 self.synced_with_hub = 0
@@ -176,7 +189,6 @@ class Item(WebsiteGenerator):
"file_url": self.website_image "file_url": self.website_image
}, fields=["name", "is_private"], order_by="is_private asc", limit_page_length=1) }, fields=["name", "is_private"], order_by="is_private asc", limit_page_length=1)
if file_doc: if file_doc:
file_doc = file_doc[0] file_doc = file_doc[0]
@@ -219,7 +231,8 @@ class Item(WebsiteGenerator):
self.website_image = None self.website_image = None
except requests.exceptions.SSLError: except requests.exceptions.SSLError:
frappe.msgprint(_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image)) frappe.msgprint(
_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
self.website_image = None self.website_image = None
# for CSV import # for CSV import
@@ -259,9 +272,10 @@ class Item(WebsiteGenerator):
def validate_retain_sample(self): def validate_retain_sample(self):
if self.retain_sample and not frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse'): if self.retain_sample and not frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse'):
frappe.throw(_("Please select Sample Retention Warehouse in Stock Settings first")); frappe.throw(_("Please select Sample Retention Warehouse in Stock Settings first"))
if self.retain_sample and not self.has_batch_no: if self.retain_sample and not self.has_batch_no:
frappe.throw(_(" {0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(self.item_code)) frappe.throw(_(" {0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(
self.item_code))
def get_context(self, context): def get_context(self, context):
context.show_search = True context.show_search = True
@@ -422,12 +436,14 @@ class Item(WebsiteGenerator):
check_list = [] check_list = []
for d in self.get('uoms'): for d in self.get('uoms'):
if cstr(d.uom) in check_list: if cstr(d.uom) in check_list:
frappe.throw(_("Unit of Measure {0} has been entered more than once in Conversion Factor Table").format(d.uom)) frappe.throw(
_("Unit of Measure {0} has been entered more than once in Conversion Factor Table").format(d.uom))
else: else:
check_list.append(cstr(d.uom)) check_list.append(cstr(d.uom))
if d.uom and cstr(d.uom) == cstr(self.stock_uom) and flt(d.conversion_factor) != 1: if d.uom and cstr(d.uom) == cstr(self.stock_uom) and flt(d.conversion_factor) != 1:
frappe.throw(_("Conversion factor for default Unit of Measure must be 1 in row {0}").format(d.idx)) frappe.throw(
_("Conversion factor for default Unit of Measure must be 1 in row {0}").format(d.idx))
def validate_item_type(self): def validate_item_type(self):
if self.has_serial_no == 1 and self.is_stock_item == 0: if self.has_serial_no == 1 and self.is_stock_item == 0:
@@ -436,12 +452,12 @@ class Item(WebsiteGenerator):
if self.has_serial_no == 0 and self.serial_no_series: if self.has_serial_no == 0 and self.serial_no_series:
self.serial_no_series = None self.serial_no_series = None
def check_for_active_boms(self): def check_for_active_boms(self):
if self.default_bom: if self.default_bom:
bom_item = frappe.db.get_value("BOM", self.default_bom, "item") bom_item = frappe.db.get_value("BOM", self.default_bom, "item")
if bom_item not in (self.name, self.variant_of): if bom_item not in (self.name, self.variant_of):
frappe.throw(_("Default BOM ({0}) must be active for this item or its template").format(bom_item)) frappe.throw(
_("Default BOM ({0}) must be active for this item or its template").format(bom_item))
def fill_customer_code(self): def fill_customer_code(self):
""" Append all the customer codes and insert into "customer_code" field of item table """ """ Append all the customer codes and insert into "customer_code" field of item table """
@@ -458,13 +474,22 @@ class Item(WebsiteGenerator):
account_type = frappe.db.get_value("Account", d.tax_type, "account_type") account_type = frappe.db.get_value("Account", d.tax_type, "account_type")
if account_type not in ['Tax', 'Chargeable', 'Income Account', 'Expense Account']: if account_type not in ['Tax', 'Chargeable', 'Income Account', 'Expense Account']:
frappe.throw(_("Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable").format(d.idx)) frappe.throw(
_("Item Tax Row {0} must have account of type Tax or Income or Expense or Chargeable").format(d.idx))
else: else:
if d.tax_type in check_list: if d.tax_type in check_list:
frappe.throw(_("{0} entered twice in Item Tax").format(d.tax_type)) frappe.throw(_("{0} entered twice in Item Tax").format(d.tax_type))
else: else:
check_list.append(d.tax_type) check_list.append(d.tax_type)
def validate_barcode(self):
if len(self.barcodes) > 0:
for barcode in self.barcodes:
duplicate = frappe.db.sql("""select name from `tabItem Barcode` where barcode = %s and parent != %s""", (barcode, self.name))
if duplicate:
frappe.throw(_("Barcode {0} already used in Item {1}").format(
self.barcode, duplicate[0][0]))
def validate_warehouse_for_reorder(self): def validate_warehouse_for_reorder(self):
'''Validate Reorder level table for duplicate and conditional mandatory''' '''Validate Reorder level table for duplicate and conditional mandatory'''
warehouse = [] warehouse = []
@@ -489,7 +514,8 @@ class Item(WebsiteGenerator):
def validate_name_with_item_group(self): def validate_name_with_item_group(self):
# causes problem with tree build # causes problem with tree build
if frappe.db.exists("Item Group", self.name): if frappe.db.exists("Item Group", self.name):
frappe.throw(_("An Item Group exists with same name, please change the item name or rename the item group")) frappe.throw(
_("An Item Group exists with same name, please change the item name or rename the item group"))
def update_item_price(self): def update_item_price(self):
frappe.db.sql("""update `tabItem Price` set item_name=%s, frappe.db.sql("""update `tabItem Price` set item_name=%s,
@@ -573,7 +599,8 @@ class Item(WebsiteGenerator):
row.description = desc row.description = desc
def update_bom_item_desc(self): def update_bom_item_desc(self):
if self.is_new(): return if self.is_new():
return
if self.db_get('description') != self.description: if self.db_get('description') != self.description:
frappe.db.sql(""" frappe.db.sql("""
@@ -639,7 +666,8 @@ class Item(WebsiteGenerator):
if self.has_variants or self.variant_of: if self.has_variants or self.variant_of:
if not self.is_child_table_same('attributes'): if not self.is_child_table_same('attributes'):
frappe.throw(_('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item')) frappe.throw(
_('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item'))
def validate_uom(self): def validate_uom(self):
if not self.get("__islocal"): if not self.get("__islocal"):
@@ -660,7 +688,8 @@ class Item(WebsiteGenerator):
frappe.throw(_("Attribute table is mandatory")) frappe.throw(_("Attribute table is mandatory"))
for d in self.attributes: for d in self.attributes:
if d.attribute in attributes: if d.attribute in attributes:
frappe.throw(_("Attribute {0} selected multiple times in Attributes Table".format(d.attribute))) frappe.throw(
_("Attribute {0} selected multiple times in Attributes Table".format(d.attribute)))
else: else:
attributes.append(d.attribute) attributes.append(d.attribute)
@@ -679,6 +708,7 @@ class Item(WebsiteGenerator):
validate_item_variant_attributes(self, args) validate_item_variant_attributes(self, args)
def get_timeline_data(doctype, name): def get_timeline_data(doctype, name):
'''returns timeline data based on stock ledger entry''' '''returns timeline data based on stock ledger entry'''
out = {} out = {}
@@ -693,6 +723,7 @@ def get_timeline_data(doctype, name):
return out return out
def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1): def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1):
if (not end_of_life) or (disabled is None): if (not end_of_life) or (disabled is None):
end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"]) end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"])
@@ -704,6 +735,7 @@ def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1):
if disabled: if disabled:
_msgprint(_("Item {0} is disabled").format(item_code), verbose) _msgprint(_("Item {0} is disabled").format(item_code), verbose)
def validate_is_stock_item(item_code, is_stock_item=None, verbose=1): def validate_is_stock_item(item_code, is_stock_item=None, verbose=1):
if not is_stock_item: if not is_stock_item:
is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item") is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item")
@@ -713,6 +745,7 @@ def validate_is_stock_item(item_code, is_stock_item=None, verbose=1):
_msgprint(msg, verbose) _msgprint(msg, verbose)
def validate_cancelled_item(item_code, docstatus=None, verbose=1): def validate_cancelled_item(item_code, docstatus=None, verbose=1):
if docstatus is None: if docstatus is None:
docstatus = frappe.db.get_value("Item", item_code, "docstatus") docstatus = frappe.db.get_value("Item", item_code, "docstatus")
@@ -721,6 +754,7 @@ def validate_cancelled_item(item_code, docstatus=None, verbose=1):
msg = _("Item {0} is cancelled").format(item_code) msg = _("Item {0} is cancelled").format(item_code)
_msgprint(msg, verbose) _msgprint(msg, verbose)
def _msgprint(msg, verbose): def _msgprint(msg, verbose):
if verbose: if verbose:
msgprint(msg, raise_exception=True) msgprint(msg, raise_exception=True)
@@ -752,9 +786,9 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
order by pr.posting_date desc, pr.posting_time desc, pr.name desc order by pr.posting_date desc, pr.posting_time desc, pr.name desc
limit 1""", (item_code, cstr(doc_name)), as_dict=1) limit 1""", (item_code, cstr(doc_name)), as_dict=1)
purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date \ purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
or "1900-01-01") or "1900-01-01")
purchase_receipt_date = getdate(last_purchase_receipt and \ purchase_receipt_date = getdate(last_purchase_receipt and
last_purchase_receipt[0].posting_date or "1900-01-01") last_purchase_receipt[0].posting_date or "1900-01-01")
if (purchase_order_date > purchase_receipt_date) or \ if (purchase_order_date > purchase_receipt_date) or \
@@ -789,6 +823,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
return out return out
def invalidate_cache_for_item(doc): def invalidate_cache_for_item(doc):
invalidate_cache_for(doc, doc.item_group) invalidate_cache_for(doc, doc.item_group)
@@ -801,6 +836,7 @@ def invalidate_cache_for_item(doc):
if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group: if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group:
invalidate_cache_for(doc, doc.old_item_group) invalidate_cache_for(doc, doc.old_item_group)
def check_stock_uom_with_bin(item, stock_uom): def check_stock_uom_with_bin(item, stock_uom):
if stock_uom == frappe.db.get_value("Item", item, "stock_uom"): if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
return return
@@ -815,7 +851,7 @@ def check_stock_uom_with_bin(item, stock_uom):
else: else:
bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1) bin_list = frappe.db.sql("select * from tabBin where item_code=%s", item, as_dict=1)
for bin in bin_list: for bin in bin_list:
if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \ if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0
or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom): or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(stock_uom):
matched = False matched = False
break break
@@ -824,4 +860,5 @@ def check_stock_uom_with_bin(item, stock_uom):
frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item)) frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item))
if not matched: if not matched:
frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item)) frappe.throw(
_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item))