From 9b955feff82986e411d073ca76803b69bea725a3 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Mon, 5 Jan 2015 16:19:12 +0530 Subject: [PATCH] [design] POS - item grid and taxes --- erpnext/accounts/doctype/sales_invoice/pos.py | 49 +++--- .../doctype/sales_invoice/sales_invoice.js | 14 +- erpnext/projects/doctype/project/project.py | 2 +- erpnext/public/build.json | 2 + erpnext/public/css/erpnext.css | 41 ++++- erpnext/public/js/pos/pos.html | 25 ++- erpnext/public/js/pos/pos.js | 145 +++++------------- erpnext/public/js/pos/pos_bill_item.html | 3 +- erpnext/public/js/pos/pos_item.html | 10 ++ erpnext/public/js/pos/pos_tax_row.html | 4 + erpnext/public/js/transaction.js | 3 +- .../setup/page/setup_wizard/setup_wizard.py | 2 +- erpnext/stock/get_item_details.py | 2 +- 13 files changed, 147 insertions(+), 155 deletions(-) create mode 100644 erpnext/public/js/pos/pos_item.html create mode 100644 erpnext/public/js/pos/pos_tax_row.html diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index f6e808dee3..986568da29 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe @frappe.whitelist() -def get_items(price_list, sales_or_purchase, item=None, item_group=None): +def get_items(price_list, sales_or_purchase, item=None): condition = "" args = {"price_list": price_list} @@ -14,10 +14,21 @@ def get_items(price_list, sales_or_purchase, item=None, item_group=None): else: condition = "i.is_purchase_item='Yes'" - if item_group and item_group != "All Item Groups": - condition += " and i.item_group='%s'" % item_group.replace("'", "\'") - if item: + # search serial no + item_code = frappe.db.sql("""select name as serial_no, item_code + from `tabSerial No` where name=%s""", (item), as_dict=1) + if item_code: + item_code[0]["name"] = item_code[0]["item_code"] + return item_code + + # search barcode + item_code = frappe.db.sql("""select name from `tabItem` where barcode=%s""", + (item), as_dict=1) + if item_code: + item_code[0]["barcode"] = item + return item_code + condition += " and CONCAT(i.name, i.item_name) like %(name)s" args["name"] = "%%%s%%" % item @@ -31,21 +42,21 @@ def get_items(price_list, sales_or_purchase, item=None, item_group=None): where %s""" % ('%(price_list)s', condition), args, as_dict=1) -@frappe.whitelist() -def get_item_code(barcode_serial_no): - input_via = "serial_no" - item_code = frappe.db.sql("""select name, item_code from `tabSerial No` where - name=%s""", (barcode_serial_no), as_dict=1) - - if not item_code: - input_via = "barcode" - item_code = frappe.db.sql("""select name from `tabItem` where barcode=%s""", - (barcode_serial_no), as_dict=1) - - if item_code: - return item_code, input_via - else: - frappe.throw(frappe._("Invalid Barcode or Serial No")) +# @frappe.whitelist() +# def get_item_code(barcode_serial_no): +# input_via = "serial_no" +# item_code = frappe.db.sql("""select name, item_code from `tabSerial No` where +# name=%s""", (barcode_serial_no), as_dict=1) +# +# if not item_code: +# input_via = "barcode" +# item_code = frappe.db.sql("""select name from `tabItem` where barcode=%s""", +# (barcode_serial_no), as_dict=1) +# +# if item_code: +# return item_code, input_via +# else: +# frappe.throw(frappe._("Invalid Barcode or Serial No")) @frappe.whitelist() def get_mode_of_payment(): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index a77ccfe935..a040438c26 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -292,13 +292,13 @@ cur_frm.fields_dict.debit_to.get_query = function(doc) { } cur_frm.fields_dict.cash_bank_account.get_query = function(doc) { - return{ - filters: { - 'account_type': 'Receivable', - 'root_type': 'Asset', - 'group_or_ledger': 'Ledger', - 'company': doc.company - } + return { + filters: [ + ["Account", "account_type", "in", ["Cash", "Bank"]], + ["Account", "root_type", "=", "Asset"], + ["Account", "group_or_ledger", "=", "Ledger"], + ["Account", "company", "=", doc.company] + ] } } diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 8cf58c8b6d..8dc65a6da1 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -12,7 +12,7 @@ from frappe.model.document import Document class Project(Document): def get_feed(self): - return self.status + return '{0}: {1}'.format(_(self.status), self.project_name) def get_gross_profit(self): pft, per_pft =0, 0 diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 588f4ec86b..60de89c8e5 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -16,6 +16,8 @@ "public/js/templates/contact_list.html", "public/js/pos/pos.html", "public/js/pos/pos_bill_item.html", + "public/js/pos/pos_item.html", + "public/js/pos/pos_tax_row.html", "public/js/pos/pos.js" ], "css/shopping-cart-web.css": [ diff --git a/erpnext/public/css/erpnext.css b/erpnext/public/css/erpnext.css index 91d1da23b8..fc42be1cee 100644 --- a/erpnext/public/css/erpnext.css +++ b/erpnext/public/css/erpnext.css @@ -20,11 +20,32 @@ } .pos-item { - height: 200px; + display: inline-block; overflow: hidden; + text-overflow: ellipsis; cursor: pointer; - padding-left: 5px !important; - padding-right: 5px !important; + padding: 10px; + height: 0px; + padding-bottom: 35%; + width: 30%; + margin: 1.6%; + border: 1px solid #d1d8dd; +} + +.pos-item .item-code { + margin-bottom: 0px; +} + +.pos-item .no-image { + background-color: #fafbfc; + border: 1px dashed #d1d8dd; +} + +.pos-item-image { + padding-bottom: 100%; + background-size: cover; + background-position: center; + border: 1px solid transparent; } .pos-item-area { @@ -38,7 +59,7 @@ } .item-list-area { - padding: 15px; + padding: 15px 0px; } .pos-toolbar, .pos-bill-toolbar { @@ -54,7 +75,7 @@ .pos-bill-wrapper { border: 1px solid #d1d8dd; border-top: none; - border-right: none; + margin-right: -1px; } .pos-bill { @@ -82,7 +103,15 @@ } .pos-qty-btn { - margin-top: 5px; + margin-top: 3px; cursor: pointer; font-size: 120%; } + +.pos .search-area .form-group { + max-width: 100% !important; +} + +.pos .tax-table { + margin-bottom: 10px; +} diff --git a/erpnext/public/js/pos/pos.html b/erpnext/public/js/pos/pos.html index dd323bcc41..d26e0b606c 100644 --- a/erpnext/public/js/pos/pos.html +++ b/erpnext/public/js/pos/pos.html @@ -2,9 +2,9 @@
-
-
- +
+
+
@@ -17,12 +17,15 @@
-
+
{%= __("Net Total") %}
-
-
{%= __("Taxes") %}
+
+
+
{%= __("Taxes") %}
+
+
{%= __("Discount Amount") %}
@@ -41,14 +44,10 @@
-
-
-
+
-
-
-
-
+
+
diff --git a/erpnext/public/js/pos/pos.js b/erpnext/public/js/pos/pos.js index fe3748397d..ae4c6a5c2c 100644 --- a/erpnext/public/js/pos/pos.js +++ b/erpnext/public/js/pos/pos.js @@ -19,8 +19,9 @@ erpnext.POS = Class.extend({ frappe.model.set_value(me.frm.doctype, me.frm.docname, "discount_amount", this.value); }); - this.call_function("remove-items", function() {me.remove_selected_items();}); - this.call_function("make-payment", function() {me.make_payment();}); + this.wrapper.find(".make-payment").on("click", function() { + me.make_payment(); + }) }, check_transaction_type: function() { var me = this; @@ -45,14 +46,9 @@ erpnext.POS = Class.extend({ // this.amount = export_or_import + "_amount"; // this.rate = export_or_import + "_rate"; }, - call_function: function(class_name, fn, event_name) { - this.wrapper.find("." + class_name).on(event_name || "click", fn); - }, make: function() { this.make_party(); - this.make_barcode(); this.make_search(); - this.make_item_group(); this.make_item_list(); }, make_party: function() { @@ -75,25 +71,6 @@ erpnext.POS = Class.extend({ me.party.toLowerCase(), this.value); }); }, - make_barcode: function() { - var me = this; - this.barcode = frappe.ui.form.make_control({ - df: { - "fieldtype": "Data", - "label": "Barcode", - "fieldname": "pos_barcode", - "placeholder": "Barcode / Serial No" - }, - parent: this.wrapper.find(".barcode-area"), - only_input: true, - }); - this.barcode.make_input(); - this.barcode.$input.on("keypress", function() { - if(me.barcode_timeout) - clearTimeout(me.barcode_timeout); - me.barcode_timeout = setTimeout(function() { me.add_item_thru_barcode(); }, 1000); - }); - }, make_search: function() { var me = this; this.search = frappe.ui.form.make_control({ @@ -114,25 +91,6 @@ erpnext.POS = Class.extend({ me.item_timeout = setTimeout(function() { me.make_item_list(); }, 1000); }); }, - make_item_group: function() { - var me = this; - this.item_group = frappe.ui.form.make_control({ - df: { - "fieldtype": "Link", - "options": "Item Group", - "label": "Item Group", - "fieldname": "pos_item_group", - "placeholder": "Item Group" - }, - parent: this.wrapper.find(".item-group-area"), - only_input: true, - }); - this.item_group.make_input(); - this.item_group.$input.on("change", function() { - if(!me.item_group.autocomplete_open) - me.make_item_list(); - }); - }, make_item_list: function() { var me = this; if(!this.price_list) { @@ -146,40 +104,40 @@ erpnext.POS = Class.extend({ args: { sales_or_purchase: this.sales_or_purchase, price_list: this.price_list, - item_group: this.item_group.$input.val(), item: this.search.$input.val() }, callback: function(r) { var $wrap = me.wrapper.find(".item-list"); me.wrapper.find(".item-list").empty(); if (r.message) { - $.each(r.message, function(index, obj) { - if (obj.image) - image = ''; - else - image = '
'; + if (r.message.length === 1) { + var item = r.message[0]; + if (item.serial_no) { + me.add_to_cart(item.item_code, item.serial_no); + this.search.$input.val(""); + return; - $(repl('
\ -
%(item_image)s
\ -
%(item_code)s
\ -
%(item_name)s
\ -
%(item_price)s
\ -
', - { - item_code: obj.name, - item_price: format_currency(obj.price_list_rate, obj.currency), - item_name: obj.name===obj.item_name ? "" : obj.item_name, - item_image: image - })).appendTo($wrap); + } else if (item.barcode) { + me.add_to_cart(item.item_code); + this.search.$input.val(""); + return; + } + } + + $.each(r.message, function(index, obj) { + $(frappe.render_template("pos_item", { + item_code: obj.name, + item_price: format_currency(obj.price_list_rate, obj.currency), + item_name: obj.name===obj.item_name ? "" : obj.item_name, + item_image: obj.image + })).appendTo($wrap); }); } // if form is local then allow this function $(me.wrapper).find("div.pos-item").on("click", function() { if(me.frm.doc.docstatus==0) { - console.log($(this).attr("data-item_code")); - me.add_to_cart($(this).attr("data-item_code")); + me.add_to_cart($(this).attr("data-item-code")); } }); } @@ -224,6 +182,7 @@ erpnext.POS = Class.extend({ var child = frappe.model.add_child(me.frm.doc, this.frm.doctype + " Item", "items"); child.item_code = item_code; + child.qty = 1; if (serial_no) child.serial_no = serial_no; @@ -240,7 +199,6 @@ erpnext.POS = Class.extend({ } }, update_qty: function(item_code, qty) { - console.log([item_code, qty]); var me = this; $.each(this.frm.doc["items"] || [], function(i, d) { if (d.item_code == item_code) { @@ -260,7 +218,6 @@ erpnext.POS = Class.extend({ this.refresh_item_list(); this.party_field.set_input(this.frm.doc[this.party.toLowerCase()]); this.wrapper.find('input.discount-amount').val(this.frm.doc.discount_amount); - this.barcode.set_input(""); this.show_items_in_item_cart(); this.show_taxes(); @@ -294,9 +251,8 @@ erpnext.POS = Class.extend({ $.each(this.frm.doc.items|| [], function(i, d) { $(frappe.render_template("pos_bill_item", { item_code: d.item_code, - item_name: d.item_name===d.item_code ? "" : ("
" + d.item_name), + item_name: (d.item_name===d.item_code || !d.item_name) ? "" : ("
" + d.item_name), qty: d.qty, - actual_qty: d.actual_qty, rate: format_currency(d.rate, me.frm.doc.currency), amount: format_currency(d.amount, me.frm.doc.currency) })).appendTo($items); @@ -309,21 +265,17 @@ erpnext.POS = Class.extend({ show_taxes: function() { var me = this; var taxes = this.frm.doc["taxes"] || []; - $(this.wrapper).find(".tax-table") - .toggle((taxes && taxes.length) ? true : false) - .find("tbody").empty(); + $(this.wrapper) + .find(".tax-area").toggleClass("hide", (taxes && taxes.length) ? false : true) + .find(".tax-table").empty(); $.each(taxes, function(i, d) { if (d.tax_amount) { - $(repl('\ - %(description)s %(rate)s\ - %(tax_amount)s\ - ', { + $(frappe.render_template("pos_tax_row", { description: d.description, - rate: ((d.charge_type == "Actual") ? '' : ("(" + d.rate + "%)")), tax_amount: format_currency(flt(d.tax_amount)/flt(me.frm.doc.conversion_rate), me.frm.doc.currency) - })).appendTo(".tax-table tbody"); + })).appendTo(me.wrapper.find(".tax-table")); } }); }, @@ -345,7 +297,7 @@ erpnext.POS = Class.extend({ // append quantity to the respective item after change from input box $(this.wrapper).find("input.pos-item-qty").on("change", function() { - var item_code = $(this).closest("tr").attr("id"); + var item_code = $(this).parents(".pos-bill-item").attr("data-item-code"); me.update_qty(item_code, $(this).val()); }); @@ -374,7 +326,7 @@ erpnext.POS = Class.extend({ }, focus: function() { if(me.frm.doc[this.party].toLowerCase()) { - this.barcode.$input.focus(); + this.search.$input.focus(); } else { if(!(this.frm.doctype == "Quotation" && this.frm.doc.quotation_to!="Customer")) this.party_field.$input.focus(); @@ -393,39 +345,24 @@ erpnext.POS = Class.extend({ var me = this; // if form is submitted & cancelled then disable all input box & buttons $(this.wrapper) - .find(".remove-items, .make-payment, .pos-qty-btn") + .find(".pos-qty-btn") .toggle(this.frm.doc.docstatus===0); $(this.wrapper).find('input, button').prop("disabled", !(this.frm.doc.docstatus===0)); + + this.wrapper.find(".pos-item-area").toggleClass("hide", me.frm.doc.docstatus!==0); + }, hide_payment_button: function() { + var toggle = !(this.frm.doctype == "Sales Invoice" && this.frm.doc.is_pos && this.frm.doc.docstatus===1); $(this.wrapper) .find(".make-payment") - .toggle(this.frm.doctype == "Sales Invoice" && this.frm.doc.is_pos); + .toggleClass("hide", toggle) + .prop("disabled", toggle); }, refresh_delete_btn: function() { $(this.wrapper).find(".remove-items").toggle($(".item-cart .warning").length ? true : false); }, - add_item_thru_barcode: function() { - var me = this; - me.barcode_timeout = null; - frappe.call({ - method: 'erpnext.accounts.doctype.sales_invoice.pos.get_item_code', - args: {barcode_serial_no: this.barcode.$input.val()}, - callback: function(r) { - if (r.message) { - if (r.message[1] == "serial_no") - me.add_to_cart(r.message[0][0].item_code, r.message[0][0].name); - else - me.add_to_cart(r.message[0][0].name); - } - else - msgprint(__("Invalid Barcode")); - - me.refresh(); - } - }); - }, remove_selected_items: function() { var me = this; var selected_items = []; @@ -457,7 +394,7 @@ erpnext.POS = Class.extend({ }, make_payment: function() { var me = this; - var no_of_items = $(this.wrapper).find("#cart tbody tr").length; + var no_of_items = this.frm.doc.items.length; var mode_of_payment = []; if (no_of_items == 0) diff --git a/erpnext/public/js/pos/pos_bill_item.html b/erpnext/public/js/pos/pos_bill_item.html index e45536f9c4..61f19022d1 100644 --- a/erpnext/public/js/pos/pos_bill_item.html +++ b/erpnext/public/js/pos/pos_bill_item.html @@ -1,11 +1,10 @@
-
{%= item_code %}{%= item_name %}
+
{%= item_code || "" %}{%= item_name || "" %}
-
{%= actual_qty %}
diff --git a/erpnext/public/js/pos/pos_item.html b/erpnext/public/js/pos/pos_item.html new file mode 100644 index 0000000000..246eb4eb9c --- /dev/null +++ b/erpnext/public/js/pos/pos_item.html @@ -0,0 +1,10 @@ +
+
+
+
{%= item_code %}
+
+ {% if (item_name) { %}{%= item_name %}
{% } %} + {%= item_price %} +
+
diff --git a/erpnext/public/js/pos/pos_tax_row.html b/erpnext/public/js/pos/pos_tax_row.html new file mode 100644 index 0000000000..788eb1f337 --- /dev/null +++ b/erpnext/public/js/pos/pos_tax_row.html @@ -0,0 +1,4 @@ +
+
{%= description %}
+
{%= tax_amount %}
+
diff --git a/erpnext/public/js/transaction.js b/erpnext/public/js/transaction.js index f8c0e7c737..3b6beed889 100644 --- a/erpnext/public/js/transaction.js +++ b/erpnext/public/js/transaction.js @@ -154,7 +154,8 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({ ignore_pricing_rule: me.frm.doc.ignore_pricing_rule, doctype: item.doctype, name: item.name, - project_name: item.project_name || me.frm.doc.project_name + project_name: item.project_name || me.frm.doc.project_name, + qty: item.qty } }, diff --git a/erpnext/setup/page/setup_wizard/setup_wizard.py b/erpnext/setup/page/setup_wizard/setup_wizard.py index c93e4044cc..83910239cf 100644 --- a/erpnext/setup/page/setup_wizard/setup_wizard.py +++ b/erpnext/setup/page/setup_wizard/setup_wizard.py @@ -230,7 +230,7 @@ def create_feed_and_todo(): """update activty feed and create todo for creation of item, customer, vendor""" frappe.get_doc({ "doctype": "Feed", - "feedtype": "Comment", + "feed_type": "Comment", "subject": "ERPNext Setup Complete!" }).insert(ignore_permissions=True) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e139c9f3cc..c834007b99 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -158,7 +158,7 @@ def get_basic_details(args, item): "uom": item.stock_uom, "min_order_qty": flt(item.min_order_qty) if args.parenttype == "Material Request" else "", "conversion_factor": 1.0, - "qty": 0.0, + "qty": args.qty or 0.0, "stock_qty": 0.0, "price_list_rate": 0.0, "base_price_list_rate": 0.0,