Update cart ui from cur_frm, Add number pad

This commit is contained in:
Faris Ansari 2017-08-14 18:25:59 +05:30
parent b1daab4284
commit 0f3d431476
3 changed files with 305 additions and 115 deletions

View File

@ -23,6 +23,9 @@
width: 40%;
margin-left: 15px;
}
.cart-wrapper {
margin-bottom: 10px;
}
.cart-wrapper .list-item__content:not(:first-child) {
justify-content: flex-end;
}
@ -30,6 +33,10 @@
height: 200px;
overflow: auto;
}
.cart-items input {
height: 22px;
font-size: 12px;
}
.fields {
display: flex;
}
@ -63,3 +70,38 @@
left: 50%;
transform: translate(-50%, -50%);
}
@keyframes yellow-fade {
0% {
background-color: #fffce7;
}
100% {
background-color: transparent;
}
}
.highlight {
animation: yellow-fade 1s ease-in 1;
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.number-pad {
border-collapse: collapse;
cursor: pointer;
display: table;
margin: auto;
}
.num-row {
display: table-row;
}
.num-col {
display: table-cell;
border: 1px solid #d1d8dd;
}
.num-col > div {
width: 50px;
height: 50px;
text-align: center;
line-height: 50px;
}

View File

@ -35,6 +35,7 @@
}
.cart-wrapper {
margin-bottom: 10px;
.list-item__content:not(:first-child) {
justify-content: flex-end;
}
@ -43,6 +44,11 @@
.cart-items {
height: 200px;
overflow: auto;
input {
height: 22px;
font-size: @text-medium;
}
}
.fields {
@ -84,4 +90,42 @@
left: 50%;
transform: translate(-50%, -50%);
}
}
@keyframes yellow-fade {
0% {background-color: @light-yellow;}
100% {background-color: transparent;}
}
.highlight {
animation: yellow-fade 1s ease-in 1;
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
// number pad
.number-pad {
border-collapse: collapse;
cursor: pointer;
display: table;
margin: auto;
}
.num-row {
display: table-row;
}
.num-col {
display: table-cell;
border: 1px solid @border-color;
& > div {
width: 50px;
height: 50px;
text-align: center;
line-height: 50px;
}
}

View File

@ -1,3 +1,5 @@
/* global Clusterize */
frappe.pages['point-of-sale'].on_page_load = function(wrapper) {
var page = frappe.ui.make_app_page({
parent: wrapper,
@ -5,11 +7,11 @@ frappe.pages['point-of-sale'].on_page_load = function(wrapper) {
single_column: true
});
wrapper.pos = new erpnext.PointOfSale(wrapper);
cur_pos = wrapper.pos;
wrapper.pos = new PointOfSale(wrapper);
window.cur_pos = wrapper.pos;
}
erpnext.PointOfSale = class PointOfSale {
class PointOfSale {
constructor(wrapper) {
this.wrapper = $(wrapper).find('.layout-main-section');
this.page = wrapper.page;
@ -20,24 +22,25 @@ erpnext.PointOfSale = class PointOfSale {
];
frappe.require(assets, () => {
this.prepare().then(() => {
this.make();
this.bind_events();
});
this.make();
});
}
prepare() {
this.set_online_status();
this.prepare_menu();
this.make_sales_invoice_frm()
return this.get_pos_profile();
}
make() {
this.make_dom();
this.make_cart();
this.make_items();
return frappe.run_serially([
() => {
this.prepare_dom();
this.prepare_menu();
this.set_online_status();
},
() => this.make_sales_invoice_frm(),
() => this.setup_pos_profile(),
() => {
this.make_cart();
this.make_items();
this.bind_events();
}
]);
}
set_online_status() {
@ -54,7 +57,7 @@ erpnext.PointOfSale = class PointOfSale {
});
}
make_dom() {
prepare_dom() {
this.wrapper.append(`
<div class="pos">
<section class="cart-container">
@ -68,29 +71,64 @@ erpnext.PointOfSale = class PointOfSale {
}
make_cart() {
this.cart = new erpnext.POSCart(this.wrapper.find('.cart-container'));
}
make_items() {
this.items = new erpnext.POSItems({
wrapper: this.wrapper.find('.item-container'),
pos_profile: this.pos_profile,
this.cart = new POSCart({
wrapper: this.wrapper.find('.cart-container'),
events: {
item_click: (item_code) => this.add_item_to_cart(item_code)
customer_change: (customer) => this.cur_frm.set_value('customer', customer),
increase_qty: (item_code) => {
this.add_item_to_cart(item_code);
},
decrease_qty: (item_code) => {
this.add_item_to_cart(item_code, -1);
}
}
});
}
add_item_to_cart(item_code) {
const item = this.items.get(item_code);
this.cart.add_item(item);
make_items() {
this.items = new POSItems({
wrapper: this.wrapper.find('.item-container'),
pos_profile: this.pos_profile,
events: {
item_click: (item_code) => {
if(!this.cur_frm.doc.customer) {
frappe.throw(__('Please select a customer'));
}
this.add_item_to_cart(item_code);
}
}
});
}
add_item_to_cart(item_code, qty = 1) {
if(this.cart.exists(item_code)) {
// increase qty by 1
this.cur_frm.doc.items.forEach((item) => {
if (item.item_code === item_code) {
frappe.model.set_value(item.doctype, item.name, 'qty', item.qty + qty);
// update cart
this.cart.add_item(item);
}
});
return;
}
// add to cur_frm
const item = this.cur_frm.add_child('items', { item_code: item_code });
this.cur_frm.script_manager
.trigger('item_code', item.doctype, item.name)
.then(() => {
// update cart
this.cart.add_item(item);
});
}
bind_events() {
}
get_pos_profile() {
setup_pos_profile() {
return frappe.call({
method: 'erpnext.stock.get_item_details.get_pos_profile',
args: {
@ -104,13 +142,14 @@ erpnext.PointOfSale = class PointOfSale {
make_sales_invoice_frm() {
const dt = 'Sales Invoice';
return new Promise(resolve => {
frappe.model.with_doctype(dt, function() {
frappe.model.with_doctype(dt, () => {
const page = $('<div>');
const frm = new _f.Frm(dt, page, false);
const name = frappe.model.make_new_doc_and_get_name(dt, true);
frm.refresh(name);
frm.doc.items = [];
resolve(frm);
this.cur_frm = frm;
resolve();
});
});
}
@ -141,16 +180,18 @@ erpnext.PointOfSale = class PointOfSale {
}
}
erpnext.POSCart = class POSCart {
constructor(wrapper) {
class POSCart {
constructor({wrapper, events}) {
this.wrapper = wrapper;
this.items = {};
this.events = events;
this.make();
this.bind_events();
}
make() {
this.make_dom();
this.make_customer_field();
this.make_numpad();
}
make_dom() {
@ -172,7 +213,10 @@ erpnext.POSCart = class POSCart {
</div>
</div>
</div>
<div class="number-pad-container">
</div>
`);
this.$cart_items = this.wrapper.find('.cart-items');
}
make_customer_field() {
@ -181,8 +225,9 @@ erpnext.POSCart = class POSCart {
fieldtype: 'Link',
label: 'Customer',
options: 'Customer',
reqd: 1,
onchange: (e) => {
cur_frm.set_value('customer', this.customer_field.value);
this.events.customer_change.apply(null, [this.customer_field.get_value()]);
}
},
parent: this.wrapper.find('.customer-field'),
@ -190,96 +235,113 @@ erpnext.POSCart = class POSCart {
});
}
add_item(item) {
const { item_code } = item;
const _item = this.items[item_code];
if (_item) {
// exists, increase quantity
_item.quantity += 1;
this.update_quantity(_item);
} else {
// add it to this.items
item['qty'] = 1;
this.child = cur_frm.add_child('items', item)
cur_frm.script_manager.trigger("item_code", this.child.doctype, this.child.name);
const _item = {
doc: item,
quantity: 1,
discount: 2,
rate: 2
make_numpad() {
this.numpad = new NumberPad({
wrapper: this.wrapper.find('.number-pad-container'),
onclick: (btn_value) => {
// on click
console.log(btn_value);
}
Object.assign(this.items, {
[item_code]: _item
});
this.add_item_to_cart(_item);
}
});
}
add_item_to_cart(item) {
add_item(item) {
this.wrapper.find('.cart-items .empty-state').hide();
const $item = $(this.get_item_html(item))
$item.appendTo(this.wrapper.find('.cart-items'));
// $item.addClass('added');
// this.wrapper.find('.cart-items').append(this.get_item_html(item))
}
update_quantity(item) {
this.wrapper.find(`.list-item[data-item-name="${item.doc.item_code}"] .quantity`)
.text(item.quantity);
$.each(cur_frm.doc["items"] || [], function(i, d) {
if (d.item_code == item.doc.item_code) {
frappe.model.set_value(d.doctype, d.name, "qty", d.qty + 1);
}
});
}
remove_item(item_code) {
delete this.items[item_code];
// this.refresh();
}
refresh() {
const item_codes = Object.keys(this.items);
const html = item_codes
.map(item_code => this.get_item_html(item_code))
.join("");
this.wrapper.find('.cart-items').html(html);
}
get_item_html(_item) {
let item;
if (typeof _item === "object") {
item = _item;
}
else if (typeof _item === "string") {
item = this.items[_item];
if (this.exists(item.item_code)) {
// update quantity
this.update_item(item);
} else {
// add to cart
const $item = $(this.get_item_html(item));
$item.appendTo(this.$cart_items);
}
this.highlight_item(item.item_code);
this.scroll_to_item(item.item_code);
}
update_item(item) {
const $item = this.$cart_items.find(`[data-item-code="${item.item_code}"]`);
if(item.qty > 0) {
$item.find('.quantity input').val(item.qty);
$item.find('.discount').text(item.discount_percentage);
$item.find('.rate').text(item.rate);
} else {
$item.remove();
}
}
exists(item_code) {
let $item = this.$cart_items.find(`[data-item-code="${item_code}"]`);
return $item.length > 0;
}
highlight_item(item_code) {
const $item = this.$cart_items.find(`[data-item-code="${item_code}"]`);
$item.addClass('highlight');
setTimeout(() => $item.removeClass('highlight'), 1000);
}
scroll_to_item(item_code) {
const $item = this.$cart_items.find(`[data-item-code="${item_code}"]`);
const scrollTop = $item.offset().top - this.$cart_items.offset().top + this.$cart_items.scrollTop();
this.$cart_items.animate({ scrollTop });
}
get_item_html(item) {
return `
<div class="list-item" data-item-name="${item.doc.item_code}">
<div class="list-item" data-item-code="${item.item_code}">
<div class="item-name list-item__content list-item__content--flex-2 ellipsis">
${item.doc.item_name}
${item.item_name}
</div>
<div class="quantity list-item__content text-right">
${item.quantity}
${get_quantity_html(item.qty)}
</div>
<div class="discount list-item__content text-right">
${item.discount}
${item.discount_percentage}%
</div>
<div class="rate list-item__content text-right">
${item.rate}
</div>
</div>
`;
function get_quantity_html(value) {
return `
<div class="input-group input-group-xs">
<span class="input-group-btn">
<button class="btn btn-default btn-xs" data-action="increment">+</button>
</span>
<input class="form-control" type="number" value="${value}">
<span class="input-group-btn">
<button class="btn btn-default btn-xs" data-action="decrement">-</button>
</span>
</div>
`;
}
}
bind_events() {
const events = this.events;
this.$cart_items.on('click',
'[data-action="increment"], [data-action="decrement"]', function() {
const $btn = $(this);
const $item = $btn.closest('.list-item[data-item-code]');
const item_code = $item.attr('data-item-code');
const action = $btn.attr('data-action');
if(action === 'increment') {
events.increase_qty(item_code);
} else if(action === 'decrement') {
events.decrease_qty(item_code);
}
});
}
}
erpnext.POSItems = class POSItems {
class POSItems {
constructor({wrapper, pos_profile, events}) {
this.wrapper = wrapper;
this.pos_profile = pos_profile;
@ -439,16 +501,10 @@ erpnext.POSItems = class POSItems {
<div class="image-field"
style="${!item_image ? 'background-color: #fafbfc;' : ''} border: 0px;"
>
${!item_image ?
`<span class="placeholder-text">
${!item_image ? `<span class="placeholder-text">
${frappe.get_abbr(item_title)}
</span>` :
''
}
${item_image ?
`<img src="${item_image}" alt="${item_title}">` :
''
}
</span>` : '' }
${item_image ? `<img src="${item_image}" alt="${item_title}">` : '' }
</div>
<span class="price-info">
${item_price}
@ -509,12 +565,12 @@ erpnext.POSItems = class POSItems {
"`tabItem`.`end_of_life`",
"`tabItem`.`total_projected_qty`"
],
filters: [['disabled', '=', '0']],
order_by: "`tabItem`.`modified` desc",
page_length: page_length,
start: start
}
})
.then(r => {
}).then(r => {
const data = r.message;
const items = frappe.utils.dict(data.keys, data.values);
@ -528,4 +584,52 @@ erpnext.POSItems = class POSItems {
});
});
}
}
class NumberPad {
constructor({wrapper, onclick}) {
this.wrapper = wrapper;
this.onclick = onclick;
this.make_dom();
this.bind_events();
}
make_dom() {
const button_array = [
[1, 2, 3, 'Qty'],
[4, 5, 6, 'Disc'],
[7, 8, 9, 'Price'],
['Del', 0, '.', 'Pay']
];
this.wrapper.html(`
<div class="number-pad">
${button_array.map(get_row).join("")}
</div>
`);
function get_row(row) {
return '<div class="num-row">' + row.map(get_col).join("") + '</div>';
}
function get_col(col) {
return `<div class="num-col" data-value="${col}"><div>${col}</div></div>`;
}
}
bind_events() {
// bind click event
const me = this;
this.wrapper.on('click', '.num-col', function() {
const $btn = $(this);
me.highlight_button($btn);
me.onclick.apply(null, [$btn.attr('data-value')]);
});
}
highlight_button($btn) {
// const $btn = this.wrapper.find(`[data-value="${value}"]`);
$btn.addClass('highlight');
setTimeout(() => $btn.removeClass('highlight'), 1000);
}
}