From f14092cbfa127ce8367779148b216284235a58e0 Mon Sep 17 00:00:00 2001 From: pha-odoo Date: Thu, 18 Oct 2018 06:32:58 +0000 Subject: [PATCH] [IMP] point_of_sale: Connect with Employee on PoS ========= PURPOSE ========= {Any employee can work on a PoS session, even if he has no user. This decreases the cost of PoS App.} We are not competitive enough on the PoS Market because customers have to pay one user per cashier while they expect to pay one user per cash station. =============== SPECIFICATIONS =============== pos.config - Add a checkbox : "Login with Employees" - If set to TRUE, an "Allowed Employees" many2many appear - If employees many2many is empty, all employees can log on this PoS - If set to FALSE, can only log in with the currently logged user hr.employee - The barcode and pin fields were be moved to the hr.employee - Set a barcode (not pin) by default at the creation of any employee - No Employees module with PoS > dependency to hr Open the PoS if "Log in with Employees" = FALSE - Same behaviour than now, when I open the PoS, I'm logged in with the current user and can start working at once. The only difference is that it is not possible to switch users anymore. - The button to close the session is visible Open he PoS if "Log in with Employees" = TRUE - When I open the PoS, the first screen I see is the login screen. (a) I choose my employee and enter my pin code (if there is pin code on my employee, otherwise not required) (I can only see employees that have access to that PoS) (b) I scan my barcode (c) I scan my rfid card (future development) - When I click on the employee name, I can log in with another employee - If the employee is linked to a user that has the pos user/manager access right, I can see the button the close the session. Otherwise I cannot close the session (and thus cannot access backend). - Add field employee on all records created by PoS (and replace the field user by that one) (on pos.orders) Lock Screen - Improve the PoS Interface - Add a new feature to allow to "lock" the session. When a session has been locked, the user/employee has to re-log-in to access the PoS - When I try to re-log-in, by default suggest the last employee logged in in the login screen Migration Plan a migration strategy > barcode and pin is now on the employee and not on the user anymore closes odoo/odoo#28567 --- addons/hr/models/hr.py | 16 +++ addons/hr/views/hr_views.xml | 5 + addons/hr_attendance/models/hr_employee.py | 47 ------- .../hr_attendance/views/hr_employee_view.xml | 8 +- addons/point_of_sale/__manifest__.py | 1 - .../point_of_sale/data/point_of_sale_demo.xml | 3 - addons/point_of_sale/models/__init__.py | 1 - addons/point_of_sale/models/pos_config.py | 1 + addons/point_of_sale/models/pos_order.py | 2 +- addons/point_of_sale/models/res_users.py | 15 --- .../point_of_sale/report/pos_order_report.py | 2 +- addons/point_of_sale/static/img/barcode.png | Bin 0 -> 1292 bytes .../static/img/login-bg-overlay.svg | 5 + addons/point_of_sale/static/src/css/pos.css | 108 +++++++++++----- addons/point_of_sale/static/src/js/chrome.js | 72 ++++------- addons/point_of_sale/static/src/js/gui.js | 80 ++---------- addons/point_of_sale/static/src/js/models.js | 60 +++------ addons/point_of_sale/static/src/js/screens.js | 27 ---- addons/point_of_sale/static/src/js/tests.js | 2 +- addons/point_of_sale/static/src/xml/pos.xml | 11 +- .../point_of_sale/views/pos_config_view.xml | 38 +++--- .../views/pos_order_report_view.xml | 4 +- addons/point_of_sale/views/pos_order_view.xml | 10 +- .../point_of_sale/views/res_users_views.xml | 18 --- addons/pos_hr/__init__.py | 4 + addons/pos_hr/__manifest__.py | 25 ++++ addons/pos_hr/models/__init__.py | 4 + addons/pos_hr/models/pos_config.py | 13 ++ addons/pos_hr/models/pos_order.py | 23 ++++ addons/pos_hr/report/__init__.py | 3 + addons/pos_hr/report/pos_order_report.py | 17 +++ addons/pos_hr/static/src/js/chrome.js | 121 ++++++++++++++++++ addons/pos_hr/static/src/js/gui.js | 70 ++++++++++ addons/pos_hr/static/src/js/models.js | 56 ++++++++ addons/pos_hr/static/src/js/screens.js | 91 +++++++++++++ addons/pos_hr/static/src/xml/pos.xml | 20 +++ addons/pos_hr/views/point_of_sale.xml | 12 ++ addons/pos_hr/views/pos_config.xml | 29 +++++ addons/pos_hr/views/pos_order_report_view.xml | 14 ++ addons/pos_hr/views/pos_order_view.xml | 39 ++++++ addons/pos_sale/views/pos_config_views.xml | 1 - 41 files changed, 751 insertions(+), 327 deletions(-) delete mode 100644 addons/point_of_sale/models/res_users.py create mode 100644 addons/point_of_sale/static/img/barcode.png create mode 100644 addons/point_of_sale/static/img/login-bg-overlay.svg delete mode 100644 addons/point_of_sale/views/res_users_views.xml create mode 100644 addons/pos_hr/__init__.py create mode 100644 addons/pos_hr/__manifest__.py create mode 100644 addons/pos_hr/models/__init__.py create mode 100644 addons/pos_hr/models/pos_config.py create mode 100644 addons/pos_hr/models/pos_order.py create mode 100644 addons/pos_hr/report/__init__.py create mode 100644 addons/pos_hr/report/pos_order_report.py create mode 100644 addons/pos_hr/static/src/js/chrome.js create mode 100644 addons/pos_hr/static/src/js/gui.js create mode 100644 addons/pos_hr/static/src/js/models.js create mode 100644 addons/pos_hr/static/src/js/screens.js create mode 100644 addons/pos_hr/static/src/xml/pos.xml create mode 100644 addons/pos_hr/views/point_of_sale.xml create mode 100644 addons/pos_hr/views/pos_config.xml create mode 100644 addons/pos_hr/views/pos_order_report_view.xml create mode 100644 addons/pos_hr/views/pos_order_view.xml diff --git a/addons/hr/models/hr.py b/addons/hr/models/hr.py index 949b6472b1e..27fd8ae6962 100644 --- a/addons/hr/models/hr.py +++ b/addons/hr/models/hr.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from random import choice +from string import digits import base64 import logging @@ -198,6 +200,16 @@ class Employee(models.Model): # misc notes = fields.Text('Notes') color = fields.Integer('Color Index', default=0) + barcode = fields.Char(string="Badge ID", help="ID used for employee identification.", copy=False) + pin = fields.Char(string="PIN", help="PIN used to Check In/Out in Kiosk Mode (if enabled in Configuration).", copy=False) + + _sql_constraints = [('barcode_uniq', 'unique (barcode)', "The Badge ID must be unique, this one is already assigned to another employee.")] + + @api.constrains('pin') + def _verify_pin(self): + for employee in self: + if employee.pin and not employee.pin.isdigit(): + raise ValidationError(_("The PIN must be a sequence of digits.")) @api.onchange('job_id') def _onchange_job_id(self): @@ -273,6 +285,10 @@ class Employee(models.Model): resources = self.mapped('resource_id') super(Employee, self).unlink() return resources.unlink() + + @api.multi + def generate_random_barcode(self): + for i in self: i.barcode = "".join(choice(digits) for i in range(8)) @api.depends('address_home_id.parent_id') def _compute_is_address_home_a_company(self): diff --git a/addons/hr/views/hr_views.xml b/addons/hr/views/hr_views.xml index 89c2cd4a2b8..ca5eecab771 100644 --- a/addons/hr/views/hr_views.xml +++ b/addons/hr/views/hr_views.xml @@ -139,6 +139,11 @@ + + + + - - - - - - + + diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py index 6d58943fc26..5428fcaa521 100644 --- a/addons/point_of_sale/__manifest__.py +++ b/addons/point_of_sale/__manifest__.py @@ -37,7 +37,6 @@ 'views/res_config_settings_views.xml', 'views/digest_views.xml', 'views/res_partner_view.xml', - 'views/res_users_views.xml', 'views/report_statement.xml', 'views/report_userlabel.xml', 'views/report_saledetails.xml', diff --git a/addons/point_of_sale/data/point_of_sale_demo.xml b/addons/point_of_sale/data/point_of_sale_demo.xml index eb792a82102..7bc0fda4a77 100644 --- a/addons/point_of_sale/data/point_of_sale_demo.xml +++ b/addons/point_of_sale/data/point_of_sale_demo.xml @@ -10,9 +10,6 @@ 0420800000008 0421800000005 - - 0410100000006 - 0410100000006 diff --git a/addons/point_of_sale/models/__init__.py b/addons/point_of_sale/models/__init__.py index 89b3974897d..2934be43e10 100644 --- a/addons/point_of_sale/models/__init__.py +++ b/addons/point_of_sale/models/__init__.py @@ -12,5 +12,4 @@ from . import pos_order from . import pos_session from . import product from . import res_partner -from . import res_users from . import res_config_settings diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index 00608a966d3..99007a7abd9 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -174,6 +174,7 @@ class PosConfig(models.Model): module_pos_reprint = fields.Boolean(string="Reprint Receipt") is_posbox = fields.Boolean("PosBox") is_header_or_footer = fields.Boolean("Header & Footer") + module_pos_hr = fields.Boolean(help="Show employee login screen") def _compute_is_installed_account_accountant(self): account_accountant = self.env['ir.module.module'].sudo().search([('name', '=', 'account_accountant'), ('state', '=', 'installed')]) diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 49fe4586442..2d17216eadc 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -524,7 +524,7 @@ class PosOrder(models.Model): company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.user.company_id) date_order = fields.Datetime(string='Order Date', readonly=True, index=True, default=fields.Datetime.now) user_id = fields.Many2one( - comodel_name='res.users', string='Salesperson', + comodel_name='res.users', string='User', help="Person who uses the cash register. It can be a reliever, a student or an interim employee.", default=lambda self: self.env.uid, states={'done': [('readonly', True)], 'invoiced': [('readonly', True)]}, diff --git a/addons/point_of_sale/models/res_users.py b/addons/point_of_sale/models/res_users.py deleted file mode 100644 index 6fb09e8a44e..00000000000 --- a/addons/point_of_sale/models/res_users.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models, _ -from odoo.exceptions import UserError - - -class ResUsers(models.Model): - _inherit = 'res.users' - - pos_security_pin = fields.Char(string='Security PIN', size=32, help='A Security PIN used to protect sensible functionality in the Point of Sale') - - @api.constrains('pos_security_pin') - def _check_pin(self): - if self.pos_security_pin and not self.pos_security_pin.isdigit(): - raise UserError(_("Security PIN can only contain digits")) diff --git a/addons/point_of_sale/report/pos_order_report.py b/addons/point_of_sale/report/pos_order_report.py index 25bd27d9bcf..4de7412ac0b 100644 --- a/addons/point_of_sale/report/pos_order_report.py +++ b/addons/point_of_sale/report/pos_order_report.py @@ -19,7 +19,7 @@ class PosOrderReport(models.Model): [('draft', 'New'), ('paid', 'Paid'), ('done', 'Posted'), ('invoiced', 'Invoiced'), ('cancel', 'Cancelled')], string='Status') - user_id = fields.Many2one('res.users', string='Salesperson', readonly=True) + user_id = fields.Many2one('res.users', string='User', readonly=True) price_total = fields.Float(string='Total Price', readonly=True) price_sub_total = fields.Float(string='Subtotal w/o discount', readonly=True) total_discount = fields.Float(string='Total Discount', readonly=True) diff --git a/addons/point_of_sale/static/img/barcode.png b/addons/point_of_sale/static/img/barcode.png new file mode 100644 index 0000000000000000000000000000000000000000..2e9d2f4bc0487f42fd9848498f821f3422c36266 GIT binary patch literal 1292 zcmeAS@N?(olHy`uVBq!ia0y~yV7vjub{s4~k(RS3dw~>FlDE4H!~gdFGy8!&&H|6f zVg?3oVGw3ym^DWND9B#o>FdgVpOb}`U()UAH3bF+mbsoTjv*Dd-d?x&xKb!{{GtED z2Le|fT(}|9cwtiTEKPHd)EF35K@1qf&bEaz4cUwG`;v;_wh9akM(xNzY#122s& z0$c5aCI+~b^)?;8`}|#9d5Z1%{5|jYK0STcyTo0lTRCv`!5`DCt_vT}z9-lDlxzJr zxA-lWf_lU@T^1GR+W9|h`s0Ly=5qZzk37FW^VY2HkJIX+mfnc|ULF3}>*)2{kHd;y z=Gm?I^pI%NWW809+A;!WD!2&uQVgfn}5g_ca0fZY89D$he zU=t8CHL-#)3pWT4w1z?IESw<)3b8>Pyf?~j&D!-(Vc(TM^QLyK_Y1Aun=zmJR;Ac; zZeVIOPu}!E`n>8npCfy(Tr29{e^z(Y0@JSyV)#~xnUd349D7?~dDmBpV8C&la$o4c}D|8Z4Op~<)9=WP3> z>+?53@=hNAQt{i9yAPk-8{c}rUU#p>#F!XQ{d<0TdP~ILtB9vx?w)+|=<=6G-A^8! z`Q_2$Cyz>mt6#5Ly#4I*C;mwv>8&88J0;<7%eI)J%?x@z6WBT>N>m|EoK<)x^Fp1_o{I59Zs| W?K=ehnXQ0j7lWs(pUXO@geCy7 + + + + diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 59ec2f0b801..5127fe75c71 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -2149,22 +2149,6 @@ td { font-family: 'Lato'; font-family: Lato; } - -.pos .modal-dialog .popup-textinput{ - display: flex; - flex-direction: column; -} - -.pos .modal-dialog .popup-textinput .title, -.pos .modal-dialog .popup-textinput .footer{ - flex: none; -} - -.pos .modal-dialog .popup-textinput textarea{ - resize: none; - flex: auto; - margin: 0 20px 80px; -} .pos .modal-dialog .popup-confirm{ height: 250px; } @@ -2172,19 +2156,6 @@ td { overflow: auto; height: 120px; } -.pos .modal-dialog .popup-textinput{ - display: flex; - flex-direction: column; -} -.pos .modal-dialog .popup-textinput .title, -.pos .modal-dialog .popup-textinput .footer{ - flex: none; -} -.pos .modal-dialog .popup-textinput textarea{ - resize: none; - flex: auto; - margin: 0 20px 80px; -} .pos .popup .title { background: rgba(255,255,255,0.5); margin: 0; @@ -2471,3 +2442,82 @@ td { .pos .not-supported-browser img{ border-collapse: separate; } + +/* ********* Login Screen ********* */ + +.pos .login-overlay{ + position: fixed; + left: 0; + top: 0; + bottom: 0; + right: 0; + width: 100%; + height:100%; + z-index:1000; + background: linear-gradient(to right bottom, #77717e, #c9a8a9); +} +.pos .login-overlay:before { + content: ''; + background-image: url(../../img/login-bg-overlay.svg); + background-color: rgba(0, 0, 0, 0.3); + position: fixed; + left: 0; + top: 0; + bottom: 0; + right: 0; + width: 100%; + height:100%; +} +.pos .screen-login{ + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + margin: auto; + width:550px; + height:300px; + text-align:center; + font-size:20px; + font-weight:bold; + background-color: #F0EEEE; + border-radius: 3px; + z-index:1200; + font-family: 'Lato'; +} +.pos .login-title{ + height: 55%; + vertical-align: middle; + line-height: 5; + font-size: larger; +} +.pos .login-body{ + height:33%; +} +.pos .login-element{ + float: left; + width: 45%; + height: 60%; +} +.pos .login-barcode-img{ + width: 80px; + height: 55px; + background: white; + border: 0px; +} +.pos .login-barcode-text{ + color: #999999; + font-size: 13px; + padding-top: 0.2em; +} +.pos .login-or{ + font-size: 15px; + font-style: italic; + float: left; + width: 10%; + height: 100%; + line-height: 5; +} +.pos .login-button{ + font-size: initial; + height: 100%; + color: #555555; + border-radius: 5px; +} diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js index a7ef2d69b86..d0d74c272f2 100644 --- a/addons/point_of_sale/static/src/js/chrome.js +++ b/addons/point_of_sale/static/src/js/chrome.js @@ -80,8 +80,7 @@ var OrderSelectorWidget = PosBaseWidget.extend({ /* ------- The User Name Widget ------- */ -// Displays the current cashier's name and allows -// to switch between cashiers. +// Displays the current cashier's name var UsernameWidget = PosBaseWidget.extend({ template: 'UsernameWidget', @@ -89,25 +88,6 @@ var UsernameWidget = PosBaseWidget.extend({ options = options || {}; this._super(parent,options); }, - renderElement: function(){ - var self = this; - this._super(); - - this.$el.click(function(){ - self.click_username(); - }); - }, - click_username: function(){ - var self = this; - this.gui.select_user({ - 'security': true, - 'current_user': this.pos.get_cashier(), - 'title': _t('Change Cashier'), - }).then(function(user){ - self.pos.set_cashier(user); - self.renderElement(); - }); - }, get_name: function(){ var user = this.pos.get_cashier(); if(user){ @@ -130,7 +110,9 @@ var HeaderButtonWidget = PosBaseWidget.extend({ options = options || {}; this._super(parent, options); this.action = options.action; - this.label = options.label; + this.label = options.label; + this.button_class = options.button_class; + }, renderElement: function(){ var self = this; @@ -141,8 +123,8 @@ var HeaderButtonWidget = PosBaseWidget.extend({ }); } }, - show: function(){ this.$el.removeClass('oe_hidden'); }, - hide: function(){ this.$el.addClass('oe_hidden'); }, + show: function() { this.$el.removeClass('oe_hidden'); }, + hide: function() { this.$el.addClass('oe_hidden'); }, }); /* --------- The Debug Widget --------- */ @@ -510,7 +492,6 @@ var ClientScreenWidget = PosBaseWidget.extend({ this.$el.click(function(){ self.pos.render_html_for_customer_facing_display().then(function(rendered_html) { self.pos.proxy.take_ownership_over_client_screen(rendered_html).then( - function(data) { if (typeof data === 'string') { data = JSON.parse(data); @@ -587,7 +568,6 @@ var Chrome = PosBaseWidget.extend(AbstractAction.prototype, { this.widget = {}; // contains references to subwidgets instances this.cleanup_dom(); - this.pos.ready.done(function(){ self.build_chrome(); self.build_widgets(); @@ -824,8 +804,9 @@ var Chrome = PosBaseWidget.extend(AbstractAction.prototype, { 'widget': HeaderButtonWidget, 'append': '.pos-rightheader', 'args': { - label: _lt('Close'), + label: _t('Close'), action: function(){ + this.$el.addClass('close_button'); var self = this; if (!this.confirmed) { this.$el.addClass('confirm'); @@ -856,30 +837,33 @@ var Chrome = PosBaseWidget.extend(AbstractAction.prototype, { }, ], - // This method instantiates all the screens, widgets, etc. - build_widgets: function() { - var classe; - - for (var i = 0; i < this.widgets.length; i++) { - var def = this.widgets[i]; - if ( !def.condition || def.condition.call(this) ) { - var args = typeof def.args === 'function' ? def.args(this) : def.args; - var w = new def.widget(this, args || {}); - if (def.replace) { - w.replace(this.$(def.replace)); - } else if (def.append) { - w.appendTo(this.$(def.append)); - } else if (def.prepend) { - w.prependTo(this.$(def.prepend)); + load_widgets: function(widgets) { + for (var i = 0; i < widgets.length; i++) { + var widget = widgets[i]; + if ( !widget.condition || widget.condition.call(this) ) { + var args = typeof widget.args === 'function' ? widget.args(this) : widget.args; + var w = new widget.widget(this, args || {}); + if (widget.replace) { + w.replace(this.$(widget.replace)); + } else if (widget.append) { + w.appendTo(this.$(widget.append)); + } else if (widget.prepend) { + w.prependTo(this.$(widget.prepend)); } else { w.appendTo(this.$el); } - this.widget[def.name] = w; + this.widget[widget.name] = w; } } + }, + + // This method instantiates all the screens, widgets, etc. + build_widgets: function() { + this.load_widgets(this.widgets); this.screens = {}; - for (i = 0; i < this.gui.screen_classes.length; i++) { + var classe; + for (var i = 0; i < this.gui.screen_classes.length; i++) { classe = this.gui.screen_classes[i]; if (!classe.condition || classe.condition.call(this)) { var screen = new classe.widget(this,{}); diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js index 7abb931d68e..a0626326b62 100644 --- a/addons/point_of_sale/static/src/js/gui.js +++ b/addons/point_of_sale/static/src/js/gui.js @@ -28,18 +28,22 @@ var Gui = core.Class.extend({ this.chrome.ready.then(function(){ self.close_other_tabs(); - var order = self.pos.get_order(); - if (order) { - self.show_saved_screen(order); - } else { - self.show_screen(self.startup_screen); - } + self._show_first_screen(); self.pos.bind('change:selectedOrder', function(){ self.show_saved_screen(self.pos.get_order()); }); }); }, + _show_first_screen: function() { + var order = this.pos.get_order(); + if (order) { + this.show_saved_screen(order); + } else { + this.show_screen(this.startup_screen); + } + }, + /* ---- Gui: SCREEN MANIPULATION ---- */ // register a screen widget to the gui, @@ -226,66 +230,8 @@ var Gui = core.Class.extend({ // - only_managers: restricts the list to managers // - current_user: password will not be asked if this // user is selected. - // - title: The title of the user selection list. - select_user: function(options){ - options = options || {}; - var self = this; - var def = new $.Deferred(); - - var list = []; - for (var i = 0; i < this.pos.users.length; i++) { - var user = this.pos.users[i]; - if (!options.only_managers || user.role === 'manager') { - list.push({ - 'label': user.name, - 'item': user, - }); - } - } - - this.show_popup('selection',{ - title: options.title || _t('Select User'), - list: list, - confirm: function(user){ def.resolve(user); }, - cancel: function(){ def.reject(); }, - is_selected: function(user){ return user === self.pos.get_cashier(); }, - }); - - return def.then(function(user){ - if (options.security && user !== options.current_user && user.pos_security_pin) { - return self.ask_password(user.pos_security_pin).then(function(){ - return user; - }); - } else { - return user; - } - }); - }, - - // Ask for a password, and checks if it this - // the same as specified by the function call. - // returns a deferred that resolves on success, - // fails on failure. - ask_password: function(password) { - var self = this; - var ret = new $.Deferred(); - if (password) { - this.show_popup('password',{ - 'title': _t('Password ?'), - confirm: function(pw) { - if (pw !== password) { - self.show_popup('error',_t('Incorrect Password')); - ret.reject(); - } else { - ret.resolve(); - } - }, - }); - } else { - ret.resolve(); - } - return ret; - }, + // - title: The title of the employee selection list. + // checks if the current user (or the user provided) has manager // access rights. If not, a popup is shown allowing the user to @@ -298,7 +244,7 @@ var Gui = core.Class.extend({ if (user.role === 'manager') { return new $.Deferred().resolve(user); } else { - return this.select_user({ + return this.select_employee({ security: true, only_managers: true, title: _t('Login as a Manager'), diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 8f82fa043da..d0855c6fe18 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -55,6 +55,8 @@ exports.PosModel = Backbone.Model.extend({ this.company = null; this.user = null; this.users = []; + this.employee = {name: null, id: null, barcode: null, user_id:null, pin:null}; + this.employees = []; this.partners = []; this.cashregisters = []; this.taxes = []; @@ -89,7 +91,7 @@ exports.PosModel = Backbone.Model.extend({ // We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched, // Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched. - // when all the data has loaded, we compute some stuff, and declare the Pos ready to be used. + // when all the data has loaded, we compute some stuff, and declare the Pos ready to be used. this.ready = this.load_server_data().then(function(){ return self.after_load_server_data(); }); @@ -162,9 +164,16 @@ exports.PosModel = Backbone.Model.extend({ },{ model: 'res.users', - fields: ['name','company_id'], + fields: ['name','company_id', 'id'], ids: function(self){ return [session.uid]; }, - loaded: function(self,users){ self.user = users[0]; }, + loaded: function(self,users){ + self.user = users[0]; + self.user.role = 'manager'; + self.employee.name = self.user.name; + self.employee.user_id = [self.user.id, self.user.name]; + self.employees = [self.employee]; + self.set_cashier(self.employee); + }, },{ model: 'res.company', fields: [ 'currency_id', 'email', 'website', 'company_registry', 'vat', 'name', 'phone', 'partner_id' , 'country_id', 'tax_calculation_rounding_method'], @@ -262,39 +271,6 @@ exports.PosModel = Backbone.Model.extend({ self.pos_session.sequence_number = Math.max(self.pos_session.sequence_number, orders[i].data.sequence_number+1); } }, - },{ - model: 'res.users', - fields: ['name','pos_security_pin','groups_id','barcode'], - domain: function(self){ return [['company_id','=',self.user.company_id[0]],'|', ['groups_id','=', self.config.group_pos_manager_id[0]],['groups_id','=', self.config.group_pos_user_id[0]]]; }, - loaded: function(self,users){ - // we attribute a role to the user, 'cashier' or 'manager', depending - // on the group the user belongs. - var pos_users = []; - var current_cashier = self.get_cashier(); - for (var i = 0; i < users.length; i++) { - var user = users[i]; - for (var j = 0; j < user.groups_id.length; j++) { - var group_id = user.groups_id[j]; - if (group_id === self.config.group_pos_manager_id[0]) { - user.role = 'manager'; - break; - } else if (group_id === self.config.group_pos_user_id[0]) { - user.role = 'cashier'; - } - } - if (user.role) { - pos_users.push(user); - } - // replace the current user with its updated version - if (user.id === self.user.id) { - self.user = user; - } - if (user.id === current_cashier.id) { - self.set_cashier(user); - } - } - self.users = pos_users; - }, },{ model: 'stock.location', fields: [], @@ -653,13 +629,13 @@ exports.PosModel = Backbone.Model.extend({ get_cashier: function(){ // reset the cashier to the current user if session is new if (this.db.load('pos_session_id') !== this.pos_session.id) { - this.set_cashier(this.user); + this.set_cashier(this.employee); } - return this.db.get_cashier() || this.get('cashier') || this.user; + return this.db.get_cashier() || this.get('cashier') || this.employee; }, // changes the current cashier - set_cashier: function(user){ - this.set('cashier', user); + set_cashier: function(employee){ + this.set('cashier', employee); this.db.set_cashier(this.get('cashier')); }, //creates a new empty order and sets it as the current order @@ -2007,6 +1983,7 @@ exports.Order = Backbone.Model.extend({ this.orderlines = new OrderlineCollection(); this.paymentlines = new PaymentlineCollection(); this.pos_session_id = this.pos.pos_session.id; + this.employee = this.pos.employee; this.finalized = false; // if true, cannot be modified. this.set_pricelist(this.pos.default_pricelist); @@ -2131,7 +2108,8 @@ exports.Order = Backbone.Model.extend({ pos_session_id: this.pos_session_id, pricelist_id: this.pricelist ? this.pricelist.id : false, partner_id: this.get_client() ? this.get_client().id : false, - user_id: this.pos.get_cashier().id, + user_id: this.pos.user.id, + employee_id: this.pos.get_cashier().id, uid: this.uid, sequence_number: this.sequence_number, creation_date: this.validation_date || this.creation_date, // todo: rename creation_date in master diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index c5873ddd324..4bf06abdc6b 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -68,32 +68,6 @@ var ScreenWidget = PosBaseWidget.extend({ this.barcode_error_action(code); } }, - - // what happens when a cashier id barcode is scanned. - // the default behavior is the following : - // - if there's a user with a matching barcode, put it as the active 'cashier', go to cashier mode, and return true - // - else : do nothing and return false. You probably want to extend this to show and appropriate error popup... - barcode_cashier_action: function(code){ - var self = this; - var users = this.pos.users; - for(var i = 0, len = users.length; i < len; i++){ - if(users[i].barcode === code.code){ - if (users[i].id !== this.pos.get_cashier().id && users[i].pos_security_pin) { - return this.gui.ask_password(users[i].pos_security_pin).then(function(){ - self.pos.set_cashier(users[i]); - self.chrome.widget.username.renderElement(); - return true; - }); - } else { - this.pos.set_cashier(users[i]); - this.chrome.widget.username.renderElement(); - return true; - } - } - } - this.barcode_error_action(code); - return false; - }, // what happens when a client id barcode is scanned. // the default behavior is the following : @@ -139,7 +113,6 @@ var ScreenWidget = PosBaseWidget.extend({ } this.pos.barcode_reader.set_action_callback({ - 'cashier': _.bind(self.barcode_cashier_action, self), 'product': _.bind(self.barcode_product_action, self), 'weight': _.bind(self.barcode_product_action, self), 'price': _.bind(self.barcode_product_action, self), diff --git a/addons/point_of_sale/static/src/js/tests.js b/addons/point_of_sale/static/src/js/tests.js index f8626cab1bd..78dfa8cdca2 100644 --- a/addons/point_of_sale/static/src/js/tests.js +++ b/addons/point_of_sale/static/src/js/tests.js @@ -359,7 +359,7 @@ odoo.define('point_of_sale.tour.acceptance', function (require) { trigger: ".header-button", }, { content: "confirm closing the frontend", - trigger: ".header-button.confirm", + trigger: ".header-button", run: function() {}, //it's a check, }]); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 95ecfc9874f..b094c035b4d 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -6,11 +6,13 @@
-
+
+ +
@@ -109,7 +111,12 @@
- + + + + + +
diff --git a/addons/point_of_sale/views/pos_config_view.xml b/addons/point_of_sale/views/pos_config_view.xml index 5669bc6ce85..8dd29cb7bfe 100644 --- a/addons/point_of_sale/views/pos_config_view.xml +++ b/addons/point_of_sale/views/pos_config_view.xml @@ -41,6 +41,14 @@