Add order bookings functionality with permissions and access control

- Introduced `order_bookings_o` class for interacting with the `order_bookings` database table.
- Added `hasDepartmentAccess` method to handle department-level permissions.
- Implemented new routes for creating and retrieving order bookings, including pagination support.
- Enhanced access control to differentiate between own and departmental bookings based on permissions.
- Updated input validation and improved error handling for order bookings.
This commit is contained in:
Jeppe Bundgaard
2025-11-05 13:57:12 +01:00
parent f206e7f4fb
commit b564d8d500
3 changed files with 202 additions and 45 deletions
@@ -0,0 +1,96 @@
<?php
namespace objects;
use classes\db;
use classes\object_property;
use Exception;
use traits\db_object_t;
class order_bookings_o extends db
{
use db_object_t;
public object_property $customer_number;
public object_property $department;
public object_property $reg_1;
public object_property $reg_2;
public object_property $reg_3;
public object_property $datetime;
public object_property $note;
public object_property $reference;
public object_property $po;
public object_property $pickup;
public object_property $items;
public object_property $created_at;
public object_property $updated_at;
public object_property $deleted_at;
public function structure(): void
{
$this->setTable('order_bookings');
}
/**
* Add an object
* @param array $data The additional data of the object (e.g. ["key" => "value"])
* @return void
* @throws Exception If the object was not created successfully
*/
public function add(
array $data,
): void
{
global /** @var db $db */
$db;
// Add the object to the database
$new_id = self::add_object($data);
self::select($new_id);
}
public function getObjectProperties(): void
{
$this->customer_number = new object_property($this->table, $this->id, 'customer_number', 'int', false);
$this->department = new object_property($this->table, $this->id, 'department', 'int', false);
$this->reg_1 = new object_property($this->table, $this->id, 'reg_1', 'string', false);
$this->reg_2 = new object_property($this->table, $this->id, 'reg_2', 'string', false);
$this->reg_3 = new object_property($this->table, $this->id, 'reg_3', 'string', false);
$this->datetime = new object_property($this->table, $this->id, 'datetime', 'timestamp', false);
$this->note = new object_property($this->table, $this->id, 'note', 'string', false);
$this->reference = new object_property($this->table, $this->id, 'reference', 'string', false);
$this->po = new object_property($this->table, $this->id, 'po', 'string', false);
$this->pickup = new object_property($this->table, $this->id, 'pickup', 'bool', false);
$this->items = new object_property($this->table, $this->id, 'items', 'json', false);
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'timestamp', false);
$this->updated_at = new object_property($this->table, $this->id, 'updated_at', 'timestamp', false);
$this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'timestamp', false);
}
public function objectChanged(): void
{
//TODO: Add cache invalidation
}
public function asArray(): array
{
return [
'id' => (int)$this->id,
'customer_number' => (int)$this->customer_number->value(),
'department' => (int)$this->department->value(),
'reg_1' => $this->reg_1->value(),
'reg_2' => $this->reg_2->value(),
'reg_3' => $this->reg_3->value(),
'datetime' => $this->datetime->value(),
'note' => $this->note->value(),
'reference' => $this->reference->value(),
'po' => $this->po->value(),
'pickup' => (bool)$this->pickup->value(),
'items' => $this->items->value(),
'created_at' => $this->created_at->value(),
'updated_at' => $this->updated_at->value(),
];
}
}
+94 -45
View File
@@ -6,6 +6,7 @@ use classes\authentication;
use Exception;
use objects\departments_o;
use objects\logs_o;
use objects\order_bookings_o;
use objects\order_items_o;
use objects\users_o;
use traits\route_t;
@@ -34,10 +35,10 @@ class orderBookingRoute
$pickup = self::getTargetPickup(); // Bool | Null
$items = self::getTargetItems(); // Array of order_items_o objects
/**
* Debug input
* Input data
*/
$response->success([
'customer_number' => $customer_number->customer_number,
$data = [
'customer_number' => (int)$customer_number->customer_number->value(),
'department' => $department->id,
'reg_1' => $reg_1,
'reg_2' => $reg_2,
@@ -48,46 +49,98 @@ class orderBookingRoute
'po' => $po,
'pickup' => $pickup,
'items' => $items,
'items_count' => count($items),
]);
exit;
//self::requirePermission('user_security_change_email');
$user = (new authentication())->get_user();
if (!$user) {
(new logs_o())->add('user_security', 'global', 0, 0, 'USER_SECURITY_CHANGE_EMAIL', 'User not logged in');
$response->error('Invalid session', 400);
}
// Require the email, and password parameters
self::requireParameters(['email', 'password']);
// Check if the email is valid
$email = (string)self::getParameter('email');
self::requireMinLength('email', 5);
self::requireMaxLength('email', 255);
self::requireType($email, self::type_string());
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
(new logs_o())->add('user_security', 'global', 0, $user->id, 'USER_SECURITY_CHANGE_EMAIL', 'Invalid email');
$response->error('Invalid email', 400);
}
// Validate the password
$password = (string)self::getParameter('password');
self::requireMinLength('password', 5);
self::requireMaxLength('password', 255);
self::requireType($password, self::type_string());
if (!$user->passwordMatches($password)) {
(new logs_o())->add('user_security', 'global', 0, $user->id, 'USER_SECURITY_CHANGE_EMAIL', 'Invalid password');
$response->error('Invalid password', 400);
} else {
// Change the email
$user->setEmail($email);
(new logs_o())->add('user_security', 'global', 0, $user->id, 'USER_SECURITY_CHANGE_EMAIL', 'Email changed');
$response->success(['message' => 'Email changed']);
}
];
/**
* Create the object
*/
$order_bookings_o = new order_bookings_o();
$order_bookings_o->add($data);
$response->success($order_bookings_o->asArray());
},
[
'user_security_change_email' => 'Change the email address of the user',
// 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 = 'list_own_bookings'; // Only permits access to bookings with the user's customer number
$permission_other = '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.',
]
);
}
/**
* @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(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 getTargetCustomer(): users_o
@@ -103,9 +156,7 @@ class orderBookingRoute
self::requireMinValue($value, 1);
self::requireMaxValue($value, 999999999);
$object = (new users_o())->getUserByCustomerNumber($value);
if (!$object->exists()) {
$response->error($error, 400);
}
if (!$object->exists()) $response->error($error, 400);
return $object;
}
@@ -125,9 +176,7 @@ class orderBookingRoute
self::requireMinValue($value, 1);
self::requireMaxValue($value, 999999999);
$object = (new departments_o())->select($value);
if (!$object->exists()) {
$response->error($error, 400);
}
if (!$object->exists()) $response->error($error, 400);
return $object;
}
+12
View File
@@ -217,6 +217,18 @@ trait route_t
self::requirePermission('department_access_' . $department . ($permission ? '_' . $permission : ''));
}
/**
* Has department access?
* @note This checks if the user has access to the department by checking if the user has the permission department_access_{department} (_{permission} if provided)
* @param string $department
* @param string|null $permission
* @return bool
*/
public function hasDepartmentAccess(string $department, string|null $permission = null): bool
{
return self::hasPermission('department_access_' . $department . ($permission ? '_' . $permission : ''));
}
/**
* Require permission
* @param string $permission