Merge branch 'develop' into payment-terms
This commit is contained in:
commit
199daf7ac5
@ -8,10 +8,6 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) {
|
||||
return { filters: { selling: 1 } };
|
||||
});
|
||||
|
||||
frm.set_query("print_format", function() {
|
||||
return { filters: { doc_type: "Sales Invoice", print_format_type: "Js"} };
|
||||
});
|
||||
|
||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||
return erpnext.queries.warehouse(frm.doc);
|
||||
});
|
||||
@ -27,6 +23,27 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) {
|
||||
});
|
||||
|
||||
frappe.ui.form.on('POS Profile', {
|
||||
setup: function(frm) {
|
||||
frm.set_query("online_print_format", function() {
|
||||
return {
|
||||
filters: [
|
||||
['Print Format', 'doc_type', '=', 'Sales Invoice'],
|
||||
['Print Format', 'print_format_type', '!=', 'Js'],
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("print_format", function() {
|
||||
return { filters: { doc_type: "Sales Invoice", print_format_type: "Js"} };
|
||||
});
|
||||
|
||||
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => {
|
||||
is_online = r && cint(r.is_online)
|
||||
frm.toggle_display('offline_pos_section', !is_online);
|
||||
frm.toggle_display('print_format_for_online', is_online);
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.company) {
|
||||
frm.trigger("toggle_display_account_head");
|
||||
|
@ -631,8 +631,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Point of Sale",
|
||||
"fieldname": "print_format",
|
||||
"fieldname": "print_format_for_online",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
@ -641,7 +640,7 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Print Format",
|
||||
"label": "Print Format for Online",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Print Format",
|
||||
@ -822,7 +821,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 +835,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 +850,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Grand Total",
|
||||
"depends_on": "apply_discount",
|
||||
"depends_on": "",
|
||||
"fieldname": "apply_discount_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
@ -883,7 +882,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "customer_details",
|
||||
"fieldname": "offline_pos_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
@ -892,7 +891,7 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "New Customer Details",
|
||||
"label": "Offline POS Section",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@ -969,6 +968,38 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Point of Sale",
|
||||
"fieldname": "print_format",
|
||||
"fieldtype": "Link",
|
||||
"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": "Print Format",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Print Format",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@ -1291,7 +1322,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-28 03:40:03.253088",
|
||||
"modified": "2017-09-01 15:55:14.890452",
|
||||
"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() {
|
||||
|
||||
}
|
||||
});
|
94
erpnext/accounts/doctype/pos_settings/pos_settings.json
Normal file
94
erpnext/accounts/doctype/pos_settings/pos_settings.json
Normal file
@ -0,0 +1,94 @@
|
||||
{
|
||||
"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,
|
||||
"default": "1",
|
||||
"fieldname": "is_online",
|
||||
"fieldtype": "Check",
|
||||
"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": "Online",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"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-30 18:34:58.960276",
|
||||
"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
|
||||
}
|
9
erpnext/accounts/doctype/pos_settings/pos_settings.py
Normal file
9
erpnext/accounts/doctype/pos_settings/pos_settings.py
Normal file
@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
|
||||
class POSSettings(Document):
|
||||
pass
|
@ -2,15 +2,15 @@
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Sales Order", function (assert) {
|
||||
QUnit.test("test: POS Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially('Sales Order', [
|
||||
// insert a new Sales Order
|
||||
() => frappe.tests.make([
|
||||
frappe.run_serially([
|
||||
// insert a new POS Settings
|
||||
() => frappe.tests.make('POS Settings', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
@ -313,7 +313,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))
|
||||
|
||||
|
@ -8,7 +8,16 @@ frappe.pages['pos'].on_page_load = function (wrapper) {
|
||||
single_column: true
|
||||
});
|
||||
|
||||
wrapper.pos = new erpnext.pos.PointOfSale(wrapper)
|
||||
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => {
|
||||
if (r && r.is_online && !cint(r.is_online)) {
|
||||
// offline
|
||||
wrapper.pos = new erpnext.pos.PointOfSale(wrapper);
|
||||
cur_pos = wrapper.pos;
|
||||
} else {
|
||||
// online
|
||||
frappe.set_route('point-of-sale');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frappe.pages['pos'].refresh = function (wrapper) {
|
||||
|
@ -1,16 +1,15 @@
|
||||
QUnit.test("test:POS Profile", function(assert) {
|
||||
assert.expect(1);
|
||||
QUnit.test("test:Sales Invoice", function(assert) {
|
||||
assert.expect(3);
|
||||
let done = assert.async();
|
||||
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make("POS Profile", [
|
||||
{naming_series: "SINV"},
|
||||
{company: "Test Company"},
|
||||
{country: "India"},
|
||||
{currency: "INR"},
|
||||
{write_off_account: "Write Off - TC"},
|
||||
{write_off_cost_center: "Main - TC"},
|
||||
{write_off_account: "Write Off - FT"},
|
||||
{write_off_cost_center: "Main - FT"},
|
||||
{payments: [
|
||||
[
|
||||
{"default": 1},
|
||||
@ -24,19 +23,10 @@ QUnit.test("test:POS Profile", function(assert) {
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.payments[0].default, 1, "Default mode of payment tested");
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
||||
QUnit.test("test:Sales Invoice", function(assert) {
|
||||
assert.expect(2);
|
||||
let done = assert.async();
|
||||
|
||||
frappe.run_serially([
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
return frappe.tests.make("Sales Invoice", [
|
||||
{customer: "Test Customer 2"},
|
||||
{company: "Test Company"},
|
||||
{is_pos: 1},
|
||||
{posting_date: frappe.datetime.get_today()},
|
||||
{due_date: frappe.datetime.get_today()},
|
||||
@ -44,7 +34,7 @@ QUnit.test("test:Sales Invoice", function(assert) {
|
||||
[
|
||||
{"item_code": "Test Product 1"},
|
||||
{"qty": 5},
|
||||
{"warehouse":'Stores - TC'}
|
||||
{"warehouse":'Stores - FT'}
|
||||
]]
|
||||
}
|
||||
]);
|
||||
|
@ -10,7 +10,7 @@
|
||||
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}<br>\n</p>\n<p>\n\t<b>{{ __(\"Customer\") }}:</b> {{ customer }}<br>\n</p>\n\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>",
|
||||
"idx": 0,
|
||||
"line_breaks": 0,
|
||||
"modified": "2017-05-19 14:36:04.740728",
|
||||
"modified": "2017-09-01 14:27:04.871233",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Point of Sale",
|
||||
|
@ -435,6 +435,7 @@ erpnext.patches.v8_5.remove_project_type_property_setter
|
||||
erpnext.patches.v8_7.add_more_gst_fields
|
||||
erpnext.patches.v8_7.fix_purchase_receipt_status
|
||||
erpnext.patches.v8_6.rename_bom_update_tool
|
||||
erpnext.patches.v8_7.set_offline_in_pos_settings
|
||||
erpnext.patches.v8_9.add_setup_progress_actions
|
||||
erpnext.patches.v8_9.rename_company_sales_target_field
|
||||
erpnext.patches.v8_8.set_bom_rate_as_per_uom
|
||||
|
12
erpnext/patches/v8_7/set_offline_in_pos_settings.py
Normal file
12
erpnext/patches/v8_7/set_offline_in_pos_settings.py
Normal file
@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2017, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('accounts', 'doctype', 'pos_settings')
|
||||
|
||||
doc = frappe.get_doc('POS Settings')
|
||||
doc.is_online = 0
|
||||
doc.save()
|
@ -308,16 +308,6 @@ body[data-route="pos"] .item-list .image-field .placeholder-text {
|
||||
body[data-route="pos"] .item-list .pos-item-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
body[data-route="pos"] .item-list .price-info {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
margin: 0 0 15px 15px;
|
||||
background-color: rgba(141, 153, 166, 0.6);
|
||||
padding: 5px 9px;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
}
|
||||
body[data-route="pos"] .pos-bill-toolbar {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@ -356,3 +346,13 @@ body[data-route="pos"] .btn-more {
|
||||
body[data-route="pos"] .collapse-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
.price-info {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
margin: 0 0 15px 15px;
|
||||
background-color: rgba(141, 153, 166, 0.6);
|
||||
padding: 5px 9px;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
}
|
||||
|
174
erpnext/public/css/pos.css
Normal file
174
erpnext/public/css/pos.css
Normal file
@ -0,0 +1,174 @@
|
||||
[data-route="point-of-sale"] .layout-main-section-wrapper {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
[data-route="point-of-sale"] .pos-items-wrapper {
|
||||
max-height: calc(100vh - 210px);
|
||||
}
|
||||
.pos {
|
||||
padding: 15px;
|
||||
}
|
||||
.list-item {
|
||||
min-height: 40px;
|
||||
height: auto;
|
||||
}
|
||||
.cart-container {
|
||||
padding: 0 15px;
|
||||
display: inline-block;
|
||||
width: 39%;
|
||||
vertical-align: top;
|
||||
}
|
||||
.item-container {
|
||||
padding: 0 15px;
|
||||
display: inline-block;
|
||||
width: 60%;
|
||||
vertical-align: top;
|
||||
}
|
||||
.search-field {
|
||||
width: 60%;
|
||||
}
|
||||
.search-field input::placeholder {
|
||||
font-size: 12px;
|
||||
}
|
||||
.item-group-field {
|
||||
width: 40%;
|
||||
margin-left: 15px;
|
||||
}
|
||||
.cart-wrapper {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.cart-wrapper .list-item__content:not(:first-child) {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.cart-wrapper .list-item--head .list-item__content:nth-child(2) {
|
||||
flex: 1.5;
|
||||
}
|
||||
.cart-items {
|
||||
height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
.cart-items .list-item.current-item {
|
||||
background-color: #fffce7;
|
||||
}
|
||||
.cart-items .list-item.current-item.qty input {
|
||||
border: 1px solid #5E64FF;
|
||||
font-weight: bold;
|
||||
}
|
||||
.cart-items .list-item.current-item.disc .discount {
|
||||
font-weight: bold;
|
||||
}
|
||||
.cart-items .list-item.current-item.rate .rate {
|
||||
font-weight: bold;
|
||||
}
|
||||
.cart-items .list-item .quantity {
|
||||
flex: 1.5;
|
||||
}
|
||||
.cart-items input {
|
||||
text-align: right;
|
||||
height: 22px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.fields {
|
||||
display: flex;
|
||||
}
|
||||
.pos-items-wrapper {
|
||||
max-height: 480px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.pos-items {
|
||||
overflow: hidden;
|
||||
}
|
||||
.pos-item-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 25%;
|
||||
}
|
||||
.image-view-container {
|
||||
display: block;
|
||||
}
|
||||
.image-view-container .image-field {
|
||||
height: auto;
|
||||
}
|
||||
.empty-state {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.empty-state span {
|
||||
position: absolute;
|
||||
color: #8D99A6;
|
||||
font-size: 12px;
|
||||
top: 50%;
|
||||
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;
|
||||
}
|
||||
.num-col.active {
|
||||
background-color: #fffce7;
|
||||
}
|
||||
.num-col.brand-primary {
|
||||
background-color: #5E64FF;
|
||||
color: #ffffff;
|
||||
}
|
||||
.discount-amount .discount-inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px 0;
|
||||
}
|
||||
.discount-amount input:first-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.taxes-and-totals {
|
||||
border-top: 1px solid #d1d8dd;
|
||||
}
|
||||
.taxes-and-totals .taxes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px 0;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.taxes-and-totals .taxes > div:first-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.grand-total {
|
||||
border-top: 1px solid #d1d8dd;
|
||||
}
|
||||
.grand-total .list-item {
|
||||
height: 60px;
|
||||
}
|
||||
.grand-total .grand-total-value {
|
||||
font-size: 24px;
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
setup: function() {
|
||||
this._super();
|
||||
frappe.flags.hide_serial_batch_dialog = false;
|
||||
frappe.ui.form.on(this.frm.doctype + " Item", "rate", function(frm, cdt, cdn) {
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
var has_margin_field = frappe.meta.has_field(cdt, 'margin_type');
|
||||
@ -314,12 +315,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
if(!r.exc) {
|
||||
me.frm.script_manager.trigger("price_list_rate", cdt, cdn);
|
||||
me.toggle_conversion_factor(item);
|
||||
if(show_batch_dialog) {
|
||||
if(show_batch_dialog && !frappe.flags.hide_serial_batch_dialog) {
|
||||
var d = locals[cdt][cdn];
|
||||
$.each(r.message, function(k, v) {
|
||||
if(!d[k]) d[k] = v;
|
||||
});
|
||||
erpnext.show_serial_batch_selector(me.frm, d);
|
||||
|
||||
erpnext.show_serial_batch_selector(me.frm, d, (item) => {
|
||||
me.frm.script_manager.trigger('qty', item.doctype, item.name);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1153,7 +1157,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.show_serial_batch_selector = function(frm, d) {
|
||||
erpnext.show_serial_batch_selector = function(frm, d, callback, show_dialog) {
|
||||
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
|
||||
new erpnext.SerialNoBatchSelector({
|
||||
frm: frm,
|
||||
@ -1162,6 +1166,7 @@ erpnext.show_serial_batch_selector = function(frm, d) {
|
||||
type: "Warehouse",
|
||||
name: d.warehouse
|
||||
},
|
||||
});
|
||||
callback: callback
|
||||
}, show_dialog);
|
||||
});
|
||||
}
|
||||
|
330
erpnext/public/js/pos/clusterize.js
Normal file
330
erpnext/public/js/pos/clusterize.js
Normal file
@ -0,0 +1,330 @@
|
||||
/* eslint-disable */
|
||||
/*! Clusterize.js - v0.17.6 - 2017-03-05
|
||||
* http://NeXTs.github.com/Clusterize.js/
|
||||
* Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */
|
||||
|
||||
;(function(name, definition) {
|
||||
if (typeof module != 'undefined') module.exports = definition();
|
||||
else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
|
||||
else this[name] = definition();
|
||||
}('Clusterize', function() {
|
||||
"use strict"
|
||||
|
||||
// detect ie9 and lower
|
||||
// https://gist.github.com/padolsey/527683#comment-786682
|
||||
var ie = (function(){
|
||||
for( var v = 3,
|
||||
el = document.createElement('b'),
|
||||
all = el.all || [];
|
||||
el.innerHTML = '<!--[if gt IE ' + (++v) + ']><i><![endif]-->',
|
||||
all[0];
|
||||
){}
|
||||
return v > 4 ? v : document.documentMode;
|
||||
}()),
|
||||
is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1;
|
||||
var Clusterize = function(data) {
|
||||
if( ! (this instanceof Clusterize))
|
||||
return new Clusterize(data);
|
||||
var self = this;
|
||||
|
||||
var defaults = {
|
||||
rows_in_block: 50,
|
||||
blocks_in_cluster: 4,
|
||||
tag: null,
|
||||
show_no_data_row: true,
|
||||
no_data_class: 'clusterize-no-data',
|
||||
no_data_text: 'No data',
|
||||
keep_parity: true,
|
||||
callbacks: {}
|
||||
}
|
||||
|
||||
// public parameters
|
||||
self.options = {};
|
||||
var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks'];
|
||||
for(var i = 0, option; option = options[i]; i++) {
|
||||
self.options[option] = typeof data[option] != 'undefined' && data[option] != null
|
||||
? data[option]
|
||||
: defaults[option];
|
||||
}
|
||||
|
||||
var elems = ['scroll', 'content'];
|
||||
for(var i = 0, elem; elem = elems[i]; i++) {
|
||||
self[elem + '_elem'] = data[elem + 'Id']
|
||||
? document.getElementById(data[elem + 'Id'])
|
||||
: data[elem + 'Elem'];
|
||||
if( ! self[elem + '_elem'])
|
||||
throw new Error("Error! Could not find " + elem + " element");
|
||||
}
|
||||
|
||||
// tabindex forces the browser to keep focus on the scrolling list, fixes #11
|
||||
if( ! self.content_elem.hasAttribute('tabindex'))
|
||||
self.content_elem.setAttribute('tabindex', 0);
|
||||
|
||||
// private parameters
|
||||
var rows = isArray(data.rows)
|
||||
? data.rows
|
||||
: self.fetchMarkup(),
|
||||
cache = {},
|
||||
scroll_top = self.scroll_elem.scrollTop;
|
||||
|
||||
// append initial data
|
||||
self.insertToDOM(rows, cache);
|
||||
|
||||
// restore the scroll position
|
||||
self.scroll_elem.scrollTop = scroll_top;
|
||||
|
||||
// adding scroll handler
|
||||
var last_cluster = false,
|
||||
scroll_debounce = 0,
|
||||
pointer_events_set = false,
|
||||
scrollEv = function() {
|
||||
// fixes scrolling issue on Mac #3
|
||||
if (is_mac) {
|
||||
if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none';
|
||||
pointer_events_set = true;
|
||||
clearTimeout(scroll_debounce);
|
||||
scroll_debounce = setTimeout(function () {
|
||||
self.content_elem.style.pointerEvents = 'auto';
|
||||
pointer_events_set = false;
|
||||
}, 50);
|
||||
}
|
||||
if (last_cluster != (last_cluster = self.getClusterNum()))
|
||||
self.insertToDOM(rows, cache);
|
||||
if (self.options.callbacks.scrollingProgress)
|
||||
self.options.callbacks.scrollingProgress(self.getScrollProgress());
|
||||
},
|
||||
resize_debounce = 0,
|
||||
resizeEv = function() {
|
||||
clearTimeout(resize_debounce);
|
||||
resize_debounce = setTimeout(self.refresh, 100);
|
||||
}
|
||||
on('scroll', self.scroll_elem, scrollEv);
|
||||
on('resize', window, resizeEv);
|
||||
|
||||
// public methods
|
||||
self.destroy = function(clean) {
|
||||
off('scroll', self.scroll_elem, scrollEv);
|
||||
off('resize', window, resizeEv);
|
||||
self.html((clean ? self.generateEmptyRow() : rows).join(''));
|
||||
}
|
||||
self.refresh = function(force) {
|
||||
if(self.getRowsHeight(rows) || force) self.update(rows);
|
||||
}
|
||||
self.update = function(new_rows) {
|
||||
rows = isArray(new_rows)
|
||||
? new_rows
|
||||
: [];
|
||||
var scroll_top = self.scroll_elem.scrollTop;
|
||||
// fixes #39
|
||||
if(rows.length * self.options.item_height < scroll_top) {
|
||||
self.scroll_elem.scrollTop = 0;
|
||||
last_cluster = 0;
|
||||
}
|
||||
self.insertToDOM(rows, cache);
|
||||
self.scroll_elem.scrollTop = scroll_top;
|
||||
}
|
||||
self.clear = function() {
|
||||
self.update([]);
|
||||
}
|
||||
self.getRowsAmount = function() {
|
||||
return rows.length;
|
||||
}
|
||||
self.getScrollProgress = function() {
|
||||
return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0;
|
||||
}
|
||||
|
||||
var add = function(where, _new_rows) {
|
||||
var new_rows = isArray(_new_rows)
|
||||
? _new_rows
|
||||
: [];
|
||||
if( ! new_rows.length) return;
|
||||
rows = where == 'append'
|
||||
? rows.concat(new_rows)
|
||||
: new_rows.concat(rows);
|
||||
self.insertToDOM(rows, cache);
|
||||
}
|
||||
self.append = function(rows) {
|
||||
add('append', rows);
|
||||
}
|
||||
self.prepend = function(rows) {
|
||||
add('prepend', rows);
|
||||
}
|
||||
}
|
||||
|
||||
Clusterize.prototype = {
|
||||
constructor: Clusterize,
|
||||
// fetch existing markup
|
||||
fetchMarkup: function() {
|
||||
var rows = [], rows_nodes = this.getChildNodes(this.content_elem);
|
||||
while (rows_nodes.length) {
|
||||
rows.push(rows_nodes.shift().outerHTML);
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
// get tag name, content tag name, tag height, calc cluster height
|
||||
exploreEnvironment: function(rows, cache) {
|
||||
var opts = this.options;
|
||||
opts.content_tag = this.content_elem.tagName.toLowerCase();
|
||||
if( ! rows.length) return;
|
||||
if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase();
|
||||
if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]);
|
||||
if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase();
|
||||
this.getRowsHeight(rows);
|
||||
},
|
||||
getRowsHeight: function(rows) {
|
||||
var opts = this.options,
|
||||
prev_item_height = opts.item_height;
|
||||
opts.cluster_height = 0;
|
||||
if( ! rows.length) return;
|
||||
var nodes = this.content_elem.children;
|
||||
var node = nodes[Math.floor(nodes.length / 2)];
|
||||
opts.item_height = node.offsetHeight;
|
||||
// consider table's border-spacing
|
||||
if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse')
|
||||
opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0;
|
||||
// consider margins (and margins collapsing)
|
||||
if(opts.tag != 'tr') {
|
||||
var marginTop = parseInt(getStyle('marginTop', node), 10) || 0;
|
||||
var marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0;
|
||||
opts.item_height += Math.max(marginTop, marginBottom);
|
||||
}
|
||||
opts.block_height = opts.item_height * opts.rows_in_block;
|
||||
opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block;
|
||||
opts.cluster_height = opts.blocks_in_cluster * opts.block_height;
|
||||
return prev_item_height != opts.item_height;
|
||||
},
|
||||
// get current cluster number
|
||||
getClusterNum: function () {
|
||||
this.options.scroll_top = this.scroll_elem.scrollTop;
|
||||
return Math.floor(this.options.scroll_top / (this.options.cluster_height - this.options.block_height)) || 0;
|
||||
},
|
||||
// generate empty row if no data provided
|
||||
generateEmptyRow: function() {
|
||||
var opts = this.options;
|
||||
if( ! opts.tag || ! opts.show_no_data_row) return [];
|
||||
var empty_row = document.createElement(opts.tag),
|
||||
no_data_content = document.createTextNode(opts.no_data_text), td;
|
||||
empty_row.className = opts.no_data_class;
|
||||
if(opts.tag == 'tr') {
|
||||
td = document.createElement('td');
|
||||
// fixes #53
|
||||
td.colSpan = 100;
|
||||
td.appendChild(no_data_content);
|
||||
}
|
||||
empty_row.appendChild(td || no_data_content);
|
||||
return [empty_row.outerHTML];
|
||||
},
|
||||
// generate cluster for current scroll position
|
||||
generate: function (rows, cluster_num) {
|
||||
var opts = this.options,
|
||||
rows_len = rows.length;
|
||||
if (rows_len < opts.rows_in_block) {
|
||||
return {
|
||||
top_offset: 0,
|
||||
bottom_offset: 0,
|
||||
rows_above: 0,
|
||||
rows: rows_len ? rows : this.generateEmptyRow()
|
||||
}
|
||||
}
|
||||
var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * cluster_num, 0),
|
||||
items_end = items_start + opts.rows_in_cluster,
|
||||
top_offset = Math.max(items_start * opts.item_height, 0),
|
||||
bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0),
|
||||
this_cluster_rows = [],
|
||||
rows_above = items_start;
|
||||
if(top_offset < 1) {
|
||||
rows_above++;
|
||||
}
|
||||
for (var i = items_start; i < items_end; i++) {
|
||||
rows[i] && this_cluster_rows.push(rows[i]);
|
||||
}
|
||||
return {
|
||||
top_offset: top_offset,
|
||||
bottom_offset: bottom_offset,
|
||||
rows_above: rows_above,
|
||||
rows: this_cluster_rows
|
||||
}
|
||||
},
|
||||
renderExtraTag: function(class_name, height) {
|
||||
var tag = document.createElement(this.options.tag),
|
||||
clusterize_prefix = 'clusterize-';
|
||||
tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' ');
|
||||
height && (tag.style.height = height + 'px');
|
||||
return tag.outerHTML;
|
||||
},
|
||||
// if necessary verify data changed and insert to DOM
|
||||
insertToDOM: function(rows, cache) {
|
||||
// explore row's height
|
||||
if( ! this.options.cluster_height) {
|
||||
this.exploreEnvironment(rows, cache);
|
||||
}
|
||||
var data = this.generate(rows, this.getClusterNum()),
|
||||
this_cluster_rows = data.rows.join(''),
|
||||
this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache),
|
||||
top_offset_changed = this.checkChanges('top', data.top_offset, cache),
|
||||
only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache),
|
||||
callbacks = this.options.callbacks,
|
||||
layout = [];
|
||||
|
||||
if(this_cluster_content_changed || top_offset_changed) {
|
||||
if(data.top_offset) {
|
||||
this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity'));
|
||||
layout.push(this.renderExtraTag('top-space', data.top_offset));
|
||||
}
|
||||
layout.push(this_cluster_rows);
|
||||
data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset));
|
||||
callbacks.clusterWillChange && callbacks.clusterWillChange();
|
||||
this.html(layout.join(''));
|
||||
this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above);
|
||||
callbacks.clusterChanged && callbacks.clusterChanged();
|
||||
} else if(only_bottom_offset_changed) {
|
||||
this.content_elem.lastChild.style.height = data.bottom_offset + 'px';
|
||||
}
|
||||
},
|
||||
// unfortunately ie <= 9 does not allow to use innerHTML for table elements, so make a workaround
|
||||
html: function(data) {
|
||||
var content_elem = this.content_elem;
|
||||
if(ie && ie <= 9 && this.options.tag == 'tr') {
|
||||
var div = document.createElement('div'), last;
|
||||
div.innerHTML = '<table><tbody>' + data + '</tbody></table>';
|
||||
while((last = content_elem.lastChild)) {
|
||||
content_elem.removeChild(last);
|
||||
}
|
||||
var rows_nodes = this.getChildNodes(div.firstChild.firstChild);
|
||||
while (rows_nodes.length) {
|
||||
content_elem.appendChild(rows_nodes.shift());
|
||||
}
|
||||
} else {
|
||||
content_elem.innerHTML = data;
|
||||
}
|
||||
},
|
||||
getChildNodes: function(tag) {
|
||||
var child_nodes = tag.children, nodes = [];
|
||||
for (var i = 0, ii = child_nodes.length; i < ii; i++) {
|
||||
nodes.push(child_nodes[i]);
|
||||
}
|
||||
return nodes;
|
||||
},
|
||||
checkChanges: function(type, value, cache) {
|
||||
var changed = value != cache[type];
|
||||
cache[type] = value;
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
||||
// support functions
|
||||
function on(evt, element, fnc) {
|
||||
return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc);
|
||||
}
|
||||
function off(evt, element, fnc) {
|
||||
return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc);
|
||||
}
|
||||
function isArray(arr) {
|
||||
return Object.prototype.toString.call(arr) === '[object Array]';
|
||||
}
|
||||
function getStyle(prop, elem) {
|
||||
return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop];
|
||||
}
|
||||
|
||||
return Clusterize;
|
||||
}));
|
@ -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) {
|
||||
|
@ -364,17 +364,6 @@ body[data-route="pos"] {
|
||||
.pos-item-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.price-info {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
margin: 0 0 15px 15px;
|
||||
background-color: rgba(141, 153, 166, 0.6);
|
||||
padding: 5px 9px;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.pos-bill-toolbar {
|
||||
@ -423,4 +412,15 @@ body[data-route="pos"] {
|
||||
.collapse-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.price-info {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
margin: 0 0 15px 15px;
|
||||
background-color: rgba(141, 153, 166, 0.6);
|
||||
padding: 5px 9px;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
}
|
222
erpnext/public/less/pos.less
Normal file
222
erpnext/public/less/pos.less
Normal file
@ -0,0 +1,222 @@
|
||||
@import "../../../../frappe/frappe/public/less/variables.less";
|
||||
|
||||
[data-route="point-of-sale"] {
|
||||
.layout-main-section-wrapper {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.pos-items-wrapper {
|
||||
max-height: ~"calc(100vh - 210px)";
|
||||
}
|
||||
}
|
||||
|
||||
.pos {
|
||||
// display: flex;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
min-height: 40px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.cart-container {
|
||||
padding: 0 15px;
|
||||
// flex: 2;
|
||||
display: inline-block;
|
||||
width: 39%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.item-container {
|
||||
padding: 0 15px;
|
||||
// flex: 3;
|
||||
display: inline-block;
|
||||
width: 60%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.search-field {
|
||||
width: 60%;
|
||||
|
||||
input::placeholder {
|
||||
font-size: @text-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.item-group-field {
|
||||
width: 40%;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.cart-wrapper {
|
||||
margin-bottom: 10px;
|
||||
.list-item__content:not(:first-child) {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.list-item--head .list-item__content:nth-child(2) {
|
||||
flex: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.cart-items {
|
||||
height: 150px;
|
||||
overflow: auto;
|
||||
|
||||
.list-item.current-item {
|
||||
background-color: @light-yellow;
|
||||
}
|
||||
|
||||
.list-item.current-item.qty input {
|
||||
border: 1px solid @brand-primary;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list-item.current-item.disc .discount {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list-item.current-item.rate .rate {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list-item .quantity {
|
||||
flex: 1.5;
|
||||
}
|
||||
|
||||
input {
|
||||
text-align: right;
|
||||
height: 22px;
|
||||
font-size: @text-medium;
|
||||
}
|
||||
}
|
||||
|
||||
.fields {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.pos-items-wrapper {
|
||||
max-height: 480px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.pos-items {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pos-item-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.image-view-container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.image-view-container .image-field {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
color: @text-muted;
|
||||
font-size: @text-medium;
|
||||
top: 50%;
|
||||
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;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: @light-yellow;
|
||||
}
|
||||
|
||||
&.brand-primary {
|
||||
background-color: @brand-primary;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
// taxes, totals and discount area
|
||||
.discount-amount {
|
||||
.discount-inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
input:first-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.taxes-and-totals {
|
||||
border-top: 1px solid @border-color;
|
||||
|
||||
.taxes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px 0;
|
||||
align-items: flex-end;
|
||||
|
||||
& > div:first-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grand-total {
|
||||
border-top: 1px solid @border-color;
|
||||
|
||||
.list-item {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.grand-total-value {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
QUnit.module('Sales Order');
|
||||
|
||||
QUnit.test("test sales order", function(assert) {
|
||||
assert.expect(8);
|
||||
assert.expect(10);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
@ -12,7 +12,7 @@ QUnit.test("test sales order", function(assert) {
|
||||
{'delivery_date': frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
||||
{'qty': 5},
|
||||
{'item_code': 'Test Product 4'},
|
||||
{'uom': 'unit'},
|
||||
{'uom': 'Nos'},
|
||||
{'margin_type': 'Percentage'},
|
||||
{'discount_percentage': 10},
|
||||
]
|
||||
@ -33,7 +33,7 @@ QUnit.test("test sales order", function(assert) {
|
||||
{additional_discount_percentage:10}
|
||||
]);
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
// get_item_details
|
||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 4', "Item name correct");
|
||||
@ -42,15 +42,19 @@ QUnit.test("test sales order", function(assert) {
|
||||
// get tax account head details
|
||||
assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
|
||||
// calculate totals
|
||||
assert.ok(cur_frm.doc.items[0].price_list_rate==1000, "Item 1 price_list_rate");
|
||||
assert.ok(cur_frm.doc.total== 4500, "total correct ");
|
||||
assert.ok(cur_frm.doc.rounded_total== 4414.5, "rounded total correct ");
|
||||
|
||||
assert.ok(cur_frm.doc.items[0].price_list_rate==90, "Item 1 price_list_rate");
|
||||
assert.ok(cur_frm.doc.total== 405, "total correct ");
|
||||
assert.ok(cur_frm.doc.net_total== 364.5, "net total correct ");
|
||||
assert.ok(cur_frm.doc.grand_total== 397.30, "grand total correct ");
|
||||
assert.ok(cur_frm.doc.rounded_total== 397.30, "rounded total correct ");
|
||||
},
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.print_doc(),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.ok($('.btn-print-print').is(':visible'), "Print Format Available");
|
||||
frappe.timeout(1);
|
||||
assert.ok($(".section-break+ .section-break .column-break:nth-child(1) .data-field:nth-child(1) .value").text().includes("Billing Street 1"), "Print Preview Works As Expected");
|
||||
},
|
||||
() => cur_frm.print_doc(),
|
||||
|
0
erpnext/selling/page/point_of_sale/__init__.py
Normal file
0
erpnext/selling/page/point_of_sale/__init__.py
Normal file
1284
erpnext/selling/page/point_of_sale/point_of_sale.js
Normal file
1284
erpnext/selling/page/point_of_sale/point_of_sale.js
Normal file
File diff suppressed because it is too large
Load Diff
20
erpnext/selling/page/point_of_sale/point_of_sale.json
Normal file
20
erpnext/selling/page/point_of_sale/point_of_sale.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"content": null,
|
||||
"creation": "2017-08-07 17:08:56.737947",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"idx": 0,
|
||||
"modified": "2017-08-07 17:08:56.737947",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "point-of-sale",
|
||||
"owner": "Administrator",
|
||||
"page_name": "Point of Sale",
|
||||
"restrict_to_domain": "Retail",
|
||||
"roles": [],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "Point of Sale"
|
||||
}
|
71
erpnext/selling/page/point_of_sale/point_of_sale.py
Normal file
71
erpnext/selling/page/point_of_sale/point_of_sale.py
Normal file
@ -0,0 +1,71 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_items(start, page_length, price_list, item_group, search_value=""):
|
||||
serial_no = ""
|
||||
batch_no = ""
|
||||
item_code = search_value
|
||||
|
||||
if search_value:
|
||||
# search serial no
|
||||
serial_no_data = frappe.db.get_value('Serial No', search_value, ['name', 'item_code'])
|
||||
if serial_no_data:
|
||||
serial_no, item_code = serial_no_data
|
||||
|
||||
if not serial_no:
|
||||
batch_no_data = frappe.db.get_value('Batch', search_value, ['name', 'item'])
|
||||
if batch_no_data:
|
||||
batch_no, item_code = batch_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
|
||||
from `tabItem` i LEFT JOIN
|
||||
(select item_code, price_list_rate, currency from
|
||||
`tabItem Price` where price_list=%(price_list)s) item_det
|
||||
ON
|
||||
(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 or i.barcode like %(item_code)s)
|
||||
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
|
||||
} , as_dict=1)
|
||||
|
||||
res = {
|
||||
'items': res
|
||||
}
|
||||
|
||||
if serial_no:
|
||||
res.update({
|
||||
'serial_no': serial_no
|
||||
})
|
||||
|
||||
if batch_no:
|
||||
res.update({
|
||||
'batch_no': batch_no
|
||||
})
|
||||
|
||||
return res
|
||||
|
||||
@frappe.whitelist()
|
||||
def submit_invoice(doc):
|
||||
if isinstance(doc, basestring):
|
||||
args = json.loads(doc)
|
||||
|
||||
doc = frappe.new_doc('Sales Invoice')
|
||||
doc.update(args)
|
||||
doc.run_method("set_missing_values")
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
doc.submit()
|
||||
|
||||
return doc
|
@ -0,0 +1,38 @@
|
||||
QUnit.test("test:Point of Sales", function(assert) {
|
||||
assert.expect(1);
|
||||
let done = assert.async();
|
||||
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('point-of-sale'),
|
||||
() => frappe.timeout(2),
|
||||
() => frappe.set_control('customer', 'Test Customer 1'),
|
||||
() => frappe.timeout(0.2),
|
||||
() => cur_frm.set_value('customer', 'Test Customer 1'),
|
||||
() => frappe.timeout(2),
|
||||
() => frappe.click_link('Test Product 2'),
|
||||
() => frappe.timeout(0.2),
|
||||
() => frappe.click_element(`.cart-items [data-item-code="Test Product 2"]`),
|
||||
() => frappe.timeout(0.2),
|
||||
() => frappe.click_element(`.number-pad [data-value="Rate"]`),
|
||||
() => frappe.timeout(0.2),
|
||||
() => frappe.click_element(`.number-pad [data-value="2"]`),
|
||||
() => frappe.timeout(0.2),
|
||||
() => frappe.click_element(`.number-pad [data-value="5"]`),
|
||||
() => frappe.timeout(0.2),
|
||||
() => frappe.click_element(`.number-pad [data-value="0"]`),
|
||||
() => frappe.timeout(0.2),
|
||||
() => frappe.click_element(`.number-pad [data-value="Pay"]`),
|
||||
() => frappe.timeout(0.2),
|
||||
() => frappe.click_element(`.frappe-control [data-value="4"]`),
|
||||
() => frappe.timeout(0.2),
|
||||
() => frappe.click_element(`.frappe-control [data-value="5"]`),
|
||||
() => frappe.timeout(0.2),
|
||||
() => frappe.click_element(`.frappe-control [data-value="0"]`),
|
||||
() => frappe.timeout(0.2),
|
||||
() => frappe.click_button('Submit'),
|
||||
() => frappe.click_button('Yes'),
|
||||
() => frappe.timeout(3),
|
||||
() => assert.ok(cur_frm.doc.docstatus==1, "Sales invoice created successfully"),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
QUnit.test("test:POS Settings", function(assert) {
|
||||
assert.expect(1);
|
||||
let done = assert.async();
|
||||
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('Form', 'POS Settings'),
|
||||
() => cur_frm.set_value('is_online', 1),
|
||||
() => frappe.timeout(0.2),
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.ui.toolbar.clear_cache(),
|
||||
() => frappe.timeout(10),
|
||||
() => assert.ok(cur_frm.doc.is_online==1, "Enabled online"),
|
||||
() => frappe.timeout(2),
|
||||
() => done()
|
||||
]);
|
||||
});
|
@ -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
|
||||
|
@ -50,6 +50,8 @@ erpnext/schools/doctype/room/test_room.js
|
||||
erpnext/schools/doctype/instructor/test_instructor.js
|
||||
erpnext/stock/doctype/warehouse/test_warehouse.js
|
||||
erpnext/manufacturing/doctype/production_order/test_production_order.js #long
|
||||
erpnext/selling/page/point_of_sale/tests/test_pos_settings.js
|
||||
erpnext/selling/page/point_of_sale/tests/test_point_of_sale.js
|
||||
erpnext/accounts/page/pos/test_pos.js
|
||||
erpnext/selling/doctype/product_bundle/test_product_bundle.js
|
||||
erpnext/stock/doctype/delivery_note/test_delivery_note.js
|
||||
|
Loading…
Reference in New Issue
Block a user