Fixed merge conflict

This commit is contained in:
Nabin Hait 2018-02-12 15:59:55 +05:30
commit e21e59614f
15 changed files with 443 additions and 181 deletions

View File

@ -2,23 +2,30 @@
# 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, json
from frappe import _ import json
from frappe.utils import nowdate
from erpnext.setup.utils import get_exchange_rate import frappe
from frappe.core.doctype.communication.email import make
from erpnext.stock.get_item_details import get_pos_profile
from erpnext.accounts.party import get_party_account_currency from erpnext.accounts.party import get_party_account_currency
from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.setup.utils import get_exchange_rate
from erpnext.stock.get_item_details import get_pos_profile
from frappe import _
from frappe.core.doctype.communication.email import make
from frappe.utils import nowdate
@frappe.whitelist() @frappe.whitelist()
def get_pos_data(): def get_pos_data():
doc = frappe.new_doc('Sales Invoice') doc = frappe.new_doc('Sales Invoice')
doc.is_pos = 1; doc.is_pos = 1
pos_profile = get_pos_profile(doc.company) or {} pos_profile = get_pos_profile(doc.company) or {}
if not pos_profile: if not pos_profile:
frappe.throw(_("POS Profile is required to use Point-of-Sale")) frappe.throw(_("POS Profile is required to use Point-of-Sale"))
if not doc.company: doc.company = pos_profile.get('company')
if not doc.company:
doc.company = pos_profile.get('company')
doc.update_stock = pos_profile.get('update_stock') doc.update_stock = pos_profile.get('update_stock')
if pos_profile.get('name'): if pos_profile.get('name'):
@ -30,18 +37,20 @@ def get_pos_data():
update_multi_mode_option(doc, pos_profile) update_multi_mode_option(doc, pos_profile)
default_print_format = pos_profile.get('print_format') or "Point of Sale" default_print_format = pos_profile.get('print_format') or "Point of Sale"
print_template = frappe.db.get_value('Print Format', default_print_format, 'html') print_template = frappe.db.get_value('Print Format', default_print_format, 'html')
items_list = get_items_list(pos_profile)
customers = get_customers_list(pos_profile) customers = get_customers_list(pos_profile)
return { return {
'doc': doc, 'doc': doc,
'default_customer': pos_profile.get('customer'), 'default_customer': pos_profile.get('customer'),
'items': get_items_list(pos_profile), 'items': items_list,
'item_groups': get_item_groups(pos_profile), 'item_groups': get_item_groups(pos_profile),
'customers': customers, 'customers': customers,
'address': get_customers_address(customers), 'address': get_customers_address(customers),
'contacts': get_contacts(customers), 'contacts': get_contacts(customers),
'serial_no_data': get_serial_no_data(pos_profile, doc.company), 'serial_no_data': get_serial_no_data(pos_profile, doc.company),
'batch_no_data': get_batch_no_data(), 'batch_no_data': get_batch_no_data(),
'barcode_data': get_barcode_data(items_list),
'tax_data': get_item_tax_data(), 'tax_data': get_item_tax_data(),
'price_list_data': get_price_list_data(doc.selling_price_list), 'price_list_data': get_price_list_data(doc.selling_price_list),
'bin_data': get_bin_data(pos_profile), 'bin_data': get_bin_data(pos_profile),
@ -51,20 +60,23 @@ def get_pos_data():
'meta': get_meta() 'meta': get_meta()
} }
def get_meta(): def get_meta():
doctype_meta = { doctype_meta = {
'customer': frappe.get_meta('Customer'), 'customer': frappe.get_meta('Customer'),
'invoice': frappe.get_meta('Sales Invoice') 'invoice': frappe.get_meta('Sales Invoice')
} }
for row in frappe.get_all('DocField', fields = ['fieldname', 'options'], for row in frappe.get_all('DocField', fields=['fieldname', 'options'],
filters = {'parent': 'Sales Invoice', 'fieldtype': 'Table'}): filters={'parent': 'Sales Invoice', 'fieldtype': 'Table'}):
doctype_meta[row.fieldname] = frappe.get_meta(row.options) doctype_meta[row.fieldname] = frappe.get_meta(row.options)
return doctype_meta return doctype_meta
def get_company_data(company): def get_company_data(company):
return frappe.get_all('Company', fields = ["*"], filters= {'name': company})[0] return frappe.get_all('Company', fields=["*"], filters={'name': company})[0]
def update_pos_profile_data(doc, pos_profile, company_data): def update_pos_profile_data(doc, pos_profile, company_data):
doc.campaign = pos_profile.get('campaign') doc.campaign = pos_profile.get('campaign')
@ -96,12 +108,14 @@ def update_pos_profile_data(doc, pos_profile, company_data):
doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get('tc_name'), 'terms') or doc.terms or '' doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get('tc_name'), 'terms') or doc.terms or ''
doc.offline_pos_name = '' doc.offline_pos_name = ''
def get_root(table): def get_root(table):
root = frappe.db.sql(""" select name from `tab%(table)s` having root = frappe.db.sql(""" select name from `tab%(table)s` having
min(lft)"""%{'table': table}, as_dict=1) min(lft)""" % {'table': table}, as_dict=1)
return root[0].name return root[0].name
def update_multi_mode_option(doc, pos_profile): def update_multi_mode_option(doc, pos_profile):
from frappe.model import default_fields from frappe.model import default_fields
@ -123,15 +137,18 @@ def update_multi_mode_option(doc, pos_profile):
doc.append('payments', payment_mode) doc.append('payments', payment_mode)
def get_mode_of_payment(doc): def get_mode_of_payment(doc):
return frappe.db.sql(""" select mpa.default_account, mpa.parent, mp.type as type from `tabMode of Payment Account` mpa, return frappe.db.sql(""" select mpa.default_account, mpa.parent, mp.type as type from `tabMode of Payment Account` mpa, \
`tabMode of Payment` mp where mpa.parent = mp.name and mpa.company = %(company)s""", {'company': doc.company}, as_dict=1) `tabMode of Payment` mp where mpa.parent = mp.name and mpa.company = %(company)s""", {'company': doc.company}, as_dict=1)
def update_tax_table(doc): def update_tax_table(doc):
taxes = get_taxes_and_charges('Sales Taxes and Charges Template', doc.taxes_and_charges) taxes = get_taxes_and_charges('Sales Taxes and Charges Template', doc.taxes_and_charges)
for tax in taxes: for tax in taxes:
doc.append('taxes', tax) doc.append('taxes', tax)
def get_items_list(pos_profile): def get_items_list(pos_profile):
cond = "1=1" cond = "1=1"
item_groups = [] item_groups = []
@ -139,19 +156,20 @@ def get_items_list(pos_profile):
# Get items based on the item groups defined in the POS profile # Get items based on the item groups defined in the POS profile
for d in pos_profile.get('item_groups'): for d in pos_profile.get('item_groups'):
item_groups.extend([d.name for d in get_child_nodes('Item Group', d.item_group)]) item_groups.extend([d.name for d in get_child_nodes('Item Group', d.item_group)])
cond = "item_group in (%s)"%(', '.join(['%s']*len(item_groups))) cond = "item_group in (%s)" % (', '.join(['%s'] * len(item_groups)))
return frappe.db.sql(""" return frappe.db.sql("""
select select
name, item_code, item_name, description, item_group, expense_account, has_batch_no, name, item_code, item_name, description, item_group, expense_account, has_batch_no,
has_serial_no, expense_account, selling_cost_center, stock_uom, image, has_serial_no, expense_account, selling_cost_center, stock_uom, image,
default_warehouse, is_stock_item, barcode, brand default_warehouse, is_stock_item, brand
from from
tabItem tabItem
where where
disabled = 0 and has_variants = 0 and is_sales_item = 1 and {cond} disabled = 0 and has_variants = 0 and is_sales_item = 1 and {cond}
""".format(cond=cond), tuple(item_groups), as_dict=1) """.format(cond=cond), tuple(item_groups), as_dict=1)
def get_item_groups(pos_profile): def get_item_groups(pos_profile):
item_group_dict = {} item_group_dict = {}
item_groups = frappe.db.sql("""Select name, item_groups = frappe.db.sql("""Select name,
@ -161,6 +179,7 @@ def get_item_groups(pos_profile):
item_group_dict[data.name] = [data.lft, data.rgt] item_group_dict[data.name] = [data.lft, data.rgt]
return item_group_dict return item_group_dict
def get_customers_list(pos_profile={}): def get_customers_list(pos_profile={}):
cond = "1=1" cond = "1=1"
customer_groups = [] customer_groups = []
@ -168,12 +187,13 @@ def get_customers_list(pos_profile={}):
# Get customers based on the customer groups defined in the POS profile # Get customers based on the customer groups defined in the POS profile
for d in pos_profile.get('customer_groups'): for d in pos_profile.get('customer_groups'):
customer_groups.extend([d.name for d in get_child_nodes('Customer Group', d.customer_group)]) customer_groups.extend([d.name for d in get_child_nodes('Customer Group', d.customer_group)])
cond = "customer_group in (%s)"%(', '.join(['%s']*len(customer_groups))) cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups)))
return frappe.db.sql(""" select name, customer_name, customer_group, return frappe.db.sql(""" select name, customer_name, customer_group,
territory, customer_pos_id from tabCustomer where disabled = 0 territory, customer_pos_id from tabCustomer where disabled = 0
and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {} and {cond}""".format(cond=cond), tuple(customer_groups), as_dict=1) or {}
def get_customers_address(customers): def get_customers_address(customers):
customer_address = {} customer_address = {}
if isinstance(customers, basestring): if isinstance(customers, basestring):
@ -185,33 +205,37 @@ def get_customers_address(customers):
(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s (select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
and parenttype = 'Address')""", data.name, as_dict=1) and parenttype = 'Address')""", data.name, as_dict=1)
address_data = {} address_data = {}
if address: address_data = address[0] if address:
address_data = address[0]
address_data.update({'full_name': data.customer_name, 'customer_pos_id': data.customer_pos_id}) address_data.update({'full_name': data.customer_name, 'customer_pos_id': data.customer_pos_id})
customer_address[data.name] = address_data customer_address[data.name] = address_data
return customer_address return customer_address
def get_contacts(customers): def get_contacts(customers):
customer_contact = {} customer_contact = {}
if isinstance(customers, basestring): if isinstance(customers, basestring):
customers = [frappe._dict({'name': customers})] customers = [frappe._dict({'name': customers})]
for data in customers: for data in customers:
contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact` contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact`
where is_primary_contact =1 and name in where is_primary_contact =1 and name in
(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s (select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
and parenttype = 'Contact')""", data.name, as_dict=1) and parenttype = 'Contact')""", data.name, as_dict=1)
if contact: if contact:
customer_contact[data.name] = contact[0] customer_contact[data.name] = contact[0]
return customer_contact return customer_contact
def get_child_nodes(group_type, root): def get_child_nodes(group_type, root):
lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"]) lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"])
return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where
lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1) lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1)
def get_serial_no_data(pos_profile, company): def get_serial_no_data(pos_profile, company):
# get itemwise serial no data # get itemwise serial no data
# example {'Nokia Lumia 1020': {'SN0001': 'Pune'}} # example {'Nokia Lumia 1020': {'SN0001': 'Pune'}}
@ -232,6 +256,7 @@ def get_serial_no_data(pos_profile, company):
return itemwise_serial_no return itemwise_serial_no
def get_batch_no_data(): def get_batch_no_data():
# get itemwise batch no data # get itemwise batch no data
# exmaple: {'LED-GRE': [Batch001, Batch002]} # exmaple: {'LED-GRE': [Batch001, Batch002]}
@ -248,6 +273,26 @@ def get_batch_no_data():
return itemwise_batch return itemwise_batch
def get_barcode_data(items_list):
# get itemwise batch no data
# exmaple: {'LED-GRE': [Batch001, Batch002]}
# where LED-GRE is item code, SN0001 is serial no and Pune is warehouse
itemwise_barcode = {}
for item in items_list:
barcodes = frappe.db.sql("""
select barcode from `tabItem Barcode` where parent = '{0}'
""".format(item.item_code), as_dict=1)
for barcode in barcodes:
if item.item_code not in itemwise_barcode:
itemwise_barcode.setdefault(item.item_code, [])
itemwise_barcode[item.item_code].append(barcode)
return itemwise_barcode
def get_item_tax_data(): def get_item_tax_data():
# get default tax of an item # get default tax of an item
# example: {'Consulting Services': {'Excise 12 - TS': '12.000'}} # example: {'Consulting Services': {'Excise 12 - TS': '12.000'}}
@ -262,17 +307,19 @@ def get_item_tax_data():
return itemwise_tax return itemwise_tax
def get_price_list_data(selling_price_list): def get_price_list_data(selling_price_list):
itemwise_price_list = {} itemwise_price_list = {}
price_lists = frappe.db.sql("""Select ifnull(price_list_rate, 0) as price_list_rate, price_lists = frappe.db.sql("""Select ifnull(price_list_rate, 0) as price_list_rate,
item_code from `tabItem Price` ip where price_list = %(price_list)s""", item_code from `tabItem Price` ip where price_list = %(price_list)s""",
{'price_list': selling_price_list}, as_dict=1) {'price_list': selling_price_list}, as_dict=1)
for item in price_lists: for item in price_lists:
itemwise_price_list[item.item_code] = item.price_list_rate itemwise_price_list[item.item_code] = item.price_list_rate
return itemwise_price_list return itemwise_price_list
def get_bin_data(pos_profile): def get_bin_data(pos_profile):
itemwise_bin_data = {} itemwise_bin_data = {}
cond = "1=1" cond = "1=1"
@ -289,6 +336,7 @@ def get_bin_data(pos_profile):
return itemwise_bin_data return itemwise_bin_data
def get_pricing_rule_data(doc): def get_pricing_rule_data(doc):
pricing_rules = "" pricing_rules = ""
if doc.ignore_pricing_rule == 0: if doc.ignore_pricing_rule == 0:
@ -297,9 +345,10 @@ def get_pricing_rule_data(doc):
and ifnull(company, '') in (%(company)s, '') and disable = 0 and %(date)s and ifnull(company, '') in (%(company)s, '') and disable = 0 and %(date)s
between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31') between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')
order by priority desc, name desc""", order by priority desc, name desc""",
{'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1) {'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1)
return pricing_rules return pricing_rules
@frappe.whitelist() @frappe.whitelist()
def make_invoice(doc_list={}, email_queue_list={}, customers_list={}): def make_invoice(doc_list={}, email_queue_list={}, customers_list={}):
if isinstance(doc_list, basestring): if isinstance(doc_list, basestring):
@ -338,14 +387,15 @@ def make_invoice(doc_list={}, email_queue_list={}, customers_list={}):
'synced_contacts': get_contacts(customers) 'synced_contacts': get_contacts(customers)
} }
def validate_records(doc): def validate_records(doc):
validate_item(doc) validate_item(doc)
def get_customer_id(doc, customer=None): def get_customer_id(doc, customer=None):
cust_id = None cust_id = None
if doc.get('customer_pos_id'): if doc.get('customer_pos_id'):
cust_id = frappe.db.get_value('Customer', cust_id = frappe.db.get_value('Customer',{'customer_pos_id': doc.get('customer_pos_id')}, 'name')
{'customer_pos_id': doc.get('customer_pos_id')}, 'name')
if not cust_id: if not cust_id:
customer = customer or doc.get('customer') customer = customer or doc.get('customer')
@ -356,6 +406,7 @@ def get_customer_id(doc, customer=None):
return cust_id return cust_id
def make_customer_and_address(customers): def make_customer_and_address(customers):
customers_list = [] customers_list = []
for customer, data in customers.items(): for customer, data in customers.items():
@ -372,6 +423,7 @@ def make_customer_and_address(customers):
frappe.db.commit() frappe.db.commit()
return customers_list return customers_list
def add_customer(data): def add_customer(data):
customer_doc = frappe.new_doc('Customer') customer_doc = frappe.new_doc('Customer')
customer_doc.customer_name = data.get('full_name') or data.get('customer') customer_doc.customer_name = data.get('full_name') or data.get('customer')
@ -380,28 +432,29 @@ def add_customer(data):
customer_doc.customer_group = get_customer_group(data) customer_doc.customer_group = get_customer_group(data)
customer_doc.territory = get_territory(data) customer_doc.territory = get_territory(data)
customer_doc.flags.ignore_mandatory = True customer_doc.flags.ignore_mandatory = True
customer_doc.save(ignore_permissions = True) customer_doc.save(ignore_permissions=True)
frappe.db.commit() frappe.db.commit()
return customer_doc.name return customer_doc.name
def get_territory(data): def get_territory(data):
if data.get('territory'): if data.get('territory'):
return data.get('territory') return data.get('territory')
return frappe.db.get_single_value('Selling Settings', return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories')
'territory') or _('All Territories')
def get_customer_group(data): def get_customer_group(data):
if data.get('customer_group'): if data.get('customer_group'):
return data.get('customer_group') return data.get('customer_group')
return frappe.db.get_single_value('Selling Settings', return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name')
'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name')
def make_contact(args,customer):
def make_contact(args, customer):
if args.get('email_id') or args.get('phone'): if args.get('email_id') or args.get('phone'):
name = frappe.db.get_value('Dynamic Link', name = frappe.db.get_value('Dynamic Link',
{'link_doctype': 'Customer', 'link_name': customer, 'parenttype': 'Contact'}, 'parent') {'link_doctype': 'Customer', 'link_name': customer, 'parenttype': 'Contact'}, 'parent')
args = { args = {
'first_name': args.get('full_name'), 'first_name': args.get('full_name'),
@ -416,16 +469,18 @@ def make_contact(args,customer):
doc.update(args) doc.update(args)
doc.is_primary_contact = 1 doc.is_primary_contact = 1
if not name: if not name:
doc.append('links',{ doc.append('links', {
'link_doctype': 'Customer', 'link_doctype': 'Customer',
'link_name': customer 'link_name': customer
}) })
doc.flags.ignore_mandatory = True doc.flags.ignore_mandatory = True
doc.save(ignore_permissions=True) doc.save(ignore_permissions=True)
def make_address(args, customer): def make_address(args, customer):
if not args.get('address_line1'): return if not args.get('address_line1'):
return
name = args.get('name') name = args.get('name')
if not name: if not name:
@ -437,7 +492,7 @@ def make_address(args, customer):
else: else:
address = frappe.new_doc('Address') address = frappe.new_doc('Address')
address.country = frappe.db.get_value('Company', args.get('company'), 'country') address.country = frappe.db.get_value('Company', args.get('company'), 'country')
address.append('links',{ address.append('links', {
'link_doctype': 'Customer', 'link_doctype': 'Customer',
'link_name': customer 'link_name': customer
}) })
@ -446,7 +501,8 @@ def make_address(args, customer):
address.is_shipping_address = 1 address.is_shipping_address = 1
address.update(args) address.update(args)
address.flags.ignore_mandatory = True address.flags.ignore_mandatory = True
address.save(ignore_permissions = True) address.save(ignore_permissions=True)
def make_email_queue(email_queue): def make_email_queue(email_queue):
name_list = [] name_list = []
@ -455,15 +511,16 @@ def make_email_queue(email_queue):
data = json.loads(data) data = json.loads(data)
sender = frappe.session.user sender = frappe.session.user
print_format = "POS Invoice" print_format = "POS Invoice"
attachments = [frappe.attach_print('Sales Invoice', name, print_format= print_format)] attachments = [frappe.attach_print('Sales Invoice', name, print_format=print_format)]
make(subject = data.get('subject'), content = data.get('content'), recipients = data.get('recipients'), make(subject=data.get('subject'), content=data.get('content'), recipients=data.get('recipients'),
sender=sender,attachments = attachments, send_email=True, sender=sender, attachments=attachments, send_email=True,
doctype='Sales Invoice', name=name) doctype='Sales Invoice', name=name)
name_list.append(key) name_list.append(key)
return name_list return name_list
def validate_item(doc): def validate_item(doc):
for item in doc.get('items'): for item in doc.get('items'):
if not frappe.db.exists('Item', item.get('item_code')): if not frappe.db.exists('Item', item.get('item_code')):
@ -486,13 +543,15 @@ def submit_invoice(si_doc, name, doc, name_list):
frappe.db.commit() frappe.db.commit()
name_list.append(name) name_list.append(name)
except Exception as e: except Exception as e:
if frappe.message_log: frappe.message_log.pop() if frappe.message_log:
frappe.message_log.pop()
frappe.db.rollback() frappe.db.rollback()
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
name_list = save_invoice(doc, name, name_list) name_list = save_invoice(doc, name, name_list)
return name_list return name_list
def save_invoice(doc, name, name_list): def save_invoice(doc, name, name_list):
try: try:
if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):

View File

@ -301,6 +301,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.customers = r.message.customers; this.customers = r.message.customers;
this.serial_no_data = r.message.serial_no_data; this.serial_no_data = r.message.serial_no_data;
this.batch_no_data = r.message.batch_no_data; this.batch_no_data = r.message.batch_no_data;
this.barcode_data = r.message.barcode_data;
this.tax_data = r.message.tax_data; this.tax_data = r.message.tax_data;
this.contacts = r.message.contacts; this.contacts = r.message.contacts;
this.address = r.message.address || {}; this.address = r.message.address || {};
@ -415,7 +416,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
}); });
this.serach_item.make_input(); this.serach_item.make_input();
this.serach_item.$input.on("keypress", function (event) { this.serach_item.$input.on("keypress", function (event) {
clearTimeout(me.last_search_timeout); clearTimeout(me.last_search_timeout);
@ -423,7 +424,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if((me.serach_item.$input.val() != "") || (event.which == 13)) { if((me.serach_item.$input.val() != "") || (event.which == 13)) {
me.items = me.get_items(); me.items = me.get_items();
me.make_item_list(); me.make_item_list();
} }
}, 400); }, 400);
}); });
@ -1110,9 +1111,9 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
search_status = false; search_status = false;
me.item_serial_no[item.item_code] = [me.serach_item.$input.val(), me.serial_no_data[item.item_code][me.serach_item.$input.val()]] me.item_serial_no[item.item_code] = [me.serach_item.$input.val(), me.serial_no_data[item.item_code][me.serach_item.$input.val()]]
return true return true
} else if (item.barcode == me.serach_item.$input.val()) { } else if (in_list(me.barcode_data[item.item_code], me.serach_item.$input.val())) {
search_status = false; search_status = false;
return item.barcode == me.serach_item.$input.val(); return true;
} else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) || } else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) ||
reg.test(item.item_name.toLowerCase()) || reg.test(item.item_group.toLowerCase())) { reg.test(item.item_name.toLowerCase()) || reg.test(item.item_group.toLowerCase())) {
return true return true
@ -1526,8 +1527,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
me.print_document(html) me.print_document(html)
}) })
} }
if (this.frm.doc.docstatus == 1) { if (this.frm.doc.docstatus == 1) {
this.page.add_menu_item(__("Email"), function () { this.page.add_menu_item(__("Email"), function () {
me.email_prompt() me.email_prompt()
}) })

View File

@ -12,7 +12,7 @@ from erpnext.controllers.sales_and_purchase_return import validate_return
from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
from erpnext.exceptions import InvalidCurrency from erpnext.exceptions import InvalidCurrency
force_item_fields = ("item_group", "barcode", "brand", "stock_uom") force_item_fields = ("item_group", "brand", "stock_uom")
class AccountsController(TransactionBase): class AccountsController(TransactionBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):

View File

@ -165,8 +165,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
and (tabItem.`{key}` LIKE %(txt)s and (tabItem.`{key}` LIKE %(txt)s
or tabItem.item_group LIKE %(txt)s or tabItem.item_group LIKE %(txt)s
or tabItem.item_name LIKE %(txt)s or tabItem.item_name LIKE %(txt)s
or tabItem.barcode LIKE %(txt)s
or tabItem.description LIKE %(txt)s) or tabItem.description LIKE %(txt)s)
or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s)
{fcond} {mcond} {fcond} {mcond}
order by order by
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),

View File

@ -493,3 +493,4 @@ erpnext.patches.v10_0.workflow_leave_application #2018-01-24
erpnext.patches.v10_0.set_default_payment_terms_based_on_company erpnext.patches.v10_0.set_default_payment_terms_based_on_company
erpnext.patches.v10_0.update_sales_order_link_to_purchase_order erpnext.patches.v10_0.update_sales_order_link_to_purchase_order
erpnext.patches.v10_0.added_extra_gst_custom_field_in_gstr2 erpnext.patches.v10_0.added_extra_gst_custom_field_in_gstr2
erpnext.patches.v10_0.item_barcode_childtable_migrate

View File

@ -0,0 +1,22 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
items_barcode = frappe.db.sql("""SELECT name, barcode FROM tabItem
WHERE barcode IS NOT NULL and barcode != ''""", as_dict=1)
frappe.reload_doc("stock", "doctype", "item")
frappe.reload_doc("stock", "doctype", "item_barcode")
for item in items_barcode:
doc = frappe.get_doc("Item", item.get("name"))
if item.get("barcode"):
doc.append("barcodes", {"barcode": item.get("barcode")})
doc.flags.ignore_validate = True
doc.flags.ignore_mandatory = True
doc.save()

View File

@ -29,7 +29,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
batch_no, item_code = batch_no_data batch_no, item_code = batch_no_data
if not serial_no and not batch_no: if not serial_no and not batch_no:
barcode_data = frappe.db.get_value('Item', {'barcode': search_value}, ['name', 'barcode']) barcode_data = frappe.db.get_value('Item Barcode', {'barcode': search_value}, ['parent', 'barcode'])
if barcode_data: if barcode_data:
item_code, barcode = barcode_data item_code, barcode = barcode_data

View File

@ -172,35 +172,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "barcode",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Barcode",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -701,6 +672,67 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb_barcodes",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Barcodes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "barcodes",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Barcodes",
"length": 0,
"no_copy": 0,
"options": "Item Barcode",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -3484,7 +3516,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 1, "max_attachments": 1,
"modified": "2018-01-24 20:42:23.303090", "modified": "2018-02-12 15:42:23.303090",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",
@ -3661,4 +3693,4 @@
"title_field": "item_name", "title_field": "item_name",
"track_changes": 1, "track_changes": 1,
"track_seen": 0 "track_seen": 0
} }

View File

@ -2,30 +2,38 @@
# 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(
page_title_field = "item_name", page_title_field="item_name",
condition_field = "show_in_website", condition_field="show_in_website",
template = "templates/generators/item.html", template="templates/generators/item.html",
no_cache = 1 no_cache=1
) )
def onload(self): def onload(self):
@ -37,14 +45,14 @@ class Item(WebsiteGenerator):
self.set_onload("asset_exists", True if asset else False) self.set_onload("asset_exists", True if asset else False)
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":
if self.variant_of: if self.variant_of:
if not self.item_code: if not self.item_code:
template_item_name = frappe.db.get_value("Item", self.variant_of, "item_name") template_item_name = frappe.db.get_value("Item", self.variant_of, "item_name")
self.item_code = make_variant_item_code(self.variant_of, template_item_name, self) self.item_code = make_variant_item_code(self.variant_of, template_item_name, self)
else: else:
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:
msgprint(_("Item Code is mandatory because Item is not automatically numbered"), raise_exception=1) msgprint(_("Item Code is mandatory because Item is not automatically numbered"), raise_exception=1)
@ -102,8 +110,8 @@ class Item(WebsiteGenerator):
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")
self.old_website_item_groups = frappe.db.sql_list("""select item_group self.old_website_item_groups = frappe.db.sql_list("""select item_group
from `tabWebsite Item Group` from `tabWebsite Item Group`
where parentfield='website_item_groups' and parenttype='Item' and parent=%s""", self.name) where parentfield='website_item_groups' and parenttype='Item' and parent=%s""", self.name)
def on_update(self): def on_update(self):
invalidate_cache_for_item(self) invalidate_cache_for_item(self)
@ -121,7 +129,7 @@ class Item(WebsiteGenerator):
'''Add a new price''' '''Add a new price'''
if not price_list: if not price_list:
price_list = (frappe.db.get_single_value('Selling Settings', 'selling_price_list') price_list = (frappe.db.get_single_value('Selling Settings', 'selling_price_list')
or frappe.db.get_value('Price List', _('Standard Selling'))) or frappe.db.get_value('Price List', _('Standard Selling')))
if price_list: if price_list:
item_price = frappe.get_doc({ item_price = frappe.get_doc({
"doctype": "Item Price", "doctype": "Item Price",
@ -147,19 +155,19 @@ class Item(WebsiteGenerator):
# default warehouse, or Stores # default warehouse, or Stores
default_warehouse = (self.default_warehouse default_warehouse = (self.default_warehouse
or frappe.db.get_single_value('Stock Settings', 'default_warehouse') or frappe.db.get_single_value('Stock Settings', 'default_warehouse')
or frappe.db.get_value('Warehouse', {'warehouse_name': _('Stores')})) or frappe.db.get_value('Warehouse', {'warehouse_name': _('Stores')}))
if default_warehouse: if default_warehouse:
stock_entry = make_stock_entry(item_code=self.name, target=default_warehouse, stock_entry = make_stock_entry(item_code=self.name, target=default_warehouse,
qty=self.opening_stock, rate=self.valuation_rate) qty=self.opening_stock, rate=self.valuation_rate)
stock_entry.add_comment("Comment", _("Opening Stock")) stock_entry.add_comment("Comment", _("Opening Stock"))
def make_route(self): def make_route(self):
if not self.route: if not self.route:
return cstr(frappe.db.get_value('Item Group', self.item_group, return cstr(frappe.db.get_value('Item Group', self.item_group,
'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5)) 'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
def validate_website_image(self): def validate_website_image(self):
"""Validate if the website image is a public file""" """Validate if the website image is a public file"""
@ -176,14 +184,13 @@ 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]
if not file_doc: if not file_doc:
if not auto_set_website_image: if not auto_set_website_image:
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found") frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found")
.format(self.website_image, self.name)) .format(self.website_image, self.name))
self.website_image = None self.website_image = None
@ -219,7 +226,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,12 +267,13 @@ 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
context.search_link = '/product_search' context.search_link = '/product_search'
context.parents = get_parent_item_groups(self.item_group) context.parents = get_parent_item_groups(self.item_group)
@ -282,8 +291,8 @@ class Item(WebsiteGenerator):
# load variants # load variants
# also used in set_attribute_context # also used in set_attribute_context
context.variants = frappe.get_all("Item", context.variants = frappe.get_all("Item",
filters={"variant_of": self.name, "show_variant_in_website": 1}, filters={"variant_of": self.name, "show_variant_in_website": 1},
order_by="name asc") order_by="name asc")
variant = frappe.form_dict.variant variant = frappe.form_dict.variant
if not variant and context.variants: if not variant and context.variants:
@ -294,7 +303,7 @@ class Item(WebsiteGenerator):
context.variant = frappe.get_doc("Item", variant) context.variant = frappe.get_doc("Item", variant)
for fieldname in ("website_image", "web_long_description", "description", for fieldname in ("website_image", "web_long_description", "description",
"website_specifications"): "website_specifications"):
if context.variant.get(fieldname): if context.variant.get(fieldname):
value = context.variant.get(fieldname) value = context.variant.get(fieldname)
if isinstance(value, list): if isinstance(value, list):
@ -317,14 +326,14 @@ class Item(WebsiteGenerator):
# load attributes # load attributes
for v in context.variants: for v in context.variants:
v.attributes = frappe.get_all("Item Variant Attribute", v.attributes = frappe.get_all("Item Variant Attribute",
fields=["attribute", "attribute_value"], filters={"parent": v.name}) fields=["attribute", "attribute_value"], filters={"parent": v.name})
for attr in v.attributes: for attr in v.attributes:
values = attribute_values_available.setdefault(attr.attribute, []) values = attribute_values_available.setdefault(attr.attribute, [])
if attr.attribute_value not in values: if attr.attribute_value not in values:
values.append(attr.attribute_value) values.append(attr.attribute_value)
if v.name==context.variant.name: if v.name == context.variant.name:
context.selected_attributes[attr.attribute] = attr.attribute_value context.selected_attributes[attr.attribute] = attr.attribute_value
# filter attributes, order based on attribute table # filter attributes, order based on attribute table
@ -338,7 +347,7 @@ class Item(WebsiteGenerator):
else: else:
# get list of values defined (for sequence) # get list of values defined (for sequence)
for attr_value in frappe.db.get_all("Item Attribute Value", for attr_value in frappe.db.get_all("Item Attribute Value",
fields=["attribute_value"], filters={"parent": attr.attribute}, order_by="idx asc"): fields=["attribute_value"], filters={"parent": attr.attribute}, order_by="idx asc"):
if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []): if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
values.append(attr_value.attribute_value) values.append(attr_value.attribute_value)
@ -373,7 +382,7 @@ class Item(WebsiteGenerator):
return True return True
for i, attr in enumerate(self.attributes): for i, attr in enumerate(self.attributes):
if i==0: if i == 0:
continue continue
combination_source = [] combination_source = []
@ -414,7 +423,7 @@ class Item(WebsiteGenerator):
for d in template.get("reorder_levels"): for d in template.get("reorder_levels"):
n = {} n = {}
for k in ("warehouse", "warehouse_reorder_level", for k in ("warehouse", "warehouse_reorder_level",
"warehouse_reorder_qty", "material_request_type"): "warehouse_reorder_qty", "material_request_type"):
n[k] = d.get(k) n[k] = d.get(k)
self.append("reorder_levels", n) self.append("reorder_levels", n)
@ -422,12 +431,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,29 +447,30 @@ 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 """
cust_code=[] cust_code = []
for d in self.get('customer_items'): for d in self.get('customer_items'):
cust_code.append(d.ref_code) cust_code.append(d.ref_code)
self.customer_code=','.join(cust_code) self.customer_code = ','.join(cust_code)
def check_item_tax(self): def check_item_tax(self):
"""Check whether Tax Rate is not entered twice for same Tax Type""" """Check whether Tax Rate is not entered twice for same Tax Type"""
check_list=[] check_list = []
for d in self.get('taxes'): for d in self.get('taxes'):
if d.tax_type: if d.tax_type:
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))
@ -466,12 +478,20 @@ class Item(WebsiteGenerator):
check_list.append(d.tax_type) check_list.append(d.tax_type)
def validate_barcode(self): def validate_barcode(self):
if self.barcode: from stdnum import ean
duplicate = frappe.db.sql("""select name from tabItem where barcode = %s if len(self.barcodes) > 0:
and name != %s""", (self.barcode, self.name)) for item_barcode in self.barcodes:
if duplicate: if item_barcode.barcode:
frappe.throw(_("Barcode {0} already used in Item {1}").format(self.barcode, duplicate[0][0])) duplicate = frappe.db.sql(
"""select parent from `tabItem Barcode` where barcode = %s and parent != %s""", (item_barcode.barcode, self.name))
if duplicate:
frappe.throw(_("Barcode {0} already used in Item {1}").format(
item_barcode.barcode, duplicate[0][0]))
if item_barcode.barcode_type:
if not ean.is_valid(item_barcode.barcode):
frappe.throw(_("Barcode {0} is not a valid {1} code").format(
item_barcode.barcode, item_barcode.barcode_type))
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'''
@ -483,7 +503,7 @@ class Item(WebsiteGenerator):
warehouse += [d.get("warehouse")] warehouse += [d.get("warehouse")]
else: else:
frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}") frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
.format(d.idx, d.warehouse), DuplicateReorderRows) .format(d.idx, d.warehouse), DuplicateReorderRows)
if d.warehouse_reorder_level and not d.warehouse_reorder_qty: if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx)) frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
@ -497,12 +517,13 @@ 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,
item_description=%s, modified=NOW() where item_code=%s""", item_description=%s, modified=NOW() where item_code=%s""",
(self.item_name, self.description, self.name)) (self.item_name, self.description, self.name))
def on_trash(self): def on_trash(self):
super(Item, self).on_trash() super(Item, self).on_trash()
@ -512,7 +533,7 @@ class Item(WebsiteGenerator):
frappe.delete_doc("Item", variant_of.name) frappe.delete_doc("Item", variant_of.name)
def before_rename(self, old_name, new_name, merge=False): def before_rename(self, old_name, new_name, merge=False):
if self.item_name==old_name: if self.item_name == old_name:
frappe.db.set_value("Item", old_name, "item_name", new_name) frappe.db.set_value("Item", old_name, "item_name", new_name)
if merge: if merge:
@ -524,7 +545,7 @@ class Item(WebsiteGenerator):
new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)] new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)]
if new_properties != [cstr(self.get(fld)) for fld in field_list]: if new_properties != [cstr(self.get(fld)) for fld in field_list]:
frappe.throw(_("To merge, following properties must be same for both items") frappe.throw(_("To merge, following properties must be same for both items")
+ ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list])) + ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
def after_rename(self, old_name, new_name, merge): def after_rename(self, old_name, new_name, merge):
if self.route: if self.route:
@ -547,7 +568,7 @@ class Item(WebsiteGenerator):
item_wise_tax_detail.pop(old_name) item_wise_tax_detail.pop(old_name)
frappe.db.set_value(dt, d.name, "item_wise_tax_detail", frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
json.dumps(item_wise_tax_detail), update_modified=False) json.dumps(item_wise_tax_detail), update_modified=False)
def set_last_purchase_rate(self, new_name): def set_last_purchase_rate(self, new_name):
last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0) last_purchase_rate = get_last_purchase_details(new_name).get("base_rate", 0)
@ -575,13 +596,14 @@ class Item(WebsiteGenerator):
self.set("website_specifications", []) self.set("website_specifications", [])
if self.item_group: if self.item_group:
for label, desc in frappe.db.get_values("Item Website Specification", for label, desc in frappe.db.get_values("Item Website Specification",
{"parent": self.item_group}, ["label", "description"]): {"parent": self.item_group}, ["label", "description"]):
row = self.append("website_specifications") row = self.append("website_specifications")
row.label = label row.label = label
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("""
@ -620,11 +642,11 @@ class Item(WebsiteGenerator):
def update_variants(self): def update_variants(self):
if self.flags.dont_update_variants or \ if self.flags.dont_update_variants or \
frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'): frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
return return
if self.has_variants: if self.has_variants:
updated = [] updated = []
variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name }) variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name})
for d in variants: for d in variants:
variant = frappe.get_doc("Item", d) variant = frappe.get_doc("Item", d)
copy_attributes_to_variant(self, variant) copy_attributes_to_variant(self, variant)
@ -641,39 +663,41 @@ class Item(WebsiteGenerator):
def validate_stock_exists_for_template_item(self): def validate_stock_exists_for_template_item(self):
if self.stock_ledger_created() and self._doc_before_save: if self.stock_ledger_created() and self._doc_before_save:
if (self._doc_before_save.has_variants != self.has_variants if (self._doc_before_save.has_variants != self.has_variants
or self._doc_before_save.variant_of != self.variant_of): or self._doc_before_save.variant_of != self.variant_of):
frappe.throw(_("Cannot change Variant properties after stock transction. You will have to make a new Item to do this.").format(self.name), frappe.throw(_("Cannot change Variant properties after stock transction. You will have to make a new Item to do this.").format(self.name),
StockExistsForTemplate) StockExistsForTemplate)
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"):
check_stock_uom_with_bin(self.name, self.stock_uom) check_stock_uom_with_bin(self.name, self.stock_uom)
if self.has_variants: if self.has_variants:
for d in frappe.db.get_all("Item", filters= {"variant_of": self.name}): for d in frappe.db.get_all("Item", filters={"variant_of": self.name}):
check_stock_uom_with_bin(d.name, self.stock_uom) check_stock_uom_with_bin(d.name, self.stock_uom)
if self.variant_of: if self.variant_of:
template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom") template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
if template_uom != self.stock_uom: if template_uom != self.stock_uom:
frappe.throw(_("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'") frappe.throw(_("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'")
.format(self.stock_uom, template_uom)) .format(self.stock_uom, template_uom))
def validate_attributes(self): def validate_attributes(self):
if (self.has_variants or self.variant_of) and self.variant_based_on=='Item Attribute': if (self.has_variants or self.variant_of) and self.variant_based_on == 'Item Attribute':
attributes = [] attributes = []
if not self.attributes: if not self.attributes:
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)
def validate_variant_attributes(self): def validate_variant_attributes(self):
if self.variant_of and self.variant_based_on=='Item Attribute': if self.variant_of and self.variant_based_on == 'Item Attribute':
args = {} args = {}
for d in self.attributes: for d in self.attributes:
if not d.attribute_value: if not d.attribute_value:
@ -683,10 +707,11 @@ class Item(WebsiteGenerator):
variant = get_variant(self.variant_of, args, self.name) variant = get_variant(self.variant_of, args, self.name)
if variant: if variant:
frappe.throw(_("Item variant {0} exists with same attributes") frappe.throw(_("Item variant {0} exists with same attributes")
.format(variant), ItemVariantExistsError) .format(variant), ItemVariantExistsError)
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 = {}
@ -697,21 +722,23 @@ def get_timeline_data(doctype, name):
for date, count in items.iteritems(): for date, count in items.iteritems():
timestamp = get_timestamp(date) timestamp = get_timestamp(date)
out.update({ timestamp: count }) out.update({timestamp: count})
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"])
if end_of_life and end_of_life!="0000-00-00" and getdate(end_of_life) <= now_datetime().date(): if end_of_life and end_of_life != "0000-00-00" and getdate(end_of_life) <= now_datetime().date():
msg = _("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life)) msg = _("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life))
_msgprint(msg, verbose) _msgprint(msg, verbose)
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")
@ -721,6 +748,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")
@ -729,6 +757,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)
@ -760,19 +789,19 @@ 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 \
(last_purchase_order and not last_purchase_receipt): (last_purchase_order and not last_purchase_receipt):
# use purchase order # use purchase order
last_purchase = last_purchase_order[0] last_purchase = last_purchase_order[0]
purchase_date = purchase_order_date purchase_date = purchase_order_date
elif (purchase_receipt_date > purchase_order_date) or \ elif (purchase_receipt_date > purchase_order_date) or \
(last_purchase_receipt and not last_purchase_order): (last_purchase_receipt and not last_purchase_order):
# use purchase receipt # use purchase receipt
last_purchase = last_purchase_receipt[0] last_purchase = last_purchase_receipt[0]
purchase_date = purchase_receipt_date purchase_date = purchase_receipt_date
@ -797,11 +826,12 @@ 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)
website_item_groups = list(set((doc.get("old_website_item_groups") or []) website_item_groups = list(set((doc.get("old_website_item_groups") or [])
+ [d.item_group for d in doc.get({"doctype":"Website Item Group"}) if d.item_group])) + [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
for item_group in website_item_groups: for item_group in website_item_groups:
invalidate_cache_for(doc, item_group) invalidate_cache_for(doc, item_group)
@ -809,13 +839,14 @@ 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
matched=True matched = True
ref_uom = frappe.db.get_value("Stock Ledger Entry", ref_uom = frappe.db.get_value("Stock Ledger Entry",
{"item_code": item}, "stock_uom") {"item_code": item}, "stock_uom")
if ref_uom: if ref_uom:
if cstr(ref_uom) != cstr(stock_uom): if cstr(ref_uom) != cstr(stock_uom):
@ -823,13 +854,14 @@ 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
if matched and bin_list: if matched and bin_list:
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))

View File

@ -0,0 +1,103 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:barcode",
"beta": 0,
"creation": "2017-12-09 18:54:50.562438",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "barcode",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Barcode",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "barcode_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Barcode Type",
"length": 0,
"no_copy": 0,
"options": "\nEAN\nUPC-A",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-12-10 20:55:23.814039",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Barcode",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class ItemBarcode(Document):
pass

View File

@ -25,7 +25,7 @@ class StockSettings(Document):
frappe.msgprint (_("`Freeze Stocks Older Than` should be smaller than %d days.") %stock_frozen_limit) frappe.msgprint (_("`Freeze Stocks Older Than` should be smaller than %d days.") %stock_frozen_limit)
# show/hide barcode field # show/hide barcode field
frappe.make_property_setter({'fieldname': 'barcode', 'property': 'hidden', frappe.make_property_setter({'fieldname': 'barcodes', 'property': 'hidden',
'value': 0 if self.show_barcode_field else 1}) 'value': 0 if self.show_barcode_field else 1})
self.cant_change_valuation_method() self.cant_change_valuation_method()

View File

@ -120,7 +120,7 @@ def process_args(args):
@frappe.whitelist() @frappe.whitelist()
def get_item_code(barcode=None, serial_no=None): def get_item_code(barcode=None, serial_no=None):
if barcode: if barcode:
item_code = frappe.db.get_value("Item", {"barcode": barcode}) item_code = frappe.db.get_value("Item Barcode", {"barcode": barcode}, fieldname=["parent"])
if not item_code: if not item_code:
frappe.throw(_("No Item with Barcode {0}").format(barcode)) frappe.throw(_("No Item with Barcode {0}").format(barcode))
elif serial_no: elif serial_no:
@ -273,7 +273,7 @@ def get_basic_details(args, item):
if not out[d[1]] or (company and args.company != company): if not out[d[1]] or (company and args.company != company):
out[d[1]] = frappe.db.get_value("Company", args.company, d[2]) if d[2] else None out[d[1]] = frappe.db.get_value("Company", args.company, d[2]) if d[2] else None
for fieldname in ("item_name", "item_group", "barcode", "brand", "stock_uom"): for fieldname in ("item_name", "item_group", "barcodes", "brand", "stock_uom"):
out[fieldname] = item.get(fieldname) out[fieldname] = item.get(fieldname)
return out return out

View File

@ -2,3 +2,4 @@ frappe
unidecode unidecode
pygithub pygithub
googlemaps googlemaps
python-stdnum