feat: grant outlet managers bypass authority for restricted POS actions and prevent order deletion when items are locked
This commit is contained in:
parent
08c4e3711b
commit
c37db8da68
@ -7,7 +7,7 @@ This module implements a robust security lock in the Odoo 19 Point of Sale syste
|
|||||||
Features
|
Features
|
||||||
========
|
========
|
||||||
|
|
||||||
* **Role-Based Security**: Only users with the "Area Manager" role (integrated with `pos_employee_role`) can delete or reduce the quantity of items already sent to the kitchen.
|
* **Role-Based Security**: Only users with the "Area Manager" or "Store Manager" (technically "outlet_manager") roles can delete or reduce the quantity of items already sent to the kitchen.
|
||||||
* **Persistent Locking**: Uses a database-backed field `x_locked_qty` on `pos.order.line` to ensure the lock state persists across browser refreshes, device synchronizations, and session restarts.
|
* **Persistent Locking**: Uses a database-backed field `x_locked_qty` on `pos.order.line` to ensure the lock state persists across browser refreshes, device synchronizations, and session restarts.
|
||||||
* **Graceful Reduction**: Allows cashiers to freely add and remove "new" quantities on an existing line. For example, if 1 item is sent and 1 is newly added (Total 2), the cashier can safely backspace to reduce the quantity to 1, but cannot drop it below the sent amount.
|
* **Graceful Reduction**: Allows cashiers to freely add and remove "new" quantities on an existing line. For example, if 1 item is sent and 1 is newly added (Total 2), the cashier can safely backspace to reduce the quantity to 1, but cannot drop it below the sent amount.
|
||||||
* **User-Friendly Alerts**: Displays a clear "Action Restricted" popup when an unauthorized deletion attempt is blocked.
|
* **User-Friendly Alerts**: Displays a clear "Action Restricted" popup when an unauthorized deletion attempt is blocked.
|
||||||
@ -32,5 +32,5 @@ Installation
|
|||||||
============
|
============
|
||||||
|
|
||||||
1. Install the module as usual.
|
1. Install the module as usual.
|
||||||
2. Ensure the "Area Manager" role is configured for supervisors who require bypass authority.
|
2. Ensure the "Area Manager" or "Store Manager" roles are configured for supervisors who require bypass authority.
|
||||||
3. Refresh the POS interface to load the new security logic.
|
3. Refresh the POS interface to load the new security logic.
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
'summary': 'Lock product deletion/reduction after order is sent to kitchen',
|
'summary': 'Lock product deletion/reduction after order is sent to kitchen',
|
||||||
'description': """
|
'description': """
|
||||||
This module prevents cashiers from deleting or reducing the quantity of products that have already been sent to the kitchen.
|
This module prevents cashiers from deleting or reducing the quantity of products that have already been sent to the kitchen.
|
||||||
Only users with the 'Area Manager' role can perform these actions.
|
Only users with the 'Area Manager' or 'Store Manager' role can perform these actions.
|
||||||
""",
|
""",
|
||||||
'author': "Suherdy Yacob",
|
'author': "Suherdy Yacob",
|
||||||
'depends': ['point_of_sale', 'pos_hr', 'pos_employee_role'],
|
'depends': ['point_of_sale', 'pos_hr', 'pos_employee_role'],
|
||||||
|
|||||||
@ -13,19 +13,19 @@ patch(PosOrder.prototype, {
|
|||||||
|
|
||||||
removeOrderline(line) {
|
removeOrderline(line) {
|
||||||
const cashier = this.models["pos.session"].getFirst()?.cashier;
|
const cashier = this.models["pos.session"].getFirst()?.cashier;
|
||||||
const isAreaManager = cashier?.pos_role === 'area_manager';
|
const isManager = cashier && ['area_manager', 'outlet_manager'].includes(cashier.pos_role);
|
||||||
|
|
||||||
// Use the same helper as the line patch
|
// Use the same helper as the line patch
|
||||||
const lockedQty = line.get_locked_qty ? line.get_locked_qty() : 0;
|
const lockedQty = line.get_locked_qty ? line.get_locked_qty() : 0;
|
||||||
|
|
||||||
if (lockedQty > 0 && !isAreaManager) {
|
if (lockedQty > 0 && !isManager) {
|
||||||
if (line.qty > lockedQty) {
|
if (line.qty > lockedQty) {
|
||||||
line.setQuantity(lockedQty);
|
line.setQuantity(lockedQty);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
title: _t("Action Restricted"),
|
title: _t("Action Restricted"),
|
||||||
body: _t("You cannot delete a product that has already been sent to the kitchen. Please call an Area Manager."),
|
body: _t("You cannot delete a product that has already been sent to the kitchen. Please call an Area Manager or Store Manager."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return super.removeOrderline(...arguments);
|
return super.removeOrderline(...arguments);
|
||||||
|
|||||||
@ -23,12 +23,12 @@ patch(PosOrderline.prototype, {
|
|||||||
|
|
||||||
setQuantity(quantity, keep_price) {
|
setQuantity(quantity, keep_price) {
|
||||||
const cashier = this.models["pos.session"].getFirst()?.cashier;
|
const cashier = this.models["pos.session"].getFirst()?.cashier;
|
||||||
const isAreaManager = cashier?.pos_role === 'area_manager';
|
const isManager = cashier && ['area_manager', 'outlet_manager'].includes(cashier.pos_role);
|
||||||
|
|
||||||
const lockedQty = this.get_locked_qty();
|
const lockedQty = this.get_locked_qty();
|
||||||
const newQty = (typeof quantity === "number" || (typeof quantity === "string" && quantity !== "")) ? parseFloat(quantity) : 0;
|
const newQty = (typeof quantity === "number" || (typeof quantity === "string" && quantity !== "")) ? parseFloat(quantity) : 0;
|
||||||
|
|
||||||
if (lockedQty > 0 && !isAreaManager) {
|
if (lockedQty > 0 && !isManager) {
|
||||||
// If trying to delete or reduce below locked qty
|
// If trying to delete or reduce below locked qty
|
||||||
if (quantity === "" || isNaN(parseFloat(quantity)) || newQty < lockedQty) {
|
if (quantity === "" || isNaN(parseFloat(quantity)) || newQty < lockedQty) {
|
||||||
// If there's unsent quantity, gracefully reduce it to the locked quantity
|
// If there's unsent quantity, gracefully reduce it to the locked quantity
|
||||||
@ -39,7 +39,7 @@ patch(PosOrderline.prototype, {
|
|||||||
// Otherwise, they are trying to delete the sent quantity, so block it
|
// Otherwise, they are trying to delete the sent quantity, so block it
|
||||||
return {
|
return {
|
||||||
title: _t("Action Restricted"),
|
title: _t("Action Restricted"),
|
||||||
body: _t("You cannot reduce the quantity below what was already sent to the kitchen. Please call an Area Manager."),
|
body: _t("You cannot reduce the quantity below what was already sent to the kitchen. Please call an Area Manager or Store Manager."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
static/src/app/services/pos_store.js
Normal file
27
static/src/app/services/pos_store.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { PosStore } from "@point_of_sale/app/services/pos_store";
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||||
|
|
||||||
|
patch(PosStore.prototype, {
|
||||||
|
async beforeDeleteOrder(order, options = {}) {
|
||||||
|
const cashier = this.getCashier();
|
||||||
|
const isManager = cashier && ['area_manager', 'outlet_manager'].includes(cashier.pos_role);
|
||||||
|
|
||||||
|
// Check if the order has any locked items (sent to kitchen)
|
||||||
|
if (!isManager && order && order.lines) {
|
||||||
|
for (const line of order.lines) {
|
||||||
|
const lockedQty = line.get_locked_qty ? line.get_locked_qty() : 0;
|
||||||
|
if (lockedQty > 0) {
|
||||||
|
this.dialog.add(AlertDialog, {
|
||||||
|
title: _t("Action Restricted"),
|
||||||
|
body: _t("You cannot delete this order because some items have already been sent to the kitchen. Please call an Area Manager or Store Manager."),
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.beforeDeleteOrder(order, options);
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user