diff --git a/addons/pos_hr/__manifest__.py b/addons/pos_hr/__manifest__.py index c92bbe1a071..6ad2705c0d1 100644 --- a/addons/pos_hr/__manifest__.py +++ b/addons/pos_hr/__manifest__.py @@ -21,5 +21,10 @@ The actual till still requires one user but an unlimited number of employees can ], 'installable': True, 'auto_install': True, - 'qweb': ['static/src/xml/pos.xml'], + 'qweb': [ + 'static/src/xml/HeaderLockButton.xml', + 'static/src/xml/Chrome.xml', + 'static/src/xml/CashierName.xml', + 'static/src/xml/LoginScreen.xml', + ], } diff --git a/addons/pos_hr/static/src/css/pos.css b/addons/pos_hr/static/src/css/pos.css index e50be169538..84b66fe9554 100644 --- a/addons/pos_hr/static/src/css/pos.css +++ b/addons/pos_hr/static/src/css/pos.css @@ -79,3 +79,14 @@ color: #555555; border-radius: 5px; } + +.pos .pos-rightheader .header-button.lock-button { + font-size: 20px; + color: green; + transition: all 200ms ease-in-out; + width: 18px; +} + +.pos .pos-rightheader .header-button.lock-button:hover { + color: red; +} diff --git a/addons/pos_hr/static/src/js/CashierName.js b/addons/pos_hr/static/src/js/CashierName.js new file mode 100644 index 00000000000..7628372032f --- /dev/null +++ b/addons/pos_hr/static/src/js/CashierName.js @@ -0,0 +1,59 @@ +odoo.define('pos_hr.CashierName', function (require) { + 'use strict'; + + const CashierName = require('point_of_sale.CashierName'); + const Registries = require('point_of_sale.Registries'); + const useSelectEmployee = require('pos_hr.useSelectEmployee'); + const { useBarcodeReader } = require('point_of_sale.custom_hooks'); + + const PosHrCashierName = (CashierName) => + class extends CashierName { + constructor() { + super(...arguments); + const { selectEmployee, askPin } = useSelectEmployee(); + this.askPin = askPin; + this.selectEmployee = selectEmployee; + useBarcodeReader({ cashier: this._onCashierScan }); + } + mounted() { + this.env.pos.on('change:cashier', this.render, this); + } + willUnmount() { + this.env.pos.off('change:cashier', null, this); + } + async selectCashier() { + if (!this.env.pos.config.module_pos_hr) return; + + const list = this.env.pos.employees + .filter((employee) => employee.id !== this.env.pos.get_cashier().id) + .map((employee) => { + return { + id: employee.id, + item: employee, + label: employee.name, + isSelected: false, + }; + }); + + const employee = await this.selectEmployee(list); + if (employee) { + this.env.pos.set_cashier(employee); + } + } + async _onCashierScan(code) { + const employee = this.env.pos.employees.find( + (emp) => emp.barcode === Sha1.hash(code.code) + ); + + if (!employee || employee === this.env.pos.get_cashier()) return; + + if (!employee.pin || (await this.askPin(employee))) { + this.env.pos.set_cashier(employee); + } + } + }; + + Registries.Component.extend(CashierName, PosHrCashierName); + + return CashierName; +}); diff --git a/addons/pos_hr/static/src/js/Chrome.js b/addons/pos_hr/static/src/js/Chrome.js new file mode 100644 index 00000000000..f4d4ac1282b --- /dev/null +++ b/addons/pos_hr/static/src/js/Chrome.js @@ -0,0 +1,22 @@ +odoo.define('pos_hr.chrome', function (require) { + 'use strict'; + + const Chrome = require('point_of_sale.Chrome'); + const Registries = require('point_of_sale.Registries'); + + const PosHrChrome = (Chrome) => + class extends Chrome { + async start() { + await super.start(); + this.env.pos.on('change:cashier', this.render, this); + if (this.env.pos.config.module_pos_hr) this.showTempScreen('LoginScreen'); + } + get headerButtonIsShown() { + return this.env.pos.get('cashier').role == 'manager'; + } + }; + + Registries.Component.extend(Chrome, PosHrChrome); + + return Chrome; +}); diff --git a/addons/pos_hr/static/src/js/HeaderLockButton.js b/addons/pos_hr/static/src/js/HeaderLockButton.js new file mode 100644 index 00000000000..3ad0ee9f02b --- /dev/null +++ b/addons/pos_hr/static/src/js/HeaderLockButton.js @@ -0,0 +1,25 @@ +odoo.define('point_of_sale.HeaderLockButton', function(require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + const { useState } = owl; + + class HeaderLockButton extends PosComponent { + constructor() { + super(...arguments); + this.state = useState({ isUnlockIcon: true, title: 'Unlocked' }); + } + async showLoginScreen() { + await this.showTempScreen('LoginScreen'); + } + onMouseOver(isMouseOver) { + this.state.isUnlockIcon = !isMouseOver; + this.state.title = isMouseOver ? 'Lock' : 'Unlocked'; + } + } + + Registries.Component.add(HeaderLockButton); + + return HeaderLockButton; +}); diff --git a/addons/pos_hr/static/src/js/LoginScreen.js b/addons/pos_hr/static/src/js/LoginScreen.js new file mode 100644 index 00000000000..bf93abeb0af --- /dev/null +++ b/addons/pos_hr/static/src/js/LoginScreen.js @@ -0,0 +1,74 @@ +odoo.define('pos_hr.LoginScreen', function (require) { + 'use strict'; + + const PosComponent = require('point_of_sale.PosComponent'); + const Registries = require('point_of_sale.Registries'); + const useSelectEmployee = require('pos_hr.useSelectEmployee'); + const { useBarcodeReader } = require('point_of_sale.custom_hooks'); + + class LoginScreen extends PosComponent { + constructor() { + super(...arguments); + const { selectEmployee, askPin } = useSelectEmployee(); + this.selectEmployee = selectEmployee; + this.askPin = askPin; + useBarcodeReader( + { + cashier: this._barcodeCashierAction, + }, + true + ); + } + back() { + this.props.resolve({ confirmed: false, payload: false }); + this.trigger('close-temp-screen'); + } + confirm() { + this.props.resolve({ confirmed: true, payload: true }); + this.trigger('close-temp-screen'); + } + get shopName() { + return this.env.pos.config.name; + } + closeSession() { + this.trigger('close-pos'); + } + async selectCashier() { + const list = this.env.pos.employees.map((employee) => { + return { + id: employee.id, + item: employee, + label: employee.name, + isSelected: false, + }; + }); + + const employee = await this.selectEmployee(list); + if (employee) { + this.env.pos.set_cashier(employee); + this.back(); + } + } + async _barcodeCashierAction(code) { + let theEmployee; + for (let employee of this.env.pos.employees) { + if (employee.barcode === Sha1.hash(code.code)) { + theEmployee = employee; + break; + } + } + + if (!theEmployee) return; + + if (!theEmployee.pin || (await this.askPin(theEmployee))) { + this.env.pos.set_cashier(theEmployee); + this.back(); + } + } + } + LoginScreen.template = 'LoginScreen'; + + Registries.Component.add(LoginScreen); + + return LoginScreen; +}); diff --git a/addons/pos_hr/static/src/js/chrome.js b/addons/pos_hr/static/src/js/chrome.js deleted file mode 100644 index 576b9a1d037..00000000000 --- a/addons/pos_hr/static/src/js/chrome.js +++ /dev/null @@ -1,124 +0,0 @@ -odoo.define('pos_hr.chrome', function (require) { - "use strict"; - - var core = require('web.core'); - var chrome = require('point_of_sale.chrome'); - var _t = core._t; - var _lt = core._lt; - var QWeb = core.qweb; - - - /* ------- The User Name Widget ------- */ - - // Displays the current cashier's name and allows - // to switch between cashiers. - - chrome.UsernameWidget.include({ - renderElement: function(){ - var self = this; - this._super(); - this.$el.click(function(){ - self.click_username(); - }); - }, - click_username: function(){ - if(!this.pos.config.module_pos_hr) { return; } - var self = this; - this.gui.select_employee({ - 'security': true, - 'current_employee': this.pos.get_cashier(), - 'title': _t('Change Cashier'), - }).then(function(employee){ - self.pos.set_cashier(employee); - self.chrome.widget.username.renderElement(); - self.renderElement(); - }); - }, - }); - - var HeaderCloseButtonWidget = chrome.HeaderButtonWidget.extend({ - start: function(){ - if (this.pos.config.module_pos_hr) { - var self = this; - this.pos.bind('change:cashier', this._show_hide_close_button , this); - } - return this._super(); - }, - _show_hide_close_button: function(){ - if (this.pos.get('cashier').role == 'manager') { - this.show(); - } else { - this.hide(); - } - } - }); - - var HeaderLockButtonWidget = chrome.HeaderButtonWidget.extend({ - init: function(parent, options) { - this._super(parent, options); - this.icon = 'fa-unlock'; - this.icon_color = 'green'; - this.icon_mouseover = 'fa-lock'; - this.icon_color_mouseover = 'red'; - }, - - start: function(){ - if (this.pos.config.module_pos_hr) { - this.show(); - } else { - this.hide(); - } - return this._super(); - }, - - renderElement: function() { - var self = this; - this._super(); - this.iconElement = this.$el.find("i"); - this.$el.css('font-size','20px'); - this.iconElement.addClass(this.icon); - this.$el.css('color',this.icon_color); - this.$el.mouseover(function(){ - self.iconElement.addClass(self.icon_mouseover).removeClass(self.icon); - $(this).css('color', self.icon_color_mouseover); - }).mouseleave(function(){ - self.iconElement.addClass(self.icon).removeClass(self.icon_mouseover); - $(this).css('color', self.icon_color); - }); - }, - }); - chrome.Chrome.include({ - lock_button_widget: { - 'name': 'lock_button', - 'widget': HeaderLockButtonWidget, - 'append': '.pos-rightheader', - 'args': { - label: _lt('Lock'), - action: function() { - this.chrome.return_to_login_screen(); - } - } - }, - build_widgets: function() { - var self = this; - this.widgets.some(function(widget, index){ - if (widget.name === 'close_button'){ - widget.widget = HeaderCloseButtonWidget; - self.widgets.splice(index, 0, self.lock_button_widget); - return true; - } - return false; - }); - this._super(); - }, - return_to_login_screen: function() { - this.gui.show_screen('login'); - }, - - }); - - return { - HeaderLockButtonWidget: HeaderLockButtonWidget, - HeaderCloseButtonWidget: HeaderCloseButtonWidget, - }; -}); diff --git a/addons/pos_hr/static/src/js/gui.js b/addons/pos_hr/static/src/js/gui.js deleted file mode 100644 index 45c20b7551e..00000000000 --- a/addons/pos_hr/static/src/js/gui.js +++ /dev/null @@ -1,74 +0,0 @@ -odoo.define('pos_hr.gui', function (require) { - "use strict"; - -var core = require('web.core'); -var gui = require('point_of_sale.gui'); -var _t = core._t; - -gui.Gui.include({ - _show_first_screen: function () { - if (this.pos.config.module_pos_hr) { - this.show_screen('login'); - } else { - this._super(); - } - }, - select_employee: function(options) { - options = options || {}; - var self = this; - - var list = []; - this.pos.employees.forEach(function(employee) { - if (!options.only_managers || employee.role === 'manager') { - list.push({ - 'label': employee.name, - 'item': employee, - }); - } - }); - - var prom = new Promise(function (resolve, reject) { - self.show_popup('selection', { - title: options.title || _t('Select User'), - list: list, - confirm: resolve, - cancel: reject, - is_selected: function (employee) { - return employee === self.pos.get_cashier(); - }, - }); - }); - - return prom.then(function (employee) { - return self.ask_password(employee.pin).then(function(){ - return employee; - }); - }); - }, - // Ask for a password, and checks if it this - // the same as specified by the function call. - // Returns a promise that resolves on success, - // fails on failure. - ask_password: function(password) { - var self = this; - var prom = new Promise(function (resolve, reject) { - if (password) { - self.show_popup('password',{ - 'title': _t('Password ?'), - confirm: function (pw) { - if (Sha1.hash(pw) !== password) { - self.show_popup('error', _t('Incorrect Password')); - reject(); - } else { - resolve(); - } - }, - }); - } else { - resolve(); - } - }); - return prom; - }, -}); -}); \ No newline at end of file diff --git a/addons/pos_hr/static/src/js/models.js b/addons/pos_hr/static/src/js/models.js index 83dbd2cfc3a..549ace40902 100644 --- a/addons/pos_hr/static/src/js/models.js +++ b/addons/pos_hr/static/src/js/models.js @@ -2,7 +2,6 @@ odoo.define('pos_hr.employees', function (require) { "use strict"; var models = require('point_of_sale.models'); -var rpc = require('web.rpc'); models.load_models([{ model: 'hr.employee', @@ -39,7 +38,7 @@ models.PosModel = models.PosModel.extend({ var self = this; return posmodel_super.load_server_data.apply(this, arguments).then(function () { var employee_ids = _.map(self.employees, function(employee){return employee.id;}); - var records = rpc.query({ + var records = self.rpc({ model: 'hr.employee', method: 'get_barcodes_and_pin_hashed', args: [employee_ids], diff --git a/addons/pos_hr/static/src/js/screens.js b/addons/pos_hr/static/src/js/screens.js deleted file mode 100644 index 402bcbd7496..00000000000 --- a/addons/pos_hr/static/src/js/screens.js +++ /dev/null @@ -1,105 +0,0 @@ -odoo.define('pos_hr.screens', function (require) { - "use strict"; - -var core = require('web.core'); -var gui = require('point_of_sale.gui'); -var ScreenWidget = require('point_of_sale.screens').ScreenWidget; - -var _t = core._t; - -ScreenWidget.include({ - - // what happens when a cashier id barcode is scanned. - // the default behavior is the following : - // - if there's an employee 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 employees = this.pos.employees; - var prom; - for(var i = 0, len = employees.length; i < len; i++){ - if(employees[i].barcode === Sha1.hash(code.code)){ - if (employees[i].id !== this.pos.get_cashier().id && employees[i].pin) { - prom = this.gui.ask_password(employees[i].pin).then(function(){ - self.pos.set_cashier(employees[i]); - self.chrome.widget.username.renderElement(); - return true; - }); - } else { - this.pos.set_cashier(employees[i]); - this.chrome.widget.username.renderElement(); - prom = Promise.resolve(true); - } - break; - } - } - if (!prom){ - this.barcode_error_action(code); - return Promise.resolve(false); - } - else { - return prom - } - }, - show: function() { - this._super(); - this.pos.barcode_reader.set_action_callback('cashier', _.bind(this.barcode_cashier_action, this)); - }, -}); - -/*--------------------------------------*\ - | THE LOGIN SCREEN | -\*======================================*/ - -// The login screen enables employees to log in to the PoS -// at startup or after it was locked, with either barcode, pin, or both. - -var LoginScreenWidget = ScreenWidget.extend({ - template: 'LoginScreenWidget', - - /** - * @override - */ - show: function() { - var self = this; - this.$('.select-employee').click(function() { - self.gui.select_employee({ - 'security': true, - 'current_employee': self.pos.get_cashier(), - 'title':_t('Change Cashier'),}) - .then(function(employee){ - self.pos.set_cashier(employee); - self.chrome.widget.username.renderElement(); - self.unlock_screen(); - }); - }); - - this.$('.close-session').click(function() { - self.gui.close(); - }); - - this._super(); - }, - - /** - * @override - */ - barcode_cashier_action: function(code) { - var self = this; - return this._super(code).then(function () { - self.unlock_screen(); - }); - }, - - unlock_screen: function() { - var screen = (this.gui.pos.get_order() ? this.gui.pos.get_order().get_screen_data('previous-screen') : this.gui.startup_screen) || this.gui.startup_screen; - this.gui.show_screen(screen); - } -}); - -gui.define_screen({name:'login', widget: LoginScreenWidget}); - -return { - LoginScreenWidget: LoginScreenWidget -}; -}); diff --git a/addons/pos_hr/static/src/js/useSelectEmployee.js b/addons/pos_hr/static/src/js/useSelectEmployee.js new file mode 100644 index 00000000000..ef8e58a5e9f --- /dev/null +++ b/addons/pos_hr/static/src/js/useSelectEmployee.js @@ -0,0 +1,46 @@ +odoo.define('pos_hr.useSelectEmployee', function (require) { + 'use strict'; + + const { Component } = owl; + + function useSelectEmployee() { + const current = Component.current; + + async function askPin(employee) { + const { confirmed, payload: inputPin } = await this.showPopup('NumberPopup', { + isPassword: true, + title: this.env._t('Password ?'), + startingValue: null, + }); + + if (!confirmed) return false; + + if (employee.pin === Sha1.hash(inputPin)) { + return employee; + } else { + await this.showPopup('ErrorPopup', { + title: this.env._t('Incorrect Password'), + }); + return false; + } + } + + async function selectEmployee(selectionList) { + const { confirmed, payload: employee } = await this.showPopup('SelectionPopup', { + title: this.env._t('Change Cashier'), + list: selectionList, + }); + + if (!confirmed) return false; + + if (!employee.pin) { + return employee; + } + + return await askPin.call(current, employee); + } + return { askPin: askPin.bind(current), selectEmployee: selectEmployee.bind(current) }; + } + + return useSelectEmployee; +}); diff --git a/addons/pos_hr/static/src/xml/CashierName.xml b/addons/pos_hr/static/src/xml/CashierName.xml new file mode 100644 index 00000000000..9caddada2e6 --- /dev/null +++ b/addons/pos_hr/static/src/xml/CashierName.xml @@ -0,0 +1,10 @@ + + + + + + selectCashier + + + + diff --git a/addons/pos_hr/static/src/xml/Chrome.xml b/addons/pos_hr/static/src/xml/Chrome.xml new file mode 100644 index 00000000000..430ed75dcdb --- /dev/null +++ b/addons/pos_hr/static/src/xml/Chrome.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/addons/pos_hr/static/src/xml/HeaderLockButton.xml b/addons/pos_hr/static/src/xml/HeaderLockButton.xml new file mode 100644 index 00000000000..d24a9f749ef --- /dev/null +++ b/addons/pos_hr/static/src/xml/HeaderLockButton.xml @@ -0,0 +1,15 @@ + + + + +
+ + + +
+
+ +
diff --git a/addons/pos_hr/static/src/xml/pos.xml b/addons/pos_hr/static/src/xml/LoginScreen.xml similarity index 55% rename from addons/pos_hr/static/src/xml/pos.xml rename to addons/pos_hr/static/src/xml/LoginScreen.xml index 4e1f9128942..5d96f2b1538 100644 --- a/addons/pos_hr/static/src/xml/pos.xml +++ b/addons/pos_hr/static/src/xml/LoginScreen.xml @@ -1,24 +1,28 @@ - + diff --git a/addons/pos_hr/static/tests/tours/PosHrTour.js b/addons/pos_hr/static/tests/tours/PosHrTour.js new file mode 100644 index 00000000000..dea8b4a2baf --- /dev/null +++ b/addons/pos_hr/static/tests/tours/PosHrTour.js @@ -0,0 +1,43 @@ +odoo.define('point_of_sale.tour.PosHr', function (require) { + 'use strict'; + + const { PosHr } = require('pos_hr.tour.PosHrTourMethods'); + const { ProductScreen } = require('point_of_sale.tour.ProductScreenTourMethods'); + const { ErrorPopup } = require('point_of_sale.tour.ErrorPopupTourMethods'); + const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods'); + const { SelectionPopup } = require('point_of_sale.tour.SelectionPopupTourMethods'); + const { getSteps, startSteps } = require('point_of_sale.tour.utils'); + const Tour = require('web_tour.tour'); + + startSteps(); + + PosHr.check.loginScreenIsShown(); + PosHr.do.clickLoginButton(); + SelectionPopup.check.isShown(); + SelectionPopup.check.hasSelectionItem('Pos Employee1'); + SelectionPopup.check.hasSelectionItem('Pos Employee2'); + SelectionPopup.check.hasSelectionItem('Mitchell Admin'); + SelectionPopup.do.clickItem('Pos Employee1'); + NumberPopup.check.isShown(); + NumberPopup.do.pressNumpad('2 5 8 1'); + NumberPopup.check.inputShownIs('••••'); + NumberPopup.do.clickConfirm(); + ErrorPopup.check.isShown(); + ErrorPopup.do.clickConfirm(); + PosHr.do.clickLoginButton(); + SelectionPopup.do.clickItem('Pos Employee1'); + NumberPopup.check.isShown(); + NumberPopup.do.pressNumpad('2 5 8 0'); + NumberPopup.check.inputShownIs('••••'); + NumberPopup.do.clickConfirm(); + ProductScreen.check.isShown(); + PosHr.check.cashierNameIs('Pos Employee1'); + PosHr.do.clickCashierName(); + SelectionPopup.do.clickItem('Mitchell Admin'); + PosHr.check.cashierNameIs('Mitchell Admin'); + PosHr.do.clickLockButton(); + PosHr.exec.login('Pos Employee2', '1234'); + ProductScreen.check.isShown(); + + Tour.register('PosHrTour', { test: true, url: '/pos/web' }, getSteps()); +}); diff --git a/addons/pos_hr/static/tests/tours/PosHrTourMethods.js b/addons/pos_hr/static/tests/tours/PosHrTourMethods.js new file mode 100644 index 00000000000..391fc1421b6 --- /dev/null +++ b/addons/pos_hr/static/tests/tours/PosHrTourMethods.js @@ -0,0 +1,67 @@ +odoo.define('pos_hr.tour.PosHrTourMethods', function (require) { + 'use strict'; + + const { createTourMethods } = require('point_of_sale.tour.utils'); + const { SelectionPopup } = require('point_of_sale.tour.SelectionPopupTourMethods'); + const { NumberPopup } = require('point_of_sale.tour.NumberPopupTourMethods'); + + class Do { + clickLoginButton() { + return [ + { + content: 'click login button', + trigger: '.login-overlay .login-button.select-employee', + }, + ]; + } + clickLockButton() { + return [ + { + content: 'click lock button', + trigger: '.header-button .lock-button', + }, + ]; + } + clickCashierName() { + return [ + { + content: 'click cashier name', + trigger: '.oe_status .username', + } + ] + } + } + class Check { + loginScreenIsShown() { + return [ + { + content: 'login screen is shown', + trigger: '.login-overlay .screen-login .login-body', + run: () => {}, + }, + ]; + } + cashierNameIs(name) { + return [ + { + content: `logged cashier is '${name}'`, + trigger: `.pos .oe_status .username:contains("${name}")`, + run: () => {}, + }, + ]; + } + } + class Execute { + login(name, pin) { + const res = this._do.clickLoginButton(); + res.push(...SelectionPopup._do.clickItem(name)); + if (pin) { + res.push(...NumberPopup._do.pressNumpad(pin.split('').join(' '))); + res.push(...NumberPopup._do.clickConfirm()); + } + return res; + } + } + + return { Do, Check, Execute, PosHr: createTourMethods('PosHr', Do, Check, Execute) }; +}); diff --git a/addons/pos_hr/tests/__init__.py b/addons/pos_hr/tests/__init__.py new file mode 100644 index 00000000000..6f4bae0c7a2 --- /dev/null +++ b/addons/pos_hr/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_frontend diff --git a/addons/pos_hr/tests/test_frontend.py b/addons/pos_hr/tests/test_frontend.py new file mode 100644 index 00000000000..a28f057e327 --- /dev/null +++ b/addons/pos_hr/tests/test_frontend.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import tools +from odoo.api import Environment +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT +from datetime import date, timedelta + +from odoo.tests import Form, tagged, new_test_user +from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon + + +class TestPosHrHttpCommon(TestPointOfSaleHttpCommon): + def setUp(self): + super().setUp() + self.main_pos_config.write({"module_pos_hr": True}) + + # Admin employee + self.env.ref("hr.employee_admin").write( + {"name": "Mitchell Admin", "pin": False} + ) + + # User employee + emp1 = self.env.ref("hr.employee_han") + emp1_user = new_test_user( + self.env, + login="emp1_user", + groups="base.group_user", + name="Pos Employee1", + email="emp1_user@pos.com", + ) + emp1.write({"name": "Pos Employee1", "pin": "2580", "user_id": emp1_user.id}) + + # Non-user employee + emp2 = self.env.ref("hr.employee_jve") + emp2.write({"name": "Pos Employee2", "pin": "1234"}) + + with Form(self.main_pos_config) as config: + config.employee_ids.add(emp1) + config.employee_ids.add(emp2) + + +@tagged("post_install", "-at_install") +class TestUi(TestPosHrHttpCommon): + def test_01_pos_hr_tour(self): + # open a session, the /pos/web controller will redirect to it + self.main_pos_config.open_session_cb(check_coa=False) + + self.start_tour( + "/pos/web?config_id=%d" % self.main_pos_config.id, + "PosHrTour", + login="admin", + step_delay=50, + ) diff --git a/addons/pos_hr/views/point_of_sale.xml b/addons/pos_hr/views/point_of_sale.xml index 4362faf76ee..4c768bc1d11 100644 --- a/addons/pos_hr/views/point_of_sale.xml +++ b/addons/pos_hr/views/point_of_sale.xml @@ -5,9 +5,18 @@ - - - + + + + + + + + +