[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:
@@ -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">
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
});
|
||||
@@ -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)]
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user