mirror of
https://github.com/bringout/oca-ocb-pos.git
synced 2026-04-22 23:02:09 +02:00
Initial commit: Pos packages
This commit is contained in:
commit
95dfb9edb0
1301 changed files with 264148 additions and 0 deletions
127
odoo-bringout-oca-ocb-pos_hr/pos_hr/static/src/css/pos.css
Normal file
127
odoo-bringout-oca-ocb-pos_hr/pos_hr/static/src/css/pos.css
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
.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 .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);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
odoo.define('pos_hr.CashierName', function (require) {
|
||||
'use strict';
|
||||
|
||||
const CashierName = require('point_of_sale.CashierName');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const SelectCashierMixin = require('pos_hr.SelectCashierMixin');
|
||||
const { useBarcodeReader } = require('point_of_sale.custom_hooks');
|
||||
|
||||
const PosHrCashierName = (CashierName) =>
|
||||
class extends SelectCashierMixin(CashierName) {
|
||||
setup() {
|
||||
super.setup();
|
||||
useBarcodeReader({ cashier: this.barcodeCashierAction });
|
||||
}
|
||||
//@Override
|
||||
get avatar() {
|
||||
if (this.env.pos.config.module_pos_hr) {
|
||||
const cashier = this.env.pos.get_cashier();
|
||||
if (!(cashier && cashier.id)) {
|
||||
return '';
|
||||
}
|
||||
return `/web/image/hr.employee.public/${cashier.id}/avatar_128`;
|
||||
}
|
||||
return super.avatar;
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(CashierName, PosHrCashierName);
|
||||
|
||||
return CashierName;
|
||||
});
|
||||
30
odoo-bringout-oca-ocb-pos_hr/pos_hr/static/src/js/Chrome.js
Normal file
30
odoo-bringout-oca-ocb-pos_hr/pos_hr/static/src/js/Chrome.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
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();
|
||||
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' || this.env.pos.get_cashier_user_id() === this.env.pos.user.id;
|
||||
}
|
||||
showCashMoveButton() {
|
||||
return super.showCashMoveButton() && (!this.env.pos.cashier || this.env.pos.cashier.role == 'manager');
|
||||
}
|
||||
shouldShowCashControl() {
|
||||
if (this.env.pos.config.module_pos_hr){
|
||||
return super.shouldShowCashControl() && this.env.pos.hasLoggedIn;
|
||||
}
|
||||
return super.shouldShowCashControl();
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(Chrome, PosHrChrome);
|
||||
|
||||
return Chrome;
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
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 {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({ isUnlockIcon: true, title: 'Unlocked' });
|
||||
}
|
||||
async showLoginScreen() {
|
||||
this.env.pos.reset_cashier();
|
||||
await this.showTempScreen('LoginScreen');
|
||||
}
|
||||
onMouseOver(isMouseOver) {
|
||||
this.state.isUnlockIcon = !isMouseOver;
|
||||
this.state.title = isMouseOver ? 'Lock' : 'Unlocked';
|
||||
}
|
||||
}
|
||||
HeaderLockButton.template = "HeaderLockButton";
|
||||
|
||||
Registries.Component.add(HeaderLockButton);
|
||||
|
||||
return HeaderLockButton;
|
||||
});
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
odoo.define('pos_hr.LoginScreen', function (require) {
|
||||
'use strict';
|
||||
|
||||
const PosComponent = require('point_of_sale.PosComponent');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
const SelectCashierMixin = require('pos_hr.SelectCashierMixin');
|
||||
const { useBarcodeReader } = require('point_of_sale.custom_hooks');
|
||||
|
||||
class LoginScreen extends SelectCashierMixin(PosComponent) {
|
||||
setup() {
|
||||
super.setup();
|
||||
useBarcodeReader({cashier: this.barcodeCashierAction}, true);
|
||||
}
|
||||
async selectCashier() {
|
||||
if (await super.selectCashier()) {
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
async barcodeCashierAction(code) {
|
||||
if (await super.barcodeCashierAction(code) && this.env.pos.get_cashier().id) {
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
back() {
|
||||
this.props.resolve({ confirmed: false, payload: false });
|
||||
this.trigger('close-temp-screen');
|
||||
this.env.pos.hasLoggedIn = true;
|
||||
this.env.posbus.trigger('start-cash-control');
|
||||
}
|
||||
confirm() {
|
||||
this.props.resolve({ confirmed: true, payload: true });
|
||||
this.trigger('close-temp-screen');
|
||||
}
|
||||
get shopName() {
|
||||
return this.env.pos.config.name;
|
||||
}
|
||||
}
|
||||
LoginScreen.template = 'LoginScreen';
|
||||
|
||||
Registries.Component.add(LoginScreen);
|
||||
|
||||
return LoginScreen;
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
odoo.define('pos_hr.PaymentScreen', function (require) {
|
||||
'use strict';
|
||||
|
||||
const PaymentScreen = require('point_of_sale.PaymentScreen');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
const PosHrPaymentScreen = (PaymentScreen_) =>
|
||||
class extends PaymentScreen_ {
|
||||
async _finalizeValidation() {
|
||||
this.currentOrder.cashier = this.env.pos.get_cashier();
|
||||
await super._finalizeValidation();
|
||||
}
|
||||
};
|
||||
|
||||
Registries.Component.extend(PaymentScreen, PosHrPaymentScreen);
|
||||
|
||||
return PaymentScreen;
|
||||
});
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/* global Sha1 */
|
||||
odoo.define('pos_hr.SelectCashierMixin', function (require) {
|
||||
'use strict';
|
||||
|
||||
const SelectCashierMixin = (PosComponent) => class ComponentWithSelectCashierMixin extends PosComponent {
|
||||
async askPin(employee) {
|
||||
const { confirmed, payload: inputPin } = await this.showPopup('NumberPopup', {
|
||||
isPassword: true,
|
||||
title: this.env._t('Password ?'),
|
||||
startingValue: null,
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
if (employee.pin === Sha1.hash(inputPin)) {
|
||||
return employee;
|
||||
} else {
|
||||
await this.showPopup('ErrorPopup', {
|
||||
title: this.env._t('Incorrect Password'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a cashier, the returning value will either be an object or nothing (undefined)
|
||||
*/
|
||||
async selectCashier() {
|
||||
if (this.env.pos.config.module_pos_hr) {
|
||||
const employeesList = 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,
|
||||
};
|
||||
});
|
||||
let {confirmed, payload: employee} = await this.showPopup('SelectionPopup', {
|
||||
title: this.env._t('Change Cashier'),
|
||||
list: employeesList,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (employee && employee.pin) {
|
||||
employee = await this.askPin(employee);
|
||||
}
|
||||
if (employee) {
|
||||
this.env.pos.set_cashier(employee);
|
||||
}
|
||||
return employee;
|
||||
}
|
||||
}
|
||||
|
||||
async barcodeCashierAction(code) {
|
||||
const employee = this.env.pos.employees.find(
|
||||
(emp) => emp.barcode === Sha1.hash(code.code)
|
||||
);
|
||||
if (employee && employee !== this.env.pos.get_cashier() && (!employee.pin || (await this.askPin(employee)))) {
|
||||
this.env.pos.set_cashier(employee);
|
||||
}
|
||||
return employee;
|
||||
}
|
||||
}
|
||||
|
||||
return SelectCashierMixin;
|
||||
});
|
||||
82
odoo-bringout-oca-ocb-pos_hr/pos_hr/static/src/js/models.js
Normal file
82
odoo-bringout-oca-ocb-pos_hr/pos_hr/static/src/js/models.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
odoo.define('pos_hr.employees', function (require) {
|
||||
"use strict";
|
||||
|
||||
var { PosGlobalState, Order } = require('point_of_sale.models');
|
||||
const Registries = require('point_of_sale.Registries');
|
||||
|
||||
|
||||
const PosHrPosGlobalState = (PosGlobalState) => class PosHrPosGlobalState extends PosGlobalState {
|
||||
async _processData(loadedData) {
|
||||
await super._processData(...arguments);
|
||||
if (this.config.module_pos_hr) {
|
||||
this.employees = loadedData['hr.employee'];
|
||||
this.employee_by_id = loadedData['employee_by_id'];
|
||||
this.reset_cashier();
|
||||
}
|
||||
}
|
||||
async after_load_server_data() {
|
||||
await super.after_load_server_data(...arguments);
|
||||
if (this.config.module_pos_hr) {
|
||||
this.hasLoggedIn = !this.config.module_pos_hr;
|
||||
}
|
||||
}
|
||||
reset_cashier() {
|
||||
this.cashier = {name: null, id: null, barcode: null, user_id: null, pin: null, role: null};
|
||||
}
|
||||
set_cashier(employee) {
|
||||
this.cashier = employee;
|
||||
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 cashier on that order to the currently set employee.
|
||||
selectedOrder.cashier = employee;
|
||||
}
|
||||
if (!this.cashierHasPriceControlRights() && this.numpadMode === 'price') {
|
||||
this.numpadMode = 'quantity';
|
||||
}
|
||||
}
|
||||
|
||||
/**{name: null, id: null, barcode: null, user_id:null, pin:null}
|
||||
* If pos_hr is activated, return {name: string, id: int, barcode: string, pin: string, user_id: int}
|
||||
* @returns {null|*}
|
||||
*/
|
||||
get_cashier() {
|
||||
if (this.config.module_pos_hr) {
|
||||
return this.cashier;
|
||||
}
|
||||
return super.get_cashier();
|
||||
}
|
||||
get_cashier_user_id() {
|
||||
if (this.config.module_pos_hr) {
|
||||
return this.cashier.user_id ? this.cashier.user_id : null;
|
||||
}
|
||||
return super.get_cashier_user_id();
|
||||
}
|
||||
}
|
||||
Registries.Model.extend(PosGlobalState, PosHrPosGlobalState);
|
||||
|
||||
|
||||
const PosHrOrder = (Order) => class PosHrOrder extends Order {
|
||||
constructor(obj, options) {
|
||||
super(...arguments);
|
||||
if (!options.json && this.pos.config.module_pos_hr) {
|
||||
this.cashier = this.pos.get_cashier();
|
||||
}
|
||||
}
|
||||
init_from_JSON(json) {
|
||||
super.init_from_JSON(...arguments);
|
||||
if (this.pos.config.module_pos_hr && json.employee_id) {
|
||||
this.cashier = this.pos.employee_by_id[json.employee_id];
|
||||
}
|
||||
}
|
||||
export_as_JSON() {
|
||||
const json = super.export_as_JSON(...arguments);
|
||||
if (this.pos.config.module_pos_hr) {
|
||||
json.employee_id = this.cashier ? this.cashier.id : false;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
}
|
||||
Registries.Model.extend(Order, PosHrOrder);
|
||||
|
||||
});
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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="() => this.onMouseOver(true)"
|
||||
t-on-click="showLoginScreen" t-on-mouseout="() => this.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>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?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-cashier"
|
||||
t-on-click="selectCashier">Select Cashier</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
Loading…
Add table
Add a link
Reference in a new issue