From 436f526102e56403cfc45a9b803b2c21fd30b8d8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 10 Feb 2014 14:47:54 +0530 Subject: [PATCH] commonify get_item_details and rename fields to sync selling and purchase --- .../doctype/pos_setting/pos_setting.py | 2 +- .../purchase_invoice_item.txt | 2 +- .../doctype/sales_invoice/sales_invoice.py | 8 +- .../purchase_common/purchase_common.py | 2 +- erpnext/buying/utils.py | 204 ------------- erpnext/controllers/accounts_controller.py | 3 +- erpnext/hooks.txt | 2 +- erpnext/patches/4_0/fields_to_be_renamed.py | 107 +++++++ erpnext/selling/doctype/customer/customer.py | 15 + erpnext/selling/sales_common.js | 5 +- erpnext/selling/utils.py | 217 -------------- erpnext/setup/doctype/currency/currency.py | 15 +- erpnext/stock/doctype/item/item.py | 67 ++++- erpnext/stock/doctype/item/test_item.py | 48 ++- erpnext/stock/get_item_details.py | 279 ++++++++++++++++++ erpnext/utilities/transaction_base.py | 38 --- 16 files changed, 539 insertions(+), 475 deletions(-) delete mode 100644 erpnext/buying/utils.py create mode 100644 erpnext/patches/4_0/fields_to_be_renamed.py delete mode 100644 erpnext/selling/utils.py create mode 100644 erpnext/stock/get_item_details.py diff --git a/erpnext/accounts/doctype/pos_setting/pos_setting.py b/erpnext/accounts/doctype/pos_setting/pos_setting.py index a5a11e9a4b..3198859945 100755 --- a/erpnext/accounts/doctype/pos_setting/pos_setting.py +++ b/erpnext/accounts/doctype/pos_setting/pos_setting.py @@ -61,4 +61,4 @@ class DocType: webnotes.defaults.set_global_default("is_pos", 1) def on_trash(self): - self.on_update() + self.on_update() \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.txt b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.txt index 7c249d728c..1fa36f379d 100755 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.txt +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.txt @@ -2,7 +2,7 @@ { "creation": "2013-05-22 12:43:10", "docstatus": 0, - "modified": "2014-02-03 12:30:39", + "modified": "2014-02-07 16:39:12", "modified_by": "Administrator", "owner": "Administrator" }, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f57f6f05c0..ce0b59d2d4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -177,7 +177,7 @@ class DocType(SellingController): if cint(self.doc.is_pos) != 1: return - from erpnext.selling.utils import get_pos_settings, apply_pos_settings + from erpnext.selling.utils import get_pos_settings_item_details, get_pos_settings pos = get_pos_settings(self.doc.company) if pos: @@ -196,9 +196,9 @@ class DocType(SellingController): # set pos values in items for item in self.doclist.get({"parentfield": "entries"}): if item.fields.get('item_code'): - for fieldname, val in apply_pos_settings(pos, item.fields).items(): - if (not for_validate) or (for_validate and not item.fields.get(fieldname)): - item.fields[fieldname] = val + for fname, val in get_pos_settings_item_details(pos, item.fields, pos).items(): + if (not for_validate) or (for_validate and not item.fields.get(fname)): + item.fields[fname] = val # fetch terms if self.doc.tc_name and not self.doc.terms: diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.py b/erpnext/buying/doctype/purchase_common/purchase_common.py index 2ec67fd0f4..72d86e9927 100644 --- a/erpnext/buying/doctype/purchase_common/purchase_common.py +++ b/erpnext/buying/doctype/purchase_common/purchase_common.py @@ -8,7 +8,7 @@ from webnotes.utils import cstr, flt from webnotes.model.utils import getlist from webnotes import msgprint, _ -from erpnext.buying.utils import get_last_purchase_details +from erpnext.stock.doctype.item.item import get_last_purchase_details from erpnext.controllers.buying_controller import BuyingController class DocType(BuyingController): diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py deleted file mode 100644 index d686b71d26..0000000000 --- a/erpnext/buying/utils.py +++ /dev/null @@ -1,204 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import webnotes -from webnotes import msgprint, _, throw -from webnotes.utils import getdate, flt, add_days, cstr -import json - -@webnotes.whitelist() -def get_item_details(args): - """ - args = { - "doctype": "", - "docname": "", - "item_code": "", - "warehouse": None, - "supplier": None, - "transaction_date": None, - "conversion_rate": 1.0, - "buying_price_list": None, - "price_list_currency": None, - "plc_conversion_rate": 1.0, - "is_subcontracted": "Yes" / "No" - } - """ - if isinstance(args, basestring): - args = json.loads(args) - - args = webnotes._dict(args) - - item_bean = webnotes.bean("Item", args.item_code) - item = item_bean.doc - - _validate_item_details(args, item) - - out = _get_basic_details(args, item_bean) - - out.supplier_part_no = _get_supplier_part_no(args, item_bean) - - if not out.warehouse: - out.warehouse = item_bean.doc.default_warehouse - - if out.warehouse: - out.projected_qty = get_projected_qty(item.name, out.warehouse) - - 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) - - meta = webnotes.get_doctype(args.doctype) - - if meta.get_field("currency"): - out.purchase_ref_rate = out.discount_rate = out.purchase_rate = \ - out.import_ref_rate = out.import_rate = 0.0 - out.update(_get_price_list_rate(args, item_bean, meta)) - - if args.doctype == "Material Request": - out.min_order_qty = flt(item.min_order_qty) - - return out - -def _get_basic_details(args, item_bean): - item = item_bean.doc - - out = webnotes._dict({ - "description": item.description_html or item.description, - "qty": 1.0, - "uom": item.stock_uom, - "conversion_factor": 1.0, - "warehouse": args.warehouse or item.default_warehouse, - "item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in - item_bean.doclist.get({"parentfield": "item_tax"})))), - "batch_no": None, - "expense_head": item.purchase_account \ - or webnotes.conn.get_value("Company", args.company, "default_expense_account"), - "cost_center": item.cost_center - }) - - for fieldname in ("item_name", "item_group", "brand", "stock_uom"): - out[fieldname] = item.fields.get(fieldname) - - return out - -def _get_price_list_rate(args, item_bean, meta): - from erpnext.utilities.transaction_base import validate_currency - item = item_bean.doc - out = webnotes._dict() - - # try fetching from price list - if args.buying_price_list and args.price_list_currency: - price_list_rate = webnotes.conn.sql("""select ip.ref_rate from - `tabItem Price` ip, `tabPrice List` pl - where ip.price_list=pl.name and ip.price_list=%s and - ip.item_code=%s and ip.buying=1 and pl.enabled=1""", - (args.buying_price_list, args.item_code), as_dict=1) - - if price_list_rate: - validate_currency(args, item_bean.doc, meta) - - out.import_ref_rate = flt(price_list_rate[0].ref_rate) * \ - flt(args.plc_conversion_rate) / flt(args.conversion_rate) - - # if not found, fetch from last purchase transaction - if not out.import_ref_rate: - last_purchase = get_last_purchase_details(item.name, args.docname, args.conversion_rate) - if last_purchase: - out.update(last_purchase) - - if out.import_ref_rate or out.import_rate: - validate_currency(args, item, meta) - - return out - -def _get_supplier_part_no(args, item_bean): - item_supplier = item_bean.doclist.get({"parentfield": "item_supplier_details", - "supplier": args.supplier}) - - return item_supplier and item_supplier[0].supplier_part_no or None - -def _validate_item_details(args, item): - # TODO - # from erpnext.utilities.transaction_base import validate_item_fetch - # validate_item_fetch(args, item) - - # validate if purchase item or subcontracted item - if item.is_purchase_item != "Yes": - throw(_("Item") + (" %s: " % item.name) + _("not a purchase item")) - - if args.is_subcontracted == "Yes" and item.is_sub_contracted_item != "Yes": - throw(_("Item") + (" %s: " % item.name) + - _("not a sub-contracted item.") + - _("Please select a sub-contracted item or do not sub-contract the transaction.")) - -def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): - """returns last purchase details in stock uom""" - # get last purchase order item details - last_purchase_order = webnotes.conn.sql("""\ - select po.name, po.transaction_date, po.conversion_rate, - po_item.conversion_factor, po_item.purchase_ref_rate, - po_item.discount_rate, po_item.purchase_rate - from `tabPurchase Order` po, `tabPurchase Order Item` po_item - where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and - po.name = po_item.parent - order by po.transaction_date desc, po.name desc - limit 1""", (item_code, cstr(doc_name)), as_dict=1) - - # get last purchase receipt item details - last_purchase_receipt = webnotes.conn.sql("""\ - select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, - pr_item.conversion_factor, pr_item.purchase_ref_rate, pr_item.discount_rate, - pr_item.purchase_rate - from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item - where pr.docstatus = 1 and pr_item.item_code = %s and pr.name != %s and - pr.name = pr_item.parent - order by pr.posting_date desc, pr.posting_time desc, pr.name desc - limit 1""", (item_code, cstr(doc_name)), as_dict=1) - - purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date \ - or "1900-01-01") - purchase_receipt_date = getdate(last_purchase_receipt and \ - last_purchase_receipt[0].posting_date or "1900-01-01") - - if (purchase_order_date > purchase_receipt_date) or \ - (last_purchase_order and not last_purchase_receipt): - # use purchase order - last_purchase = last_purchase_order[0] - purchase_date = purchase_order_date - - elif (purchase_receipt_date > purchase_order_date) or \ - (last_purchase_receipt and not last_purchase_order): - # use purchase receipt - last_purchase = last_purchase_receipt[0] - purchase_date = purchase_receipt_date - - else: - return webnotes._dict() - - conversion_factor = flt(last_purchase.conversion_factor) - out = webnotes._dict({ - "purchase_ref_rate": flt(last_purchase.purchase_ref_rate) / conversion_factor, - "purchase_rate": flt(last_purchase.purchase_rate) / conversion_factor, - "discount_rate": flt(last_purchase.discount_rate), - "purchase_date": purchase_date - }) - - conversion_rate = flt(conversion_rate) or 1.0 - out.update({ - "import_ref_rate": out.purchase_ref_rate / conversion_rate, - "import_rate": out.purchase_rate / conversion_rate, - "rate": out.purchase_rate - }) - - return out - -@webnotes.whitelist() -def get_conversion_factor(item_code, uom): - return {"conversion_factor": webnotes.conn.get_value("UOM Conversion Detail", - {"parent": item_code, "uom": uom}, "conversion_factor")} - -@webnotes.whitelist() -def get_projected_qty(item_code, warehouse): - return webnotes.conn.get_value("Bin", {"item_code": item_code, - "warehouse": warehouse}, "projected_qty") \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2e05903040..bdbf8c6b51 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -8,7 +8,7 @@ from webnotes.utils import flt, cint, today, cstr from webnotes.model.code import get_obj from erpnext.setup.utils import get_company_currency from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year -from erpnext.utilities.transaction_base import TransactionBase, validate_conversion_rate +from erpnext.utilities.transaction_base import TransactionBase import json class AccountsController(TransactionBase): @@ -149,6 +149,7 @@ class AccountsController(TransactionBase): self.doc.currency = company_currency self.doc.conversion_rate = 1.0 else: + from erpnext.setup.doctype.currency.currency import validate_conversion_rate validate_conversion_rate(self.doc.currency, self.doc.conversion_rate, self.meta.get_label("conversion_rate"), self.doc.company) diff --git a/erpnext/hooks.txt b/erpnext/hooks.txt index 23e0c0f38c..433fef798a 100644 --- a/erpnext/hooks.txt +++ b/erpnext/hooks.txt @@ -36,7 +36,7 @@ bean_event:*:on_trash = webnotes.core.doctype.notification_count.notification_co bean_event:Stock Entry:on_submit = erpnext.stock.doctype.material_request.material_request.update_completed_qty bean_event:Stock Entry:on_cancel = erpnext.stock.doctype.material_request.material_request.update_completed_qty -standard_queries = Customer:erpnext.selling.utils.get_customer_list +standard_queries = Customer:erpnext.selling.doctype.customer.customer.get_customer_list # Schedulers # ------------------------- diff --git a/erpnext/patches/4_0/fields_to_be_renamed.py b/erpnext/patches/4_0/fields_to_be_renamed.py new file mode 100644 index 0000000000..9adba729cd --- /dev/null +++ b/erpnext/patches/4_0/fields_to_be_renamed.py @@ -0,0 +1,107 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes + +def execute(): + rename_map = { + "Quotation Item": [ + ["ref_rate", "price_list_rate"], + ["base_ref_rate", "base_price_list_rate"], + ["adj_rate", "discount_percentage"], + ["export_rate", "rate"], + ["basic_rate", "base_rate"], + ["amount", "base_amount"], + ["export_amount", "amount"] + ], + + "Sales Order Item": [ + ["ref_rate", "price_list_rate"], + ["base_ref_rate", "base_price_list_rate"], + ["adj_rate", "discount_percentage"], + ["export_rate", "rate"], + ["basic_rate", "base_rate"], + ["amount", "base_amount"], + ["export_amount", "amount"], + ["reserved_warehouse", "warehouse"] + ], + + "Delivery Note Item": [ + ["ref_rate", "price_list_rate"], + ["base_ref_rate", "base_price_list_rate"], + ["adj_rate", "discount_percentage"], + ["export_rate", "rate"], + ["basic_rate", "base_rate"], + ["amount", "base_amount"], + ["export_amount", "amount"] + ], + + "Sales Invoice Item": [ + ["ref_rate", "price_list_rate"], + ["base_ref_rate", "base_price_list_rate"], + ["adj_rate", "discount_percentage"], + ["export_rate", "rate"], + ["basic_rate", "base_rate"], + ["amount", "base_amount"], + ["export_amount", "amount"] + ], + + "Supplier Quotation Item": [ + ["import_ref_rate", "price_list_rate"], + ["purchase_ref_rate", "base_price_list_rate"], + ["discount_rate", "discount_percentage"], + ["import_rate", "rate"], + ["purchase_rate", "base_rate"], + ["amount", "base_amount"], + ["import_amount", "amount"] + ], + + "Purchase Order Item": [ + ["import_ref_rate", "price_list_rate"], + ["purchase_ref_rate", "base_price_list_rate"], + ["discount_rate", "discount_percentage"], + ["import_rate", "rate"], + ["purchase_rate", "base_rate"], + ["amount", "base_amount"], + ["import_amount", "amount"] + ], + + "Purchase Receipt Item": [ + ["import_ref_rate", "price_list_rate"], + ["purchase_ref_rate", "base_price_list_rate"], + ["discount_rate", "discount_percentage"], + ["import_rate", "rate"], + ["purchase_rate", "base_rate"], + ["amount", "base_amount"], + ["import_amount", "amount"] + ], + + "Purchase Invoice Item": [ + ["import_ref_rate", "price_list_rate"], + ["purchase_ref_rate", "base_price_list_rate"], + ["discount_rate", "discount_percentage"], + ["import_rate", "rate"], + ["rate", "base_rate"], + ["amount", "base_amount"], + ["import_amount", "amount"], + ["expense_head", "expense_account"] + ], + + "Item": [ + ["purchase_account", "expense_account"], + ["default_sales_cost_center", "selling_cost_center"], + ["cost_center", "buying_cost_center"] + ], + } + + from webnotes.model import rename_field + + for dt, field_list in rename_map.items(): + # reload doctype + webnotes.reload_doc(webnotes.conn.get_value("DocType", dt, "module").lower(), + "doctype", dt.lower().replace(" ", "_")) + + # rename field + for field in field_list: + rename_field(dt, field[0], field[1]) \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index df17689df7..d574fe74f9 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -160,3 +160,18 @@ def get_dashboard_info(customer): out["total_unpaid"] = billing[0][1] return out + + +def get_customer_list(doctype, txt, searchfield, start, page_len, filters): + if webnotes.conn.get_default("cust_master_name") == "Customer Name": + fields = ["name", "customer_group", "territory"] + else: + fields = ["name", "customer_name", "customer_group", "territory"] + + return webnotes.conn.sql("""select %s from `tabCustomer` where docstatus < 2 + and (%s like %s or customer_name like %s) order by + case when name like %s then 0 else 1 end, + case when customer_name like %s then 0 else 1 end, + name, customer_name limit %s, %s""" % + (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), + ("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) \ No newline at end of file diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 718d8a6fb2..bfc1ad1271 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -127,7 +127,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ cur_frm.fields_dict[me.frm.cscript.fname].grid.grid_rows[item.idx - 1].remove(); } else { return this.frm.call({ - method: "erpnext.selling.utils.get_item_details", + method: "erpnext.stock.get_item_details.get_item_details", child: item, args: { args: { @@ -140,12 +140,13 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ customer: me.frm.doc.customer, currency: me.frm.doc.currency, conversion_rate: me.frm.doc.conversion_rate, - selling_price_list: me.frm.doc.selling_price_list, + price_list: me.frm.doc.selling_price_list, price_list_currency: me.frm.doc.price_list_currency, plc_conversion_rate: me.frm.doc.plc_conversion_rate, company: me.frm.doc.company, order_type: me.frm.doc.order_type, is_pos: cint(me.frm.doc.is_pos), + "transaction_type": "selling" } }, callback: function(r) { diff --git a/erpnext/selling/utils.py b/erpnext/selling/utils.py deleted file mode 100644 index dbb691df49..0000000000 --- a/erpnext/selling/utils.py +++ /dev/null @@ -1,217 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import webnotes -from webnotes import _, throw -from webnotes.utils import flt, cint -import json - -def get_customer_list(doctype, txt, searchfield, start, page_len, filters): - if webnotes.conn.get_default("cust_master_name") == "Customer Name": - fields = ["name", "customer_group", "territory"] - else: - fields = ["name", "customer_name", "customer_group", "territory"] - - return webnotes.conn.sql("""select %s from `tabCustomer` where docstatus < 2 - and (%s like %s or customer_name like %s) order by - case when name like %s then 0 else 1 end, - case when customer_name like %s then 0 else 1 end, - name, customer_name limit %s, %s""" % - (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), - ("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) - -@webnotes.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 - } - """ - - if isinstance(args, basestring): - args = json.loads(args) - args = webnotes._dict(args) - - 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) - - item_bean = webnotes.bean("Item", args.item_code) - - _validate_item_details(args, item_bean.doc) - - meta = webnotes.get_doctype(args.doctype) - - # hack! for Sales Order Item - warehouse_fieldname = "warehouse" - if meta.get_field("reserved_warehouse", parentfield=args.parentfield): - warehouse_fieldname = "reserved_warehouse" - - out = _get_basic_details(args, item_bean, warehouse_fieldname) - - if meta.get_field("currency"): - out.base_ref_rate = out.basic_rate = out.ref_rate = out.export_rate = 0.0 - - if args.selling_price_list and args.price_list_currency: - out.update(_get_price_list_rate(args, item_bean, meta)) - - out.update(_get_item_discount(out.item_group, args.customer)) - - if out.get(warehouse_fieldname): - out.update(get_available_qty(args.item_code, out.get(warehouse_fieldname))) - - out.customer_item_code = _get_customer_item_code(args, item_bean) - - if cint(args.is_pos): - pos_settings = get_pos_settings(args.company) - if pos_settings: - out.update(apply_pos_settings(pos_settings, out)) - - if args.doctype in ("Sales Invoice", "Delivery Note"): - if item_bean.doc.has_serial_no == "Yes" and not args.serial_no: - out.serial_no = _get_serial_nos_by_fifo(args, item_bean) - - return out - -def _get_serial_nos_by_fifo(args, item_bean): - return "\n".join(webnotes.conn.sql_list("""select name from `tabSerial No` - where item_code=%(item_code)s and warehouse=%(warehouse)s and status='Available' - order by timestamp(purchase_date, purchase_time) asc limit %(qty)s""", { - "item_code": args.item_code, - "warehouse": args.warehouse, - "qty": cint(args.qty) - })) - -def _get_item_code(barcode=None, serial_no=None): - if barcode: - input_type = "Barcode" - item_code = webnotes.conn.sql_list("""select name from `tabItem` where barcode=%s""", barcode) - elif serial_no: - input_type = "Serial No" - item_code = webnotes.conn.sql_list("""select item_code from `tabSerial No` - where name=%s""", serial_no) - - if not item_code: - throw(_("No Item found with ") + input_type + ": %s" % (barcode or serial_no)) - - return item_code[0] - -def _validate_item_details(args, item): - # TODO - # from erpnext.utilities.transaction_base import validate_item_fetch - # validate_item_fetch(args, item) - - # validate if sales item or service item - if args.order_type == "Maintenance": - if item.is_service_item != "Yes": - throw(_("Item") + (" %s: " % item.name) + - _("not a service item.") + - _("Please select a service item or change the order type to Sales.")) - - elif item.is_sales_item != "Yes": - throw(_("Item") + (" %s: " % item.name) + _("not a sales item")) - -def _get_basic_details(args, item_bean, warehouse_fieldname): - item = item_bean.doc - - from webnotes.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 = webnotes._dict({ - "item_code": item.name, - "description": item.description_html or item.description, - warehouse_fieldname: user_default_warehouse or item.default_warehouse \ - or args.get(warehouse_fieldname), - "income_account": item.default_income_account or args.income_account \ - or webnotes.conn.get_value("Company", args.company, "default_income_account"), - "expense_account": item.purchase_account or args.expense_account \ - or webnotes.conn.get_value("Company", args.company, "default_expense_account"), - "cost_center": item.default_sales_cost_center or args.cost_center, - "qty": 1.0, - "export_amount": 0.0, - "amount": 0.0, - "batch_no": None, - "item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in - item_bean.doclist.get({"parentfield": "item_tax"})))), - }) - - for fieldname in ("item_name", "item_group", "barcode", "brand", "stock_uom"): - out[fieldname] = item.fields.get(fieldname) - - return out - -def _get_price_list_rate(args, item_bean, meta): - ref_rate = webnotes.conn.sql("""select ip.ref_rate from - `tabItem Price` ip, `tabPrice List` pl - where ip.price_list=pl.name and ip.price_list=%s and - ip.item_code=%s and ip.selling=1 and pl.enabled=1""", - (args.selling_price_list, args.item_code), as_dict=1) - - if not ref_rate: - return {} - - # found price list rate - now we can validate - from erpnext.utilities.transaction_base import validate_currency - validate_currency(args, item_bean.doc, meta) - - return {"ref_rate": flt(ref_rate[0].ref_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate)} - -def _get_item_discount(item_group, customer): - parent_item_groups = [x[0] for x in webnotes.conn.sql("""SELECT parent.name - FROM `tabItem Group` AS node, `tabItem Group` AS parent - WHERE parent.lft <= node.lft and parent.rgt >= node.rgt and node.name = %s - GROUP BY parent.name - ORDER BY parent.lft desc""", (item_group,))] - - discount = 0 - for d in parent_item_groups: - res = webnotes.conn.sql("""select discount, name from `tabCustomer Discount` - where parent = %s and item_group = %s""", (customer, d)) - if res: - discount = flt(res[0][0]) - break - - return {"adj_rate": discount} - -@webnotes.whitelist() -def get_available_qty(item_code, warehouse): - return webnotes.conn.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, - ["projected_qty", "actual_qty"], as_dict=True) or {} - -def _get_customer_item_code(args, item_bean): - customer_item_code = item_bean.doclist.get({"parentfield": "item_customer_details", - "customer_name": args.customer}) - - return customer_item_code and customer_item_code[0].ref_code or None - -def get_pos_settings(company): - pos_settings = webnotes.conn.sql("""select * from `tabPOS Setting` where user = %s - and company = %s""", (webnotes.session['user'], company), as_dict=1) - - if not pos_settings: - pos_settings = webnotes.conn.sql("""select * from `tabPOS Setting` - where ifnull(user,'') = '' and company = %s""", company, as_dict=1) - - return pos_settings and pos_settings[0] or None - -def apply_pos_settings(pos_settings, opts): - out = {} - - for fieldname in ("income_account", "cost_center", "warehouse", "expense_account"): - if not opts.get(fieldname): - out[fieldname] = pos_settings.get(fieldname) - - if out.get("warehouse"): - out["actual_qty"] = get_available_qty(opts.item_code, out.get("warehouse")).get("actual_qty") - - return out diff --git a/erpnext/setup/doctype/currency/currency.py b/erpnext/setup/doctype/currency/currency.py index cb6190f298..8eb964bd93 100644 --- a/erpnext/setup/doctype/currency/currency.py +++ b/erpnext/setup/doctype/currency/currency.py @@ -3,7 +3,20 @@ from __future__ import unicode_literals import webnotes +from webnotes import throw, _ class DocType: def __init__(self, d, dl): - self.doc, self.doclist = d, dl \ No newline at end of file + self.doc, self.doclist = d, dl + +def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company): + """common validation for currency and price list currency""" + + company_currency = webnotes.conn.get_value("Company", company, "default_currency") + + if not conversion_rate: + throw(_('%(conversion_rate_label)s is mandatory. Maybe Currency Exchange record is not created for %(from_currency)s to %(to_currency)s') % { + "conversion_rate_label": conversion_rate_label, + "from_currency": currency, + "to_currency": company_currency + }) \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index dcb6c0fdee..b17ec4f277 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import webnotes -from webnotes.utils import cstr, flt +from webnotes.utils import cstr, flt, getdate, now_datetime, formatdate from webnotes.model.doc import addchild from webnotes.model.bean import getlist from webnotes import msgprint, _ @@ -265,7 +265,6 @@ def validate_end_of_life(item_code, end_of_life=None, verbose=1): if not end_of_life: end_of_life = webnotes.conn.get_value("Item", item_code, "end_of_life") - from webnotes.utils import getdate, now_datetime, formatdate if end_of_life and getdate(end_of_life) <= now_datetime().date(): msg = (_("Item") + " %(item_code)s: " + _("reached its end of life on") + \ " %(date)s. " + _("Please check") + ": %(end_of_life_label)s " + \ @@ -303,4 +302,66 @@ def _msgprint(msg, verbose): if verbose: msgprint(msg, raise_exception=True) else: - raise webnotes.ValidationError, msg \ No newline at end of file + raise webnotes.ValidationError, msg + + +def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): + """returns last purchase details in stock uom""" + # get last purchase order item details + last_purchase_order = webnotes.conn.sql("""\ + select po.name, po.transaction_date, po.conversion_rate, + po_item.conversion_factor, po_item.purchase_ref_rate, + po_item.discount_rate, po_item.purchase_rate + from `tabPurchase Order` po, `tabPurchase Order Item` po_item + where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and + po.name = po_item.parent + order by po.transaction_date desc, po.name desc + limit 1""", (item_code, cstr(doc_name)), as_dict=1) + + # get last purchase receipt item details + last_purchase_receipt = webnotes.conn.sql("""\ + select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, + pr_item.conversion_factor, pr_item.purchase_ref_rate, pr_item.discount_rate, + pr_item.purchase_rate + from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item + where pr.docstatus = 1 and pr_item.item_code = %s and pr.name != %s and + pr.name = pr_item.parent + order by pr.posting_date desc, pr.posting_time desc, pr.name desc + limit 1""", (item_code, cstr(doc_name)), as_dict=1) + + purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date \ + or "1900-01-01") + purchase_receipt_date = getdate(last_purchase_receipt and \ + last_purchase_receipt[0].posting_date or "1900-01-01") + + if (purchase_order_date > purchase_receipt_date) or \ + (last_purchase_order and not last_purchase_receipt): + # use purchase order + last_purchase = last_purchase_order[0] + purchase_date = purchase_order_date + + elif (purchase_receipt_date > purchase_order_date) or \ + (last_purchase_receipt and not last_purchase_order): + # use purchase receipt + last_purchase = last_purchase_receipt[0] + purchase_date = purchase_receipt_date + + else: + return webnotes._dict() + + conversion_factor = flt(last_purchase.conversion_factor) + out = webnotes._dict({ + "purchase_ref_rate": flt(last_purchase.purchase_ref_rate) / conversion_factor, + "purchase_rate": flt(last_purchase.purchase_rate) / conversion_factor, + "discount_rate": flt(last_purchase.discount_rate), + "purchase_date": purchase_date + }) + + conversion_rate = flt(conversion_rate) or 1.0 + out.update({ + "import_ref_rate": out.purchase_ref_rate / conversion_rate, + "import_rate": out.purchase_rate / conversion_rate, + "rate": out.purchase_rate + }) + + return out \ No newline at end of file diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 00ce548567..ad25ef8a68 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -5,6 +5,8 @@ from __future__ import unicode_literals import unittest import webnotes +from webnotes.test_runner import make_test_records + test_ignore = ["BOM"] test_dependencies = ["Warehouse"] @@ -15,6 +17,49 @@ class TestItem(unittest.TestCase): item.doc.is_stock_item = "Yes" item.doc.default_warehouse = None self.assertRaises(WarehouseNotSet, item.insert) + + def test_get_item_details(self): + from erpnext.stock.get_item_details import get_item_details + to_check = { + "item_code": "_Test Item", + "item_name": "_Test Item", + "description": "_Test Item", + "warehouse": "_Test Warehouse - _TC", + "income_account": "Sales - _TC", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + "qty": 1.0, + "price_list_rate": 100.0, + "base_price_list_rate": 0.0, + "discount_percentage": 0.0, + "rate": 0.0, + "base_rate": 0.0, + "amount": 0.0, + "base_amount": 0.0, + "batch_no": None, + "item_tax_rate": {}, + "uom": "_Test UOM", + "conversion_factor": 1.0, + } + + make_test_records("Item Price") + + details = get_item_details({ + "item_code": "_Test Item", + "company": "_Test Company", + "price_list": "_Test Price List", + "currency": "_Test Currency", + "doctype": "Sales Order", + "conversion_rate": 1, + "price_list_currency": "_Test Currency", + "plc_conversion_rate": 1, + "order_type": "Sales", + "transaction_type": "selling" + }) + + for key, value in to_check.iteritems(): + print key + self.assertEquals(value, details.get(key)) test_records = [ [{ @@ -36,7 +81,8 @@ test_records = [ "stock_uom": "_Test UOM", "default_income_account": "Sales - _TC", "default_warehouse": "_Test Warehouse - _TC", - "purchase_account": "_Test Account Cost for Goods Sold - _TC" + "purchase_account": "_Test Account Cost for Goods Sold - _TC", + "selling_cost_center": "_Test Cost Center - _TC", }, { "doctype": "Item Reorder", "parentfield": "item_reorder", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py new file mode 100644 index 0000000000..7647bbf017 --- /dev/null +++ b/erpnext/stock/get_item_details.py @@ -0,0 +1,279 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes +from webnotes import _, throw +from webnotes.utils import flt, cint, add_days +import json + +@webnotes.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": "", + "docname": "", + "supplier": None, + "transaction_date": None, + "conversion_rate": 1.0, + "buying_price_list": None, + "is_subcontracted": "Yes" / "No", + "transaction_type": "selling" + } + """ + + if isinstance(args, basestring): + args = json.loads(args) + args = webnotes._dict(args) + + 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) + + item_bean = webnotes.bean("Item", args.item_code) + item = item_bean.doc + + validate_item_details(args, item) + + out = get_basic_details(args, item_bean) + + get_party_item_code(args, item_bean, out) + + if out.get("warehouse"): + out.update(get_available_qty(args.item_code, out.warehouse)) + out.update(get_projected_qty(item.name, out.warehouse)) + + 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) + + get_price_list_rate(args, item_bean, out) + + # out.update(_get_item_discount(out.item_group, args.customer)) + + if args.transaction_type == "selling" and cint(args.is_pos): + out.update(get_pos_settings_item_details(args.company, args)) + + if args.doctype in ("Sales Invoice", "Delivery Note"): + if item_bean.doc.has_serial_no == "Yes" and not args.serial_no: + out.serial_no = _get_serial_nos_by_fifo(args, item_bean) + + return out + +def _get_serial_nos_by_fifo(args, item_bean): + return "\n".join(webnotes.conn.sql_list("""select name from `tabSerial No` + where item_code=%(item_code)s and warehouse=%(warehouse)s and status='Available' + order by timestamp(purchase_date, purchase_time) asc limit %(qty)s""", { + "item_code": args.item_code, + "warehouse": args.warehouse, + "qty": cint(args.qty) + })) + +def get_item_code(barcode=None, serial_no=None): + if barcode: + item_code = webnotes.conn.get_value("Item", {"barcode": barcode}) + elif serial_no: + item_code = webnotes.conn.get_value("Serial No", serial_no, "item_code") + + if not item_code: + throw(_("No Item found with ") + _("Barcode") if barcode else _("Serial No") + + ": %s" % (barcode or 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) + + if args.transaction_type == "selling": + # validate if sales item or service item + if args.order_type == "Maintenance": + if item.is_service_item != "Yes": + throw(_("Item") + (" %s: " % item.name) + + _("not a service item.") + + _("Please select a service item or change the order type to Sales.")) + + elif item.is_sales_item != "Yes": + throw(_("Item") + (" %s: " % item.name) + _("not a sales item")) + + elif args.transaction_type == "buying": + # validate if purchase item or subcontracted item + if item.is_purchase_item != "Yes": + throw(_("Item") + (" %s: " % item.name) + _("not a purchase item")) + + if args.is_subcontracted == "Yes" and item.is_sub_contracted_item != "Yes": + throw(_("Item") + (" %s: " % item.name) + + _("not a sub-contracted item.") + + _("Please select a sub-contracted item or do not sub-contract the transaction.")) + +def get_basic_details(args, item_bean): + item = item_bean.doc + + from webnotes.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 = webnotes._dict({ + "item_code": item.name, + "item_name": item.item_name, + "description": item.description_html or item.description, + "warehouse": user_default_warehouse or args.warehouse or item.default_warehouse, + "income_account": item.default_income_account or args.income_account \ + or webnotes.conn.get_value("Company", args.company, "default_income_account"), + "expense_account": item.purchase_account or args.expense_account \ + or webnotes.conn.get_value("Company", args.company, "default_expense_account"), + "cost_center": item.selling_cost_center \ + if args.transaction_type == "selling" else args.buying_cost_center, + "batch_no": None, + "item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in + item_bean.doclist.get({"parentfield": "item_tax"})))), + "uom": item.stock_uom, + "min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "", + "conversion_factor": 1.0, + "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, + "discount_percentage": 0.0 + }) + + for fieldname in ("item_name", "item_group", "barcode", "brand", "stock_uom"): + out[fieldname] = item.fields.get(fieldname) + + return out + +def get_price_list_rate(args, item_bean, out): + meta = webnotes.get_doctype(args.doctype) + + if meta.get_field("currency"): + validate_price_list(args) + validate_conversion_rate(args, meta) + + price_list_rate = webnotes.conn.get_value("Item Price", + {"price_list": args.price_list, "item_code": args.item_code}, "ref_rate") + + if not price_list_rate: 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_bean.doc.name, + args.docname, args.conversion_rate)) + +def validate_price_list(args): + if args.get("price_list"): + if not webnotes.conn.get_value("Price List", + {"name": args.price_list, args.transaction_type: 1, "enabled": 1}): + throw(_("Price List is either disabled or for not ") + _(args.transaction_type)) + else: + throw(_("Price List not selected")) + +def validate_conversion_rate(args, meta): + from erpnext.setup.doctype.currency.currency import validate_conversion_rate + from webnotes.model.meta import get_field_precision + + # 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"), + webnotes._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"), + webnotes._dict({"fields": args}))) + +# def _get_item_discount(item_group, customer): +# parent_item_groups = [x[0] for x in webnotes.conn.sql("""SELECT parent.name +# FROM `tabItem Group` AS node, `tabItem Group` AS parent +# WHERE parent.lft <= node.lft and parent.rgt >= node.rgt and node.name = %s +# GROUP BY parent.name +# ORDER BY parent.lft desc""", (item_group,))] +# +# discount = 0 +# for d in parent_item_groups: +# res = webnotes.conn.sql("""select discount, name from `tabCustomer Discount` +# where parent = %s and item_group = %s""", (customer, d)) +# if res: +# discount = flt(res[0][0]) +# break +# +# return {"adj_rate": discount} + +def get_party_item_code(args, item_bean, out): + if args.transaction_type == "selling": + customer_item_code = item_bean.doclist.get({"parentfield": "item_customer_details", + "customer_name": args.customer}) + out.customer_item_code = customer_item_code[0].ref_code if customer_item_code else None + else: + item_supplier = item_bean.doclist.get({"parentfield": "item_supplier_details", + "supplier": args.supplier}) + out.supplier_part_no = item_supplier[0].supplier_part_no if item_supplier else None + + +def get_pos_settings_item_details(company, args, pos_settings=None): + res = webnotes._dict() + + if not pos_settings: + pos_settings = get_pos_settings(company) + + if pos_settings: + for fieldname in ("income_account", "cost_center", "warehouse", "expense_account"): + if not args.get(fieldname): + res[fieldname] = pos_settings.get(fieldname) + + if res.get("warehouse"): + res.actual_qty = get_available_qty(args.item_code, + res.warehouse).get("actual_qty") + + return res + +def get_pos_settings(company): + pos_settings = webnotes.conn.sql("""select * from `tabPOS Setting` where user = %s + and company = %s""", (webnotes.session['user'], company), as_dict=1) + + if not pos_settings: + pos_settings = webnotes.conn.sql("""select * from `tabPOS Setting` + where ifnull(user,'') = '' and company = %s""", company, as_dict=1) + + return pos_settings and pos_settings[0] or None + +@webnotes.whitelist() +def get_conversion_factor(item_code, uom): + return {"conversion_factor": webnotes.conn.get_value("UOM Conversion Detail", + {"parent": item_code, "uom": uom}, "conversion_factor")} + +@webnotes.whitelist() +def get_projected_qty(item_code, warehouse): + return {"projected_qty": webnotes.conn.get_value("Bin", + {"item_code": item_code, "warehouse": warehouse}, "projected_qty")} + +@webnotes.whitelist() +def get_available_qty(item_code, warehouse): + return webnotes.conn.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, + ["projected_qty", "actual_qty"], as_dict=True) or {} \ No newline at end of file diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 83a3857807..b239fdc903 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -190,44 +190,6 @@ def get_address_territory(address_doc): return territory -def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company): - """common validation for currency and price list currency""" - - company_currency = webnotes.conn.get_value("Company", company, "default_currency") - - if not conversion_rate: - msgprint(_('%(conversion_rate_label)s is mandatory. Maybe Currency Exchange record is not created for %(from_currency)s to %(to_currency)s') % { - "conversion_rate_label": conversion_rate_label, - "from_currency": currency, - "to_currency": company_currency - }, raise_exception=True) - -def validate_currency(args, item, meta=None): - from webnotes.model.meta import get_field_precision - if not meta: - meta = webnotes.get_doctype(args.doctype) - - # validate conversion rate - if meta.get_field("currency"): - validate_conversion_rate(args.currency, args.conversion_rate, - meta.get_label("conversion_rate"), args.company) - - # round it - args.conversion_rate = flt(args.conversion_rate, - get_field_precision(meta.get_field("conversion_rate"), - webnotes._dict({"fields": args}))) - - # validate price list conversion rate - if meta.get_field("price_list_currency") and (args.selling_price_list or args.buying_price_list) \ - and args.price_list_currency: - validate_conversion_rate(args.price_list_currency, args.plc_conversion_rate, - meta.get_label("plc_conversion_rate"), args.company) - - # round it - args.plc_conversion_rate = flt(args.plc_conversion_rate, - get_field_precision(meta.get_field("plc_conversion_rate"), - webnotes._dict({"fields": args}))) - def delete_events(ref_type, ref_name): webnotes.delete_doc("Event", webnotes.conn.sql_list("""select name from `tabEvent` where ref_type=%s and ref_name=%s""", (ref_type, ref_name)), for_reload=True)