Files
api/services/nginx/app/routes/orderBookingRoute.php
T
Jeppe Bundgaard 953067cba5 Extend subuser session handling and refactor permissions in order booking routes
- Add token creation for subusers in `subusers_o` during session generation.
- Refactor `orderBookingRoute` to use dynamic subuser permission definitions with `subusers_permission_node_key`.
2026-02-12 14:01:05 +01:00

542 lines
23 KiB
PHP

<?php
namespace routes;
use classes\authentication;
use Exception;
use modules\subusers\helpers\subusers_permission_node_key;
use objects\departments_o;
use objects\logs_o;
use objects\order_bookings_o;
use objects\order_items_o;
use objects\products_o;
use objects\users_o;
use traits\route_t;
class orderBookingRoute
{
use route_t;
public function run(): void
{
$this->post('/order-bookings', function () {
// Require the user to be logged in
global $response;
/**
* Parameters
*/
$customer_number = self::getTargetCustomer(); // users_o object
$department = self::getTargetDepartment(); // Int
$reg_1 = self::getTargetReg(1); // String
$reg_2 = self::getTargetReg(2); // String | Null
$reg_3 = self::getTargetReg(3); // String | Null
$datetime = self::getTargetDateTime(); // DateTime
$note = self::getTargetNote(); // String | Null
$reference = self::getTargetReference(); // String | Null
$po = self::getTargetPo(); // String | Null
$pickup = self::getTargetPickup(); // Bool | Null
$items = self::getTargetItems(); // Array of order_items_o objects
/**
* Input data
*/
$data = [
'customer_number' => (int)$customer_number->customer_number->value(),
'department' => $department->id,
'reg_1' => $reg_1,
'reg_2' => $reg_2,
'reg_3' => $reg_3,
'datetime' => $datetime->format('Y-m-d H:i:s'),
'note' => $note,
'reference' => $reference,
'po' => $po,
'pickup' => $pickup,
'items' => $items,
];
/**
* Create the object
*/
$order_bookings_o = new order_bookings_o();
$order_bookings_o->add($data);
$response->success($order_bookings_o->asArray());
},
[
// No Permissions required.
]
);
$this->get('/order-bookings', function () {
// Require the user to be logged in
global $response;
/**
* Parameters
*/
$object = self::getTargetObject();
/**
* Authentication
*/
$user = (new authentication())->get_user();
/**
* Permissions
*/
$permission_own = self::definePermission('list_own_bookings', subusers_permission_node_key::BOOKINGS_LIST); // Only permits access to bookings with the user's customer number
$permission_other = self::definePermission('list_bookings'); // Requires access to the department as an admin to view other users' bookings
$has_permission_own = self::hasPermission($permission_own);
$has_permission_other = self::hasPermission($permission_other);
$has_permission = false; // Updated below
// Check if the user has permission to view their own bookings
if ($has_permission_own && (!$object || (int)$object->customer_number->value() === (int)$user->customer_number->value())) {
$has_permission = true;
}
// Check if the user has permission to view other users' bookings
if (!$has_permission && $has_permission_other) {
if (!empty($object)) self::requireDepartmentAccess((int)$object->department->value());
// Check if the user has access to the department
$has_permission = true;
}
// If the user does not have permission, return an error
if (!$has_permission) {
$response->error('You do not have permission to view this order booking.', 403);
}
/**
* Return the object
*/
if (!empty($object)) $response->success($object->asArray());
/**
* Return the filtered list of objects
*/
$object = new order_bookings_o(); // New object for listing
$response->success($object->listObjectsWithPaginationIfSet(
function ($booking) { return (new order_bookings_o())->select((int)$booking['id'])->asArray(); },
$object->forceRestrictFilters([
...($has_permission_other ? [
'department' => $user->getGroup()->getDepartments()
] : []),
...(!$has_permission_other ? [
'customer_number' => [(int)$user->customer_number->value()]
] : [])
])
));
},
[
'list_own_bookings' => 'Permission to list own order bookings.',
'list_bookings' => 'Permission to list department order bookings.',
]
);
$this->put('/order-bookings', function () {
// Require the user to be logged in
global $response;
/**
* Parameters
*/
$object = self::getTargetObject();
$customer_number = self::getTargetCustomer(false); // users_o object (Can only be changed by admins)
$department = self::getTargetDepartment(false); // Int
$reg_1 = self::getTargetReg(1, false); // String
$reg_2 = self::getTargetReg(2, false); // String | Null
$reg_3 = self::getTargetReg(3, false); // String | Null
$datetime = self::getTargetDateTime(false); // DateTime
$note = self::getTargetNote(false); // String | Null
$reference = self::getTargetReference(false); // String | Null
$po = self::getTargetPo(false); // String | Null
$pickup = self::getTargetPickup(false); // Bool | Null
$items = self::getTargetItems(false); // Array of order_items_o objects
$order_id = self::getTargetOrderId(false); // Int | Null
/**
* Authentication
*/
$user = (new authentication())->get_user();
/**
* Permissions
*/
if (!$object || !$object->exists()) {
$response->error('Order booking does not exist.', 400);
}
$permission_own = 'edit_own_bookings'; // Only permits editing bookings with the user's customer number
$permission_other = 'edit_bookings'; // Requires access to the department as an admin to edit other users' bookings
$has_permission_own = self::hasPermission($permission_own);
$has_permission_other = self::hasPermission($permission_other);
$has_permission = false; // Updated below
// Check if the user has permission to edit their own bookings
if ($has_permission_own && (int)$object->customer_number->value() === (int)$user->customer_number->value()) {
// Check if the booking has a transaction associated with it
// If this is the case, prevent user from editing the booking
if (!$has_permission_other && $object->hasTransaction()) {
$response->error('You cannot edit this order booking because it has a transaction associated with it.', 403);
}
$has_permission = true;
}
// Check if the user has permission to edit other users' bookings
if (!$has_permission && $has_permission_other) {
self::requireDepartmentAccess((int)$object->department->value());
// Check if the user has access to the department
$has_permission = true;
}
// If the user does not have permission, return an error
if (!$has_permission) {
$response->error('You do not have permission to edit this order booking.', 403);
}
/**
* Update the object
*/
$data = [
...(self::hasPermission($permission_other) && !empty($customer_number) ? [
'customer_number' => (int)$customer_number->customer_number->value(),
] : []),
...(isset($department) ? ['department' => $department->id] : []),
...(isset($reg_1) ? ['reg_1' => $reg_1] : []),
...(isset($reg_2) ? ['reg_2' => $reg_2] : []),
...(isset($reg_3) ? ['reg_3' => $reg_3] : []),
...(isset($datetime) ? ['datetime' => $datetime->format('Y-m-d H:i:s')] : []),
...(isset($note) ? ['note' => $note] : []),
...(isset($reference) ? ['reference' => $reference] : []),
...(isset($po) ? ['po' => $po] : []),
...(isset($pickup) ? ['pickup' => $pickup] : []),
...(isset($items) ? ['items' => $items] : []),
...(isset($order_id) ? ['order_id' => $order_id] : []),
];
$object->update($data);
/**
* Return the object
*/
$response->success($object->asArray());
},
[
'edit_bookings' => 'Permission to edit order bookings. Only applies to bookings already permitted.',
'edit_own_bookings' => 'Permission to edit own order bookings.'
]
);
$this->delete('/order-bookings', function () {
// Require the user to be logged in
global $response;
/**
* Parameters
*/
$object = self::getTargetObject();
/**
* Authentication
*/
$user = (new authentication())->get_user();
/**
* Permissions
*/
if (!$object || !$object->exists()) {
$response->error('Order booking does not exist.', 400);
}
$permission_own = 'delete_own_bookings'; // Only permits deleting bookings with the user's customer number
$permission_other = 'delete_bookings'; // Requires access to the department as an admin to delete other users' bookings
$has_permission_own = self::hasPermission($permission_own);
$has_permission_other = self::hasPermission($permission_other);
$has_permission = false; // Updated below
// Check if the user has permission to delete their own bookings
if ($has_permission_own && (int)$object->customer_number->value() === (int)$user->customer_number->value()) {
$has_permission = true;
}
// Check if the user has permission to delete other users' bookings
if (!$has_permission && $has_permission_other) {
self::requireDepartmentAccess((int)$object->department->value());
// Check if the user has access to the department
$has_permission = true;
}
// If the user does not have permission, return an error
if (!$has_permission) {
$response->error('You do not have permission to delete this order booking.', 403);
}
/**
* Delete the object
*/
$object->delete();
/**
* Return success
*/
$response->success(['message' => 'Order booking deleted successfully.']);
},
[
'delete_bookings' => 'Permission to delete order bookings. Only applies to bookings already permitted.',
'delete_own_bookings' => 'Permission to delete own order bookings. Only applies to uncompleted bookings.'
]
);
$this->post('/order-bookings/complete', function () {
// Require the user to be logged in
global $response;
/**
* Parameters
*/
$object = self::getTargetObject();
$safetySeal = self::getSafetySeal(false); // Int | Null
/**
* Authentication
*/
$user = (new authentication())->get_user();
/**
* Permissions
*/
self::requireDepartmentAccess((int)$object->department->value());
if (!$object || !$object->exists()) {
$response->error('Order booking does not exist.', 400);
}
/**
* Complete the booking
*/
$object->completeBooking((int)$user->id, $safetySeal);
/**
* Return the object
*/
$response->success($object->asArray());
},
[
'complete_bookings' => 'Permission to complete order bookings.'
]
);
}
/**
* @throws Exception
*/
private function getTargetObject(): order_bookings_o|null
{
global $response;
$parameter = 'id';
$error = 'Invalid ' . $parameter;
if (!$this->isParametersSet([$parameter])) return null;
self::requireParameters(['id']);
self::requireType((int)self::getParameter($parameter), self::type_int());
self::requireMinLength($parameter, 1);
self::requireMaxLength($parameter, 9);
$value = (int)self::getParameter($parameter);
self::requireMinValue($value, 1);
self::requireMaxValue($value, 999999999);
$object = (new order_bookings_o())->select($value);
if (!$object->exists()) $response->error($error, 400);
return $object;
}
private function getSafetySeal(bool $required = true): int|null
{
global $response;
$parameter = 'safety_seal';
$error = 'Invalid safety seal';
if (!$required && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
if ($required) {
self::requireType(self::getParameter($parameter), self::type_int());
} else {
self::requireTypeIn(self::getParameter($parameter), [self::type_int(), self::type_null()]);
// Check if the value is null
if ($this->getParameter($parameter) === null) return null;
}
self::requireMinLength($parameter, 1);
self::requireMaxLength($parameter, 9);
$value = (int)self::getParameter($parameter);
self::requireMinValue($value, 1);
self::requireMaxValue($value, 999999999);
return $value;
}
private function getTargetOrderId(bool $required = true): int|string|null
{
global $response;
$parameter = 'order_id';
$error = 'Invalid order ID';
if (!$required && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
if ($required) {
self::requireType(self::getParameter($parameter), self::type_int());
} else {
self::requireTypeIn(self::getParameter($parameter), [self::type_int(), self::type_null()]);
// Check if the value is null
if ($this->getParameter($parameter) === null) return "null";
}
self::requireMinLength($parameter, 1);
self::requireMaxLength($parameter, 9);
$value = (int)self::getParameter($parameter);
self::requireMinValue($value, 1);
self::requireMaxValue($value, 999999999);
return $value;
}
private function getTargetCustomer(bool $required = true): users_o|null
{
global $response;
$parameter = 'customer_number';
$error = 'Invalid customer number';
if (!$required && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
self::requireType(self::getParameter($parameter), self::type_int());
self::requireMinLength($parameter, 2);
self::requireMaxLength($parameter, 9);
$value = (int)self::getParameter($parameter);
self::requireMinValue($value, 1);
self::requireMaxValue($value, 999999999);
$object = (new users_o())->getUserByCustomerNumber($value);
if (!$object->exists()) $response->error($error, 400);
return $object;
}
/**
* @throws Exception If the Department is invalid.
*/
private function getTargetDepartment(bool $required = true): departments_o|null
{
global $response;
$parameter = 'department';
$error = 'Invalid department';
if (!$required && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
self::requireType(self::getParameter($parameter), self::type_int());
self::requireMinLength($parameter, 1);
self::requireMaxLength($parameter, 9);
$value = (int)self::getParameter($parameter);
self::requireMinValue($value, 1);
self::requireMaxValue($value, 999999999);
$object = (new departments_o())->select($value);
if (!$object->exists()) $response->error($error, 400);
return $object;
}
private function getTargetReg(int $index = 1, bool $required = true): string|null
{
global $response;
$parameter = 'reg_' . $index;
$error = 'Invalid ' . $parameter;
$requiredParam = $required && $index === 1;
if (!$requiredParam && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
if ($requiredParam) {
self::requireType(self::getParameter($parameter), self::type_string());
} else {
self::requireTypeIn(self::getParameter($parameter), [self::type_string(), self::type_null()]);
// Check if the value is null
if ($this->getParameter($parameter) === null) return "null";
}
self::requireMinLength($parameter, 0);
self::requireMaxLength($parameter, 12);
$value = self::getParameter($parameter);
return self::cleanReg($value);
}
private function getTargetDateTime(bool $required = true): \DateTime|null
{
global $response;
$parameter = 'datetime';
$error = 'Invalid datetime';
if (!$required && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
self::requireType(self::getParameter($parameter), self::type_string());
self::requireMinLength($parameter, 10);
self::requireMaxLength($parameter, 25);
$value = (string)self::getParameter($parameter);
$fromFormat = strlen($value) === 10 ? 'Y-m-d' : 'Y-m-d H:i:s';
$datetime = \DateTime::createFromFormat($fromFormat, $value);
if (!$datetime) {
$response->error($error, 400);
}
return $datetime;
}
private function getTargetNote(bool $required = false): string|null
{
$parameter = 'note';
if (!$required && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
self::requireTypeIn(self::getParameter($parameter), [self::type_string(), self::type_null()]);
if (self::getParameter($parameter) === null) return null;
self::requireMinLength($parameter, 0);
self::requireMaxLength($parameter, 1000);
return (string)self::getParameter($parameter);
}
private function getTargetReference(bool $required = false): string|null
{
$parameter = 'reference';
if (!$required && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
self::requireTypeIn(self::getParameter($parameter), [self::type_string(), self::type_null()]);
if (self::getParameter($parameter) === null) return null;
self::requireMinLength($parameter, 0);
self::requireMaxLength($parameter, 100);
return (string)self::getParameter($parameter);
}
private function getTargetPo(bool $required = false): string|null
{
$parameter = 'po';
if (!$required && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
self::requireTypeIn(self::getParameter($parameter), [self::type_string(), self::type_null()]);
if (self::getParameter($parameter) === null) return null;
self::requireMinLength($parameter, 0);
self::requireMaxLength($parameter, 100);
return (string)self::getParameter($parameter);
}
private function getTargetPickup(bool $required = false): bool|null
{
$parameter = 'pickup';
if (!$required && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
self::requireType(self::getParameter($parameter), self::type_bool());
return (bool)self::getParameter($parameter);
}
/**
* @throws Exception
*/
private function getTargetItems(bool $required = true): array|null {
global $response;
$parameter = 'items';
$error = 'Invalid items';
if (!$required && !$this->isParametersSet([$parameter])) return null;
self::requireParameters([$parameter]);
self::requireType(self::getParameter($parameter), self::type_array());
$items = (array)self::getParameter($parameter);
if (count($items) === 0) {
$response->error($error, 400);
}
// Verify items
foreach ($items as $key => $item) {
self::requireType($item, self::type_array());
self::requireValidItem((array)$item, $key);
$items[$key]['name'] = (new products_o())->select((int)$item['id'])->name->value();
}
return $items;
}
private function cleanReg(string $reg): string
{
// Remove all non-alphanumeric characters
return strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $reg));
}
private function requireValidItem(array $item, int $index): void
{
global $response;
/**
* {
* "id": 22,
* "name": "Double Duty - Kemi",
* "description": " ",
* "price": 239,
* "subscription_allowed": 0,
* "category": "4",
* "piktogram": "",
* "economic_product_id": "24",
* "apply_category_discount": false,
* "requires_note": true,
* "is_wash": false,
* "display_in_booking_form": true,
* "order_priority": 1021,
* "created_at": "2024-12-09 14:31:49",
* "updated_at": "2025-10-28 13:55:58",
* "quantity": 4
* }
*/
// Require id field
if (!isset($item['id']) || !is_int($item['id']) || $item['id'] < 1) {
$response->error('Invalid item id at index ' . $index, 400);
}
// Require quantity field
if (!isset($item['quantity']) || !is_int($item['quantity']) || $item['quantity'] < 1) {
$response->error('Invalid item quantity at index ' . $index, 400);
}
}
}