brotherton-erpnext/erpnext/stock/get_item_details.py
Anand Doshi 01de945388 [cleanup] Removed 'Is Service Item' checkbox and filters
'Is Service Item' checkbox only worked for filtering Items in Maintenance Schedule and Maintenance Visit, and validating that Items in a Maintenance type Order were of type 'Service'. However, it doesn't fit an actual use case where any Sales Item could be given for Maintenance, making the checkbox an unnecessary addition.
2016-01-26 16:22:51 +05:30

466 lines
16 KiB
Python

# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _, throw
from frappe.utils import flt, cint, add_days, cstr
import json
from erpnext.accounts.doctype.pricing_rule.pricing_rule import get_pricing_rule_for_item, set_transaction_type
from erpnext.setup.utils import get_exchange_rate
from frappe.model.meta import get_field_precision
@frappe.whitelist()
def get_item_details(args):
"""
args = {
"item_code": "",
"warehouse": None,
"customer": "",
"conversion_rate": 1.0,
"selling_price_list": None,
"price_list_currency": None,
"plc_conversion_rate": 1.0,
"doctype": "",
"name": "",
"supplier": None,
"transaction_date": None,
"conversion_rate": 1.0,
"buying_price_list": None,
"is_subcontracted": "Yes" / "No",
"ignore_pricing_rule": 0/1
"project_name": ""
}
"""
args = process_args(args)
item_doc = frappe.get_doc("Item", args.item_code)
item = item_doc
validate_item_details(args, item)
out = get_basic_details(args, item)
get_party_item_code(args, item_doc, out)
if out.get("warehouse"):
out.update(get_available_qty(args.item_code, out.warehouse))
out.update(get_projected_qty(item.name, out.warehouse))
get_price_list_rate(args, item_doc, out)
if args.customer and cint(args.is_pos):
out.update(get_pos_profile_item_details(args.company, args))
# update args with out, if key or value not exists
for key, value in out.iteritems():
if args.get(key) is None:
args[key] = value
out.update(get_pricing_rule_for_item(args))
if args.get("doctype") in ("Sales Invoice", "Delivery Note"):
if item_doc.has_serial_no == 1 and not args.serial_no:
out.serial_no = get_serial_nos_by_fifo(args, item_doc)
if args.transaction_date and item.lead_time_days:
out.schedule_date = out.lead_time_date = add_days(args.transaction_date,
item.lead_time_days)
if args.get("is_subcontracted") == "Yes":
out.bom = get_default_bom(args.item_code)
return out
def process_args(args):
if isinstance(args, basestring):
args = json.loads(args)
args = frappe._dict(args)
if not args.get("price_list"):
args.price_list = args.get("selling_price_list") or args.get("buying_price_list")
if args.barcode:
args.item_code = get_item_code(barcode=args.barcode)
elif not args.item_code and args.serial_no:
args.item_code = get_item_code(serial_no=args.serial_no)
set_transaction_type(args)
return args
@frappe.whitelist()
def get_item_code(barcode=None, serial_no=None):
if barcode:
item_code = frappe.db.get_value("Item", {"barcode": barcode})
if not item_code:
frappe.throw(_("No Item with Barcode {0}").format(barcode))
elif serial_no:
item_code = frappe.db.get_value("Serial No", serial_no, "item_code")
if not item_code:
frappe.throw(_("No Item with Serial No {0}").format(serial_no))
return item_code
def validate_item_details(args, item):
if not args.company:
throw(_("Please specify Company"))
from erpnext.stock.doctype.item.item import validate_end_of_life
validate_end_of_life(item.name, item.end_of_life, item.disabled)
if args.transaction_type=="selling":
# validate if sales item or service item
if item.is_sales_item != 1:
throw(_("Item {0} must be a Sales Item").format(item.name))
if cint(item.has_variants):
throw(_("Item {0} is a template, please select one of its variants").format(item.name))
elif args.transaction_type=="buying" and args.doctype != "Material Request":
# validate if purchase item or subcontracted item
if item.is_purchase_item != 1:
throw(_("Item {0} must be a Purchase Item").format(item.name))
if args.get("is_subcontracted") == "Yes" and item.is_sub_contracted_item != 1:
throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
def get_basic_details(args, item):
if not item:
item = frappe.get_doc("Item", args.get("item_code"))
if item.variant_of:
item.update_template_tables()
from frappe.defaults import get_user_default_as_list
user_default_warehouse_list = get_user_default_as_list('Warehouse')
user_default_warehouse = user_default_warehouse_list[0] \
if len(user_default_warehouse_list)==1 else ""
out = frappe._dict({
"item_code": item.name,
"item_name": item.item_name,
"description": cstr(item.description).strip(),
"image": cstr(item.image).strip(),
"warehouse": user_default_warehouse or args.warehouse or item.default_warehouse,
"income_account": get_default_income_account(args, item),
"expense_account": get_default_expense_account(args, item),
"cost_center": get_default_cost_center(args, item),
"batch_no": None,
"item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in
item.get("taxes")))),
"uom": item.stock_uom,
"min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "",
"conversion_factor": 1.0,
"qty": args.qty or 1.0,
"stock_qty": 1.0,
"price_list_rate": 0.0,
"base_price_list_rate": 0.0,
"rate": 0.0,
"base_rate": 0.0,
"amount": 0.0,
"base_amount": 0.0,
"net_rate": 0.0,
"net_amount": 0.0,
"discount_percentage": 0.0,
"supplier": item.default_supplier,
"delivered_by_supplier": item.delivered_by_supplier,
})
# if default specified in item is for another company, fetch from company
for d in [["Account", "income_account", "default_income_account"], ["Account", "expense_account", "default_expense_account"],
["Cost Center", "cost_center", "cost_center"], ["Warehouse", "warehouse", ""]]:
company = frappe.db.get_value(d[0], out.get(d[1]), "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
for fieldname in ("item_name", "item_group", "barcode", "brand", "stock_uom"):
out[fieldname] = item.get(fieldname)
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"))
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"))
def get_default_cost_center(args, item):
return (frappe.db.get_value("Project", args.get("project_name"), "cost_center")
or (item.selling_cost_center if args.get("customer") else item.buying_cost_center)
or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
or args.get("cost_center"))
def get_price_list_rate(args, item_doc, out):
meta = frappe.get_meta(args.doctype)
if meta.get_field("currency"):
validate_price_list(args)
validate_conversion_rate(args, meta)
price_list_rate = get_price_list_rate_for(args.price_list, item_doc.name)
# variant
if not price_list_rate and item_doc.variant_of:
price_list_rate = get_price_list_rate_for(args.price_list, item_doc.variant_of)
# insert in database
if not price_list_rate:
if args.price_list and args.rate:
insert_item_price(args)
return {}
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
/ flt(args.conversion_rate)
if not out.price_list_rate and args.transaction_type=="buying":
from erpnext.stock.doctype.item.item import get_last_purchase_details
out.update(get_last_purchase_details(item_doc.name,
args.name, args.conversion_rate))
def insert_item_price(args):
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same"""
if frappe.db.get_value("Price List", args.price_list, "currency") == args.currency \
and cint(frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")):
if frappe.has_permission("Item Price", "write"):
price_list_rate = args.rate / args.conversion_factor \
if args.get("conversion_factor") else args.rate
item_price = frappe.get_doc({
"doctype": "Item Price",
"price_list": args.price_list,
"item_code": args.item_code,
"currency": args.currency,
"price_list_rate": price_list_rate
})
item_price.insert()
frappe.msgprint(_("Item Price added for {0} in Price List {1}").format(args.item_code,
args.price_list))
def get_price_list_rate_for(price_list, item_code):
return frappe.db.get_value("Item Price",
{"price_list": price_list, "item_code": item_code}, "price_list_rate")
def validate_price_list(args):
if args.get("price_list"):
if not frappe.db.get_value("Price List",
{"name": args.price_list, args.transaction_type: 1, "enabled": 1}):
throw(_("Price List {0} is disabled").format(args.price_list))
else:
throw(_("Price List not selected"))
def validate_conversion_rate(args, meta):
from erpnext.controllers.accounts_controller import validate_conversion_rate
if (not args.conversion_rate
and args.currency==frappe.db.get_value("Company", args.company, "default_currency")):
args.conversion_rate = 1.0
# validate currency conversion rate
validate_conversion_rate(args.currency, args.conversion_rate,
meta.get_label("conversion_rate"), args.company)
args.conversion_rate = flt(args.conversion_rate,
get_field_precision(meta.get_field("conversion_rate"),
frappe._dict({"fields": args})))
# validate price list currency conversion rate
if not args.get("price_list_currency"):
throw(_("Price List Currency not selected"))
else:
validate_conversion_rate(args.price_list_currency, args.plc_conversion_rate,
meta.get_label("plc_conversion_rate"), args.company)
args.plc_conversion_rate = flt(args.plc_conversion_rate,
get_field_precision(meta.get_field("plc_conversion_rate"),
frappe._dict({"fields": args})))
def get_party_item_code(args, item_doc, out):
if args.transaction_type=="selling" and args.customer:
customer_item_code = item_doc.get("customer_items", {"customer_name": args.customer})
out.customer_item_code = customer_item_code[0].ref_code if customer_item_code else None
if args.transaction_type=="buying" and args.supplier:
item_supplier = item_doc.get("supplier_items", {"supplier": args.supplier})
out.supplier_part_no = item_supplier[0].supplier_part_no if item_supplier else None
def get_pos_profile_item_details(company, args, pos_profile=None):
res = frappe._dict()
if not pos_profile:
pos_profile = get_pos_profile(company)
if pos_profile:
for fieldname in ("income_account", "cost_center", "warehouse", "expense_account"):
if not args.get(fieldname) and pos_profile.get(fieldname):
res[fieldname] = pos_profile.get(fieldname)
if res.get("warehouse"):
res.actual_qty = get_available_qty(args.item_code,
res.warehouse).get("actual_qty")
return res
@frappe.whitelist()
def get_pos_profile(company):
pos_profile = frappe.db.sql("""select * from `tabPOS Profile` where user = %s
and company = %s""", (frappe.session['user'], company), as_dict=1)
if not pos_profile:
pos_profile = frappe.db.sql("""select * from `tabPOS Profile`
where ifnull(user,'') = '' and company = %s""", company, as_dict=1)
return pos_profile and pos_profile[0] or None
def get_serial_nos_by_fifo(args, item_doc):
if frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo"):
return "\n".join(frappe.db.sql_list("""select name from `tabSerial No`
where item_code=%(item_code)s and warehouse=%(warehouse)s
order by timestamp(purchase_date, purchase_time) asc limit %(qty)s""", {
"item_code": args.item_code,
"warehouse": args.warehouse,
"qty": abs(cint(args.qty))
}))
def get_actual_batch_qty(batch_no,warehouse,item_code):
actual_batch_qty = 0
if batch_no:
actual_batch_qty = flt(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry`
where warehouse=%s and item_code=%s and batch_no=%s""",
(warehouse, item_code, batch_no))[0][0])
return actual_batch_qty
@frappe.whitelist()
def get_conversion_factor(item_code, uom):
variant_of = frappe.db.get_value("Item", item_code, "variant_of")
filters = {"parent": item_code, "uom": uom}
if variant_of:
filters["parent"] = ("in", (item_code, variant_of))
return {"conversion_factor": frappe.db.get_value("UOM Conversion Detail",
filters, "conversion_factor")}
@frappe.whitelist()
def get_projected_qty(item_code, warehouse):
return {"projected_qty": frappe.db.get_value("Bin",
{"item_code": item_code, "warehouse": warehouse}, "projected_qty")}
@frappe.whitelist()
def get_available_qty(item_code, warehouse):
return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
["projected_qty", "actual_qty"], as_dict=True) or {"projected_qty": 0, "actual_qty": 0}
@frappe.whitelist()
def get_batch_qty(batch_no,warehouse,item_code):
actual_batch_qty = get_actual_batch_qty(batch_no,warehouse,item_code)
if batch_no:
return {'actual_batch_qty': actual_batch_qty}
@frappe.whitelist()
def apply_price_list(args, as_doc=False):
"""Apply pricelist on a document-like dict object and return as
{'parent': dict, 'children': list}
:param args: See below
:param as_doc: Updates value in the passed dict
args = {
"doctype": "",
"name": "",
"items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...],
"conversion_rate": 1.0,
"selling_price_list": None,
"price_list_currency": None,
"plc_conversion_rate": 1.0,
"doctype": "",
"name": "",
"supplier": None,
"transaction_date": None,
"conversion_rate": 1.0,
"buying_price_list": None,
"ignore_pricing_rule": 0/1
}
"""
args = process_args(args)
parent = get_price_list_currency_and_exchange_rate(args)
children = []
if "items" in args:
item_list = args.get("items")
args.update(parent)
for item in item_list:
args_copy = frappe._dict(args.copy())
args_copy.update(item)
item_details = apply_price_list_on_item(args_copy)
children.append(item_details)
if as_doc:
args.price_list_currency = parent.price_list_currency
args.plc_conversion_rate = parent.plc_conversion_rate
if args.get('items'):
for i, item in enumerate(args.get('items')):
for fieldname in children[i]:
# if the field exists in the original doc
# update the value
if fieldname in item and fieldname not in ("name", "doctype"):
item[fieldname] = children[i][fieldname]
return args
else:
return {
"parent": parent,
"children": children
}
def apply_price_list_on_item(args):
item_details = frappe._dict()
item_doc = frappe.get_doc("Item", args.item_code)
get_price_list_rate(args, item_doc, item_details)
item_details.update(get_pricing_rule_for_item(args))
return item_details
def get_price_list_currency(price_list):
if price_list:
result = frappe.db.get_value("Price List", {"name": price_list,
"enabled": 1}, ["name", "currency"], as_dict=True)
if not result:
throw(_("Price List {0} is disabled").format(price_list))
return result.currency
def get_price_list_currency_and_exchange_rate(args):
if not args.price_list:
return {}
price_list_currency = get_price_list_currency(args.price_list)
plc_conversion_rate = args.plc_conversion_rate
if (not plc_conversion_rate) or (price_list_currency and args.price_list_currency \
and price_list_currency != args.price_list_currency):
plc_conversion_rate = get_exchange_rate(price_list_currency, args.currency) or plc_conversion_rate
return frappe._dict({
"price_list_currency": price_list_currency,
"plc_conversion_rate": plc_conversion_rate
})
@frappe.whitelist()
def get_default_bom(item_code=None):
if item_code:
bom = frappe.db.get_value("BOM", {"docstatus": 1, "is_default": 1, "is_active": 1, "item": item_code})
if bom:
return bom
else:
frappe.throw(_("No default BOM exists for Item {0}").format(item_code))