Added serial no, batch no, item group functionality
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json
index 6991da2..c4e6dab 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.json
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json
@@ -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",
diff --git a/erpnext/accounts/doctype/pos_settings/__init__.py b/erpnext/accounts/doctype/pos_settings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_settings/__init__.py
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js
new file mode 100644
index 0000000..fab766b
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js
@@ -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) {
+
+ }
+});
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.json b/erpnext/accounts/doctype/pos_settings/pos_settings.json
new file mode 100644
index 0000000..ab3976e
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.json
@@ -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
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.py b/erpnext/accounts/doctype/pos_settings/pos_settings.py
new file mode 100644
index 0000000..4a71775
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.py
@@ -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()
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_settings/test_pos_settings.js b/erpnext/accounts/doctype/pos_settings/test_pos_settings.js
new file mode 100644
index 0000000..639c94e
--- /dev/null
+++ b/erpnext/accounts/doctype/pos_settings/test_pos_settings.js
@@ -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()
+ ]);
+
+});
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 9dfacbd..0b6926f 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -303,7 +303,7 @@
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))
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 08630e5..ed5a0f6 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -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 @@
}
});
+ if(this.show_dialog) {
+ let d = this.item;
+ this.dialog.set_value('serial_no', d.serial_no);
+ }
+
this.dialog.show();
},
@@ -140,6 +146,7 @@
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) {
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js
index a3ea04e..4096ed4 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.js
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.js
@@ -120,6 +120,7 @@
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 @@
}
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 @@
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 @@
<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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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;
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 73546a0..d34fc54 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -12,7 +12,7 @@
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 @@
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 @@
(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
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 80ef708..8d084dc 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -79,7 +79,7 @@
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 @@
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 @@
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