diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/pos_hr/static/src | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/pos_hr/static/src')
| -rw-r--r-- | addons/pos_hr/static/src/css/pos.css | 130 | ||||
| -rw-r--r-- | addons/pos_hr/static/src/js/CashierName.js | 59 | ||||
| -rw-r--r-- | addons/pos_hr/static/src/js/Chrome.js | 22 | ||||
| -rw-r--r-- | addons/pos_hr/static/src/js/HeaderLockButton.js | 25 | ||||
| -rw-r--r-- | addons/pos_hr/static/src/js/LoginScreen.js | 74 | ||||
| -rw-r--r-- | addons/pos_hr/static/src/js/models.js | 97 | ||||
| -rw-r--r-- | addons/pos_hr/static/src/js/useSelectEmployee.js | 46 | ||||
| -rw-r--r-- | addons/pos_hr/static/src/xml/CashierName.xml | 10 | ||||
| -rw-r--r-- | addons/pos_hr/static/src/xml/Chrome.xml | 11 | ||||
| -rw-r--r-- | addons/pos_hr/static/src/xml/HeaderLockButton.xml | 15 | ||||
| -rw-r--r-- | addons/pos_hr/static/src/xml/LoginScreen.xml | 29 |
11 files changed, 518 insertions, 0 deletions
diff --git a/addons/pos_hr/static/src/css/pos.css b/addons/pos_hr/static/src/css/pos.css new file mode 100644 index 00000000..ce9be2a9 --- /dev/null +++ b/addons/pos_hr/static/src/css/pos.css @@ -0,0 +1,130 @@ +.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; + max-width: 550px; + width:100%; + 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: 40%; + vertical-align: middle; + line-height: 5; + font-size: larger; +} +.pos .login-body{ + display: flex; + justify-content: center; + height:37%; +} + +.pos .login-footer{ + height:18%; +} + +.pos .login-element{ + float: left; + width: 40%; + 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; +} +@media screen and (max-width: 576px) { + .pos .screen-login { + font-family: 'Lato', sans-serif; + width: 100%; + height: 100%; + text-align: center; + background-color: #fff; + overflow: hidden; + } + .pos .login-body { + flex-direction: column; + align-items: center; + } + .pos .login-button { + color: #fff; + background-color: #00A09D; + border-color: #00A09D; + height: 38px; + } + .pos .popups .popup-selection { + width: 90%; + } + .pos .login-barcode-text { + color: #adb5bd; + margin-top: 8px; + margin-bottom: 0; + font-size: 15px; + font-weight: 400; + line-height: 1.2; + } + .pos .login-element .o_barcode_mobile_container .o_mobile_barcode { + top:0; + height: 55px; + } +} + +.pos .pos-rightheader .header-button.lock-button { + font-size: 20px; + color: rgb(94, 185, 55); + transition: all 200ms ease-in-out; + width: 18px; +} + +.pos .pos-rightheader .header-button.lock-button:hover { + color: rgb(197, 52, 0); +} 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 00000000..76283720 --- /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 00000000..eacc9561 --- /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.config.module_pos_hr || 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 00000000..3ad0ee9f --- /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 00000000..bf93abeb --- /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/models.js b/addons/pos_hr/static/src/js/models.js new file mode 100644 index 00000000..9b4c8f4a --- /dev/null +++ b/addons/pos_hr/static/src/js/models.js @@ -0,0 +1,97 @@ +odoo.define('pos_hr.employees', function (require) { + "use strict"; + +var models = require('point_of_sale.models'); + +models.load_models([{ + model: 'hr.employee', + fields: ['name', 'id', 'user_id'], + domain: function(self){ + return self.config.employee_ids.length > 0 + ? [ + '&', + ['company_id', '=', self.config.company_id[0]], + '|', + ['user_id', '=', self.user.id], + ['id', 'in', self.config.employee_ids], + ] + : [['company_id', '=', self.config.company_id[0]]]; + }, + loaded: function(self, employees) { + if (self.config.module_pos_hr) { + self.employees = employees; + self.employee_by_id = {}; + self.employees.forEach(function(employee) { + self.employee_by_id[employee.id] = employee; + var hasUser = self.users.some(function(user) { + if (user.id === employee.user_id[0]) { + employee.role = user.role; + return true; + } + return false; + }); + if (!hasUser) { + employee.role = 'cashier'; + } + }); + } + } +}]); + +var posmodel_super = models.PosModel.prototype; +models.PosModel = models.PosModel.extend({ + load_server_data: function () { + 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 = self.rpc({ + model: 'hr.employee', + method: 'get_barcodes_and_pin_hashed', + args: [employee_ids], + }); + return records.then(function (employee_data) { + self.employees.forEach(function (employee) { + var data = _.findWhere(employee_data, {'id': employee.id}); + if (data !== undefined){ + employee.barcode = data.barcode; + employee.pin = data.pin; + } + }); + }); + }); + }, + set_cashier: function(employee) { + posmodel_super.set_cashier.apply(this, arguments); + const selectedOrder = this.get_order(); + if (selectedOrder && !selectedOrder.get_orderlines().length) { + // Order without lines can be considered to be un-owned by any employee. + // We set the employee on that order to the currently set employee. + selectedOrder.employee = employee; + } + } +}); + +var super_order_model = models.Order.prototype; +models.Order = models.Order.extend({ + initialize: function (attributes, options) { + super_order_model.initialize.apply(this, arguments); + if (!options.json) { + this.employee = this.pos.get_cashier(); + } + }, + init_from_JSON: function (json) { + super_order_model.init_from_JSON.apply(this, arguments); + if (this.pos.config.module_pos_hr) { + this.employee = this.pos.employee_by_id[json.employee_id]; + } + }, + export_as_JSON: function () { + const json = super_order_model.export_as_JSON.apply(this, arguments); + if (this.pos.config.module_pos_hr) { + json.employee_id = this.employee ? this.employee.id : false; + } + return json; + }, +}); + +}); 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 00000000..ef8e58a5 --- /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 00000000..9caddada --- /dev/null +++ b/addons/pos_hr/static/src/xml/CashierName.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates id="template" xml:space="preserve"> + + <t t-name="CashierName" t-inherit="point_of_sale.CashierName" t-inherit-mode="extension" owl="1"> + <xpath expr="//div" position="attributes"> + <attribute name="t-on-click">selectCashier</attribute> + </xpath> + </t> + +</templates> 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 00000000..430ed75d --- /dev/null +++ b/addons/pos_hr/static/src/xml/Chrome.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates id="template" xml:space="preserve"> + + <t t-name="Chrome" t-inherit="point_of_sale.Chrome" t-inherit-mode="extension" owl="1"> + <xpath expr="//HeaderButton" position="replace"> + <HeaderLockButton t-if="env.pos.config.module_pos_hr" /> + <HeaderButton t-if="headerButtonIsShown" /> + </xpath> + </t> + +</templates> 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 00000000..d24a9f74 --- /dev/null +++ b/addons/pos_hr/static/src/xml/HeaderLockButton.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates id="template" xml:space="preserve"> + + <t t-name="HeaderLockButton" owl="1"> + <div class="header-button lock-button" t-on-mouseover="onMouseOver(true)" + t-on-click="showLoginScreen" t-on-mouseout="onMouseOver(false)"> + <span class="lock-button"> + <i class="fa" + t-att-class="{ 'fa-unlock': state.isUnlockIcon, 'fa-lock': !state.isUnlockIcon }" + role="img" t-att-aria-label="state.title" t-att-title="state.title"></i> + </span> + </div> + </t> + +</templates> diff --git a/addons/pos_hr/static/src/xml/LoginScreen.xml b/addons/pos_hr/static/src/xml/LoginScreen.xml new file mode 100644 index 00000000..5d96f2b1 --- /dev/null +++ b/addons/pos_hr/static/src/xml/LoginScreen.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates id="template" xml:space="preserve"> + <t t-name="LoginScreen" owl="1"> + <div class="login-overlay"> + <div class="screen-login"> + <div class="login-title"><small>Log in to </small> + <t t-esc="shopName" /> + </div> + <div class="login-body"> + <span class="login-element"> + <img class="login-barcode-img" + src="/point_of_sale/static/img/barcode.png" /> + <div class="login-barcode-text">Scan your badge</div> + </span> + <span class="login-or">or</span> + <span class="login-element"> + <button class="login-button select-employee" + t-on-click="selectCashier">Select Cashier</button> + </span> + </div> + <div class="login-footer"> + <small> + <button class="login-button close-session" t-on-click="closeSession">Close session</button> + </small> + </div> + </div> + </div> + </t> +</templates> |
