[IMP] point_of_sale,pos_hr: add employee manager role

Currently, POS-allowed employees can manage cash:
- at the opening (cash control)
- on cash payment

Now, after adding the mananger role, it is possible for an employee to
open the cash register. In the settings of a `pos_config`, you can add
employees with a "basic" role and with a "manager" role, the latter
grants the same rights as a classic odoo manager user.

closes odoo/odoo#122287

Related: odoo/upgrade#4700
Signed-off-by: Guilliams Adrien (adgu) <adgu@odoo.com>
This commit is contained in:
David Monnom (moda)
2023-06-15 17:36:08 +02:00
parent d387acffb7
commit a3d530c846
17 changed files with 104 additions and 54 deletions
+1 -5
View File
@@ -1561,11 +1561,6 @@ class PosSession(models.Model):
for session in sessions
])
message_content = [f"Cash {extras['translatedType']}", f'- Amount: {extras["formattedAmount"]}']
if reason:
message_content.append(f'- Reason: {reason}')
self.message_post(body=Markup('<br/>\n').join(message_content))
def get_onboarding_data(self):
return {
"categories": self._load_model('pos.category'),
@@ -2132,6 +2127,7 @@ class PosSession(models.Model):
body = 'Action cancelled ({ACTION})'.format(ACTION=action)
elif message_type == 'CASH_DRAWER_ACTION':
body = 'Cash drawer opened ({ACTION})'.format(ACTION=action)
self.message_post(body=body, author_id=partner_id)
def load_product_frontend(self):
@@ -65,6 +65,11 @@ export class CashMovePopup extends AbstractAwaitablePopup {
reason,
extras,
]);
await this.pos.globalState.logEmployeeMessage(
`${_t("Cash")} ${translatedType} - ${_t("Amount")}: ${formattedAmount}`,
"CASH_DRAWER_ACTION"
);
if (this.hardwareProxy.printer) {
const renderedReceipt = renderToElement("point_of_sale.CashMoveReceipt", {
_receipt: {
@@ -26,9 +26,7 @@ export class Navbar extends Component {
SyncNotification,
BackButton,
};
static props = {
showCashMoveButton: Boolean,
};
static props = {};
setup() {
this.pos = usePos();
this.ui = useState(useService("ui"));
@@ -49,7 +47,9 @@ export class Navbar extends Component {
get customerFacingDisplayButtonIsShown() {
return this.pos.globalState.config.iface_customer_facing_display;
}
get showCashMoveButton() {
return Boolean(this.pos.globalState?.config?.cash_control);
}
onCashMoveButtonClick() {
this.hardwareProxy.openCashbox(_t("Cash in / out"));
this.popup.add(CashMovePopup);
@@ -29,7 +29,7 @@
<span>Orders</span>
</div>
</li>
<li t-if="props.showCashMoveButton" class="menu-item navbar-button" t-on-click="onCashMoveButtonClick">
<li t-if="showCashMoveButton" class="menu-item navbar-button" t-on-click="onCashMoveButtonClick">
<span>Cash In/Out</span>
</li>
<CustomerFacingDisplayButton t-if="CustomerFacingDisplayButtonIsShown"/>
@@ -33,11 +33,6 @@ export class Chrome extends Component {
onMounted(this.props.disableLoader);
}
// GETTERS //
get showCashMoveButton() {
return Boolean(this.pos.globalState?.config?.cash_control);
}
/**
* Unmounts the tempScreen on error and dispatches the error in a separate
* stack so that it can be handled by the error service and display an error
@@ -4,7 +4,7 @@
<t t-name="point_of_sale.Chrome" owl="1">
<div class="pos" t-att-class="{ 'big-scrollbars': pos.hasBigScrollBars }">
<div class="pos-receipt-print"></div>
<Navbar showCashMoveButton="showCashMoveButton"/>
<Navbar />
<div class="pos-content">
<div class="window">
<div class="subwindow">
-2
View File
@@ -26,8 +26,6 @@ The actual till still requires one user but an unlimited number of employees can
'pos_hr/static/src/css/pos.css',
'pos_hr/static/src/js/models.js',
'pos_hr/static/src/js/SelectCashierMixin.js',
'pos_hr/static/src/js/Chrome.js',
'pos_hr/static/src/js/HeaderLockButton.js',
'pos_hr/static/src/js/CashierName.js',
'pos_hr/static/src/js/LoginScreen.js',
'pos_hr/static/src/js/PaymentScreen.js',
+3 -3
View File
@@ -25,12 +25,12 @@ class HrEmployee(models.Model):
@api.ondelete(at_uninstall=False)
def _unlink_except_active_pos_session(self):
configs_with_employees = self.env['pos.config'].sudo().search([('module_pos_hr', '=', 'True')]).filtered(lambda c: c.current_session_id)
configs_with_all_employees = configs_with_employees.filtered(lambda c: not c.employee_ids)
configs_with_specific_employees = configs_with_employees.filtered(lambda c: c.employee_ids & self)
configs_with_all_employees = configs_with_employees.filtered(lambda c: not c.basic_employee_ids and not c.advanced_employee_ids)
configs_with_specific_employees = configs_with_employees.filtered(lambda c: (c.basic_employee_ids or c.advanced_employee_ids) & self)
if configs_with_all_employees or configs_with_specific_employees:
error_msg = _("You cannot delete an employee that may be used in an active PoS session, close the session(s) first: \n")
for employee in self:
config_ids = configs_with_all_employees | configs_with_specific_employees.filtered(lambda c: employee in c.employee_ids)
config_ids = configs_with_all_employees | configs_with_specific_employees.filtered(lambda c: employee in c.basic_employee_ids)
if config_ids:
error_msg += _("Employee: %s - PoS Config(s): %s \n") % (employee.name, ', '.join(config.name for config in config_ids))
+19 -6
View File
@@ -1,13 +1,26 @@
# -*- coding: utf-8 -*-
from functools import partial
from odoo import models, fields
from odoo import models, fields, api
class PosConfig(models.Model):
_inherit = 'pos.config'
employee_ids = fields.Many2many(
'hr.employee', string="Employees with access",
help='If left empty, all employees can log in to the PoS session')
basic_employee_ids = fields.Many2many(
'hr.employee', 'pos_hr_basic_employee_hr_employee', string="Employees with basic access",
help='If left empty, all employees can log in to PoS')
advanced_employee_ids = fields.Many2many(
'hr.employee', 'pos_hr_advanced_employee_hr_employee', string="Employees with manager access",
help='If left empty, only Odoo users have extended rights in PoS')
@api.onchange('basic_employee_ids')
def _onchange_basic_employee_ids(self):
for employee in self.basic_employee_ids:
if employee in self.advanced_employee_ids:
self.advanced_employee_ids -= employee
@api.onchange('advanced_employee_ids')
def _onchange_advanced_employee_ids(self):
for employee in self.advanced_employee_ids:
if employee in self.basic_employee_ids:
self.basic_employee_ids -= employee
+8 -3
View File
@@ -21,8 +21,10 @@ class PosSession(models.Model):
return result
def _loader_params_hr_employee(self):
if len(self.config_id.employee_ids) > 0:
domain = ['&', ('company_id', '=', self.config_id.company_id.id), '|', ('user_id', '=', self.user_id.id), ('id', 'in', self.config_id.employee_ids.ids)]
if len(self.config_id.basic_employee_ids) > 0:
domain = [
'&', ('company_id', '=', self.config_id.company_id.id),
'|', ('user_id', '=', self.user_id.id), ('id', 'in', self.config_id.basic_employee_ids.ids + self.config_id.advanced_employee_ids.ids)]
else:
domain = [('company_id', '=', self.config_id.company_id.id)]
return {'search_params': {'domain': domain, 'fields': ['name', 'id', 'user_id', 'work_contact_id'], 'load': False}}
@@ -36,7 +38,10 @@ class PosSession(models.Model):
employees_barcode_pin = self.env['hr.employee'].browse(employee_ids).get_barcodes_and_pin_hashed()
bp_per_employee_id = {bp_e['id']: bp_e for bp_e in employees_barcode_pin}
for employee in employees:
employee['role'] = 'manager' if employee['user_id'] and employee['user_id'] in manager_ids else 'cashier'
if employee['user_id'] and employee['user_id'] in manager_ids or employee['id'] in self.config_id.advanced_employee_ids.ids:
employee['role'] = 'manager'
else:
employee['role'] = 'cashier'
employee['barcode'] = bp_per_employee_id[employee['id']]['barcode']
employee['pin'] = bp_per_employee_id[employee['id']]['pin']
+17 -2
View File
@@ -1,10 +1,25 @@
# -*- coding: utf-8 -*-
from odoo import fields, models
from odoo import fields, models, api
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
# pos.config fields
pos_employee_ids = fields.Many2many(related='pos_config_id.employee_ids', readonly=False)
pos_basic_employee_ids = fields.Many2many(related='pos_config_id.basic_employee_ids', readonly=False,
help='If left empty, all employees can log in to PoS')
pos_advanced_employee_ids = fields.Many2many(related='pos_config_id.advanced_employee_ids', readonly=False,
help='If left empty, only Odoo users have extended rights in PoS')
@api.onchange('pos_basic_employee_ids')
def _onchange_basic_employee_ids(self):
for employee in self.pos_basic_employee_ids:
if employee in self.pos_advanced_employee_ids:
self.pos_advanced_employee_ids -= employee
@api.onchange('pos_advanced_employee_ids')
def _onchange_advanced_employee_ids(self):
for employee in self.pos_advanced_employee_ids:
if employee in self.pos_basic_employee_ids:
self.pos_basic_employee_ids -= employee
+25 -3
View File
@@ -1,9 +1,31 @@
/** @odoo-module */
import { Navbar } from "@point_of_sale/app/navbar/navbar";
import { HeaderLockButton } from "@pos_hr/js/HeaderLockButton";
import { patch } from "@web/core/utils/patch";
patch(Navbar, "pos_hr.Navbar", {
components: { ...Navbar.components, HeaderLockButton },
patch(Navbar.prototype, "pos_hr.Navbar", {
get showCashMoveButton() {
const { cashier } = this.pos.globalState;
return this._super(...arguments) && (!cashier || cashier.role == "manager");
},
get showCloseSessionButton() {
return (
!this.pos.globalState.config.module_pos_hr ||
(this.pos.globalState.get_cashier().role === "manager" &&
this.pos.globalState.get_cashier().user_id) ||
this.pos.globalState.get_cashier_user_id() === this.pos.globalState.user.id
);
},
get showBackendButton() {
return (
!this.pos.globalState.config.module_pos_hr ||
(this.pos.globalState.get_cashier().role === "manager" &&
this.pos.globalState.get_cashier().user_id) ||
this.pos.globalState.get_cashier_user_id() === this.pos.globalState.user.id
);
},
async showLoginScreen() {
this.pos.globalState.reset_cashier();
await this.pos.showTempScreen("LoginScreen");
},
});
+7 -3
View File
@@ -4,18 +4,22 @@
<t t-name="pos_hr.Navbar" t-inherit="point_of_sale.Navbar" t-inherit-mode="extension" owl="1">
<xpath expr="//li[hasclass('backend-button')]" position="attributes">
<attribute name="t-if">
!pos.globalState.config.module_pos_hr or pos.globalState.get_cashier().role === 'manager' or pos.globalState.get_cashier_user_id() === pos.globalState.user.id
showBackendButton
</attribute>
</xpath>
<xpath expr="//li[hasclass('close-button')]" position="attributes">
<attribute name="t-if">
!pos.globalState.config.module_pos_hr or pos.globalState.get_cashier().role === 'manager' or pos.globalState.get_cashier_user_id() === pos.globalState.user.id
showCloseSessionButton
</attribute>
</xpath>
<xpath expr="//li[hasclass('backend-button')]" position="after">
<HeaderLockButton t-if="pos.globalState.config.module_pos_hr" />
<li t-if="pos.globalState.config.module_pos_hr" class="menu-item navbar-button lock-button" t-on-click="showLoginScreen">
<span>
Lock
</span>
</li>
</xpath>
</t>
-11
View File
@@ -1,11 +0,0 @@
/** @odoo-module */
import { Chrome } from "@point_of_sale/app/pos_app";
import { patch } from "@web/core/utils/patch";
patch(Chrome.prototype, "pos_hr.Chrome", {
get showCashMoveButton() {
const { cashier } = this.pos.globalState;
return this._super(...arguments) && (!cashier || cashier.role == "manager");
},
});
+1 -1
View File
@@ -44,7 +44,7 @@ class TestPosHrHttpCommon(TestPointOfSaleHttpCommon):
(admin + emp1 + emp2).company_id = cls.env.company
cls.main_pos_config.write({
'employee_ids': [Command.link(emp1.id), Command.link(emp2.id)]
'basic_employee_ids': [Command.link(emp1.id), Command.link(emp2.id)]
})
+6 -2
View File
@@ -8,8 +8,12 @@
<xpath expr="//div[@id='warning_text_employees']" position='replace'>
<field name="company_id" invisible="1" />
<div class="row">
<label for="employee_ids" string="Allowed Employees" class="col-lg-3 o_light_label" />
<field name="employee_ids" widget="many2many_tags" domain="[('company_id', '=', company_id)]" />
<label for="basic_employee_ids" string="Basic rights" class="col-lg-4 o_light_label" />
<field name="basic_employee_ids" widget="many2many_tags" domain="[('company_id', '=', company_id)]" />
</div>
<div class="row">
<label for="advanced_employee_ids" string="Advanced rights" class="col-lg-4 o_light_label" />
<field name="advanced_employee_ids" widget="many2many_tags" domain="[('company_id', '=', company_id)]" />
</div>
</xpath>
</field>
@@ -7,8 +7,12 @@
<field name="arch" type="xml">
<xpath expr="//div[@id='warning_text_employees']" position='replace'>
<div class="row">
<label for="pos_employee_ids" string="Allowed Employees" class="col-lg-3 o_light_label" />
<field name="pos_employee_ids" widget="many2many_tags" domain="[('company_id', '=', company_id)]" />
<label for="pos_basic_employee_ids" string="Basic rights" class="col-lg-4 o_light_label" />
<field name="pos_basic_employee_ids" widget="many2many_tags" placeholder="All employees" domain="[('company_id', '=', company_id)]" />
</div>
<div class="row">
<label for="pos_advanced_employee_ids" string="Advanced rights" class="col-lg-4 o_light_label" />
<field name="pos_advanced_employee_ids" widget="many2many_tags" placeholder="No employee" domain="[('company_id', '=', company_id)]" />
</div>
</xpath>
</field>