feat: enforce PIN on order load and improve employee attribution logic for POS orders

This commit is contained in:
Suherdy Yacob 2026-05-25 17:46:08 +07:00
parent 1d7162cf61
commit 16e5e25c76
6 changed files with 99 additions and 0 deletions

View File

@ -5,6 +5,7 @@ This Odoo 19 module enhances the security and auditability of the Point of Sale
## Features
- **Mandatory PIN on Table Selection**: Prevents unauthorized access to tables. Every table selection triggers a PIN prompt to identify the employee taking the order.
- **Mandatory PIN on Load Order**: Enforces PIN entry when a draft order is loaded/resumed from the Orders tab (`TicketScreen`).
- **Mandatory PIN on Payment**: Requires PIN authentication before processing payments to ensure the transaction is handled by an authorized employee.
- **Role-Based Payment Gating**: Cross-checks employee roles (using `pos_employee_role`) to prevent roles like 'waiter' from processing payments.
- **Order Attribution**:
@ -15,6 +16,7 @@ This Odoo 19 module enhances the security and auditability of the Point of Sale
- Hides the "Course" button on the POS UI.
- Hides the "Transfer Course" button on the POS UI.
- Hides the save order for later (upload icon) button from the POS UI.
- Hides the "Set Table" button from Register/Direct Sale orders.
## Dependencies

View File

@ -0,0 +1,21 @@
import { PosOrder } from "@point_of_sale/app/models/pos_order";
import { patch } from "@web/core/utils/patch";
patch(PosOrder.prototype, {
setup(vals) {
super.setup(...arguments);
if (vals.employee_id) {
this.original_employee_id = this.models["hr.employee"].get(vals.employee_id.id || vals.employee_id) || vals.employee_id;
} else if (this.employee_id) {
this.original_employee_id = this.employee_id;
}
},
serializeForORM(opts = {}) {
const data = super.serializeForORM(opts);
if (this.original_employee_id) {
data.employee_id = this.original_employee_id.id || this.original_employee_id;
}
return data;
}
});

View File

@ -4,6 +4,9 @@
<xpath expr="//button[hasclass('pay-order-button')]" position="attributes">
<attribute name="t-if">pos.canPay</attribute>
</xpath>
<xpath expr="//button[hasclass('set-table')]" position="replace">
<!-- Hidden "Set Table" button on Register order -->
</xpath>
</t>
<t t-name="pos_custom_access.ProductScreen" t-inherit="point_of_sale.ProductScreen" t-inherit-mode="extension">

View File

@ -0,0 +1,26 @@
/** @odoo-module */
import { patch } from "@web/core/utils/patch";
import { TicketScreen } from "@point_of_sale/app/screens/ticket_screen/ticket_screen";
patch(TicketScreen.prototype, {
async setOrder(order) {
// Enforce PIN entry when loading/selecting an order from the TicketScreen (Orders tab)
const cashier = await this.pos._selectCashierByPin();
if (!cashier) {
return;
}
this.pos.setCashier(cashier);
// Ensure the order has its original employee id preserved when loading
if (order) {
if (!order.original_employee_id) {
order.original_employee_id = order.employee_id || cashier;
}
order.uiState.is_authorized = true;
}
return super.setOrder(order);
}
});

View File

@ -67,6 +67,39 @@ patch(PosStore.prototype, {
return super.pay();
},
createNewOrder() {
const order = super.createNewOrder(...arguments);
if (order && this.config.module_pos_hr) {
order.original_employee_id = this.getCashier();
}
return order;
},
setCashier(employee) {
super.setCashier(...arguments);
const order = this.getOrder();
if (order && !order.getOrderlines().length) {
order.original_employee_id = employee;
}
},
addLineToCurrentOrder(vals, opt = {}, configure = true) {
const order = this.getOrder();
if (order && !order.original_employee_id) {
order.original_employee_id = order.employee_id || this.getCashier();
}
const res = super.addLineToCurrentOrder(...arguments);
if (order && order.original_employee_id) {
order.employee_id = order.original_employee_id;
}
return res;
},
async _selectCashierByPin() {
if (!this.config.module_pos_hr) {
return this.getCashier();

View File

@ -0,0 +1,14 @@
import OrderPaymentValidation from "@point_of_sale/app/utils/order_payment_validation";
import { patch } from "@web/core/utils/patch";
patch(OrderPaymentValidation.prototype, {
async validateOrder(isForceValidate) {
const originalEmployeeId = this.order.original_employee_id || this.order.employee_id;
await super.validateOrder(...arguments);
if (originalEmployeeId) {
this.order.employee_id = originalEmployeeId;
}
},
});