Added serial no, batch no, item group functionality
This commit is contained in:
parent
3ae9e91bcd
commit
ba3f0e6b70
@ -822,7 +822,7 @@
|
||||
"columns": 0,
|
||||
"fieldname": "apply_discount",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
@ -836,7 +836,7 @@
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@ -851,7 +851,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Grand Total",
|
||||
"depends_on": "apply_discount",
|
||||
"depends_on": "",
|
||||
"fieldname": "apply_discount_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
@ -1291,7 +1291,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-28 03:40:03.253088",
|
||||
"modified": "2017-08-27 16:39:00.713225",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
0
erpnext/accounts/doctype/pos_settings/__init__.py
Normal file
0
erpnext/accounts/doctype/pos_settings/__init__.py
Normal file
8
erpnext/accounts/doctype/pos_settings/pos_settings.js
Normal file
8
erpnext/accounts/doctype/pos_settings/pos_settings.js
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('POS Settings', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
});
|
93
erpnext/accounts/doctype/pos_settings/pos_settings.json
Normal file
93
erpnext/accounts/doctype/pos_settings/pos_settings.json
Normal file
@ -0,0 +1,93 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-08-28 16:46:41.732676",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "type_of_pos",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Type of POS",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Online\nOffline",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-08-28 16:46:41.732676",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
16
erpnext/accounts/doctype/pos_settings/pos_settings.py
Normal file
16
erpnext/accounts/doctype/pos_settings/pos_settings.py
Normal file
@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class POSSettings(Document):
|
||||
def validate(self):
|
||||
link = 'point-of-sale' if self.type_of_pos == 'Online' else 'pos'
|
||||
desktop_icon = frappe.db.get_value('Desktop Icon', {'module_name': 'POS'}, 'name')
|
||||
if desktop_icon:
|
||||
doc = frappe.get_doc('Desktop Icon', desktop_icon)
|
||||
doc.link = link
|
||||
doc.save()
|
23
erpnext/accounts/doctype/pos_settings/test_pos_settings.js
Normal file
23
erpnext/accounts/doctype/pos_settings/test_pos_settings.js
Normal file
@ -0,0 +1,23 @@
|
||||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: POS Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new POS Settings
|
||||
() => frappe.tests.make('POS Settings', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
@ -303,7 +303,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name',
|
||||
'selling_price_list', 'company', 'select_print_heading', 'cash_bank_account',
|
||||
'write_off_account', 'write_off_cost_center'):
|
||||
'write_off_account', 'write_off_cost_center', 'apply_discount_on'):
|
||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
||||
self.set(fieldname, pos.get(fieldname))
|
||||
|
||||
|
@ -1,15 +1,16 @@
|
||||
|
||||
erpnext.SerialNoBatchSelector = Class.extend({
|
||||
init: function(opts) {
|
||||
init: function(opts, show_dialog) {
|
||||
$.extend(this, opts);
|
||||
this.show_dialog = show_dialog;
|
||||
// frm, item, warehouse_details, has_batch, oldest
|
||||
let d = this.item;
|
||||
|
||||
// Don't show dialog if batch no or serial no already set
|
||||
if(d && d.has_batch_no && !d.batch_no) {
|
||||
if(d && d.has_batch_no && (!d.batch_no || this.show_dialog)) {
|
||||
this.has_batch = 1;
|
||||
this.setup();
|
||||
} else if(d && d.has_serial_no && !d.serial_no) {
|
||||
} else if(d && d.has_serial_no && (!d.serial_no || this.show_dialog)) {
|
||||
this.has_batch = 0;
|
||||
this.setup();
|
||||
}
|
||||
@ -93,6 +94,11 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
||||
}
|
||||
});
|
||||
|
||||
if(this.show_dialog) {
|
||||
let d = this.item;
|
||||
this.dialog.set_value('serial_no', d.serial_no);
|
||||
}
|
||||
|
||||
this.dialog.show();
|
||||
},
|
||||
|
||||
@ -140,6 +146,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
||||
this.map_row_values(this.item, this.values, 'serial_no', 'qty');
|
||||
}
|
||||
refresh_field("items");
|
||||
this.callback && this.callback(this.item)
|
||||
},
|
||||
|
||||
map_row_values: function(row, values, number, qty_field, warehouse) {
|
||||
|
@ -120,6 +120,7 @@ class PointOfSale {
|
||||
if(!this.frm.doc.customer) {
|
||||
frappe.throw(__('Please select a customer'));
|
||||
}
|
||||
|
||||
this.update_item_in_cart(item_code, 'qty', '+1');
|
||||
this.cart && this.cart.unselect_all();
|
||||
},
|
||||
@ -131,53 +132,76 @@ class PointOfSale {
|
||||
}
|
||||
|
||||
update_item_in_cart(item_code, field='qty', value=1) {
|
||||
|
||||
if(this.cart.exists(item_code)) {
|
||||
const item = this.frm.doc.items.find(i => i.item_code === item_code);
|
||||
|
||||
if (typeof value === 'string') {
|
||||
if (typeof value === 'string' && !in_list(['serial_no', 'batch_no'], field)) {
|
||||
// value can be of type '+1' or '-1'
|
||||
value = item[field] + flt(value);
|
||||
}
|
||||
|
||||
if (field === 'serial_no') {
|
||||
value = item.serial_no + '\n' + value;
|
||||
if(field === 'serial_no') {
|
||||
value = item.serial_no + '\n'+ value;
|
||||
}
|
||||
|
||||
this.update_item_in_frm(item, field, value)
|
||||
.then(() => {
|
||||
// update cart
|
||||
this.cart.add_item(item);
|
||||
})
|
||||
.then(() => {
|
||||
this.cart.update_taxes_and_totals();
|
||||
this.cart.update_grand_total();
|
||||
});
|
||||
|
||||
// if (barcode) {
|
||||
// const value = barcode['serial_no'] ?
|
||||
// item.serial_no + '\n' + barcode['serial_no'] : barcode['batch_no'];
|
||||
// frappe.model.set_value(item.doctype, item.name,
|
||||
// Object.keys(barcode)[0], value);
|
||||
// } else {
|
||||
// }
|
||||
if(field === 'qty' && (item.serial_no || item.batch_no)) {
|
||||
this.select_batch_and_serial_no(item)
|
||||
} else {
|
||||
this.update_item_in_frm(item, field, value)
|
||||
.then(() => {
|
||||
// update cart
|
||||
this.update_cart_data(item);
|
||||
})
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let args = { item_code: item_code };
|
||||
if (in_list(['serial_no', 'batch_no'], field)) {
|
||||
args[field] = value;
|
||||
}
|
||||
|
||||
// add to cur_frm
|
||||
const item = this.frm.add_child('items', { item_code: item_code });
|
||||
const item = this.frm.add_child('items', args);
|
||||
this.frm.script_manager
|
||||
.trigger('item_code', item.doctype, item.name)
|
||||
.then(() => {
|
||||
// update cart
|
||||
this.cart.add_item(item);
|
||||
this.cart.update_taxes_and_totals();
|
||||
this.cart.update_grand_total();
|
||||
this.update_cart_data(item)
|
||||
});
|
||||
}
|
||||
|
||||
select_batch_and_serial_no(item) {
|
||||
let dialog = new erpnext.SerialNoBatchSelector({
|
||||
frm: this.frm,
|
||||
item: item,
|
||||
warehouse_details: {
|
||||
type: "Warehouse",
|
||||
name: item.warehouse
|
||||
},
|
||||
callback: (item) => {
|
||||
this.update_item_in_frm(item)
|
||||
.then(() => {
|
||||
// update cart
|
||||
this.update_cart_data(item);
|
||||
})
|
||||
}
|
||||
}, true)
|
||||
}
|
||||
|
||||
update_cart_data(item) {
|
||||
this.cart.add_item(item);
|
||||
this.cart.update_taxes_and_totals();
|
||||
this.cart.update_grand_total();
|
||||
}
|
||||
|
||||
update_item_in_frm(item, field, value) {
|
||||
return frappe.model.set_value(item.doctype, item.name, field, value)
|
||||
if (field) {
|
||||
frappe.model.set_value(item.doctype, item.name, field, value)
|
||||
}
|
||||
|
||||
return this.frm.script_manager
|
||||
.trigger('qty', item.doctype, item.name)
|
||||
.then(() => {
|
||||
if (field === 'qty' && value === 0) {
|
||||
frappe.model.clear_doc(item.doctype, item.name);
|
||||
@ -307,7 +331,7 @@ class PointOfSale {
|
||||
this.make_new_invoice();
|
||||
});
|
||||
|
||||
this.page.add_menu_item(__("Email"), function () {
|
||||
this.page.add_menu_item(__("Email"), () => {
|
||||
this.frm.email_doc();
|
||||
});
|
||||
}
|
||||
@ -398,11 +422,11 @@ class POSCart {
|
||||
<div class="list-item__content list-item__content--flex-2 text-muted">${__('Discount')}</div>
|
||||
<div class="list-item__content discount-inputs">
|
||||
<input type="text"
|
||||
class="form-control discount-percentage text-right"
|
||||
class="form-control additional_discount_percentage text-right"
|
||||
placeholder="% 0.00"
|
||||
>
|
||||
<input type="text"
|
||||
class="form-control discount-amount text-right"
|
||||
class="form-control discount_amount text-right"
|
||||
placeholder="${get_currency_symbol(this.frm.doc.currency)} 0.00"
|
||||
>
|
||||
</div>
|
||||
@ -561,7 +585,7 @@ class POSCart {
|
||||
if(item.qty > 0) {
|
||||
$item.find('.quantity input').val(item.qty);
|
||||
$item.find('.discount').text(item.discount_percentage);
|
||||
$item.find('.rate').text(item.rate);
|
||||
$item.find('.rate').text(format_currency(item.rate, this.frm.doc.currency));
|
||||
} else {
|
||||
$item.remove();
|
||||
}
|
||||
@ -671,6 +695,28 @@ class POSCart {
|
||||
// me.$cart_items.find('.list-item').removeClass('current-item qty disc rate');
|
||||
// me.selected_item = null;
|
||||
// });
|
||||
|
||||
this.wrapper.find('.additional_discount_percentage').on('change', (e) => {
|
||||
frappe.model.set_value(this.frm.doctype, this.frm.docname,
|
||||
'additional_discount_percentage', e.target.value)
|
||||
.then(() => {
|
||||
let discount_wrapper = this.wrapper.find('.discount_amount')
|
||||
discount_wrapper.val(this.frm.doc.discount_amount)
|
||||
discount_wrapper.trigger('change')
|
||||
})
|
||||
})
|
||||
|
||||
this.wrapper.find('.discount_amount').on('change', (e) => {
|
||||
frappe.model.set_value(this.frm.doctype, this.frm.docname,
|
||||
'discount_amount', e.target.value)
|
||||
this.frm.trigger('discount_amount')
|
||||
.then(() => {
|
||||
let discount_wrapper = this.wrapper.find('.additional_discount_percentage');
|
||||
discount_wrapper.val(this.frm.doc.additional_discount_percentage);
|
||||
this.update_taxes_and_totals()
|
||||
this.update_grand_total()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
set_selected_item($item) {
|
||||
@ -749,21 +795,19 @@ class POSItems {
|
||||
|
||||
this.search_field.$input.on('input', (e) => {
|
||||
const search_term = e.target.value;
|
||||
this.filter_items(search_term);
|
||||
this.filter_items({ search_term });
|
||||
});
|
||||
|
||||
|
||||
// Item group field
|
||||
this.item_group_field = frappe.ui.form.make_control({
|
||||
df: {
|
||||
fieldtype: 'Select',
|
||||
fieldtype: 'Link',
|
||||
label: 'Item Group',
|
||||
options: [
|
||||
'All Item Groups',
|
||||
'Raw Materials',
|
||||
'Finished Goods'
|
||||
],
|
||||
default: 'All Item Groups'
|
||||
options: 'Item Group',
|
||||
default: 'All Item Groups',
|
||||
onchange: () => {
|
||||
console.log("in the item_group")
|
||||
this.filter_items({ item_group: this.item_group_field.get_value() })
|
||||
},
|
||||
},
|
||||
parent: this.wrapper.find('.item-group-field'),
|
||||
render_input: true
|
||||
@ -805,21 +849,24 @@ class POSItems {
|
||||
this.clusterize.update(row_items);
|
||||
}
|
||||
|
||||
filter_items(search_term) {
|
||||
search_term = search_term.toLowerCase();
|
||||
filter_items({ search_term='', item_group='All Item Groups' }={}) {
|
||||
if (search_term) {
|
||||
search_term = search_term.toLowerCase();
|
||||
|
||||
|
||||
// memoize
|
||||
this.search_index = this.search_index || {};
|
||||
if (this.search_index[search_term]) {
|
||||
const items = this.search_index[search_term];
|
||||
this.render_items(items);
|
||||
return;
|
||||
// memoize
|
||||
this.search_index = this.search_index || {};
|
||||
if (this.search_index[search_term]) {
|
||||
const items = this.search_index[search_term];
|
||||
this.render_items(items);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.get_items({search_value: search_term})
|
||||
this.get_items({search_value: search_term, item_group })
|
||||
.then((items) => {
|
||||
this.search_index[search_term] = items;
|
||||
if (search_term) {
|
||||
this.search_index[search_term] = items;
|
||||
}
|
||||
|
||||
this.render_items(items);
|
||||
if(this.serial_no) {
|
||||
@ -884,7 +931,7 @@ class POSItems {
|
||||
return template;
|
||||
}
|
||||
|
||||
get_items({start = 0, page_length = 40, search_value=''}={}) {
|
||||
get_items({start = 0, page_length = 40, search_value='', item_group="All Item Groups"}={}) {
|
||||
return new Promise(res => {
|
||||
frappe.call({
|
||||
method: "erpnext.selling.page.point_of_sale.point_of_sale.get_items",
|
||||
@ -892,7 +939,8 @@ class POSItems {
|
||||
start,
|
||||
page_length,
|
||||
'price_list': this.pos_profile.selling_price_list,
|
||||
search_value,
|
||||
item_group,
|
||||
search_value
|
||||
}
|
||||
}).then(r => {
|
||||
const { items, serial_no } = r.message;
|
||||
|
@ -12,7 +12,7 @@ from erpnext.accounts.party import get_party_account_currency
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_items(start, page_length, price_list, search_value=""):
|
||||
def get_items(start, page_length, price_list, item_group, search_value=""):
|
||||
condition = ""
|
||||
serial_no = ""
|
||||
item_code = search_value
|
||||
@ -23,6 +23,7 @@ def get_items(start, page_length, price_list, search_value=""):
|
||||
if serial_no_data:
|
||||
serial_no, item_code = serial_no_data
|
||||
|
||||
lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt'])
|
||||
# locate function is used to sort by closest match from the beginning of the value
|
||||
res = frappe.db.sql("""select i.name as item_code, i.item_name, i.image as item_image,
|
||||
item_det.price_list_rate, item_det.currency
|
||||
@ -33,9 +34,10 @@ def get_items(start, page_length, price_list, search_value=""):
|
||||
(item_det.item_code=i.name or item_det.item_code=i.variant_of)
|
||||
where
|
||||
i.disabled = 0 and i.has_variants = 0
|
||||
and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt})
|
||||
and (i.item_code like %(item_code)s
|
||||
or i.item_name like %(item_code)s)
|
||||
limit {start}, {page_length}""".format(start=start, page_length=page_length),
|
||||
limit {start}, {page_length}""".format(start=start, page_length=page_length, lft=lft, rgt=rgt),
|
||||
{
|
||||
'item_code': '%%%s%%'%(frappe.db.escape(item_code)),
|
||||
'price_list': price_list
|
||||
|
@ -79,7 +79,7 @@ def get_item_details(args):
|
||||
and out.warehouse and out.stock_qty > 0:
|
||||
|
||||
if out.has_serial_no:
|
||||
out.serial_no = get_serial_no(out)
|
||||
out.serial_no = get_serial_no(out, args.serial_no)
|
||||
|
||||
if out.has_batch_no and not args.get("batch_no"):
|
||||
out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty)
|
||||
@ -554,7 +554,8 @@ def get_gross_profit(out):
|
||||
return out
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_serial_no(args):
|
||||
def get_serial_no(args, serial_nos=None):
|
||||
serial_no = None
|
||||
if isinstance(args, basestring):
|
||||
args = json.loads(args)
|
||||
args = frappe._dict(args)
|
||||
@ -568,4 +569,9 @@ def get_serial_no(args):
|
||||
args = json.dumps({"item_code": args.get('item_code'),"warehouse": args.get('warehouse'),"stock_qty": args.get('stock_qty')})
|
||||
args = process_args(args)
|
||||
serial_no = get_serial_nos_by_fifo(args)
|
||||
return serial_no
|
||||
|
||||
if not serial_no and serial_nos:
|
||||
# For POS
|
||||
serial_no = serial_nos
|
||||
|
||||
return serial_no
|
||||
|
Loading…
x
Reference in New Issue
Block a user