Add subuser permission templates service and related tests
This commit is contained in:
@@ -21,6 +21,306 @@ class vehiclesRoute
|
||||
{
|
||||
use route_t;
|
||||
|
||||
private function routePositiveInt(string $name): int
|
||||
{
|
||||
global $response;
|
||||
|
||||
$raw = $this->fromRoute($name);
|
||||
if (!is_string($raw) || !preg_match('/^[1-9][0-9]*$/', $raw)) {
|
||||
$response->error('Invalid route parameter', 400);
|
||||
}
|
||||
|
||||
return (int)$raw;
|
||||
}
|
||||
|
||||
private function resolveSuperuserVehicleTargetUser(int $userId): array
|
||||
{
|
||||
global $response;
|
||||
|
||||
$targetUser = (new users_o())->select($userId);
|
||||
if (!$targetUser->exists()) {
|
||||
$response->error('User not found', 404);
|
||||
}
|
||||
$targetUser->getObjectProperties();
|
||||
|
||||
$customerNumber = (int)$targetUser->customer_number->value();
|
||||
if ($customerNumber <= 0) {
|
||||
$response->error('Selected user does not have a customer number', 400);
|
||||
}
|
||||
|
||||
return [
|
||||
'user_id' => (int)$targetUser->id,
|
||||
'customer_number' => $customerNumber,
|
||||
'customer_name' => (string)$targetUser->getCustomerName($customerNumber),
|
||||
];
|
||||
}
|
||||
|
||||
private function addUserScopedVehicleMeta(array $targetUser): void
|
||||
{
|
||||
global $response;
|
||||
|
||||
$response->add_meta('user_context', $targetUser);
|
||||
$response->add_meta('vehicles_summary', $this->buildVehicleSummaryForCustomer((int)$targetUser['customer_number']));
|
||||
}
|
||||
|
||||
private function buildVehiclePayload(array $vehicle): array
|
||||
{
|
||||
return [...(new customer_vehicles_o())->select((int)$vehicle['id'])->asArray()];
|
||||
}
|
||||
|
||||
private function listVehiclesForCustomer(int $customerNumber): array
|
||||
{
|
||||
$vehicles = new customer_vehicles_o();
|
||||
|
||||
return $vehicles->listObjectsWithPaginationIfSet(
|
||||
fn ($vehicle) => $this->buildVehiclePayload($vehicle),
|
||||
$vehicles->forceRestrictFilters([
|
||||
'customer_id' => [$customerNumber],
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
private function buildVehicleSummaryForCustomer(int $customerNumber): array
|
||||
{
|
||||
$rows = (new customer_vehicles_o())->getFieldsWhere([
|
||||
'customer_id' => $customerNumber,
|
||||
'deleted_at' => null,
|
||||
], [
|
||||
'id',
|
||||
'wash_subscription',
|
||||
]);
|
||||
|
||||
$summary = [
|
||||
'total' => 0,
|
||||
'wash_subscription' => 0,
|
||||
'self_service' => 0,
|
||||
];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$summary['total']++;
|
||||
if ((int)($row['wash_subscription'] ?? 0) === 1) {
|
||||
$summary['wash_subscription']++;
|
||||
}
|
||||
|
||||
try {
|
||||
$vehicle = (new customer_vehicles_o())->select((int)$row['id']);
|
||||
if ($vehicle->exists() && $vehicle->hasXLVask()) {
|
||||
$summary['self_service']++;
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// XLVask availability should not prevent the customer vehicle summary from loading.
|
||||
}
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
private function requireScopedVehicle(int $vehicleId, int $customerNumber): customer_vehicles_o
|
||||
{
|
||||
global $response;
|
||||
|
||||
$vehicle = (new customer_vehicles_o())->select($vehicleId);
|
||||
if (!$vehicle->exists()) {
|
||||
$response->error('Vehicle not found', 404);
|
||||
}
|
||||
$vehicle->getObjectProperties();
|
||||
if ((int)$vehicle->customer_id->value() !== $customerNumber) {
|
||||
$response->error('Vehicle does not belong to selected user', 404);
|
||||
}
|
||||
|
||||
return $vehicle;
|
||||
}
|
||||
|
||||
private function validateOptionalCustomerIdMatches(int $customerNumber): void
|
||||
{
|
||||
global $response;
|
||||
|
||||
if (!self::isParametersSet(['customer_id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$requestedCustomerNumber = (int)self::getParameter('customer_id');
|
||||
self::requireType($requestedCustomerNumber, self::type_int());
|
||||
if ($requestedCustomerNumber !== $customerNumber) {
|
||||
$response->error('Customer number does not match selected user', 400);
|
||||
}
|
||||
}
|
||||
|
||||
private function createVehicleForCustomer(int $customerNumber): array
|
||||
{
|
||||
global $response;
|
||||
|
||||
self::requireParameters([
|
||||
'type',
|
||||
'reg',
|
||||
]);
|
||||
$this->validateOptionalCustomerIdMatches($customerNumber);
|
||||
|
||||
$reference = null;
|
||||
if (self::isParametersSet(['reference']) && !empty(self::getParameter('reference'))) {
|
||||
$reference = (string)self::getParameter('reference');
|
||||
self::requireType($reference, self::type_string());
|
||||
self::requireMinLength('reference', 1);
|
||||
self::requireMaxLength('reference', 255);
|
||||
}
|
||||
|
||||
self::requireType(self::getParameter('reg'), self::type_string());
|
||||
self::requireType(self::getParameter('type'), self::type_int());
|
||||
$subscription = false;
|
||||
if (self::isParametersSet(['wash_subscription'])) {
|
||||
self::requireType(self::getParameter('wash_subscription'), self::type_bool());
|
||||
$subscription = (bool)self::getParameter('wash_subscription');
|
||||
}
|
||||
|
||||
$reg = trim((string)self::getParameter('reg'));
|
||||
self::requireMinLength('reg', 2);
|
||||
self::requireMaxLength('reg', 12);
|
||||
$type = (int)self::getParameter('type');
|
||||
|
||||
$vehicle = new customer_vehicles_o();
|
||||
$vehicle->add($customerNumber, $type, $reg, $subscription, $reference);
|
||||
|
||||
try {
|
||||
(new economic_v2_versioning_service())->recordVehicleSubscriptionVersion(
|
||||
[
|
||||
'vehicle_id' => (int)$vehicle->id,
|
||||
'customer_number' => $customerNumber,
|
||||
'reg' => $reg,
|
||||
'vehicle_type' => $type,
|
||||
'wash_subscription' => $subscription,
|
||||
],
|
||||
date('Y-m-d H:i:s'),
|
||||
'live.vehicle.route',
|
||||
1.0,
|
||||
false,
|
||||
[
|
||||
'route' => '/superuser/users/{user_id}/vehicles',
|
||||
'method' => 'POST',
|
||||
'actor_user_id' => (int)((new authentication())->get_user()->id ?? 0),
|
||||
]
|
||||
);
|
||||
} catch (\Throwable $exception) {
|
||||
(new logs_o())->add('vehicles', 'global', 0, (int)((new authentication())->get_user()->id ?? 0), 'ADD_VEHICLE_VERSIONING_FAILED', $exception->getMessage());
|
||||
}
|
||||
|
||||
return $vehicle->asArray();
|
||||
}
|
||||
|
||||
private function updateScopedVehicle(customer_vehicles_o $vehicle, int $customerNumber): array
|
||||
{
|
||||
global $response;
|
||||
|
||||
$this->validateOptionalCustomerIdMatches($customerNumber);
|
||||
|
||||
$beforeState = [
|
||||
'vehicle_id' => (int)$vehicle->id,
|
||||
'customer_number' => (int)$vehicle->customer_id->value(),
|
||||
'reg' => (string)$vehicle->reg->value(),
|
||||
'vehicle_type' => (int)$vehicle->type->value(),
|
||||
'wash_subscription' => (bool)$vehicle->wash_subscription->value(),
|
||||
];
|
||||
|
||||
if (self::isParametersSet(['type'])) {
|
||||
$type = (int)self::getParameter('type');
|
||||
self::requireType($type, self::type_int());
|
||||
self::requireMinValue($type, 0);
|
||||
if ($type === 0) {
|
||||
$vehicle->type->set(0);
|
||||
$vehicle->wash_subscription->set(0);
|
||||
} else {
|
||||
$product = new products_o();
|
||||
$product->select($type);
|
||||
if (!$product->exists() || !$product->subscription_allowed->value()) {
|
||||
$response->error('Invalid type', 400);
|
||||
}
|
||||
$vehicle->type->set($type);
|
||||
}
|
||||
}
|
||||
|
||||
if (self::isParametersSet(['reg'])) {
|
||||
$reg = (string)self::getParameter('reg');
|
||||
self::requireType($reg, self::type_string());
|
||||
self::requireMinLength('reg', 2);
|
||||
self::requireMaxLength('reg', 12);
|
||||
$vehicle->reg->set(preg_replace('/\s+/', '', $reg));
|
||||
}
|
||||
|
||||
if (self::isParametersSet(['wash_subscription'])) {
|
||||
$subscription = (bool)self::getParameter('wash_subscription');
|
||||
self::requireType($subscription, self::type_bool());
|
||||
if ((int)$vehicle->type->value() === 0 && $subscription) {
|
||||
$response->error('Unable to set subscription, type is not set', 400);
|
||||
}
|
||||
$vehicle->wash_subscription->set($subscription ? 1 : 0);
|
||||
}
|
||||
|
||||
if (self::isParametersSet(['reference'])) {
|
||||
if (empty(self::getParameter('reference'))) {
|
||||
$vehicle->reference->nullify();
|
||||
} else {
|
||||
$reference = (string)self::getParameter('reference');
|
||||
self::requireType($reference, self::type_string());
|
||||
self::requireMinLength('reference', 1);
|
||||
self::requireMaxLength('reference', 255);
|
||||
$vehicle->reference->set($reference);
|
||||
}
|
||||
}
|
||||
|
||||
$vehicle->objectChanged();
|
||||
|
||||
$afterState = [
|
||||
'vehicle_id' => (int)$vehicle->id,
|
||||
'customer_number' => (int)$vehicle->customer_id->value(),
|
||||
'reg' => (string)$vehicle->reg->value(),
|
||||
'vehicle_type' => (int)$vehicle->type->value(),
|
||||
'wash_subscription' => (bool)$vehicle->wash_subscription->value(),
|
||||
];
|
||||
|
||||
$versionRelevantChange = (
|
||||
(string)$beforeState['reg'] !== (string)$afterState['reg'] ||
|
||||
(int)$beforeState['vehicle_type'] !== (int)$afterState['vehicle_type'] ||
|
||||
(bool)$beforeState['wash_subscription'] !== (bool)$afterState['wash_subscription']
|
||||
);
|
||||
if ($versionRelevantChange) {
|
||||
try {
|
||||
$versioning = new economic_v2_versioning_service();
|
||||
$effectiveAt = date('Y-m-d H:i:s');
|
||||
if ((string)$beforeState['reg'] !== (string)$afterState['reg']) {
|
||||
$versioning->closeActiveVehicleSubscriptionVersion(
|
||||
(int)$beforeState['customer_number'],
|
||||
(string)$beforeState['reg'],
|
||||
$effectiveAt,
|
||||
'live.vehicle.route',
|
||||
1.0,
|
||||
false,
|
||||
[
|
||||
'route' => '/superuser/users/{user_id}/vehicles',
|
||||
'method' => 'PUT',
|
||||
'actor_user_id' => (int)((new authentication())->get_user()->id ?? 0),
|
||||
'reason' => 'identity_change',
|
||||
]
|
||||
);
|
||||
}
|
||||
$versioning->recordVehicleSubscriptionVersion(
|
||||
$afterState,
|
||||
$effectiveAt,
|
||||
'live.vehicle.route',
|
||||
1.0,
|
||||
false,
|
||||
[
|
||||
'route' => '/superuser/users/{user_id}/vehicles',
|
||||
'method' => 'PUT',
|
||||
'actor_user_id' => (int)((new authentication())->get_user()->id ?? 0),
|
||||
]
|
||||
);
|
||||
} catch (\Throwable $exception) {
|
||||
(new logs_o())->add('vehicles', 'global', 0, (int)((new authentication())->get_user()->id ?? 0), 'EDIT_VEHICLE_VERSIONING_FAILED', $exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $vehicle->asArray();
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->get('/vehicles', function () {
|
||||
@@ -119,6 +419,93 @@ class vehiclesRoute
|
||||
]
|
||||
);
|
||||
|
||||
$this->get('/superuser/users/{user_id}/vehicles', function () {
|
||||
global $response;
|
||||
$this->requirePermission('list_vehicles_other');
|
||||
$targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id'));
|
||||
$this->addUserScopedVehicleMeta($targetUser);
|
||||
$response->success($this->listVehiclesForCustomer((int)$targetUser['customer_number']));
|
||||
}, [
|
||||
'list_vehicles_other' => 'List vehicles for a selected superuser customer account.',
|
||||
]);
|
||||
|
||||
$this->get('/superuser/users/{user_id}/vehicles/summary', function () {
|
||||
global $response;
|
||||
$this->requirePermission('list_vehicles_other');
|
||||
$targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id'));
|
||||
$response->add_meta('user_context', $targetUser);
|
||||
$response->success($this->buildVehicleSummaryForCustomer((int)$targetUser['customer_number']));
|
||||
}, [
|
||||
'list_vehicles_other' => 'Summarize vehicles for a selected superuser customer account.',
|
||||
]);
|
||||
|
||||
$this->post('/superuser/users/{user_id}/vehicles', function () {
|
||||
global $response;
|
||||
$this->requirePermission('add_vehicle_other');
|
||||
$targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id'));
|
||||
$response->add_meta('user_context', $targetUser);
|
||||
$response->success($this->createVehicleForCustomer((int)$targetUser['customer_number']));
|
||||
}, [
|
||||
'add_vehicle_other' => 'Add a vehicle for a selected superuser customer account.',
|
||||
]);
|
||||
|
||||
$this->put('/superuser/users/{user_id}/vehicles', function () {
|
||||
global $response;
|
||||
$this->requirePermission('edit_vehicle_other');
|
||||
self::requireParameters(['id']);
|
||||
$targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id'));
|
||||
$vehicleId = (int)self::getParameter('id');
|
||||
self::requireType($vehicleId, self::type_int());
|
||||
self::requireMinValue($vehicleId, 1);
|
||||
$vehicle = $this->requireScopedVehicle($vehicleId, (int)$targetUser['customer_number']);
|
||||
$response->add_meta('user_context', $targetUser);
|
||||
$response->success($this->updateScopedVehicle($vehicle, (int)$targetUser['customer_number']));
|
||||
}, [
|
||||
'edit_vehicle_other' => 'Edit a vehicle for a selected superuser customer account.',
|
||||
]);
|
||||
|
||||
$this->delete('/superuser/users/{user_id}/vehicles', function () {
|
||||
global $response;
|
||||
$this->requirePermission('delete_vehicle_other');
|
||||
self::requireParameters(['id']);
|
||||
$targetUser = $this->resolveSuperuserVehicleTargetUser($this->routePositiveInt('user_id'));
|
||||
$vehicleId = (int)self::getParameter('id');
|
||||
self::requireType($vehicleId, self::type_int());
|
||||
self::requireMinValue($vehicleId, 1);
|
||||
$vehicle = $this->requireScopedVehicle($vehicleId, (int)$targetUser['customer_number']);
|
||||
|
||||
$beforeState = [
|
||||
'customer_number' => (int)$vehicle->customer_id->value(),
|
||||
'reg' => (string)$vehicle->reg->value(),
|
||||
];
|
||||
$vehicle->delete();
|
||||
try {
|
||||
(new economic_v2_versioning_service())->closeActiveVehicleSubscriptionVersion(
|
||||
(int)$beforeState['customer_number'],
|
||||
(string)$beforeState['reg'],
|
||||
date('Y-m-d H:i:s'),
|
||||
'live.vehicle.route',
|
||||
1.0,
|
||||
false,
|
||||
[
|
||||
'route' => '/superuser/users/{user_id}/vehicles',
|
||||
'method' => 'DELETE',
|
||||
'actor_user_id' => (int)((new authentication())->get_user()->id ?? 0),
|
||||
]
|
||||
);
|
||||
} catch (\Throwable $exception) {
|
||||
(new logs_o())->add('vehicles', 'global', 0, (int)((new authentication())->get_user()->id ?? 0), 'DELETE_VEHICLE_VERSIONING_FAILED', $exception->getMessage());
|
||||
}
|
||||
|
||||
$response->add_meta('user_context', $targetUser);
|
||||
$response->success([
|
||||
'success' => true,
|
||||
'message' => 'Vehicle deleted successfully',
|
||||
]);
|
||||
}, [
|
||||
'delete_vehicle_other' => 'Delete a vehicle for a selected superuser customer account.',
|
||||
]);
|
||||
|
||||
$this->post('/vehicles', function () {
|
||||
global $response;
|
||||
$auth = new authentication();
|
||||
|
||||
Reference in New Issue
Block a user