[enhancement] Item Selector

This commit is contained in:
Rushabh Mehta 2016-04-07 15:25:43 +05:30
parent 9b8937d257
commit 203cc962f5
13 changed files with 196 additions and 43 deletions

View File

@ -50,6 +50,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
},
load_defaults: function() {
this.frm.show_print_first = true;
if(this.frm.doc.__islocal && this.frm.doc.company) {
frappe.model.set_default_values(this.frm.doc);
$.each(this.frm.doc.accounts || [], function(i, jvd) {

View File

@ -160,10 +160,10 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
return tax_accounts
def item_query(doctype, txt, searchfield, start, page_len, filters):
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
conditions = []
return frappe.db.sql("""select tabItem.name,tabItem.item_group,
return frappe.db.sql("""select tabItem.name, tabItem.item_group, tabItem.image,
if(length(tabItem.item_name) > 40,
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
if(length(tabItem.description) > 40, \
@ -192,7 +192,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
"_txt": txt.replace("%", ""),
"start": start,
"page_len": page_len
})
}, as_dict=as_dict)
def bom(doctype, txt, searchfield, start, page_len, filters):
conditions = []

View File

@ -18,6 +18,8 @@
"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"
"public/js/pos/pos.js",
"public/js/utils/item_selector.js",
"public/js/templates/item_selector.html"
]
}

View File

@ -13,17 +13,20 @@
margin: -10px auto;
}
/* pos */
.pos-item-area {
padding: 0px 10px;
}
.pos-item-wrapper {
padding: 5px;
}
.pos-item {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
padding: 5px;
height: 0px;
padding-bottom: 38%;
width: 30%;
margin: 1.6%;
padding-bottom: 15px;
border: 1px solid #d1d8dd;
margin-bottom: 5px;
}
.pos-item-text {
padding: 0px 5px;
@ -36,7 +39,13 @@
border: 1px dashed #d1d8dd;
}
.pos-item-image {
padding-bottom: 100%;
width: 100%;
height: 0px;
padding: 50% 0;
text-align: center;
line-height: 0;
color: #fff;
font-size: 30px;
background-size: cover;
border: 1px solid transparent;
background-position: top center;

View File

@ -8,6 +8,7 @@ frappe.require("assets/erpnext/js/utils.js");
erpnext.TransactionController = erpnext.taxes_and_totals.extend({
onload: function() {
var me = this;
this.frm.show_print_first = true;
if(this.frm.doc.__islocal) {
var today = get_today(),
currency = frappe.defaults.get_user_default("currency");
@ -81,7 +82,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
me.calculate_taxes_and_totals();
}
if(frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "item_code")) {
cur_frm.get_field("items").grid.set_multiple_add("item_code", "qty");
this.setup_item_selector();
}
},
@ -890,7 +891,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
rate = flt(item.rate) * flt(this.frm.doc.conversion_rate || 1);
item.gross_profit = flt(((rate - item.valuation_rate) * item.qty), precision("amount", item));
}
},
setup_item_selector: function() {
if(!this.item_selector) {
this.item_selector = new erpnext.ItemSelector({frm: this.frm});
}
}
});
frappe.ui.form.on(cur_frm.doctype + " Item", "rate", function(frm, cdt, cdn) {

View File

@ -47,7 +47,10 @@
</div>
</div>
</div>
<div class="col-sm-7 pos-item-area">
<div class="col-sm-7 pos-items-section">
<div class="row pos-item-area">
</div>
<div class="row pos-item-toolbar">
<div class="search-area col-xs-12"></div>
</div>

View File

@ -131,7 +131,9 @@ erpnext.pos.PointOfSale = Class.extend({
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 ? "url('" + obj.image + "')" : null
item_image: obj.image ? "url('" + obj.image + "')" : null,
color: frappe.get_palette(obj.item_name),
abbr: frappe.get_abbr(obj.item_name)
})).tooltip().appendTo($wrap);
});
}

View File

@ -1,9 +1,13 @@
<div class="pos-item-wrapper col-xs-3">
<div class="pos-item" data-item-code="{%= item_code %}" title="{%= item_name || item_code %}">
<div class="pos-item-image {% if (!item_image) { %} no-image {% } %}"
style="{% if (item_image) { %} background-image: {%= item_image %} {% } %}">
<div class="pos-item-image"
style="{% if (item_image) { %} background-image: {{ item_image }} {% }
else { %} background-color: {{ color }} {% } %}">
{% if (!item_image) { %}{{ abbr }}{% } %}
</div>
<div class="pos-item-text">
<h6 class="item-code text-ellipsis">{%= item_name ? (item_name + " (" + item_code + ")") : item_code %}</h6>
<div class="small text-ellipsis">{%= item_price %}</div>
</div>
</div>
</div>

View File

@ -0,0 +1,16 @@
<div class="row pos-item-area">
{% for (var i=0; i < data.length; i++) { var item = data[i]; %}
<div class="col-xs-3 pos-item-wrapper">
<div class="pos-item" data-name="{{ item.name }}">
<div class="pos-item-image"
{% if(item.image) { %}style="background-image: url({{ item.image }});"{% }
else { %}style="background-color: {{ item.color }};"{% } %}>
{% if(!item.image) { %}{{ item.abbr }}{% } %}
</div>
<div class="pos-item-text">
<h6 class="item-code text-ellipsis">{{ item.name }}</h6>
</div>
</div>
</div>
{% } %}
</div>

View File

@ -0,0 +1,94 @@
erpnext.ItemSelector = Class.extend({
init: function(opts) {
$.extend(this, opts);
this.grid = this.frm.get_field("items").grid;
this.setup();
},
setup: function() {
var me = this;
if(!this.grid.add_items_button) {
this.grid.add_items_button = this.grid.add_custom_button(__('Add Items'), function() {
if(!me.dialog) {
me.make_dialog();
}
me.dialog.show();
me.render_items();
});
}
},
make_dialog: function() {
this.dialog = new frappe.ui.Dialog({
title: __('Add Items')
});
var body = $(this.dialog.body);
body.html('<div><p><input type="text" class="form-control"></p>\
<br><div class="results"></div></div>');
this.dialog.input = body.find('.form-control');
this.dialog.results = body.find('.results');
var me = this;
this.dialog.results.on('click', '.pos-item', function() {
me.add_item($(this).attr('data-name'))
});
this.dialog.input.on('keyup', function() {
if(me.timeout_id) {
clearTimeout(me.timeout_id);
}
me.timeout_id = setTimeout(function() {
me.render_items();
me.timeout_id = undefined;
}, 500);
});
},
add_item: function(item_code) {
// add row or update qty
var added = false;
// find row with item if exists
$.each(this.frm.doc.items || [], function(i, d) {
if(d.item_code===item_code) {
frappe.model.set_value(d.doctype, d.name, 'qty', d.qty + 1);
show_alert(__("Added {0} ({1})", [item_code, d.qty]));
added = true;
return false;
}
});
if(!added) {
var d = this.grid.add_new_row();
frappe.model.set_value(d.doctype, d.name, 'item_code', item_code);
// after item fetch
frappe.after_ajax(function() {
setTimeout(function() {
frappe.model.set_value(d.doctype, d.name, 'qty', 1);
show_alert(__("Added {0} ({1})", [item_code, 1]));
}, 100);
});
}
},
render_items: function() {
var args = erpnext.queries.item();
args.txt = this.dialog.input.val();
args.as_dict = 1;
var me = this;
frappe.link_search("Item", args, function(r) {
$.each(r.values, function(i, d) {
if(!d.image) {
d.abbr = frappe.get_abbr(d.item_name);
d.color = frappe.get_palette(d.item_name);
}
});
me.dialog.results.html(frappe.render_template('item_selector', {'data':r.values}));
});
}
})

View File

@ -16,20 +16,23 @@
}
/* pos */
.pos {
.pos-item-area {
padding: 0px 10px;
}
.pos-item-wrapper {
padding: 5px;
}
.pos-item {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
padding: 5px;
height: 0px;
padding-bottom: 38%;
width: 30%;
margin: 1.6%;
padding-bottom: 15px;
border: 1px solid #d1d8dd;
margin-bottom: 5px;
}
.pos-item-text {
@ -46,7 +49,13 @@
}
.pos-item-image {
padding-bottom: 100%;
width: 100%;
height: 0px;
padding: 50% 0;
text-align: center;
line-height: 0;
color: #fff;
font-size: 30px;
background-size: cover;
border: 1px solid transparent;
background-position: top center;

View File

@ -300,7 +300,7 @@
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible": 1,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"hidden": 0,
@ -308,7 +308,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "",
"label": "Currency and Price List",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@ -525,7 +525,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "accounts",
"collapsible_depends_on": "",
"fieldname": "default_receivable_accounts",
"fieldtype": "Section Break",
"hidden": 0,
@ -533,7 +533,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Default Receivable Accounts",
"label": "Accounting",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@ -927,7 +927,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-04-06 03:15:14.488537",
"modified": "2016-04-07 01:25:25.676480",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",

View File

@ -55,12 +55,15 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
onload_post_render: function() {
var me = this;
cur_frm.get_field("items").grid.set_multiple_add("item_code", "qty");
this.set_default_account(function() {
if(me.frm.doc.__islocal && me.frm.doc.company && !me.frm.doc.amended_from) {
cur_frm.script_manager.trigger("company");
}
});
if(!this.item_selector) {
this.item_selector = new erpnext.ItemSelector({frm: this.frm});
}
},
refresh: function() {