Browse Source

Merge pull request #26198 from nextchamp-saqib/pos-fixes-9-v13

refactor(pos): use pos invoice item name as unique identifier
develop
Saqib 3 years ago
committed by GitHub
parent
commit
81d164134d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 126
      erpnext/selling/page/point_of_sale/pos_controller.js
  2. 31
      erpnext/selling/page/point_of_sale/pos_item_cart.js
  3. 89
      erpnext/selling/page/point_of_sale/pos_item_details.js
  4. 6
      erpnext/selling/page/point_of_sale/pos_item_selector.js

126
erpnext/selling/page/point_of_sale/pos_controller.js

@ -241,8 +241,8 @@ erpnext.PointOfSale.Controller = class {
events: { events: {
get_frm: () => this.frm, get_frm: () => this.frm,
cart_item_clicked: (item_code, batch_no, uom, rate) => { cart_item_clicked: (item) => {
const item_row = this.get_item_from_frm(item_code, batch_no, uom, rate); const item_row = this.get_item_from_frm(item);
this.item_details.toggle_item_details_section(item_row); this.item_details.toggle_item_details_section(item_row);
}, },
@ -273,17 +273,15 @@ erpnext.PointOfSale.Controller = class {
this.cart.toggle_numpad(minimize); this.cart.toggle_numpad(minimize);
}, },
form_updated: (cdt, cdn, fieldname, value) => { form_updated: (item, field, value) => {
const item_row = frappe.model.get_doc(cdt, cdn); const item_row = frappe.model.get_doc(item.doctype, item.name);
if (item_row && item_row[fieldname] != value) { if (item_row && item_row[field] != value) {
const args = {
const { item_code, batch_no, uom, rate } = this.item_details.current_item; field,
const event = {
field: fieldname,
value, value,
item: { item_code, batch_no, uom, rate } item: this.item_details.current_item
} };
return this.on_cart_update(event) return this.on_cart_update(args);
} }
return Promise.resolve(); return Promise.resolve();
@ -300,19 +298,18 @@ erpnext.PointOfSale.Controller = class {
set_value_in_current_cart_item: (selector, value) => { set_value_in_current_cart_item: (selector, value) => {
this.cart.update_selector_value_in_cart_item(selector, value, this.item_details.current_item); this.cart.update_selector_value_in_cart_item(selector, value, this.item_details.current_item);
}, },
clone_new_batch_item_in_frm: (batch_serial_map, current_item) => { clone_new_batch_item_in_frm: (batch_serial_map, item) => {
// called if serial nos are 'auto_selected' and if those serial nos belongs to multiple batches // called if serial nos are 'auto_selected' and if those serial nos belongs to multiple batches
// for each unique batch new item row is added in the form & cart // for each unique batch new item row is added in the form & cart
Object.keys(batch_serial_map).forEach(batch => { Object.keys(batch_serial_map).forEach(batch => {
const { item_code, batch_no } = current_item; const item_to_clone = this.frm.doc.items.find(i => i.name == item.name);
const item_to_clone = this.frm.doc.items.find(i => i.item_code === item_code && i.batch_no === batch_no);
const new_row = this.frm.add_child("items", { ...item_to_clone }); const new_row = this.frm.add_child("items", { ...item_to_clone });
// update new serialno and batch // update new serialno and batch
new_row.batch_no = batch; new_row.batch_no = batch;
new_row.serial_no = batch_serial_map[batch].join(`\n`); new_row.serial_no = batch_serial_map[batch].join(`\n`);
new_row.qty = batch_serial_map[batch].length; new_row.qty = batch_serial_map[batch].length;
this.frm.doc.items.forEach(row => { this.frm.doc.items.forEach(row => {
if (item_code === row.item_code) { if (item.item_code === row.item_code) {
this.update_cart_html(row); this.update_cart_html(row);
} }
}); });
@ -321,8 +318,8 @@ erpnext.PointOfSale.Controller = class {
remove_item_from_cart: () => this.remove_item_from_cart(), remove_item_from_cart: () => this.remove_item_from_cart(),
get_item_stock_map: () => this.item_stock_map, get_item_stock_map: () => this.item_stock_map,
close_item_details: () => { close_item_details: () => {
this.item_details.toggle_item_details_section(undefined); this.item_details.toggle_item_details_section(null);
this.cart.prev_action = undefined; this.cart.prev_action = null;
this.cart.toggle_item_highlight(); this.cart.toggle_item_highlight();
}, },
get_available_stock: (item_code, warehouse) => this.get_available_stock(item_code, warehouse) get_available_stock: (item_code, warehouse) => this.get_available_stock(item_code, warehouse)
@ -506,50 +503,47 @@ erpnext.PointOfSale.Controller = class {
let item_row = undefined; let item_row = undefined;
try { try {
let { field, value, item } = args; let { field, value, item } = args;
const { item_code, batch_no, serial_no, uom, rate } = item; item_row = this.get_item_from_frm(item);
item_row = this.get_item_from_frm(item_code, batch_no, uom, rate); const item_row_exists = !$.isEmptyObject(item_row);
const item_selected_from_selector = field === 'qty' && value === "+1"
if (item_row) { const from_selector = field === 'qty' && value === "+1";
item_selected_from_selector && (value = item_row.qty + flt(value)) if (from_selector)
value = flt(item_row.qty) + flt(value);
field === 'qty' && (value = flt(value)); if (item_row_exists) {
if (field === 'qty')
value = flt(value);
if (['qty', 'conversion_factor'].includes(field) && value > 0 && !this.allow_negative_stock) { if (['qty', 'conversion_factor'].includes(field) && value > 0 && !this.allow_negative_stock) {
const qty_needed = field === 'qty' ? value * item_row.conversion_factor : item_row.qty * value; const qty_needed = field === 'qty' ? value * item_row.conversion_factor : item_row.qty * value;
await this.check_stock_availability(item_row, qty_needed, this.frm.doc.set_warehouse); await this.check_stock_availability(item_row, qty_needed, this.frm.doc.set_warehouse);
} }
if (this.is_current_item_being_edited(item_row) || item_selected_from_selector) { if (this.is_current_item_being_edited(item_row) || from_selector) {
await frappe.model.set_value(item_row.doctype, item_row.name, field, value); await frappe.model.set_value(item_row.doctype, item_row.name, field, value);
this.update_cart_html(item_row); this.update_cart_html(item_row);
} }
} else { } else {
if (!this.frm.doc.customer) { if (!this.frm.doc.customer)
frappe.dom.unfreeze(); return this.raise_customer_selection_alert();
frappe.show_alert({
message: __('You must select a customer before adding an item.'),
indicator: 'orange'
});
frappe.utils.play_sound("error");
return;
}
if (!item_code) return;
item_selected_from_selector && (value = flt(value)) const { item_code, batch_no, serial_no, rate } = item;
const args = { item_code, batch_no, rate, [field]: value }; if (!item_code)
return;
const new_item = { item_code, batch_no, rate, [field]: value };
if (serial_no) { if (serial_no) {
await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no); await this.check_serial_no_availablilty(item_code, this.frm.doc.set_warehouse, serial_no);
args['serial_no'] = serial_no; new_item['serial_no'] = serial_no;
} }
if (field === 'serial_no') args['qty'] = value.split(`\n`).length || 0; if (field === 'serial_no')
new_item['qty'] = value.split(`\n`).length || 0;
item_row = this.frm.add_child('items', args); item_row = this.frm.add_child('items', new_item);
if (field === 'qty' && value !== 0 && !this.allow_negative_stock) if (field === 'qty' && value !== 0 && !this.allow_negative_stock)
await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse); await this.check_stock_availability(item_row, value, this.frm.doc.set_warehouse);
@ -558,8 +552,11 @@ erpnext.PointOfSale.Controller = class {
this.update_cart_html(item_row); this.update_cart_html(item_row);
this.item_details.$component.is(':visible') && this.edit_item_details_of(item_row); if (this.item_details.$component.is(':visible'))
this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row); this.edit_item_details_of(item_row);
if (this.check_serial_batch_selection_needed(item_row))
this.edit_item_details_of(item_row);
} }
} catch (error) { } catch (error) {
@ -570,14 +567,33 @@ erpnext.PointOfSale.Controller = class {
} }
} }
get_item_from_frm(item_code, batch_no, uom, rate) { raise_customer_selection_alert() {
const has_batch_no = batch_no; frappe.dom.unfreeze();
return this.frm.doc.items.find( frappe.show_alert({
i => i.item_code === item_code message: __('You must select a customer before adding an item.'),
&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no)) indicator: 'orange'
&& (i.uom === uom) });
&& (i.rate == rate) frappe.utils.play_sound("error");
); }
get_item_from_frm({ name, item_code, batch_no, uom, rate }) {
let item_row = null;
if (name) {
item_row = this.frm.doc.items.find(i => i.name == name);
} else {
// if item is clicked twice from item selector
// then "item_code, batch_no, uom, rate" will help in getting the exact item
// to increase the qty by one
const has_batch_no = batch_no;
item_row = this.frm.doc.items.find(
i => i.item_code === item_code
&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
&& (i.uom === uom)
&& (i.rate == rate)
);
}
return item_row || {};
} }
edit_item_details_of(item_row) { edit_item_details_of(item_row) {
@ -585,9 +601,7 @@ erpnext.PointOfSale.Controller = class {
} }
is_current_item_being_edited(item_row) { is_current_item_being_edited(item_row) {
const { item_code, batch_no } = this.item_details.current_item; return item_row.name == this.item_details.current_item.name;
return item_code !== item_row.item_code || batch_no != item_row.batch_no ? false : true;
} }
update_cart_html(item_row, remove_item) { update_cart_html(item_row, remove_item) {
@ -669,7 +683,7 @@ erpnext.PointOfSale.Controller = class {
update_item_field(value, field_or_action) { update_item_field(value, field_or_action) {
if (field_or_action === 'checkout') { if (field_or_action === 'checkout') {
this.item_details.toggle_item_details_section(undefined); this.item_details.toggle_item_details_section(null);
} else if (field_or_action === 'remove') { } else if (field_or_action === 'remove') {
this.remove_item_from_cart(); this.remove_item_from_cart();
} else { } else {
@ -688,7 +702,7 @@ erpnext.PointOfSale.Controller = class {
.then(() => { .then(() => {
frappe.model.clear_doc(doctype, name); frappe.model.clear_doc(doctype, name);
this.update_cart_html(current_item, true); this.update_cart_html(current_item, true);
this.item_details.toggle_item_details_section(undefined); this.item_details.toggle_item_details_section(null);
frappe.dom.unfreeze(); frappe.dom.unfreeze();
}) })
.catch(e => console.log(e)); .catch(e => console.log(e));

31
erpnext/selling/page/point_of_sale/pos_item_cart.js

@ -181,11 +181,8 @@ erpnext.PointOfSale.ItemCart = class {
me.$totals_section.find(".edit-cart-btn").click(); me.$totals_section.find(".edit-cart-btn").click();
} }
const item_code = unescape($cart_item.attr('data-item-code')); const item_row_name = unescape($cart_item.attr('data-row-name'));
const batch_no = unescape($cart_item.attr('data-batch-no')); me.events.cart_item_clicked({ name: item_row_name });
const uom = unescape($cart_item.attr('data-uom'));
const rate = unescape($cart_item.attr('data-rate'));
me.events.cart_item_clicked(item_code, batch_no, uom, rate);
this.numpad_value = ''; this.numpad_value = '';
}); });
@ -521,25 +518,14 @@ erpnext.PointOfSale.ItemCart = class {
} }
} }
get_cart_item({ item_code, batch_no, uom, rate }) { get_cart_item({ name }) {
const batch_attr = `[data-batch-no="${escape(batch_no)}"]`; const item_selector = `.cart-item-wrapper[data-row-name="${escape(name)}"]`;
const item_code_attr = `[data-item-code="${escape(item_code)}"]`;
const uom_attr = `[data-uom="${escape(uom)}"]`;
const rate_attr = `[data-rate="${escape(rate)}"]`;
const item_selector = batch_no ?
`.cart-item-wrapper${batch_attr}${uom_attr}${rate_attr}` : `.cart-item-wrapper${item_code_attr}${uom_attr}${rate_attr}`;
return this.$cart_items_wrapper.find(item_selector); return this.$cart_items_wrapper.find(item_selector);
} }
get_item_from_frm(item) { get_item_from_frm(item) {
const doc = this.events.get_frm().doc; const doc = this.events.get_frm().doc;
const { item_code, batch_no, uom, rate } = item; return doc.items.find(i => i.name == item.name);
const search_field = batch_no ? 'batch_no' : 'item_code';
const search_value = batch_no || item_code;
return doc.items.find(i => i[search_field] === search_value && i.uom === uom && i.rate === rate);
} }
update_item_html(item, remove_item) { update_item_html(item, remove_item) {
@ -564,10 +550,7 @@ erpnext.PointOfSale.ItemCart = class {
if (!$item_to_update.length) { if (!$item_to_update.length) {
this.$cart_items_wrapper.append( this.$cart_items_wrapper.append(
`<div class="cart-item-wrapper" `<div class="cart-item-wrapper" data-row-name="${escape(item_data.name)}"></div>
data-item-code="${escape(item_data.item_code)}" data-uom="${escape(item_data.uom)}"
data-batch-no="${escape(item_data.batch_no || '')}" data-rate="${escape(item_data.rate)}">
</div>
<div class="seperator"></div>` <div class="seperator"></div>`
) )
$item_to_update = this.get_cart_item(item_data); $item_to_update = this.get_cart_item(item_data);
@ -642,7 +625,7 @@ erpnext.PointOfSale.ItemCart = class {
function get_item_image_html() { function get_item_image_html() {
const { image, item_name } = item_data; const { image, item_name } = item_data;
if (image) { if (!me.hide_images && image) {
return ` return `
<div class="item-image"> <div class="item-image">
<img <img

89
erpnext/selling/page/point_of_sale/pos_item_details.js

@ -2,6 +2,7 @@ erpnext.PointOfSale.ItemDetails = class {
constructor({ wrapper, events, settings }) { constructor({ wrapper, events, settings }) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.events = events; this.events = events;
this.hide_images = settings.hide_images;
this.allow_rate_change = settings.allow_rate_change; this.allow_rate_change = settings.allow_rate_change;
this.allow_discount_change = settings.allow_discount_change; this.allow_discount_change = settings.allow_discount_change;
this.current_item = {}; this.current_item = {};
@ -54,36 +55,28 @@ erpnext.PointOfSale.ItemDetails = class {
this.$dicount_section = this.$component.find('.discount-section'); this.$dicount_section = this.$component.find('.discount-section');
} }
has_item_has_changed(item) { compare_with_current_item(item) {
const { item_code, batch_no, uom, rate } = this.current_item; // returns true if `item` is currently being edited
const item_code_is_same = item && item_code === item.item_code; return item && item.name == this.current_item.name;
const batch_is_same = item && batch_no == item.batch_no;
const uom_is_same = item && uom === item.uom;
const rate_is_same = item && rate === item.rate;
if (!item)
return false;
if (item_code_is_same && batch_is_same && uom_is_same && rate_is_same)
return false;
return true;
} }
toggle_item_details_section(item) { toggle_item_details_section(item) {
this.item_has_changed = this.has_item_has_changed(item); const current_item_changed = !this.compare_with_current_item(item);
this.events.toggle_item_selector(this.item_has_changed); // if item is null or highlighted cart item is clicked twice
this.toggle_component(this.item_has_changed); const hide_item_details = !Boolean(item) || !current_item_changed;
this.events.toggle_item_selector(!hide_item_details);
this.toggle_component(!hide_item_details);
if (this.item_has_changed) { if (item && current_item_changed) {
this.doctype = item.doctype; this.doctype = item.doctype;
this.item_meta = frappe.get_meta(this.doctype); this.item_meta = frappe.get_meta(this.doctype);
this.name = item.name; this.name = item.name;
this.item_row = item; this.item_row = item;
this.currency = this.events.get_frm().doc.currency; this.currency = this.events.get_frm().doc.currency;
this.current_item = { item_code: item.item_code, batch_no: item.batch_no, uom: item.uom, rate: item.rate }; this.current_item = item;
this.render_dom(item); this.render_dom(item);
this.render_discount_dom(item); this.render_discount_dom(item);
@ -132,7 +125,7 @@ erpnext.PointOfSale.ItemDetails = class {
this.$item_name.html(item_name); this.$item_name.html(item_name);
this.$item_description.html(get_description_html()); this.$item_description.html(get_description_html());
this.$item_price.html(format_currency(price_list_rate, this.currency)); this.$item_price.html(format_currency(price_list_rate, this.currency));
if (image) { if (!this.hide_images && image) {
this.$item_image.html( this.$item_image.html(
`<img `<img
onerror="cur_pos.item_details.handle_broken_image(this)" onerror="cur_pos.item_details.handle_broken_image(this)"
@ -180,7 +173,7 @@ erpnext.PointOfSale.ItemDetails = class {
df: { df: {
...field_meta, ...field_meta,
onchange: function() { onchange: function() {
me.events.form_updated(me.doctype, me.name, fieldname, this.value); me.events.form_updated(me.current_item, fieldname, this.value);
} }
}, },
parent: this.$form_container.find(`.${fieldname}-control`), parent: this.$form_container.find(`.${fieldname}-control`),
@ -218,22 +211,17 @@ erpnext.PointOfSale.ItemDetails = class {
bind_custom_control_change_event() { bind_custom_control_change_event() {
const me = this; const me = this;
if (this.rate_control) { if (this.rate_control) {
if (this.allow_rate_change) { this.rate_control.df.onchange = function() {
this.rate_control.df.onchange = function() { if (this.value || flt(this.value) === 0) {
if (this.value || flt(this.value) === 0) { me.events.form_updated(me.current_item, 'rate', this.value).then(() => {
me.events.set_value_in_current_cart_item('rate', this.value); const item_row = frappe.get_doc(me.doctype, me.name);
me.events.form_updated(me.doctype, me.name, 'rate', this.value).then(() => { const doc = me.events.get_frm().doc;
const item_row = frappe.get_doc(me.doctype, me.name); me.$item_price.html(format_currency(item_row.rate, doc.currency));
const doc = me.events.get_frm().doc; me.render_discount_dom(item_row);
me.$item_price.html(format_currency(item_row.rate, doc.currency)); });
me.render_discount_dom(item_row); }
}); };
me.current_item.rate = this.value; this.rate_control.df.read_only = !this.allow_rate_change;
}
};
} else {
this.rate_control.df.read_only = 1;
}
this.rate_control.refresh(); this.rate_control.refresh();
} }
@ -246,7 +234,7 @@ erpnext.PointOfSale.ItemDetails = class {
this.warehouse_control.df.reqd = 1; this.warehouse_control.df.reqd = 1;
this.warehouse_control.df.onchange = function() { this.warehouse_control.df.onchange = function() {
if (this.value) { if (this.value) {
me.events.form_updated(me.doctype, me.name, 'warehouse', this.value).then(() => { me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => {
me.item_stock_map = me.events.get_item_stock_map(); me.item_stock_map = me.events.get_item_stock_map();
const available_qty = me.item_stock_map[me.item_row.item_code][this.value]; const available_qty = me.item_stock_map[me.item_row.item_code][this.value];
if (available_qty === undefined) { if (available_qty === undefined) {
@ -278,7 +266,7 @@ erpnext.PointOfSale.ItemDetails = class {
this.serial_no_control.df.reqd = 1; this.serial_no_control.df.reqd = 1;
this.serial_no_control.df.onchange = async function() { this.serial_no_control.df.onchange = async function() {
!me.current_item.batch_no && await me.auto_update_batch_no(); !me.current_item.batch_no && await me.auto_update_batch_no();
me.events.form_updated(me.doctype, me.name, 'serial_no', this.value); me.events.form_updated(me.current_item, 'serial_no', this.value);
} }
this.serial_no_control.refresh(); this.serial_no_control.refresh();
} }
@ -295,19 +283,12 @@ erpnext.PointOfSale.ItemDetails = class {
} }
} }
}; };
this.batch_no_control.df.onchange = function() {
me.events.set_value_in_current_cart_item('batch-no', this.value);
me.events.form_updated(me.doctype, me.name, 'batch_no', this.value);
me.current_item.batch_no = this.value;
}
this.batch_no_control.refresh(); this.batch_no_control.refresh();
} }
if (this.uom_control) { if (this.uom_control) {
this.uom_control.df.onchange = function() { this.uom_control.df.onchange = function() {
me.events.set_value_in_current_cart_item('uom', this.value); me.events.form_updated(me.current_item, 'uom', this.value);
me.events.form_updated(me.doctype, me.name, 'uom', this.value);
me.current_item.uom = this.value;
const item_row = frappe.get_doc(me.doctype, me.name); const item_row = frappe.get_doc(me.doctype, me.name);
me.conversion_factor_control.df.read_only = (item_row.stock_uom == this.value); me.conversion_factor_control.df.read_only = (item_row.stock_uom == this.value);
@ -317,9 +298,9 @@ erpnext.PointOfSale.ItemDetails = class {
frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => { frappe.model.on("POS Invoice Item", "*", (fieldname, value, item_row) => {
const field_control = this[`${fieldname}_control`]; const field_control = this[`${fieldname}_control`];
const item_is_same = !this.has_item_has_changed(item_row); const item_row_is_being_edited = this.compare_with_current_item(item_row);
if (item_is_same && field_control && field_control.get_value() !== value) { if (item_row_is_being_edited && field_control && field_control.get_value() !== value) {
field_control.set_value(value); field_control.set_value(value);
cur_pos.update_cart_html(item_row); cur_pos.update_cart_html(item_row);
} }
@ -337,7 +318,9 @@ erpnext.PointOfSale.ItemDetails = class {
fields: ["batch_no", "name"] fields: ["batch_no", "name"]
}); });
const batch_serial_map = serials_with_batch_no.reduce((acc, r) => { const batch_serial_map = serials_with_batch_no.reduce((acc, r) => {
acc[r.batch_no] || (acc[r.batch_no] = []); if (!acc[r.batch_no]) {
acc[r.batch_no] = [];
}
acc[r.batch_no] = [...acc[r.batch_no], r.name]; acc[r.batch_no] = [...acc[r.batch_no], r.name];
return acc; return acc;
}, {}); }, {});
@ -353,12 +336,10 @@ erpnext.PointOfSale.ItemDetails = class {
if (serial_nos_belongs_to_other_batch) { if (serial_nos_belongs_to_other_batch) {
this.serial_no_control.set_value(batch_serial_nos); this.serial_no_control.set_value(batch_serial_nos);
this.qty_control.set_value(batch_serial_map[batch_no].length); this.qty_control.set_value(batch_serial_map[batch_no].length);
}
delete batch_serial_map[batch_no];
if (serial_nos_belongs_to_other_batch) delete batch_serial_map[batch_no];
this.events.clone_new_batch_item_in_frm(batch_serial_map, this.current_item); this.events.clone_new_batch_item_in_frm(batch_serial_map, this.current_item);
}
} }
} }

6
erpnext/selling/page/point_of_sale/pos_item_selector.js

@ -232,7 +232,11 @@ erpnext.PointOfSale.ItemSelector = class {
uom = uom === "undefined" ? undefined : uom; uom = uom === "undefined" ? undefined : uom;
rate = rate === "undefined" ? undefined : rate; rate = rate === "undefined" ? undefined : rate;
me.events.item_selected({ field: 'qty', value: "+1", item: { item_code, batch_no, serial_no, uom, rate }}); me.events.item_selected({
field: 'qty',
value: "+1",
item: { item_code, batch_no, serial_no, uom, rate }
});
me.set_search_value(''); me.set_search_value('');
}); });

Loading…
Cancel
Save