Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce43c4e064 | ||
|
|
579ddcf510 | ||
|
|
0b342a7780 | ||
|
|
57bcbaf72a | ||
|
|
d9fbba3130 | ||
|
|
e4465d9d91 | ||
|
|
734cd13c87 | ||
|
|
d0f94ac549 | ||
|
|
1d25cbe21c | ||
|
|
53d0636193 | ||
|
|
04bb26f1b0 | ||
|
|
df0d4783d0 | ||
|
|
39c06ceab6 | ||
|
|
7dd428d18e | ||
|
|
0103a40156 | ||
|
|
e208b1b2a4 |
@@ -12445,6 +12445,29 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema: {}
|
schema: {}
|
||||||
|
|
||||||
|
/roles/limited-backoffice-permission-templates:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Roles
|
||||||
|
summary: List limited backoffice permission templates
|
||||||
|
operationId: listLimitedBackofficeRolePermissionTemplates
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
key: {type: string}
|
||||||
|
label: {type: string}
|
||||||
|
description: {type: string}
|
||||||
|
permissions:
|
||||||
|
type: array
|
||||||
|
items: {type: string}
|
||||||
|
|
||||||
/roles/permissions:
|
/roles/permissions:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
@@ -29,6 +29,21 @@ class limited_backoffice_service
|
|||||||
self::PERMISSION_PUBLIC_EMPLOYEE_DATA,
|
self::PERMISSION_PUBLIC_EMPLOYEE_DATA,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permissions that are always granted to managed employees when present in a role preset,
|
||||||
|
* regardless of whether the creating manager holds those permissions themselves.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
private const ROLE_UNCONDITIONAL_PERMISSIONS = [
|
||||||
|
'list_departments',
|
||||||
|
'list_department_daily_reports',
|
||||||
|
'list_notifications',
|
||||||
|
'list_own_notifications',
|
||||||
|
'statistics_orders_new',
|
||||||
|
'statistics_bookings_new',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array<string, array{label:string,description:string,permissions:array<int,string>}>
|
* @var array<string, array{label:string,description:string,permissions:array<int,string>}>
|
||||||
*/
|
*/
|
||||||
@@ -47,6 +62,7 @@ class limited_backoffice_service
|
|||||||
'permissions' => [
|
'permissions' => [
|
||||||
'user',
|
'user',
|
||||||
'permissions_list_own',
|
'permissions_list_own',
|
||||||
|
'list_departments',
|
||||||
'list_orders',
|
'list_orders',
|
||||||
'fetch_order',
|
'fetch_order',
|
||||||
'add_order',
|
'add_order',
|
||||||
@@ -89,6 +105,11 @@ class limited_backoffice_service
|
|||||||
'add_bookings',
|
'add_bookings',
|
||||||
'complete_bookings',
|
'complete_bookings',
|
||||||
'resend_booking_confirmations',
|
'resend_booking_confirmations',
|
||||||
|
'list_department_daily_reports',
|
||||||
|
'list_notifications',
|
||||||
|
'list_own_notifications',
|
||||||
|
'statistics_orders_new',
|
||||||
|
'statistics_bookings_new',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'booking_coordinator' => [
|
'booking_coordinator' => [
|
||||||
@@ -97,6 +118,7 @@ class limited_backoffice_service
|
|||||||
'permissions' => [
|
'permissions' => [
|
||||||
'user',
|
'user',
|
||||||
'permissions_list_own',
|
'permissions_list_own',
|
||||||
|
'list_departments',
|
||||||
'list_orders',
|
'list_orders',
|
||||||
'list_bookings',
|
'list_bookings',
|
||||||
'list_own_bookings',
|
'list_own_bookings',
|
||||||
@@ -108,6 +130,11 @@ class limited_backoffice_service
|
|||||||
'department_timebookings_entries_get',
|
'department_timebookings_entries_get',
|
||||||
'department_timebookings_entries_post',
|
'department_timebookings_entries_post',
|
||||||
'department_timebookings_entries_put',
|
'department_timebookings_entries_put',
|
||||||
|
'list_department_daily_reports',
|
||||||
|
'list_notifications',
|
||||||
|
'list_own_notifications',
|
||||||
|
'statistics_orders_new',
|
||||||
|
'statistics_bookings_new',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'operations_lead' => [
|
'operations_lead' => [
|
||||||
@@ -116,6 +143,7 @@ class limited_backoffice_service
|
|||||||
'permissions' => [
|
'permissions' => [
|
||||||
'user',
|
'user',
|
||||||
'permissions_list_own',
|
'permissions_list_own',
|
||||||
|
'list_departments',
|
||||||
'list_orders',
|
'list_orders',
|
||||||
'fetch_order',
|
'fetch_order',
|
||||||
'add_order',
|
'add_order',
|
||||||
@@ -159,6 +187,9 @@ class limited_backoffice_service
|
|||||||
'add_bookings',
|
'add_bookings',
|
||||||
'complete_bookings',
|
'complete_bookings',
|
||||||
'resend_booking_confirmations',
|
'resend_booking_confirmations',
|
||||||
|
'list_department_daily_reports',
|
||||||
|
'list_notifications',
|
||||||
|
'list_own_notifications',
|
||||||
'statistics_orders_new',
|
'statistics_orders_new',
|
||||||
'statistics_bookings_new',
|
'statistics_bookings_new',
|
||||||
],
|
],
|
||||||
@@ -169,6 +200,7 @@ class limited_backoffice_service
|
|||||||
'permissions' => [
|
'permissions' => [
|
||||||
'user',
|
'user',
|
||||||
'permissions_list_own',
|
'permissions_list_own',
|
||||||
|
'list_departments',
|
||||||
'list_orders',
|
'list_orders',
|
||||||
'fetch_order',
|
'fetch_order',
|
||||||
'add_order',
|
'add_order',
|
||||||
@@ -212,6 +244,9 @@ class limited_backoffice_service
|
|||||||
'add_bookings',
|
'add_bookings',
|
||||||
'complete_bookings',
|
'complete_bookings',
|
||||||
'resend_booking_confirmations',
|
'resend_booking_confirmations',
|
||||||
|
'list_department_daily_reports',
|
||||||
|
'list_notifications',
|
||||||
|
'list_own_notifications',
|
||||||
'statistics_orders_new',
|
'statistics_orders_new',
|
||||||
'statistics_bookings_new',
|
'statistics_bookings_new',
|
||||||
self::PERMISSION_ACCESS,
|
self::PERMISSION_ACCESS,
|
||||||
@@ -417,6 +452,22 @@ class limited_backoffice_service
|
|||||||
'group' => 'time_bookings',
|
'group' => 'time_bookings',
|
||||||
'capability' => 'edit_time_booking_entries',
|
'capability' => 'edit_time_booking_entries',
|
||||||
],
|
],
|
||||||
|
'list_departments' => [
|
||||||
|
'group' => 'departments',
|
||||||
|
'capability' => 'view_departments',
|
||||||
|
],
|
||||||
|
'list_department_daily_reports' => [
|
||||||
|
'group' => 'departments',
|
||||||
|
'capability' => 'view_daily_reports',
|
||||||
|
],
|
||||||
|
'list_notifications' => [
|
||||||
|
'group' => 'notifications',
|
||||||
|
'capability' => 'view_notifications',
|
||||||
|
],
|
||||||
|
'list_own_notifications' => [
|
||||||
|
'group' => 'notifications',
|
||||||
|
'capability' => 'view_notifications',
|
||||||
|
],
|
||||||
'statistics_orders_new' => [
|
'statistics_orders_new' => [
|
||||||
'group' => 'reports',
|
'group' => 'reports',
|
||||||
'capability' => 'view_order_statistics',
|
'capability' => 'view_order_statistics',
|
||||||
@@ -444,6 +495,7 @@ class limited_backoffice_service
|
|||||||
*/
|
*/
|
||||||
private const ROLE_PERMISSION_GROUP_ORDER = [
|
private const ROLE_PERMISSION_GROUP_ORDER = [
|
||||||
'account',
|
'account',
|
||||||
|
'departments',
|
||||||
'orders',
|
'orders',
|
||||||
'products',
|
'products',
|
||||||
'customers',
|
'customers',
|
||||||
@@ -452,6 +504,7 @@ class limited_backoffice_service
|
|||||||
'scanner',
|
'scanner',
|
||||||
'bookings',
|
'bookings',
|
||||||
'time_bookings',
|
'time_bookings',
|
||||||
|
'notifications',
|
||||||
'reports',
|
'reports',
|
||||||
'limited_backoffice',
|
'limited_backoffice',
|
||||||
];
|
];
|
||||||
@@ -498,6 +551,23 @@ class limited_backoffice_service
|
|||||||
return $roles;
|
return $roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, array{key:string,label:string,description:string,permissions:array<int,string>}>
|
||||||
|
*/
|
||||||
|
public function rolePermissionTemplates(): array
|
||||||
|
{
|
||||||
|
$templates = [];
|
||||||
|
foreach (self::ROLE_PRESETS as $key => $preset) {
|
||||||
|
$templates[] = [
|
||||||
|
'key' => $key,
|
||||||
|
'label' => $preset['label'],
|
||||||
|
'description' => $preset['description'],
|
||||||
|
'permissions' => array_values($preset['permissions']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $templates;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int, string> $permissions
|
* @param array<int, string> $permissions
|
||||||
* @return array<int, array{key:string,capabilities:array<int,string>}>
|
* @return array<int, array{key:string,capabilities:array<int,string>}>
|
||||||
@@ -1600,6 +1670,11 @@ class limited_backoffice_service
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (in_array($permission, self::ROLE_UNCONDITIONAL_PERMISSIONS, true)) {
|
||||||
|
$permissions[] = $permission;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ($manager->hasPermission($permission)) {
|
if ($manager->hasPermission($permission)) {
|
||||||
$permissions[] = $permission;
|
$permissions[] = $permission;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12422,6 +12422,29 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema: {}
|
schema: {}
|
||||||
|
|
||||||
|
/roles/limited-backoffice-permission-templates:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Roles
|
||||||
|
summary: List limited backoffice permission templates
|
||||||
|
operationId: listLimitedBackofficeRolePermissionTemplates
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
key: {type: string}
|
||||||
|
label: {type: string}
|
||||||
|
description: {type: string}
|
||||||
|
permissions:
|
||||||
|
type: array
|
||||||
|
items: {type: string}
|
||||||
|
|
||||||
/roles/permissions:
|
/roles/permissions:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
@@ -75,13 +75,12 @@ class orderItemsRoute
|
|||||||
if (!$order->exists()) {
|
if (!$order->exists()) {
|
||||||
$response->error('Order not found', 404);
|
$response->error('Order not found', 404);
|
||||||
}
|
}
|
||||||
|
// Check if the user has access to the department
|
||||||
|
self::requireDepartmentAccess((string)(int)$order->department_id->value());
|
||||||
$product = (new products_o())->getProductById((int)$data['product_id']);
|
$product = (new products_o())->getProductById((int)$data['product_id']);
|
||||||
if (!$product->exists()) {
|
if (!$product->exists()) {
|
||||||
$response->error('Product not found', 404);
|
$response->error('Product not found', 404);
|
||||||
}
|
}
|
||||||
if ($product->requiresOrderItemNote() && trim((string)($notes ?? '')) === '') {
|
|
||||||
$response->error('Notes is required for this product', 400);
|
|
||||||
}
|
|
||||||
$customerRuleViolation = (new customer_product_rule_service())
|
$customerRuleViolation = (new customer_product_rule_service())
|
||||||
->firstViolationForOrderItem((int)$data['order_id'], (int)$data['product_id'], $related_item_id);
|
->firstViolationForOrderItem((int)$data['order_id'], (int)$data['product_id'], $related_item_id);
|
||||||
if ($customerRuleViolation !== null) {
|
if ($customerRuleViolation !== null) {
|
||||||
@@ -95,6 +94,9 @@ class orderItemsRoute
|
|||||||
);
|
);
|
||||||
$response->error($customerRuleViolation['message'], 400);
|
$response->error($customerRuleViolation['message'], 400);
|
||||||
}
|
}
|
||||||
|
if ($product->requiresOrderItemNote() && trim((string)($notes ?? '')) === '') {
|
||||||
|
$response->error('Notes is required for this product', 400);
|
||||||
|
}
|
||||||
|
|
||||||
// Add the order item to the order This is done individually, to make the notes to the individual order items possible
|
// Add the order item to the order This is done individually, to make the notes to the individual order items possible
|
||||||
$order_items = (new order_items_o());
|
$order_items = (new order_items_o());
|
||||||
@@ -173,18 +175,38 @@ class orderItemsRoute
|
|||||||
|
|
||||||
$this->delete('/order/items', function () {
|
$this->delete('/order/items', function () {
|
||||||
// Require the user to be logged in
|
// Require the user to be logged in
|
||||||
global $response;
|
global $response, $db;
|
||||||
$this->requirePermission('delete_order_items');
|
$this->requirePermission('delete_order_items');
|
||||||
// Get the user object
|
// Get the user object
|
||||||
$user = (new authentication())->get_user();
|
$user = (new authentication())->get_user();
|
||||||
// Check if the request was successful
|
// Check if the request was successful
|
||||||
if ($user) {
|
if ($user) {
|
||||||
// Get the query data
|
// Get the order item id from the query string or request body
|
||||||
$data = $_GET;
|
$itemIdRaw = $this->fromRequest('id');
|
||||||
// Check if the required fields are set
|
if ($itemIdRaw === null || $itemIdRaw === '') {
|
||||||
if (!isset($data['id'])) {
|
|
||||||
$response->error('Order Item ID is required', 400);
|
$response->error('Order Item ID is required', 400);
|
||||||
}
|
}
|
||||||
|
$data = ['id' => $itemIdRaw];
|
||||||
|
// Look up the order item to check department access
|
||||||
|
$itemId = (int)$data['id'];
|
||||||
|
$stmt = $db->prepare('SELECT oi.order_id FROM order_items oi WHERE oi.id = ? LIMIT 1');
|
||||||
|
if ($stmt === false) {
|
||||||
|
(new logs_o())->add('order_items', 'global', 1, 0, 'DELETE_ORDER_ITEMS', 'Database error while preparing department access check query');
|
||||||
|
$response->error('Database error while checking department access', 500);
|
||||||
|
}
|
||||||
|
$stmt->bind_param('i', $itemId);
|
||||||
|
$stmt->execute();
|
||||||
|
$orderItemRow = $stmt->get_result()->fetch_assoc();
|
||||||
|
$stmt->close();
|
||||||
|
if ($orderItemRow !== null) {
|
||||||
|
$orderForAccess = (new orders_o())->getOrderById((int)$orderItemRow['order_id']);
|
||||||
|
if (!$orderForAccess->exists()) {
|
||||||
|
$response->error('Order not found', 404);
|
||||||
|
}
|
||||||
|
self::requireDepartmentAccess((string)(int)$orderForAccess->department_id->value());
|
||||||
|
} else {
|
||||||
|
$response->error('Order item not found', 404);
|
||||||
|
}
|
||||||
// Delete the order item
|
// Delete the order item
|
||||||
(new order_items_o())->removeOrderItem((int)$data['id']);
|
(new order_items_o())->removeOrderItem((int)$data['id']);
|
||||||
// Return the list of departments
|
// Return the list of departments
|
||||||
@@ -261,6 +283,8 @@ class orderItemsRoute
|
|||||||
if (!$order->exists()) {
|
if (!$order->exists()) {
|
||||||
$response->error('Order not found', 404);
|
$response->error('Order not found', 404);
|
||||||
}
|
}
|
||||||
|
// Check if the user has access to the department
|
||||||
|
self::requireDepartmentAccess((string)(int)$order->department_id->value());
|
||||||
|
|
||||||
$canAccessAllOrderItems = $this->hasPermission('list_order_items');
|
$canAccessAllOrderItems = $this->hasPermission('list_order_items');
|
||||||
if (!$canAccessAllOrderItems && !$order->isOwnOrder((int)$user->customer_number->value())) {
|
if (!$canAccessAllOrderItems && !$order->isOwnOrder((int)$user->customer_number->value())) {
|
||||||
|
|||||||
@@ -172,6 +172,8 @@ class ordersRoute
|
|||||||
if (!(new departments_o())->getDepartmentById((int)$data['department_id'])) {
|
if (!(new departments_o())->getDepartmentById((int)$data['department_id'])) {
|
||||||
$response->error('Department not found', 400);
|
$response->error('Department not found', 400);
|
||||||
}
|
}
|
||||||
|
// Check if the user has access to the department
|
||||||
|
self::requireDepartmentAccess((string)(int)$data['department_id']);
|
||||||
// Make sure the customer number set is valid
|
// Make sure the customer number set is valid
|
||||||
$targetUser = (new users_o())->getUserByCustomerNumber((int)$data['customer_id']);
|
$targetUser = (new users_o())->getUserByCustomerNumber((int)$data['customer_id']);
|
||||||
if (!$targetUser->exists()) {
|
if (!$targetUser->exists()) {
|
||||||
@@ -472,6 +474,8 @@ class ordersRoute
|
|||||||
if (!$order->exists()) {
|
if (!$order->exists()) {
|
||||||
$response->error('Order not found', 400);
|
$response->error('Order not found', 400);
|
||||||
}
|
}
|
||||||
|
// Check if the user has access to the department
|
||||||
|
self::requireDepartmentAccess((string)(int)$order->department_id->value());
|
||||||
// Get the base64 file
|
// Get the base64 file
|
||||||
$base64_file = (string)$this->getParameter('base64_file');
|
$base64_file = (string)$this->getParameter('base64_file');
|
||||||
$attachment_store = new attachment_store();
|
$attachment_store = new attachment_store();
|
||||||
@@ -530,6 +534,8 @@ class ordersRoute
|
|||||||
if (!$order->exists()) {
|
if (!$order->exists()) {
|
||||||
$response->error('Order not found', 400);
|
$response->error('Order not found', 400);
|
||||||
}
|
}
|
||||||
|
// Check if the user has access to the department
|
||||||
|
self::requireDepartmentAccess((string)(int)$order->department_id->value());
|
||||||
// Delete the attachment
|
// Delete the attachment
|
||||||
$order->removeAttachment((int)$attachment_id);
|
$order->removeAttachment((int)$attachment_id);
|
||||||
// Log the incident
|
// Log the incident
|
||||||
@@ -568,6 +574,8 @@ class ordersRoute
|
|||||||
if (!$order->exists()) {
|
if (!$order->exists()) {
|
||||||
$response->error('Order not found', 400);
|
$response->error('Order not found', 400);
|
||||||
}
|
}
|
||||||
|
// Check if the user has access to the department
|
||||||
|
self::requireDepartmentAccess((string)(int)$order->department_id->value());
|
||||||
// Mark the order as completed
|
// Mark the order as completed
|
||||||
$order->markAsCompleted((string)$user->display_name->value());
|
$order->markAsCompleted((string)$user->display_name->value());
|
||||||
// Log the incident
|
// Log the incident
|
||||||
@@ -1154,7 +1162,8 @@ class ordersRoute
|
|||||||
}
|
}
|
||||||
// Admin/department path (requires edit_order)
|
// Admin/department path (requires edit_order)
|
||||||
self::requirePermission($permission_other);
|
self::requirePermission($permission_other);
|
||||||
/** Departmental access */
|
/** Departmental access — user must have access to the order's current department */
|
||||||
|
self::requireDepartmentAccess((string)(int)$order->department_id->value());
|
||||||
$originalCustomerNumber = (int)$order->customer_id->value();
|
$originalCustomerNumber = (int)$order->customer_id->value();
|
||||||
$newCustomerNumber = $originalCustomerNumber;
|
$newCustomerNumber = $originalCustomerNumber;
|
||||||
$shouldAutoReassignInvoiceCollection = false;
|
$shouldAutoReassignInvoiceCollection = false;
|
||||||
@@ -1219,6 +1228,8 @@ class ordersRoute
|
|||||||
if (!(new departments_o())->getDepartmentById((int)$data['department_id'])) {
|
if (!(new departments_o())->getDepartmentById((int)$data['department_id'])) {
|
||||||
$response->error('Department not found', 400);
|
$response->error('Department not found', 400);
|
||||||
}
|
}
|
||||||
|
// Check if the user has access to the target department
|
||||||
|
self::requireDepartmentAccess((string)(int)$data['department_id']);
|
||||||
$order->department_id->set((int)$data['department_id']);
|
$order->department_id->set((int)$data['department_id']);
|
||||||
}
|
}
|
||||||
// If the booking ID is set, validate it
|
// If the booking ID is set, validate it
|
||||||
@@ -1362,11 +1373,7 @@ class ordersRoute
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$user = (new authentication())->get_user();
|
$user = (new authentication())->get_user();
|
||||||
if ($user !== false && isset($user->customer_number) && (int)$user->customer_number->value() === $customerNumber) {
|
return $user !== false && isset($user->customer_number) && (int)$user->customer_number->value() === $customerNumber;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->hasDepartmentAccess((string)$departmentId);
|
|
||||||
} catch (\Throwable) {
|
} catch (\Throwable) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace routes;
|
namespace routes;
|
||||||
|
|
||||||
use classes\authentication;
|
use classes\authentication;
|
||||||
|
use classes\limited_backoffice_service;
|
||||||
use objects\groups_o;
|
use objects\groups_o;
|
||||||
use objects\logs_o;
|
use objects\logs_o;
|
||||||
use traits\route_t;
|
use traits\route_t;
|
||||||
@@ -106,6 +107,25 @@ class rolesRoute
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
self::get('/roles/limited-backoffice-permission-templates', function () {
|
||||||
|
global $response;
|
||||||
|
self::requirePermission('superuser');
|
||||||
|
self::requirePermission('add_role_permission');
|
||||||
|
$user = (new authentication())->get_user();
|
||||||
|
if ($user) {
|
||||||
|
(new logs_o())->add('roles', 'global', 1, $user->id, 'ROLES', 'User accessed limited backoffice role permission templates');
|
||||||
|
$response->success((new limited_backoffice_service())->rolePermissionTemplates());
|
||||||
|
} else {
|
||||||
|
(new logs_o())->add('roles', 'global', 0, 0, 'ROLES', 'User tried to access limited backoffice role permission templates without a valid session');
|
||||||
|
$response->error('Invalid session', 400);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
'superuser' => 'Access the superuser interface',
|
||||||
|
'add_role_permission' => 'Add a permission to a role'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
self::post('/roles/permissions', function () {
|
self::post('/roles/permissions', function () {
|
||||||
// Require the user to be logged in
|
// Require the user to be logged in
|
||||||
global $response;
|
global $response;
|
||||||
@@ -182,4 +202,4 @@ class rolesRoute
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ class usersRoute
|
|||||||
$enabled = strtolower((string)($this->fromQuery('include_limited_backoffice_employees') ?? 'false')) === 'true';
|
$enabled = strtolower((string)($this->fromQuery('include_limited_backoffice_employees') ?? 'false')) === 'true';
|
||||||
$filters = $this->fromQuery('filters');
|
$filters = $this->fromQuery('filters');
|
||||||
|
|
||||||
if (!$enabled || $filters === null || $filters === '') {
|
if ($filters === null || $filters === '') {
|
||||||
return [
|
return [
|
||||||
'enabled' => false,
|
'enabled' => false,
|
||||||
'filters' => null,
|
'filters' => null,
|
||||||
@@ -257,16 +257,31 @@ class usersRoute
|
|||||||
|| (is_array($customerNumberFilter) && in_array('0', $customerNumberFilter, true));
|
|| (is_array($customerNumberFilter) && in_array('0', $customerNumberFilter, true));
|
||||||
|
|
||||||
if (!$isEmployeeFilter) {
|
if (!$isEmployeeFilter) {
|
||||||
|
// When include mode is on but the filter is not a customer_number:0 query,
|
||||||
|
// pass the original filter through as forced filters so they are not discarded.
|
||||||
|
// When include mode is off, null causes listObjectsWithPaginationIfSet to fall
|
||||||
|
// back to reading the filters from the request, which is equivalent.
|
||||||
return [
|
return [
|
||||||
'enabled' => false,
|
'enabled' => false,
|
||||||
'filters' => $filters,
|
'filters' => $enabled ? $filters : null,
|
||||||
'additional_where' => null,
|
'additional_where' => null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($filterArray['customer_number']);
|
// $activeLimitedEmployeeSubquery is a hardcoded constant with no user input.
|
||||||
$activeLimitedEmployeeSubquery = 'SELECT `user_id` FROM `limited_backoffice_employees` WHERE `deactivated_at` IS NULL';
|
$activeLimitedEmployeeSubquery = 'SELECT `user_id` FROM `limited_backoffice_employees` WHERE `deactivated_at` IS NULL';
|
||||||
|
|
||||||
|
if (!$enabled) {
|
||||||
|
// Exclude active limited backoffice employees when the include flag is not set.
|
||||||
|
return [
|
||||||
|
'enabled' => false,
|
||||||
|
'filters' => null,
|
||||||
|
'additional_where' => '`id` NOT IN (' . $activeLimitedEmployeeSubquery . ')',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($filterArray['customer_number']);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'filters' => $filterArray === [] ? 'id:NOT ZERO' : $users->array_to_filters($filterArray),
|
'filters' => $filterArray === [] ? 'id:NOT ZERO' : $users->array_to_filters($filterArray),
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ function limited_backoffice_all_role_permissions(): array
|
|||||||
return [
|
return [
|
||||||
'user',
|
'user',
|
||||||
'permissions_list_own',
|
'permissions_list_own',
|
||||||
|
'list_departments',
|
||||||
'list_orders',
|
'list_orders',
|
||||||
'fetch_order',
|
'fetch_order',
|
||||||
'add_order',
|
'add_order',
|
||||||
@@ -71,6 +72,9 @@ function limited_backoffice_all_role_permissions(): array
|
|||||||
'department_timebookings_entries_get',
|
'department_timebookings_entries_get',
|
||||||
'department_timebookings_entries_post',
|
'department_timebookings_entries_post',
|
||||||
'department_timebookings_entries_put',
|
'department_timebookings_entries_put',
|
||||||
|
'list_department_daily_reports',
|
||||||
|
'list_notifications',
|
||||||
|
'list_own_notifications',
|
||||||
'statistics_orders_new',
|
'statistics_orders_new',
|
||||||
'statistics_bookings_new',
|
'statistics_bookings_new',
|
||||||
];
|
];
|
||||||
@@ -887,6 +891,7 @@ it('creates updates lists and deactivates scoped employees without exposing raw
|
|||||||
});
|
});
|
||||||
foreach ([
|
foreach ([
|
||||||
'list_orders',
|
'list_orders',
|
||||||
|
'list_departments',
|
||||||
'fetch_order',
|
'fetch_order',
|
||||||
'add_order',
|
'add_order',
|
||||||
'edit_order',
|
'edit_order',
|
||||||
@@ -897,19 +902,15 @@ it('creates updates lists and deactivates scoped employees without exposing raw
|
|||||||
'edit_order_items',
|
'edit_order_items',
|
||||||
'delete_order_items',
|
'delete_order_items',
|
||||||
'list_order_attachments',
|
'list_order_attachments',
|
||||||
'add_order_attachments',
|
|
||||||
'download_order_attachments',
|
|
||||||
'list_products',
|
'list_products',
|
||||||
'list_categories',
|
'list_categories',
|
||||||
'list_department_categories',
|
'list_department_categories',
|
||||||
'list_department_order_recommended',
|
'list_department_order_recommended',
|
||||||
'vehicle_product_suggestions',
|
'vehicle_product_suggestions',
|
||||||
'search_customers',
|
|
||||||
'get_user_from_customer_number',
|
'get_user_from_customer_number',
|
||||||
'list_customer_notes',
|
'list_customer_notes',
|
||||||
'add_customer_note',
|
'add_customer_note',
|
||||||
'list_customer_attributes',
|
'list_customer_attributes',
|
||||||
'search_vehicles',
|
|
||||||
'view_vehicle_status',
|
'view_vehicle_status',
|
||||||
'list_unknown_customer_vehicles',
|
'list_unknown_customer_vehicles',
|
||||||
'list_vehicle_customer_suggestions',
|
'list_vehicle_customer_suggestions',
|
||||||
@@ -932,6 +933,9 @@ it('creates updates lists and deactivates scoped employees without exposing raw
|
|||||||
'department_timebookings_entries_get',
|
'department_timebookings_entries_get',
|
||||||
'department_timebookings_entries_post',
|
'department_timebookings_entries_post',
|
||||||
'department_timebookings_entries_put',
|
'department_timebookings_entries_put',
|
||||||
|
'list_department_daily_reports',
|
||||||
|
'list_notifications',
|
||||||
|
'list_own_notifications',
|
||||||
'statistics_orders_new',
|
'statistics_orders_new',
|
||||||
'statistics_bookings_new',
|
'statistics_bookings_new',
|
||||||
'limited_backoffice_access',
|
'limited_backoffice_access',
|
||||||
@@ -1079,7 +1083,7 @@ it('creates updates lists and deactivates scoped employees without exposing raw
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('caps limited employee permissions to the manager permissions and selected departments', function (): void {
|
it('caps manager-gated limited employee permissions while keeping baseline role permissions', function (): void {
|
||||||
api_test_covers('POST /limited-backoffice/employees', 'auth');
|
api_test_covers('POST /limited-backoffice/employees', 'auth');
|
||||||
api_test_covers('GET /limited-backoffice/roles', 'auth');
|
api_test_covers('GET /limited-backoffice/roles', 'auth');
|
||||||
|
|
||||||
@@ -1092,9 +1096,11 @@ it('caps limited employee permissions to the manager permissions and selected de
|
|||||||
$rolesByKey = array_column($roles->data(), null, 'key');
|
$rolesByKey = array_column($roles->data(), null, 'key');
|
||||||
$operationsLeadGroups = array_column($rolesByKey['operations_lead']['permission_groups'] ?? [], 'capabilities', 'key');
|
$operationsLeadGroups = array_column($rolesByKey['operations_lead']['permission_groups'] ?? [], 'capabilities', 'key');
|
||||||
expect($operationsLeadGroups['account'] ?? null)->toBe(['sign_in', 'view_own_permissions']);
|
expect($operationsLeadGroups['account'] ?? null)->toBe(['sign_in', 'view_own_permissions']);
|
||||||
|
expect($operationsLeadGroups['departments'] ?? null)->toBe(['view_departments', 'view_daily_reports']);
|
||||||
expect($operationsLeadGroups['orders'] ?? null)->toBe(['view_orders']);
|
expect($operationsLeadGroups['orders'] ?? null)->toBe(['view_orders']);
|
||||||
|
expect($operationsLeadGroups['notifications'] ?? null)->toBe(['view_notifications']);
|
||||||
|
expect($operationsLeadGroups['reports'] ?? null)->toBe(['view_order_statistics', 'view_booking_statistics']);
|
||||||
expect($roles->body)->not->toContain('create_orders');
|
expect($roles->body)->not->toContain('create_orders');
|
||||||
expect($roles->body)->not->toContain('view_order_statistics');
|
|
||||||
|
|
||||||
$created = api_client()->post('/limited-backoffice/employees', [
|
$created = api_client()->post('/limited-backoffice/employees', [
|
||||||
'display_name' => 'Limited Capped Lead',
|
'display_name' => 'Limited Capped Lead',
|
||||||
@@ -1126,7 +1132,13 @@ it('caps limited employee permissions to the manager permissions and selected de
|
|||||||
->toContain('user')
|
->toContain('user')
|
||||||
->toContain('permissions_list_own')
|
->toContain('permissions_list_own')
|
||||||
->toContain('employee_public_data')
|
->toContain('employee_public_data')
|
||||||
|
->toContain('list_departments')
|
||||||
->toContain('list_orders')
|
->toContain('list_orders')
|
||||||
|
->toContain('list_department_daily_reports')
|
||||||
|
->toContain('list_notifications')
|
||||||
|
->toContain('list_own_notifications')
|
||||||
|
->toContain('statistics_orders_new')
|
||||||
|
->toContain('statistics_bookings_new')
|
||||||
->toContain('department_access_' . (int)$department['id'])
|
->toContain('department_access_' . (int)$department['id'])
|
||||||
->not->toContain('add_order')
|
->not->toContain('add_order')
|
||||||
->not->toContain('fetch_order')
|
->not->toContain('fetch_order')
|
||||||
@@ -1134,7 +1146,6 @@ it('caps limited employee permissions to the manager permissions and selected de
|
|||||||
->not->toContain('search_customers')
|
->not->toContain('search_customers')
|
||||||
->not->toContain('add_bookings')
|
->not->toContain('add_bookings')
|
||||||
->not->toContain('delete_order')
|
->not->toContain('delete_order')
|
||||||
->not->toContain('statistics_orders_new')
|
|
||||||
->not->toContain(limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES);
|
->not->toContain(limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1610,3 +1621,554 @@ it('rejects invalid limited backoffice employee contact details', function (): v
|
|||||||
->assertSuccess(false)
|
->assertSuccess(false)
|
||||||
->assertMessage('Phone number must be 4-15 digits.');
|
->assertMessage('Phone number must be 4-15 digits.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('includes list_departments in all active limited backoffice role presets', function (): void {
|
||||||
|
foreach (['cashier', 'booking_coordinator', 'operations_lead', 'department_admin'] as $roleKey) {
|
||||||
|
$permissions = limited_backoffice_role_preset_permissions($roleKey);
|
||||||
|
expect($permissions)->toContain('list_departments');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes list_department_daily_reports (dagsopgørelse) in all active limited backoffice role presets', function (): void {
|
||||||
|
foreach (['cashier', 'booking_coordinator', 'operations_lead', 'department_admin'] as $roleKey) {
|
||||||
|
$permissions = limited_backoffice_role_preset_permissions($roleKey);
|
||||||
|
expect($permissions)->toContain('list_department_daily_reports');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes list_notifications and list_own_notifications (Notifikationer) in all active limited backoffice role presets', function (): void {
|
||||||
|
foreach (['cashier', 'booking_coordinator', 'operations_lead', 'department_admin'] as $roleKey) {
|
||||||
|
$permissions = limited_backoffice_role_preset_permissions($roleKey);
|
||||||
|
expect($permissions)
|
||||||
|
->toContain('list_notifications')
|
||||||
|
->toContain('list_own_notifications');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes statistics_orders_new and statistics_bookings_new (Overblik) in all active limited backoffice role presets', function (): void {
|
||||||
|
foreach (['cashier', 'booking_coordinator', 'operations_lead', 'department_admin'] as $roleKey) {
|
||||||
|
$permissions = limited_backoffice_role_preset_permissions($roleKey);
|
||||||
|
expect($permissions)
|
||||||
|
->toContain('statistics_orders_new')
|
||||||
|
->toContain('statistics_bookings_new');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns list_departments to managed employees and enforces list_departments permission on GET /departments', function (): void {
|
||||||
|
api_test_covers('GET /departments', 'limited backoffice employee');
|
||||||
|
|
||||||
|
$department = api_fixtures()->createDepartment(['name' => 'LB Dept List Test']);
|
||||||
|
$manager = limited_backoffice_manager_session([(int)$department['id']]);
|
||||||
|
|
||||||
|
$result = api_client()->post('/limited-backoffice/employees', [
|
||||||
|
'role_key' => 'cashier',
|
||||||
|
'department_ids' => [(int)$department['id']],
|
||||||
|
'display_name' => 'Dept List Test Employee',
|
||||||
|
'email' => 'dept-list-test@example.test',
|
||||||
|
'password' => 'Secret123!',
|
||||||
|
], $manager['headers']);
|
||||||
|
|
||||||
|
$result->assertStatus(200)->assertEnvelope()->assertSuccess();
|
||||||
|
$employeeId = (int)($result->data()['id'] ?? 0);
|
||||||
|
expect($employeeId)->toBeGreaterThan(0);
|
||||||
|
limited_backoffice_cleanup_created_employee($employeeId);
|
||||||
|
|
||||||
|
$stmt = api_test_runtime()->db()->prepare(
|
||||||
|
'SELECT `managed_group_id` FROM `limited_backoffice_employees` WHERE `user_id` = ? LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmt->bind_param('i', $employeeId);
|
||||||
|
$stmt->execute();
|
||||||
|
$employeeRow = $stmt->get_result()->fetch_assoc();
|
||||||
|
$stmt->close();
|
||||||
|
$managedGroupId = (int)($employeeRow['managed_group_id'] ?? 0);
|
||||||
|
expect($managedGroupId)->toBeGreaterThan(0);
|
||||||
|
|
||||||
|
$permStmt = api_test_runtime()->db()->prepare(
|
||||||
|
'SELECT 1 FROM `groups_permissions` WHERE `group_id` = ? AND `permission` = ? LIMIT 1'
|
||||||
|
);
|
||||||
|
$listDepts = 'list_departments';
|
||||||
|
$permStmt->bind_param('is', $managedGroupId, $listDepts);
|
||||||
|
$permStmt->execute();
|
||||||
|
$permRow = $permStmt->get_result()->fetch_assoc();
|
||||||
|
$permStmt->close();
|
||||||
|
expect($permRow)->not->toBeNull('Managed employee must have list_departments permission');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns list_department_daily_reports to managed employees', function (): void {
|
||||||
|
$department = api_fixtures()->createDepartment(['name' => 'LB Daily Report Perm Test']);
|
||||||
|
$manager = limited_backoffice_manager_session([(int)$department['id']]);
|
||||||
|
|
||||||
|
$result = api_client()->post('/limited-backoffice/employees', [
|
||||||
|
'role_key' => 'cashier',
|
||||||
|
'department_ids' => [(int)$department['id']],
|
||||||
|
'display_name' => 'Daily Report Perm Employee',
|
||||||
|
'email' => 'daily-report-perm@example.test',
|
||||||
|
'password' => 'Secret123!',
|
||||||
|
], $manager['headers']);
|
||||||
|
|
||||||
|
$result->assertStatus(200)->assertEnvelope()->assertSuccess();
|
||||||
|
$employeeId = (int)($result->data()['id'] ?? 0);
|
||||||
|
expect($employeeId)->toBeGreaterThan(0);
|
||||||
|
limited_backoffice_cleanup_created_employee($employeeId);
|
||||||
|
|
||||||
|
$stmt = api_test_runtime()->db()->prepare(
|
||||||
|
'SELECT `managed_group_id` FROM `limited_backoffice_employees` WHERE `user_id` = ? LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmt->bind_param('i', $employeeId);
|
||||||
|
$stmt->execute();
|
||||||
|
$employeeRow = $stmt->get_result()->fetch_assoc();
|
||||||
|
$stmt->close();
|
||||||
|
$managedGroupId = (int)($employeeRow['managed_group_id'] ?? 0);
|
||||||
|
expect($managedGroupId)->toBeGreaterThan(0);
|
||||||
|
|
||||||
|
$permStmt = api_test_runtime()->db()->prepare(
|
||||||
|
'SELECT 1 FROM `groups_permissions` WHERE `group_id` = ? AND `permission` = ? LIMIT 1'
|
||||||
|
);
|
||||||
|
$dailyReports = 'list_department_daily_reports';
|
||||||
|
$permStmt->bind_param('is', $managedGroupId, $dailyReports);
|
||||||
|
$permStmt->execute();
|
||||||
|
$permRow = $permStmt->get_result()->fetch_assoc();
|
||||||
|
$permStmt->close();
|
||||||
|
expect($permRow)->not->toBeNull('Managed employee must have list_department_daily_reports permission');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns list_notifications and list_own_notifications to managed employees', function (): void {
|
||||||
|
$department = api_fixtures()->createDepartment(['name' => 'LB Notifications Perm Test']);
|
||||||
|
$manager = limited_backoffice_manager_session([(int)$department['id']]);
|
||||||
|
|
||||||
|
$result = api_client()->post('/limited-backoffice/employees', [
|
||||||
|
'role_key' => 'cashier',
|
||||||
|
'department_ids' => [(int)$department['id']],
|
||||||
|
'display_name' => 'Notifications Perm Employee',
|
||||||
|
'email' => 'notifications-perm@example.test',
|
||||||
|
'password' => 'Secret123!',
|
||||||
|
], $manager['headers']);
|
||||||
|
|
||||||
|
$result->assertStatus(200)->assertEnvelope()->assertSuccess();
|
||||||
|
$employeeId = (int)($result->data()['id'] ?? 0);
|
||||||
|
expect($employeeId)->toBeGreaterThan(0);
|
||||||
|
limited_backoffice_cleanup_created_employee($employeeId);
|
||||||
|
|
||||||
|
$stmt = api_test_runtime()->db()->prepare(
|
||||||
|
'SELECT `managed_group_id` FROM `limited_backoffice_employees` WHERE `user_id` = ? LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmt->bind_param('i', $employeeId);
|
||||||
|
$stmt->execute();
|
||||||
|
$employeeRow = $stmt->get_result()->fetch_assoc();
|
||||||
|
$stmt->close();
|
||||||
|
$managedGroupId = (int)($employeeRow['managed_group_id'] ?? 0);
|
||||||
|
expect($managedGroupId)->toBeGreaterThan(0);
|
||||||
|
|
||||||
|
foreach (['list_notifications', 'list_own_notifications'] as $perm) {
|
||||||
|
$stmt = api_test_runtime()->db()->prepare(
|
||||||
|
'SELECT 1 FROM `groups_permissions` WHERE `group_id` = ? AND `permission` = ? LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmt->bind_param('is', $managedGroupId, $perm);
|
||||||
|
$stmt->execute();
|
||||||
|
$permRow = $stmt->get_result()->fetch_assoc();
|
||||||
|
$stmt->close();
|
||||||
|
expect($permRow)->not->toBeNull("Managed employee must have $perm permission");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('assigns statistics permissions (Overblik) to all active limited backoffice role presets including cashier and booking_coordinator', function (): void {
|
||||||
|
$department = api_fixtures()->createDepartment(['name' => 'LB Stats Perm Test']);
|
||||||
|
$manager = limited_backoffice_manager_session([(int)$department['id']]);
|
||||||
|
|
||||||
|
foreach (['cashier', 'booking_coordinator'] as $roleKey) {
|
||||||
|
$result = api_client()->post('/limited-backoffice/employees', [
|
||||||
|
'role_key' => $roleKey,
|
||||||
|
'department_ids' => [(int)$department['id']],
|
||||||
|
'display_name' => 'Stats Perm ' . $roleKey,
|
||||||
|
'email' => 'stats-perm-' . $roleKey . '@example.test',
|
||||||
|
'password' => 'Secret123!',
|
||||||
|
], $manager['headers']);
|
||||||
|
|
||||||
|
$result->assertStatus(200)->assertEnvelope()->assertSuccess();
|
||||||
|
$employeeId = (int)($result->data()['id'] ?? 0);
|
||||||
|
expect($employeeId)->toBeGreaterThan(0);
|
||||||
|
limited_backoffice_cleanup_created_employee($employeeId);
|
||||||
|
|
||||||
|
$stmtEmp = api_test_runtime()->db()->prepare(
|
||||||
|
'SELECT `managed_group_id` FROM `limited_backoffice_employees` WHERE `user_id` = ? LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmtEmp->bind_param('i', $employeeId);
|
||||||
|
$stmtEmp->execute();
|
||||||
|
$employeeRow = $stmtEmp->get_result()->fetch_assoc();
|
||||||
|
$stmtEmp->close();
|
||||||
|
$managedGroupId = (int)($employeeRow['managed_group_id'] ?? 0);
|
||||||
|
expect($managedGroupId)->toBeGreaterThan(0);
|
||||||
|
|
||||||
|
foreach (['statistics_orders_new', 'statistics_bookings_new'] as $perm) {
|
||||||
|
$stmt = api_test_runtime()->db()->prepare(
|
||||||
|
'SELECT 1 FROM `groups_permissions` WHERE `group_id` = ? AND `permission` = ? LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmt->bind_param('is', $managedGroupId, $perm);
|
||||||
|
$stmt->execute();
|
||||||
|
$permRow = $stmt->get_result()->fetch_assoc();
|
||||||
|
$stmt->close();
|
||||||
|
expect($permRow)->not->toBeNull("$roleKey managed employee must have $perm permission");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enforces department access when creating an order via POST /orders', function (): void {
|
||||||
|
api_test_covers('POST /orders', 'department access');
|
||||||
|
|
||||||
|
$allowedDepartment = api_fixtures()->createDepartment(['name' => 'Order Create Allowed Dept']);
|
||||||
|
$deniedDepartment = api_fixtures()->createDepartment(['name' => 'Order Create Denied Dept']);
|
||||||
|
$customer = api_fixtures()->createUser(['display_name' => 'Order Create Customer']);
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession([
|
||||||
|
'add_order',
|
||||||
|
'department_access_' . (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Should succeed for accessible department
|
||||||
|
$created = api_client()->post('/orders', [
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$allowedDepartment['id'],
|
||||||
|
'reference' => 'DEPT-ACCESS-TEST',
|
||||||
|
'notes' => '',
|
||||||
|
'reg_1' => 'ABCD111',
|
||||||
|
], $session['headers']);
|
||||||
|
$created->assertStatus(200)->assertEnvelope()->assertSuccess();
|
||||||
|
|
||||||
|
// Should fail for inaccessible department
|
||||||
|
api_client()->post('/orders', [
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$deniedDepartment['id'],
|
||||||
|
'reference' => 'DEPT-ACCESS-DENIED',
|
||||||
|
'notes' => '',
|
||||||
|
'reg_1' => 'ABCD222',
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['department_access_' . (int)$deniedDepartment['id']]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enforces department access on the existing order when editing via PUT /order', function (): void {
|
||||||
|
api_test_covers('PUT /order', 'department access');
|
||||||
|
|
||||||
|
$allowedDepartment = api_fixtures()->createDepartment(['name' => 'Order Edit Allowed Dept']);
|
||||||
|
$deniedDepartment = api_fixtures()->createDepartment(['name' => 'Order Edit Denied Dept']);
|
||||||
|
$customer = api_fixtures()->createUser(['display_name' => 'Order Edit Customer']);
|
||||||
|
|
||||||
|
$orderInAllowed = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
$orderInDenied = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$deniedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession([
|
||||||
|
'edit_order',
|
||||||
|
'department_access_' . (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Should succeed editing order in accessible department
|
||||||
|
api_client()->put('/order', [
|
||||||
|
'id' => (int)$orderInAllowed['id'],
|
||||||
|
'notes' => 'updated',
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess();
|
||||||
|
|
||||||
|
// Should fail editing order in inaccessible department
|
||||||
|
api_client()->put('/order', [
|
||||||
|
'id' => (int)$orderInDenied['id'],
|
||||||
|
'notes' => 'should be denied',
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['department_access_' . (int)$deniedDepartment['id']]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enforces department access when moving an order to a new department via PUT /order', function (): void {
|
||||||
|
api_test_covers('PUT /order', 'department access move');
|
||||||
|
|
||||||
|
$allowedDepartment = api_fixtures()->createDepartment(['name' => 'Order Move Allowed Dept']);
|
||||||
|
$deniedDepartment = api_fixtures()->createDepartment(['name' => 'Order Move Denied Dept']);
|
||||||
|
$customer = api_fixtures()->createUser(['display_name' => 'Order Move Customer']);
|
||||||
|
|
||||||
|
$order = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$sessionBothDepts = api_fixtures()->createUserSession([
|
||||||
|
'edit_order',
|
||||||
|
'department_access_' . (int)$allowedDepartment['id'],
|
||||||
|
'department_access_' . (int)$deniedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$sessionOnlyAllowed = api_fixtures()->createUserSession([
|
||||||
|
'edit_order',
|
||||||
|
'department_access_' . (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Should fail when moving to inaccessible department (user only has access to allowedDepartment)
|
||||||
|
api_client()->put('/order', [
|
||||||
|
'id' => (int)$order['id'],
|
||||||
|
'department_id' => (int)$deniedDepartment['id'],
|
||||||
|
], $sessionOnlyAllowed['headers'])
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['department_access_' . (int)$deniedDepartment['id']]);
|
||||||
|
|
||||||
|
// Should succeed when user has access to both departments
|
||||||
|
api_client()->put('/order', [
|
||||||
|
'id' => (int)$order['id'],
|
||||||
|
'department_id' => (int)$deniedDepartment['id'],
|
||||||
|
], $sessionBothDepts['headers'])
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enforces department access when marking an order as completed via POST /orders/mark_as_completed', function (): void {
|
||||||
|
api_test_covers('POST /orders/mark_as_completed', 'department access');
|
||||||
|
|
||||||
|
$allowedDepartment = api_fixtures()->createDepartment(['name' => 'Order Complete Allowed Dept']);
|
||||||
|
$deniedDepartment = api_fixtures()->createDepartment(['name' => 'Order Complete Denied Dept']);
|
||||||
|
$customer = api_fixtures()->createUser(['display_name' => 'Order Complete Customer']);
|
||||||
|
|
||||||
|
$orderAllowed = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
$orderDenied = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$deniedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession([
|
||||||
|
'mark_order_as_completed',
|
||||||
|
'department_access_' . (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Should fail for inaccessible department
|
||||||
|
api_client()->post('/orders/mark_as_completed', [
|
||||||
|
'id' => (int)$orderDenied['id'],
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['department_access_' . (int)$deniedDepartment['id']]);
|
||||||
|
|
||||||
|
// Should succeed for accessible department
|
||||||
|
api_client()->post('/orders/mark_as_completed', [
|
||||||
|
'id' => (int)$orderAllowed['id'],
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enforces department access when deleting order attachments via DELETE /orders/attachments', function (): void {
|
||||||
|
api_test_covers('DELETE /orders/attachments', 'department access');
|
||||||
|
|
||||||
|
$allowedDepartment = api_fixtures()->createDepartment(['name' => 'Order Del Attach Allowed']);
|
||||||
|
$deniedDepartment = api_fixtures()->createDepartment(['name' => 'Order Del Attach Denied']);
|
||||||
|
$customer = api_fixtures()->createUser(['display_name' => 'Order Del Attach Customer']);
|
||||||
|
|
||||||
|
$orderAllowed = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
$orderDenied = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$deniedDepartment['id'],
|
||||||
|
]);
|
||||||
|
$attachmentAllowed = api_fixtures()->createOrderAttachment(['order_id' => (int)$orderAllowed['id']]);
|
||||||
|
$attachmentDenied = api_fixtures()->createOrderAttachment(['order_id' => (int)$orderDenied['id']]);
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession([
|
||||||
|
'delete_order_attachments',
|
||||||
|
'department_access_' . (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Should fail for inaccessible department
|
||||||
|
api_client()->delete('/orders/attachments', [
|
||||||
|
'order_id' => (int)$orderDenied['id'],
|
||||||
|
'attachment_id' => (int)$attachmentDenied['id'],
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['department_access_' . (int)$deniedDepartment['id']]);
|
||||||
|
|
||||||
|
// Should succeed for accessible department
|
||||||
|
api_client()->delete('/orders/attachments', [
|
||||||
|
'order_id' => (int)$orderAllowed['id'],
|
||||||
|
'attachment_id' => (int)$attachmentAllowed['id'],
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enforces department access when adding order items via POST /order/items', function (): void {
|
||||||
|
api_test_covers('POST /order/items', 'department access');
|
||||||
|
|
||||||
|
$allowedDepartment = api_fixtures()->createDepartment(['name' => 'Order Item Add Allowed']);
|
||||||
|
$deniedDepartment = api_fixtures()->createDepartment(['name' => 'Order Item Add Denied']);
|
||||||
|
$customer = api_fixtures()->createUser(['display_name' => 'Order Item Add Customer']);
|
||||||
|
$category = api_fixtures()->createCategory(['name' => 'Order Item Dept Access Category']);
|
||||||
|
$product = api_fixtures()->createProduct(['name' => 'Order Item Dept Access Product', 'category' => $category['id']]);
|
||||||
|
|
||||||
|
$orderAllowed = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
$orderDenied = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$deniedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession([
|
||||||
|
'add_order_items',
|
||||||
|
'department_access_' . (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Should fail for inaccessible department
|
||||||
|
api_client()->post('/order/items', [
|
||||||
|
'order_id' => (int)$orderDenied['id'],
|
||||||
|
'product_id' => (int)$product['id'],
|
||||||
|
'quantity' => 1,
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['department_access_' . (int)$deniedDepartment['id']]);
|
||||||
|
|
||||||
|
// Should succeed for accessible department
|
||||||
|
api_client()->post('/order/items', [
|
||||||
|
'order_id' => (int)$orderAllowed['id'],
|
||||||
|
'product_id' => (int)$product['id'],
|
||||||
|
'quantity' => 1,
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enforces department access when deleting order items via DELETE /order/items', function (): void {
|
||||||
|
api_test_covers('DELETE /order/items', 'department access');
|
||||||
|
|
||||||
|
$allowedDepartment = api_fixtures()->createDepartment(['name' => 'Order Item Del Allowed']);
|
||||||
|
$deniedDepartment = api_fixtures()->createDepartment(['name' => 'Order Item Del Denied']);
|
||||||
|
$customer = api_fixtures()->createUser(['display_name' => 'Order Item Del Customer']);
|
||||||
|
$category = api_fixtures()->createCategory(['name' => 'Order Item Del Category']);
|
||||||
|
$product = api_fixtures()->createProduct(['name' => 'Order Item Del Product', 'category' => $category['id']]);
|
||||||
|
|
||||||
|
$orderAllowed = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
$orderDenied = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$deniedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession([
|
||||||
|
'delete_order_items',
|
||||||
|
'department_access_' . (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$superuserSession = api_fixtures()->createUserSession([], ['group_id' => 1]);
|
||||||
|
$itemAllowedResponse = api_client()->post('/order/items', [
|
||||||
|
'order_id' => (int)$orderAllowed['id'],
|
||||||
|
'product_id' => (int)$product['id'],
|
||||||
|
'quantity' => 1,
|
||||||
|
], $superuserSession['headers']);
|
||||||
|
$itemAllowed = $itemAllowedResponse->data();
|
||||||
|
|
||||||
|
$itemDeniedResponse = api_client()->post('/order/items', [
|
||||||
|
'order_id' => (int)$orderDenied['id'],
|
||||||
|
'product_id' => (int)$product['id'],
|
||||||
|
'quantity' => 1,
|
||||||
|
], $superuserSession['headers']);
|
||||||
|
$itemDenied = $itemDeniedResponse->data();
|
||||||
|
|
||||||
|
// Should fail for inaccessible department
|
||||||
|
api_client()->delete('/order/items', ['id' => (int)$itemDenied['id']], $session['headers'])
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['department_access_' . (int)$deniedDepartment['id']]);
|
||||||
|
|
||||||
|
// Should succeed for accessible department
|
||||||
|
api_client()->delete('/order/items', ['id' => (int)$itemAllowed['id']], $session['headers'])
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enforces department access when editing order items via PUT /order/items', function (): void {
|
||||||
|
api_test_covers('PUT /order/items', 'department access');
|
||||||
|
|
||||||
|
$allowedDepartment = api_fixtures()->createDepartment(['name' => 'Order Item Edit Allowed']);
|
||||||
|
$deniedDepartment = api_fixtures()->createDepartment(['name' => 'Order Item Edit Denied']);
|
||||||
|
$customer = api_fixtures()->createUser(['display_name' => 'Order Item Edit Customer']);
|
||||||
|
$category = api_fixtures()->createCategory(['name' => 'Order Item Edit Category']);
|
||||||
|
$product = api_fixtures()->createProduct(['name' => 'Order Item Edit Product', 'category' => $category['id']]);
|
||||||
|
|
||||||
|
$orderAllowed = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
$orderDenied = api_fixtures()->createOrder([
|
||||||
|
'customer_id' => $customer['customer_number'],
|
||||||
|
'department_id' => (int)$deniedDepartment['id'],
|
||||||
|
]);
|
||||||
|
$itemAllowed = api_fixtures()->createOrderItem([
|
||||||
|
'order_id' => (int)$orderAllowed['id'],
|
||||||
|
'product_id' => (int)$product['id'],
|
||||||
|
'cashier_id' => 1,
|
||||||
|
'price' => 100,
|
||||||
|
]);
|
||||||
|
$itemDenied = api_fixtures()->createOrderItem([
|
||||||
|
'order_id' => (int)$orderDenied['id'],
|
||||||
|
'product_id' => (int)$product['id'],
|
||||||
|
'cashier_id' => 1,
|
||||||
|
'price' => 200,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession([
|
||||||
|
'edit_order_items',
|
||||||
|
'list_order_items',
|
||||||
|
'department_access_' . (int)$allowedDepartment['id'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Should fail for inaccessible department
|
||||||
|
api_client()->put('/order/items', [
|
||||||
|
'id' => (int)$itemDenied['id'],
|
||||||
|
'price' => 999,
|
||||||
|
'notes' => '',
|
||||||
|
'reference' => '',
|
||||||
|
'quantity' => 1,
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['department_access_' . (int)$deniedDepartment['id']]);
|
||||||
|
|
||||||
|
// Should succeed for accessible department
|
||||||
|
api_client()->put('/order/items', [
|
||||||
|
'id' => (int)$itemAllowed['id'],
|
||||||
|
'price' => 150,
|
||||||
|
'notes' => '',
|
||||||
|
'reference' => '',
|
||||||
|
'quantity' => 1,
|
||||||
|
], $session['headers'])
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess();
|
||||||
|
});
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ it('uses a product fixed price instead of the best discount when adding an order
|
|||||||
'cashier_id' => $cashier['id'],
|
'cashier_id' => $cashier['id'],
|
||||||
'reference' => 'FIXED-PRICE',
|
'reference' => 'FIXED-PRICE',
|
||||||
]);
|
]);
|
||||||
$session = api_fixtures()->createUserSession(['add_order_items', 'list_products']);
|
$session = api_fixtures()->createUserSession(['add_order_items', 'list_products', 'department_access_' . $department['id']]);
|
||||||
|
|
||||||
$productResponse = api_client()->get(
|
$productResponse = api_client()->get(
|
||||||
'/products?final_price=true&id=' . $product['id'] . '&customer_id=' . $customer['customer_number'],
|
'/products?final_price=true&id=' . $product['id'] . '&customer_id=' . $customer['customer_number'],
|
||||||
@@ -281,7 +281,7 @@ it('does not allow clearing notes for order items whose product requires notes',
|
|||||||
'quantity' => 1,
|
'quantity' => 1,
|
||||||
'notes' => 'Initial note',
|
'notes' => 'Initial note',
|
||||||
]);
|
]);
|
||||||
$session = api_fixtures()->createUserSession(['edit_order_items', 'list_order_items']);
|
$session = api_fixtures()->createUserSession(['edit_order_items', 'list_order_items', 'department_access_' . $department['id']]);
|
||||||
|
|
||||||
api_client()
|
api_client()
|
||||||
->put('/order/items', [
|
->put('/order/items', [
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ it('creates orders through the orders endpoint', function (): void {
|
|||||||
|
|
||||||
$customer = api_fixtures()->createUser(['display_name' => 'Order Create Customer']);
|
$customer = api_fixtures()->createUser(['display_name' => 'Order Create Customer']);
|
||||||
$department = api_fixtures()->createDepartment(['name' => 'Order Create Department']);
|
$department = api_fixtures()->createDepartment(['name' => 'Order Create Department']);
|
||||||
$session = api_fixtures()->createUserSession(['add_order']);
|
$session = api_fixtures()->createUserSession(['add_order', 'department_access_' . $department['id']]);
|
||||||
|
|
||||||
$response = api_client()->post('/orders', [
|
$response = api_client()->post('/orders', [
|
||||||
'customer_id' => $customer['customer_number'],
|
'customer_id' => $customer['customer_number'],
|
||||||
@@ -128,7 +128,7 @@ it('defaults order PO only from a matching active booking', function (): void {
|
|||||||
'po' => 'DELETED-BOOKING-PO',
|
'po' => 'DELETED-BOOKING-PO',
|
||||||
'deleted_at' => date('Y-m-d H:i:s'),
|
'deleted_at' => date('Y-m-d H:i:s'),
|
||||||
]);
|
]);
|
||||||
$session = api_fixtures()->createUserSession(['add_order', 'edit_order'], [
|
$session = api_fixtures()->createUserSession(['add_order', 'edit_order', 'department_access_' . $department['id']], [
|
||||||
'customer_number' => $customer['customer_number'],
|
'customer_number' => $customer['customer_number'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ it('defaults order PO only from a matching active booking', function (): void {
|
|||||||
$matchingOrderId = (int)($createResponse->data()['id'] ?? 0);
|
$matchingOrderId = (int)($createResponse->data()['id'] ?? 0);
|
||||||
expect($createResponse->data()['po'] ?? null)->toBe('MATCHING-BOOKING-PO');
|
expect($createResponse->data()['po'] ?? null)->toBe('MATCHING-BOOKING-PO');
|
||||||
|
|
||||||
$unauthorizedSession = api_fixtures()->createUserSession(['add_order'], [
|
$unauthorizedSession = api_fixtures()->createUserSession(['add_order', 'department_access_' . $department['id']], [
|
||||||
'customer_number' => $otherCustomer['customer_number'],
|
'customer_number' => $otherCustomer['customer_number'],
|
||||||
]);
|
]);
|
||||||
$unauthorizedResponse = api_client()->post('/orders', [
|
$unauthorizedResponse = api_client()->post('/orders', [
|
||||||
@@ -233,7 +233,7 @@ it('rejects invalid order creation requests', function (): void {
|
|||||||
|
|
||||||
$customer = api_fixtures()->createUser();
|
$customer = api_fixtures()->createUser();
|
||||||
$department = api_fixtures()->createDepartment();
|
$department = api_fixtures()->createDepartment();
|
||||||
$session = api_fixtures()->createUserSession(['add_order']);
|
$session = api_fixtures()->createUserSession(['add_order', 'department_access_' . $department['id']]);
|
||||||
|
|
||||||
api_client()->post('/orders', [
|
api_client()->post('/orders', [
|
||||||
'customer_id' => $customer['customer_number'],
|
'customer_id' => $customer['customer_number'],
|
||||||
@@ -262,7 +262,7 @@ it('updates orders through the primary and legacy endpoints', function (): void
|
|||||||
'notes' => 'Before update',
|
'notes' => 'Before update',
|
||||||
'reg_1' => 'BEFORE1',
|
'reg_1' => 'BEFORE1',
|
||||||
]);
|
]);
|
||||||
$session = api_fixtures()->createUserSession(['edit_order']);
|
$session = api_fixtures()->createUserSession(['edit_order', 'department_access_' . $department['id']]);
|
||||||
|
|
||||||
api_client()->put('/orders', [
|
api_client()->put('/orders', [
|
||||||
'id' => $order['id'],
|
'id' => $order['id'],
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
usesApiSuite();
|
||||||
|
|
||||||
|
it('lists limited backoffice permission templates for superuser role maintenance', function (): void {
|
||||||
|
api_test_covers('GET /roles/limited-backoffice-permission-templates', 'happy');
|
||||||
|
|
||||||
|
$session = api_fixtures()->createUserSession([
|
||||||
|
'superuser',
|
||||||
|
'add_role_permission',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = api_client()->get('/roles/limited-backoffice-permission-templates', $session['headers']);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertStatus(200)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess();
|
||||||
|
|
||||||
|
$templates = $response->data();
|
||||||
|
expect(array_column($templates, 'key'))->toBe([
|
||||||
|
'viewer',
|
||||||
|
'cashier',
|
||||||
|
'booking_coordinator',
|
||||||
|
'operations_lead',
|
||||||
|
'department_admin',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$templatesByKey = array_column($templates, null, 'key');
|
||||||
|
expect($templatesByKey['cashier']['permissions'] ?? [])->toContain('list_department_daily_reports');
|
||||||
|
expect($templatesByKey['cashier']['permissions'] ?? [])->toContain('list_notifications');
|
||||||
|
expect($templatesByKey['cashier']['permissions'] ?? [])->toContain('statistics_orders_new');
|
||||||
|
expect($templatesByKey['department_admin']['permissions'] ?? [])->toContain('limited_backoffice_access');
|
||||||
|
expect($templatesByKey['department_admin']['permissions'] ?? [])->toContain('limited_backoffice_prices_manage');
|
||||||
|
expect($templatesByKey['department_admin']['permissions'] ?? [])->toContain('limited_backoffice_employees_manage');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('requires superuser and role permission edit access for limited backoffice permission templates', function (): void {
|
||||||
|
api_test_covers('GET /roles/limited-backoffice-permission-templates', 'auth');
|
||||||
|
|
||||||
|
api_client()
|
||||||
|
->get('/roles/limited-backoffice-permission-templates', api_fixtures()->createUserSession(['add_role_permission'])['headers'])
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['superuser']);
|
||||||
|
|
||||||
|
api_client()
|
||||||
|
->get('/roles/limited-backoffice-permission-templates', api_fixtures()->createUserSession(['superuser'])['headers'])
|
||||||
|
->assertStatus(403)
|
||||||
|
->assertEnvelope()
|
||||||
|
->assertSuccess(false)
|
||||||
|
->assertMissingPermissions(['add_role_permission']);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user