From 1dde46aff00b2a401447219d09d350c650c46fe3 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Wed, 15 May 2013 21:15:57 +0530 Subject: [PATCH] [buying, selling] [refactor] get item details --- .../purchase_common/purchase_common.js | 56 ++++++--- buying/utils.py | 83 +++++++++----- controllers/buying_controller.py | 30 +---- controllers/selling_controller.py | 7 ++ selling/doctype/quotation/quotation.py | 2 + selling/doctype/sales_common/sales_common.js | 107 +++++++++++++----- selling/doctype/sales_order/sales_order.py | 2 + selling/utils.py | 101 ++++++++++++++++- setup/utils.py | 2 +- utilities/transaction_base.py | 41 ++++++- 10 files changed, 327 insertions(+), 104 deletions(-) diff --git a/buying/doctype/purchase_common/purchase_common.js b/buying/doctype/purchase_common/purchase_common.js index dacee80e55..ff875bae64 100644 --- a/buying/doctype/purchase_common/purchase_common.js +++ b/buying/doctype/purchase_common/purchase_common.js @@ -83,26 +83,48 @@ erpnext.buying.BuyingController = wn.ui.form.Controller.extend({ item_code: function(doc, cdt, cdn) { var me = this; - var item = locals[cdt][cdn]; + var item = wn.model.get_doc(cdt, cdn); + // validate company if(item.item_code) { - this.frm.call({ - method: "buying.utils.get_item_details", - child: item, - args: { - args: { - doctype: me.frm.doc.doctype, - docname: me.frm.doc.name, - item_code: item.item_code, - warehouse: item.warehouse, - supplier: me.frm.doc.supplier, - conversion_rate: me.frm.doc.conversion_rate, - price_list_name: me.frm.doc.price_list_name, - price_list_currency: me.frm.doc.price_list_currency, - plc_conversion_rate: me.frm.doc.plc_conversion_rate - } - }, + var fetch = true; + $.each(["company", "supplier"], function(i, fieldname) { + if(!me.frm.doc[fieldname]) { + fetch = false; + msgprint(wn._("Please specify") + ": " + + wn.meta.get_label(me.frm.doc.doctype, fieldname, me.frm.doc.name) + + ". " + wn._("It is needed to fetch Item Details.")); + } }); + + if(!fetch) { + item.item_code = null; + refresh_field("item_code", item.name, item.parentfield); + } else { + this.frm.call({ + method: "buying.utils.get_item_details", + child: item, + args: { + args: { + item_code: item.item_code, + warehouse: item.warehouse, + doctype: me.frm.doc.doctype, + docname: me.frm.doc.name, + supplier: me.frm.doc.supplier, + conversion_rate: me.frm.doc.conversion_rate, + price_list_name: me.frm.doc.price_list_name, + price_list_currency: me.frm.doc.price_list_currency, + plc_conversion_rate: me.frm.doc.plc_conversion_rate, + is_subcontracted: me.frm.doc.is_subcontracted, + company: me.frm.doc.company, + currency: me.frm.doc.currency + } + }, + callback: function(r) { + // TODO: calculate + } + }); + } } }, diff --git a/buying/utils.py b/buying/utils.py index 0431e642cb..54197b49fb 100644 --- a/buying/utils.py +++ b/buying/utils.py @@ -16,6 +16,7 @@ from __future__ import unicode_literals import webnotes +from webnotes import msgprint, _ from webnotes.utils import getdate, flt, add_days import json @@ -29,7 +30,11 @@ def get_item_details(args): "warehouse": None, "supplier": None, "transaction_date": None, - "conversion_rate": 1.0 + "conversion_rate": 1.0, + "price_list_name": None, + "price_list_currency": None, + "plc_conversion_rate": 1.0, + "is_subcontracted": "Yes" / "No" } """ if isinstance(args, basestring): @@ -37,36 +42,14 @@ def get_item_details(args): args = webnotes._dict(args) - item_wrapper = webnotes.bean("Item", args.item_code) - item = item_wrapper.doc + item_bean = webnotes.bean("Item", args.item_code) + item = item_bean.doc - from stock.utils import validate_end_of_life - validate_end_of_life(item.name, item.end_of_life) + _validate_item_details(args, item) - # fetch basic values - out = webnotes._dict() - out.update({ - "item_name": item.item_name, - "item_group": item.item_group, - "brand": item.brand, - "description": item.description, - "qty": 0, - "stock_uom": item.stock_uom, - "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_wrapper.doclist.get({"parentfield": "item_tax"})))), - "batch_no": None, - "expense_head": item.purchase_account, - "cost_center": item.cost_center - }) + out = _get_basic_details(args, item_bean) - if args.supplier: - item_supplier = item_wrapper.doclist.get({"parentfield": "item_supplier_details", - "supplier": args.supplier}) - if item_supplier: - out["supplier_part_no"] = item_supplier[0].supplier_part_no + out.supplier_part_no = _get_supplier_part_no(args, item_bean) if out.warehouse: out.projected_qty = webnotes.conn.get_value("Bin", {"item_code": item.name, @@ -84,7 +67,7 @@ def get_item_details(args): "Supplier Quotation"]: # try fetching from price list if args.price_list_name and args.price_list_currency: - rates_as_per_price_list = get_rates_as_per_price_list(args, item_wrapper.doclist) + rates_as_per_price_list = get_rates_as_per_price_list(args, item_bean.doclist) if rates_as_per_price_list: out.update(rates_as_per_price_list) @@ -95,6 +78,33 @@ def get_item_details(args): out.update(last_purchase) return out + +def _get_basic_details(args, item_bean): + item = item_bean.doc + + out = webnotes._dict({ + "description": item.description_html or item.description, + "qty": 0.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, + "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_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 get_rates_as_per_price_list(args, item_doclist=None): if not item_doclist: @@ -117,6 +127,21 @@ def get_rates_as_per_price_list(args, item_doclist=None): }) else: return webnotes._dict() + +def _validate_item_details(args, item): + from 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": + msgprint(_("Item") + (" %s: " % item.name) + _("not a purchase item"), + raise_exception=True) + + if args.is_subcontracted == "Yes" and item.is_sub_contracted_item != "Yes": + msgprint(_("Item") + (" %s: " % item.name) + + _("not a sub-contracted item.") + + _("Please select a sub-contracted item or do not sub-contract the transaction."), + raise_exception=True) def get_last_purchase_details(item_code, doc_name, conversion_rate=1.0): """returns last purchase details in stock uom""" diff --git a/controllers/buying_controller.py b/controllers/buying_controller.py index 209bc60b1b..1fc411fad4 100644 --- a/controllers/buying_controller.py +++ b/controllers/buying_controller.py @@ -22,6 +22,7 @@ import json from buying.utils import get_item_details from setup.utils import get_company_currency +from utilities.transaction_base import validate_conversion_rate from controllers.stock_controller import StockController @@ -32,12 +33,11 @@ class BuyingController(StockController): super(BuyingController, self).validate() self.validate_stock_or_nonstock_items() self.validate_warehouse_belongs_to_company() + if self.meta.get_field("currency"): self.company_currency = get_company_currency(self.doc.company) - self.validate_conversion_rate("currency", "conversion_rate") - - if self.doc.price_list_name and self.doc.price_list_currency: - self.validate_conversion_rate("price_list_currency", "plc_conversion_rate") + validate_conversion_rate(self.doc.currency, self.doc.conversion_rate, + self.meta.get_label("conversion_rate"), self.doc.company) # IMPORTANT: enable this only when client side code is similar to this one # self.calculate_taxes_and_totals() @@ -88,28 +88,6 @@ class BuyingController(StockController): if not item.fields.get(r): item.fields[r] = ret[r] - def validate_conversion_rate(self, currency_field, conversion_rate_field): - """common validation for currency and price list currency""" - - currency = self.doc.fields.get(currency_field) - conversion_rate = flt(self.doc.fields.get(conversion_rate_field)) - conversion_rate_label = self.meta.get_label(conversion_rate_field) - - if conversion_rate == 0: - msgprint(conversion_rate_label + _(' cannot be 0'), raise_exception=True) - - # parenthesis for 'OR' are necessary as we want it to evaluate as - # mandatory valid condition and (1st optional valid condition - # or 2nd optional valid condition) - valid_conversion_rate = (conversion_rate and - ((currency == self.company_currency and conversion_rate == 1.00) - or (currency != self.company_currency and conversion_rate != 1.00))) - - if not valid_conversion_rate: - msgprint(_('Please enter valid ') + conversion_rate_label + (': ') - + ("1 %s = [?] %s" % (currency, self.company_currency)), - raise_exception=True) - def set_total_in_words(self): from webnotes.utils import money_in_words company_currency = get_company_currency(self.doc.company) diff --git a/controllers/selling_controller.py b/controllers/selling_controller.py index 36d9d8ed7c..63b87e1eea 100644 --- a/controllers/selling_controller.py +++ b/controllers/selling_controller.py @@ -353,3 +353,10 @@ class SellingController(StockController): del tax.fields[fieldname] tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail) + + def validate_order_type(self): + valid_types = ["Sales", "Maintenance"] + if self.doc.order_type not in valid_types: + msgprint(_(self.meta.get_label("order_type")) + " " + + _("must be one of") + ": " + comma_or(valid_types), + raise_exception=True) diff --git a/selling/doctype/quotation/quotation.py b/selling/doctype/quotation/quotation.py index c154a6a3b0..7e83131204 100644 --- a/selling/doctype/quotation/quotation.py +++ b/selling/doctype/quotation/quotation.py @@ -142,6 +142,8 @@ class DocType(SellingController): #do not allow sales item in maintenance quotation and service item in sales quotation #----------------------------------------------------------------------------------------------- def validate_order_type(self): + super(DocType, self).validate_order_type() + if self.doc.order_type in ['Maintenance', 'Service']: for d in getlist(self.doclist, 'quotation_details'): is_service_item = sql("select is_service_item from `tabItem` where name=%s", d.item_code) diff --git a/selling/doctype/sales_common/sales_common.js b/selling/doctype/sales_common/sales_common.js index 1d020e6780..67c7539b05 100644 --- a/selling/doctype/sales_common/sales_common.js +++ b/selling/doctype/sales_common/sales_common.js @@ -21,6 +21,83 @@ // cur_frm.cscript.other_fname - wn.require('app/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js'); fieldname // cur_frm.cscript.sales_team_fname - Sales Team fieldname +wn.provide("erpnext.selling"); + +erpnext.selling.SellingController = wn.ui.form.Controller.extend({ + setup: function() { + + }, + + refresh: function() { + + }, + + item_code: function(doc, cdt, cdn) { + var me = this; + var item = wn.model.get_doc(cdt, cdn); + if(item.item_code) { + var fetch = true; + $.each(["company", "customer"], function(i, fieldname) { + if(!me.frm.doc[fieldname]) { + fetch = false; + msgprint(wn._("Please specify") + ": " + + wn.meta.get_label(me.frm.doc.doctype, fieldname, me.frm.doc.name) + + ". " + wn._("It is needed to fetch Item Details.")); + } + }); + + if(!fetch) { + item.item_code = null; + refresh_field("item_code", item.name, item.parentfield); + } else { + this.frm.call({ + method: "selling.utils.get_item_details", + child: item, + args: { + args: { + item_code: item.item_code, + warehouse: item.warehouse, + doctype: me.frm.doc.doctype, + customer: me.frm.doc.customer, + currency: me.frm.doc.currency, + conversion_rate: me.frm.doc.conversion_rate, + price_list_name: me.frm.doc.price_list_name, + 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 + + } + }, + callback: function(r) { + // TODO: calculate + } + }); + } + } + }, + + update_item_details: function() { + + }, + + set_dynamic_labels: function() { + + }, + + +}); + +// to save previous state of cur_frm.cscript +var prev_cscript = {}; +$.extend(prev_cscript, cur_frm.cscript); + +cur_frm.cscript = new erpnext.selling.SellingController({frm: cur_frm}); + +// for backward compatibility: combine new and previous states +$.extend(cur_frm.cscript, prev_cscript); + + // ============== Load Default Taxes =================== cur_frm.cscript.load_taxes = function(doc, cdt, cdn, callback) { // run if this is not executed from dt_map... @@ -264,7 +341,7 @@ cur_frm.cscript.price_list_name = function(doc, cdt, cdn) { // ******************** ITEM CODE ******************************** cur_frm.fields_dict[cur_frm.cscript.fname].grid.get_field("item_code").get_query = function(doc, cdt, cdn) { - if (inList(['Maintenance', 'Service'], doc.order_type)) { + if (doc.order_type == "Maintenance") { return erpnext.queries.item({ 'ifnull(tabItem.is_service_item, "No")': 'Yes' }); @@ -275,34 +352,6 @@ cur_frm.fields_dict[cur_frm.cscript.fname].grid.get_field("item_code").get_query } } - -cur_frm.cscript.item_code = function(doc, cdt, cdn) { - var fname = cur_frm.cscript.fname; - var d = locals[cdt][cdn]; - if (d.item_code) { - if (!doc.company) { - msgprint("Please select company to proceed"); - d.item_code = ''; - refresh_field('item_code', d.name, fname); - } else { - var callback = function(r, rt){ - cur_frm.cscript.recalc(doc, 1); - } - var args = { - 'item_code':d.item_code, - 'income_account':d.income_account, - 'cost_center': d.cost_center, - 'warehouse': d.warehouse - }; - get_server_fields('get_item_details',JSON.stringify(args), - fname,doc,cdt,cdn,1,callback); - } - } - if(cur_frm.cscript.custom_item_code){ - cur_frm.cscript.custom_item_code(doc, cdt, cdn); - } -} - //Barcode // cur_frm.cscript.barcode = function(doc, cdt, cdn) { diff --git a/selling/doctype/sales_order/sales_order.py b/selling/doctype/sales_order/sales_order.py index 6a52e5a0fa..c74e7e1f9d 100644 --- a/selling/doctype/sales_order/sales_order.py +++ b/selling/doctype/sales_order/sales_order.py @@ -194,6 +194,8 @@ class DocType(SellingController): and current Sales Order""" % (self.doc.order_type, d.prevdoc_docname)) def validate_order_type(self): + super(DocType, self).validate_order_type() + #validate delivery date if self.doc.order_type == 'Sales' and not self.doc.delivery_date: msgprint("Please enter 'Expected Delivery Date'") diff --git a/selling/utils.py b/selling/utils.py index 21e94f7234..23574dfa9e 100644 --- a/selling/utils.py +++ b/selling/utils.py @@ -16,6 +16,9 @@ from __future__ import unicode_literals import webnotes +from webnotes import msgprint, _ +from webnotes.utils import flt +import json def get_customer_list(doctype, txt, searchfield, start, page_len, filters): if webnotes.conn.get_default("cust_master_name") == "Customer Name": @@ -29,4 +32,100 @@ def get_customer_list(doctype, txt, searchfield, start, page_len, filters): 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 + ("%%%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, + "price_list_name": None, + "price_list_currency": None, + "plc_conversion_rate": 1.0 + } + """ + if isinstance(args, basestring): + args = json.loads(args) + args = webnotes._dict(args) + + item_bean = webnotes.bean("Item", args.item_code) + + _validate_item_details(args, item_bean.doc) + + out = _get_basic_details(args, item_bean) + + if args.price_list_name and args.price_list_currency: + out.update(_get_price_list_rate(args, item_bean)) + + if out.warehouse or out.reserved_warehouse: + out.update(_get_available_qty(args, out.warehouse or out.reserved_warehouse)) + + out.customer_item_code = _get_customer_item_code(args, item_bean) + + return out + +def _validate_item_details(args, item): + from 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": + msgprint(_("Item") + (" %s: " % item.name) + + _("not a service item.") + + _("Please select a service item or change the order type to Sales."), + raise_exception=True) + + elif item.is_sales_item != "Yes": + msgprint(_("Item") + (" %s: " % item.name) + _("not a sales item"), + raise_exception=True) + +def _get_basic_details(args, item_bean): + item = item_bean.doc + out = webnotes._dict({ + "description": item.description_html or item.description, + "reserved_warehouse": item.default_warehouse, + "warehouse": item.default_warehouse or args.warehouse, + "income_account": item.default_income_account or args.income_account, + "expense_account": item.purchase_account or args.expense_account, + "cost_center": item.default_sales_cost_center or args.cost_center, + "qty": 1.0, + "adj_rate": 0.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): + base_ref_rate = item_bean.doclist.get({ + "parentfield": "ref_rate_details", + "price_list_name": args.price_list_name, + "price_list_currency": args.price_list_currency, + "selling": 1}) + out = webnotes._dict() + out.base_ref_rate = flt(base_ref_rate[0].ref_rate) if base_ref_rate else 0.0 + out.basic_rate = out.base_ref_rate + out.ref_rate = out.base_ref_rate / flt(args.conversion_rate) + out.export_rate = out.ref_rate + return out + +def _get_available_qty(args, warehouse): + return webnotes.conn.get_value("Bin", {"item_code": args.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 + \ No newline at end of file diff --git a/setup/utils.py b/setup/utils.py index 1a86921692..33fa3e286e 100644 --- a/setup/utils.py +++ b/setup/utils.py @@ -46,4 +46,4 @@ def get_price_list_currency(args): if result and len(result)==1: return {"price_list_currency": result[0][0]} else: - return {} \ No newline at end of file + return {} diff --git a/utilities/transaction_base.py b/utilities/transaction_base.py index 5d7d1a84b1..540b385d51 100644 --- a/utilities/transaction_base.py +++ b/utilities/transaction_base.py @@ -16,6 +16,7 @@ from __future__ import unicode_literals import webnotes +from webnotes import msgprint, _ from webnotes.utils import load_json, cstr, flt, now_datetime from webnotes.model.doc import addchild @@ -268,4 +269,42 @@ class TransactionBase(DocListController): def validate_posting_time(self): if not self.doc.posting_time: self.doc.posting_time = now_datetime().strftime('%H:%M:%S') - \ No newline at end of file + +def validate_conversion_rate(currency, conversion_rate, conversion_rate_label, company): + """common validation for currency and price list currency""" + if conversion_rate == 0: + msgprint(conversion_rate_label + _(' cannot be 0'), raise_exception=True) + + company_currency = webnotes.conn.get_value("Company", company, "default_currency") + + # parenthesis for 'OR' are necessary as we want it to evaluate as + # mandatory valid condition and (1st optional valid condition + # or 2nd optional valid condition) + valid_conversion_rate = (conversion_rate and + ((currency == company_currency and conversion_rate == 1.00) + or (currency != company_currency and conversion_rate != 1.00))) + + if not valid_conversion_rate: + msgprint(_('Please enter valid ') + conversion_rate_label + (': ') + + ("1 %s = [?] %s" % (currency, company_currency)), + raise_exception=True) + +def validate_item_fetch(args, item): + from stock.utils import validate_end_of_life + validate_end_of_life(item.name, item.end_of_life) + + # validate company + if not args.company: + msgprint(_("Please specify Company"), raise_exception=True) + + # validate conversion rates + meta = webnotes.get_doctype(args.doctype) + if meta.get_field("currency"): + # validate conversion rate + validate_conversion_rate(args.currency, args.conversion_rate, + meta.get_label("conversion_rate"), args.company) + + # validate price list conversion rate + if args.price_list_name and args.price_list_currency: + validate_conversion_rate(args.price_list_currency, args.plc_conversion_rate, + meta.get_label("plc_conversion_rate"), args.company) \ No newline at end of file