1498 lines
65 KiB
PHP
1498 lines
65 KiB
PHP
<?php
|
|
|
|
namespace routes;
|
|
|
|
use classes\authentication;
|
|
use classes\economic_v2_versioning_service;
|
|
use customers\economic_customer_mo;
|
|
use objects\bookings_o;
|
|
use objects\customer_vehicles_o;
|
|
use objects\departments_o;
|
|
use objects\logs_o;
|
|
use objects\order_bookings_o;
|
|
use objects\orders_o;
|
|
use objects\plate_scans_o;
|
|
use objects\products_o;
|
|
use objects\users_o;
|
|
use traits\route_t;
|
|
use modules\subusers\helpers\subusers_permission_node_key;
|
|
|
|
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
|
|
{
|
|
$vehicles = new customer_vehicles_o();
|
|
$filters = [
|
|
'customer_id' => $customerNumber,
|
|
];
|
|
if ($vehicles->columnsExist(['deleted_at'])) {
|
|
$filters['deleted_at'] = null;
|
|
}
|
|
|
|
$rows = $vehicles->getFieldsWhere($filters, [
|
|
'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 () {
|
|
global $response;
|
|
$auth = new authentication();
|
|
$user = $auth->get_user();
|
|
// Define permissions with subuser node linkage
|
|
$permission_own = self::definePermission('list_own_vehicles', subusers_permission_node_key::VEHICLES_LIST);
|
|
$permission_other = self::definePermission('list_vehicles_other');
|
|
$has_permission_other = self::hasPermission($permission_other);
|
|
|
|
// If a specific ID is requested, validate access against that vehicle's customer context
|
|
if ($this->isParametersSet(['id'])) {
|
|
$id = (int)$this->getParameter('id');
|
|
$this->requireType($id, self::type_int());
|
|
$this->requireMinValue($id, 1);
|
|
$this->requireMaxValue($id, 9999999999);
|
|
$vehicle = (new customer_vehicles_o())->select($id);
|
|
if (!$vehicle->exists()) {
|
|
(new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'LIST_OWN_VEHICLES', 'Vehicle not found');
|
|
$response->error('Vehicle not found', 404);
|
|
}
|
|
$targetCustomer = (int)$vehicle->customer_id->value();
|
|
self::allowOwnOrDepartmentAccess(
|
|
$permission_own,
|
|
$permission_other,
|
|
$targetCustomer,
|
|
null,
|
|
null,
|
|
'You do not have permission to view this vehicle.'
|
|
);
|
|
$response->success([...$vehicle->asArray()]);
|
|
}
|
|
|
|
// Listing: restrict to effective customer when lacking the broader permission
|
|
$vehicles_o = new customer_vehicles_o();
|
|
$effectiveCustomer = self::resolveEffectiveCustomerNumber();
|
|
$permitted = self::allowOwnOrDepartmentAccess(
|
|
$permission_own,
|
|
$permission_other,
|
|
$effectiveCustomer,
|
|
null,
|
|
null,
|
|
'You do not have permission to view vehicles.'
|
|
);
|
|
if (!$permitted) {
|
|
$response->error('You do not have permission to view vehicles.');
|
|
}
|
|
$response->success(
|
|
$vehicles_o->listObjectsWithPaginationIfSet(
|
|
function ($vehicle) {
|
|
return [...(new customer_vehicles_o())->select($vehicle['id'])->asArray()];
|
|
},
|
|
$vehicles_o->forceRestrictFilters([
|
|
...(!$has_permission_other && $effectiveCustomer !== null ? [
|
|
'customer_id' => [(int)$effectiveCustomer]
|
|
] : [])
|
|
])
|
|
)
|
|
);
|
|
},
|
|
[
|
|
'list_own_vehicles' => 'List own vehicles',
|
|
'list_vehicles_other' => 'List other users vehicles',
|
|
]
|
|
);
|
|
|
|
$this->get('/department/vehicles/unknown-customer', function () {
|
|
// Require the user to be logged in
|
|
global $response;
|
|
$this->requirePermission('list_unknown_customer_vehicles');
|
|
// Get the user object
|
|
$user = (new authentication())->get_user();
|
|
// Check if the request was successful
|
|
if ($user) {
|
|
// Log the incident
|
|
(new logs_o())->add('vehicles', 'global', 1, $user->id, 'LIST_UNKNOWN_CUSTOMER_VEHICLES', 'Successfully listed unknown customer vehicles');
|
|
// Return the list of the user's vehicles
|
|
$vehicles_o = new orders_o();
|
|
$vehicles_o->setView('unique_order_reg_1');
|
|
$response->success($vehicles_o
|
|
->setSearchableFields([
|
|
'reg_1'
|
|
])
|
|
->listObjectsWithPaginationIfSet()
|
|
);
|
|
} else {
|
|
// Log the incident
|
|
(new logs_o())->add('vehicles', 'global', 1, 0, 'LIST_UNKNOWN_CUSTOMER_VEHICLES', 'No user found, or invalid session');
|
|
// Return an error
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_unknown_customer_vehicles' => 'List unknown customer vehicles',
|
|
]
|
|
);
|
|
|
|
$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();
|
|
$user = $auth->get_user();
|
|
|
|
// Define permissions with subuser node linkage
|
|
$permission_own = self::definePermission('add_vehicle', subusers_permission_node_key::VEHICLES_ADD);
|
|
$permission_other = self::definePermission('add_vehicle_other');
|
|
|
|
// Require the parameters
|
|
self::requireParameters([
|
|
'type',
|
|
'reg',
|
|
]);
|
|
|
|
// Determine target customer
|
|
$targetCustomer = self::isParametersSet(['customer_id'])
|
|
? (int)self::getParameter('customer_id')
|
|
: (int)(self::resolveEffectiveCustomerNumber() ?? 0);
|
|
self::requireType($targetCustomer, self::type_int());
|
|
self::requireMinValue($targetCustomer, 1);
|
|
self::requireMaxValue($targetCustomer, 9999999999);
|
|
|
|
// Enforce access (own vs broader)
|
|
self::allowOwnOrDepartmentAccess(
|
|
$permission_own,
|
|
$permission_other,
|
|
$targetCustomer,
|
|
null,
|
|
null,
|
|
'You are not allowed to add vehicles to this customer'
|
|
);
|
|
|
|
$reference = null;
|
|
if (self::isParametersSet(['reference'])) {
|
|
if (!empty(self::getParameter('reference'))) {
|
|
$reference = (string)self::getParameter('reference');
|
|
self::requireType($reference, self::type_string());
|
|
self::requireMinLength('reference', 1);
|
|
self::requireMaxLength('reference', 255);
|
|
}
|
|
}
|
|
// Validate the parameters
|
|
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');
|
|
}
|
|
// Get the parameters
|
|
$reg = (string)self::getParameter('reg');
|
|
$type = (int)self::getParameter('type');
|
|
$reg = trim($reg);
|
|
|
|
// Create a new vehicle
|
|
$vehicle = new customer_vehicles_o();
|
|
$vehicle->add(
|
|
$targetCustomer,
|
|
$type,
|
|
$reg,
|
|
$subscription ? 1 : 0,
|
|
$reference
|
|
);
|
|
try {
|
|
(new economic_v2_versioning_service())->recordVehicleSubscriptionVersion(
|
|
[
|
|
'vehicle_id' => (int)$vehicle->id,
|
|
'customer_number' => (int)$targetCustomer,
|
|
'reg' => (string)$reg,
|
|
'vehicle_type' => (int)$type,
|
|
'wash_subscription' => (bool)$subscription,
|
|
],
|
|
date('Y-m-d H:i:s'),
|
|
'live.vehicle.route',
|
|
1.0,
|
|
false,
|
|
[
|
|
'route' => '/vehicles',
|
|
'method' => 'POST',
|
|
'actor_user_id' => (int)($user->id ?? 0),
|
|
]
|
|
);
|
|
} catch (\Throwable $e) {
|
|
(new logs_o())->add('vehicles', 'global', 0, (int)($user->id ?? 0), 'ADD_VEHICLE_VERSIONING_FAILED', $e->getMessage());
|
|
}
|
|
|
|
(new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'ADD_VEHICLE', 'Successfully added vehicle');
|
|
$response->success($vehicle->asArray());
|
|
},
|
|
[
|
|
'add_vehicle' => 'Add a vehicle to own vehicles',
|
|
'add_vehicle_other' => 'Add a vehicle to another users vehicles',
|
|
]
|
|
);
|
|
|
|
$this->put('/vehicles', function () {
|
|
global $response;
|
|
$auth = new authentication();
|
|
$user = $auth->get_user();
|
|
$permission_own = self::definePermission('edit_vehicle', subusers_permission_node_key::VEHICLES_EDIT);
|
|
$permission_other = self::definePermission('edit_vehicle_other');
|
|
// Get the request data
|
|
self::requireParameters(['id']);
|
|
$id = (int)self::getParameter('id');
|
|
$vehicle = (new customer_vehicles_o())->select($id);
|
|
if (!$vehicle->exists()) {
|
|
(new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'EDIT_VEHICLE', 'Vehicle not found');
|
|
$response->error('Vehicle not found', 404);
|
|
}
|
|
|
|
$before_state = [
|
|
'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(),
|
|
];
|
|
// Enforce access (own vs broader)
|
|
self::allowOwnOrDepartmentAccess(
|
|
$permission_own,
|
|
$permission_other,
|
|
(int)$vehicle->customer_id->value(),
|
|
null,
|
|
null,
|
|
'You are not allowed to edit vehicles from other users'
|
|
);
|
|
if (self::isParametersSet(['customer_id'])) {
|
|
$new_customer_number = (int)self::getParameter('customer_id');
|
|
self::requireType($new_customer_number, self::type_int());
|
|
self::requireMinValue($new_customer_number, 1);
|
|
self::requireMaxValue($new_customer_number, 9999999999);
|
|
// Require access for the destination customer context as well.
|
|
self::allowOwnOrDepartmentAccess(
|
|
$permission_own,
|
|
$permission_other,
|
|
$new_customer_number,
|
|
null,
|
|
null,
|
|
'You are not allowed to move vehicles to this customer'
|
|
);
|
|
$vehicle->customer_id->set($new_customer_number);
|
|
}
|
|
// Check all the fields, and if they are set, validate and set them
|
|
if (self::isParametersSet(['type'])) {
|
|
$type = (int)self::getParameter('type');
|
|
// Make sure the type is an integer
|
|
self::requireType($type, self::type_int());
|
|
self::requireMinValue($type, 0);
|
|
// Make sure the type is a valid type (If it isn't 0)
|
|
if ($type === 0) {
|
|
// Set the type to null
|
|
$vehicle->type->set(0);
|
|
// Turn off the subscription
|
|
$vehicle->wash_subscription->set(0);
|
|
} else {
|
|
$products_o = new products_o();
|
|
$products_o->select((int)$type);
|
|
if (!$products_o->exists() || !$products_o->subscription_allowed->value()) {
|
|
// Log the incident
|
|
(new logs_o())->add('vehicles', 'global', 1, $user->id, 'EDIT_VEHICLE', 'Invalid type');
|
|
// Return an error
|
|
$response->error('Invalid type', 400);
|
|
}
|
|
// Set the type
|
|
$vehicle->type->set(
|
|
(int)$type
|
|
);
|
|
// TODO: Make the potential vehicleTypeId reflect the correct type.
|
|
// It's currently possible to set the vehicleTypeId to a type that is not allowed for the vehicle.
|
|
}
|
|
}
|
|
if (self::isParametersSet(['reg'])) {
|
|
$reg = (string)self::getParameter('reg');
|
|
self::requireType($reg, self::type_string());
|
|
self::requireMinLength('reg', 2);
|
|
self::requireMaxLength('reg', 12);
|
|
// Strip the registration number of whitespace
|
|
$reg = preg_replace('/\s+/', '', $reg);
|
|
// Set the registration number
|
|
$vehicle->reg->set($reg);
|
|
}
|
|
if (self::isParametersSet(['wash_subscription'])) {
|
|
$subscription = (bool)self::getParameter('wash_subscription');
|
|
self::requireType($subscription, self::type_bool());
|
|
// Check if the type is a valid type (If it isn't 0)
|
|
if ((int)$vehicle->type->value() === 0 && $subscription) {
|
|
// Set the subscription to null
|
|
$response->error('Unable to set subscription, type is not set', 400);
|
|
}
|
|
// Set the wash subscription
|
|
$vehicle->wash_subscription->set($subscription ? 1 : 0);
|
|
}
|
|
if (self::isParametersSet(['reference'])) {
|
|
// Check if the reference is set to null/empty
|
|
if (empty(self::getParameter('reference'))) {
|
|
// Set the reference to null
|
|
$vehicle->reference->nullify();
|
|
} else {
|
|
// Check if the reference is valid
|
|
$reference = (string)self::getParameter('reference');
|
|
self::requireType($reference, self::type_string());
|
|
self::requireMinLength('reference', 1);
|
|
self::requireMaxLength('reference', 255);
|
|
// Set the reference
|
|
$vehicle->reference->set($reference);
|
|
}
|
|
}
|
|
$vehicle->objectChanged();
|
|
|
|
$after_state = [
|
|
'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(),
|
|
];
|
|
|
|
$version_relevant_change = (
|
|
(int)$before_state['customer_number'] !== (int)$after_state['customer_number'] ||
|
|
(string)$before_state['reg'] !== (string)$after_state['reg'] ||
|
|
(int)$before_state['vehicle_type'] !== (int)$after_state['vehicle_type'] ||
|
|
(bool)$before_state['wash_subscription'] !== (bool)$after_state['wash_subscription']
|
|
);
|
|
if ($version_relevant_change) {
|
|
try {
|
|
$versioning = new economic_v2_versioning_service();
|
|
$effective_at = date('Y-m-d H:i:s');
|
|
$identity_changed = (
|
|
(int)$before_state['customer_number'] !== (int)$after_state['customer_number'] ||
|
|
(string)$before_state['reg'] !== (string)$after_state['reg']
|
|
);
|
|
if ($identity_changed) {
|
|
$versioning->closeActiveVehicleSubscriptionVersion(
|
|
(int)$before_state['customer_number'],
|
|
(string)$before_state['reg'],
|
|
$effective_at,
|
|
'live.vehicle.route',
|
|
1.0,
|
|
false,
|
|
[
|
|
'route' => '/vehicles',
|
|
'method' => 'PUT',
|
|
'actor_user_id' => (int)($user->id ?? 0),
|
|
'reason' => 'identity_change',
|
|
]
|
|
);
|
|
}
|
|
$versioning->recordVehicleSubscriptionVersion(
|
|
$after_state,
|
|
$effective_at,
|
|
'live.vehicle.route',
|
|
1.0,
|
|
false,
|
|
[
|
|
'route' => '/vehicles',
|
|
'method' => 'PUT',
|
|
'actor_user_id' => (int)($user->id ?? 0),
|
|
]
|
|
);
|
|
} catch (\Throwable $e) {
|
|
(new logs_o())->add('vehicles', 'global', 0, (int)($user->id ?? 0), 'EDIT_VEHICLE_VERSIONING_FAILED', $e->getMessage());
|
|
}
|
|
}
|
|
// Return the vehicle
|
|
$response->success($vehicle->asArray());
|
|
},
|
|
[
|
|
'edit_vehicle' => 'Edit a vehicle',
|
|
'edit_vehicle_other' => 'Edit a vehicle from another user'
|
|
]
|
|
);
|
|
|
|
$this->delete('/vehicles', function () {
|
|
global $response;
|
|
$auth = new authentication();
|
|
$user = $auth->get_user();
|
|
$permission_own = self::definePermission('delete_vehicle', subusers_permission_node_key::VEHICLES_DELETE);
|
|
$permission_other = self::definePermission('delete_vehicle_other');
|
|
// Get the request data
|
|
self::requireParameters(['id']);
|
|
$id = (int)self::getParameter('id');
|
|
$vehicle = (new customer_vehicles_o())->select($id);
|
|
if (!$vehicle->exists()) {
|
|
(new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'DELETE_VEHICLE', 'Vehicle not found');
|
|
$response->error('Vehicle not found', 404);
|
|
}
|
|
|
|
$before_state = [
|
|
'customer_number' => (int)$vehicle->customer_id->value(),
|
|
'reg' => (string)$vehicle->reg->value(),
|
|
];
|
|
// Enforce access (own vs broader)
|
|
self::allowOwnOrDepartmentAccess(
|
|
$permission_own,
|
|
$permission_other,
|
|
(int)$vehicle->customer_id->value(),
|
|
null,
|
|
null,
|
|
'You are not allowed to delete vehicles from other users'
|
|
);
|
|
// Delete the vehicle
|
|
$vehicle->delete();
|
|
try {
|
|
(new economic_v2_versioning_service())->closeActiveVehicleSubscriptionVersion(
|
|
(int)$before_state['customer_number'],
|
|
(string)$before_state['reg'],
|
|
date('Y-m-d H:i:s'),
|
|
'live.vehicle.route',
|
|
1.0,
|
|
false,
|
|
[
|
|
'route' => '/vehicles',
|
|
'method' => 'DELETE',
|
|
'actor_user_id' => (int)($user->id ?? 0),
|
|
]
|
|
);
|
|
} catch (\Throwable $e) {
|
|
(new logs_o())->add('vehicles', 'global', 0, (int)($user->id ?? 0), 'DELETE_VEHICLE_VERSIONING_FAILED', $e->getMessage());
|
|
}
|
|
$response->success([
|
|
'success' => true,
|
|
'message' => 'Vehicle deleted successfully'
|
|
]);
|
|
},
|
|
[
|
|
'delete_vehicle' => 'Delete a vehicle',
|
|
'delete_vehicle_other' => 'Remove (delete) a vehicle from another user'
|
|
]
|
|
);
|
|
|
|
$this->get('/department/vehicle/customer-suggestions', function () {
|
|
// Require the user to be logged in
|
|
global $response;
|
|
$this->requirePermission('list_vehicle_customer_suggestions');
|
|
// Get the user object
|
|
$user = (new authentication())->get_user();
|
|
// Check if the request was successful
|
|
if ($user) {
|
|
self::requireParameters(['reg_1']);
|
|
self::requireType((string)self::getParameter('reg_1'), self::type_string());
|
|
$plate = (string)self::getParameter('reg_1');
|
|
self::requireMinLength('reg_1', 1);
|
|
self::requireMaxLength('reg_1', 12);
|
|
// Return the list of the user's vehicles
|
|
$orders_o = new orders_o();
|
|
// Check if the user is allowed to list other user's vehicles
|
|
// Get all the customer_ids that has orders with the same plate
|
|
$customer_ids = $orders_o->getFieldsWhere([
|
|
'reg_1' => $plate,
|
|
'deleted_at' => null,
|
|
], [
|
|
'customer_id',
|
|
]);
|
|
// Get all the customer_ids that has orders with the same plate
|
|
$customer_ids = array_map(function ($customer_id) {
|
|
return (int)$customer_id['customer_id'];
|
|
}, $customer_ids);
|
|
// Remove duplicates
|
|
$customer_ids = array_unique($customer_ids);
|
|
// Get all the customers that has orders with the same plate
|
|
$customers = [];
|
|
foreach ( $customer_ids as $customer_id ) {
|
|
$customers[] = (new users_o())->getUserByCustomerNumber($customer_id);
|
|
}
|
|
// Log the incident
|
|
(new logs_o())->add('vehicles', 'global', 1, $user->id, 'LIST_VEHICLE_CUSTOMER_SUGGESTIONS', 'Successfully listed customer suggestions for a vehicle');
|
|
// Format the response
|
|
$response->success(
|
|
array_map(function ($customer) {
|
|
/** @var users_o $customer */
|
|
$customer_number = (int)$customer->customer_number->value();
|
|
return [
|
|
'id' => (int)$customer->id,
|
|
'customer_name' => (string)$customer->getCustomerName((int)$customer_number),
|
|
'customer_number' => (int)$customer_number,
|
|
'barred' => (new economic_customer_mo())->getCustomerByCustomerNumber((int)$customer_number)->asArray()['barred'],
|
|
];
|
|
}, $customers)
|
|
);
|
|
|
|
} else {
|
|
// Log the incident
|
|
(new logs_o())->add('vehicles', 'global', 1, 0, 'LIST_VEHICLE_CUSTOMER_SUGGESTIONS', 'No user found, or invalid session');
|
|
// Return an error
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_vehicle_customer_suggestions' => 'List customer suggestions for a vehicle',
|
|
]
|
|
);
|
|
|
|
$this->post('/vehicles/set-auto-start-on-lpr', function () {
|
|
global $response;
|
|
$auth = new authentication();
|
|
$user = $auth->get_user();
|
|
$permission_own = self::definePermission('set_auto_start_on_lpr', subusers_permission_node_key::VEHICLES_EDIT);
|
|
$permission_other = self::definePermission('set_auto_start_on_lpr_other');
|
|
self::requireParameters(['id', 'active']);
|
|
$id = (int)self::getParameter('id');
|
|
self::requireType($id, self::type_int());
|
|
self::requireMinValue($id, 1);
|
|
self::requireMaxValue($id, 9999999999);
|
|
$autoStartOnLpr = (bool)self::getParameter('active');
|
|
self::requireType($autoStartOnLpr, self::type_bool());
|
|
$vehicle = (new customer_vehicles_o())->select($id);
|
|
if (!$vehicle->exists()) {
|
|
(new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'SET_AUTO_START_ON_LPR', 'Vehicle not found');
|
|
$response->error('Vehicle not found', 404);
|
|
}
|
|
self::allowOwnOrDepartmentAccess(
|
|
$permission_own,
|
|
$permission_other,
|
|
(int)$vehicle->customer_id->value(),
|
|
null,
|
|
null,
|
|
'You are not allowed to edit vehicles from other users'
|
|
);
|
|
$vehicle->setAutoStartOnLpr($autoStartOnLpr);
|
|
$response->success([...$vehicle->asArray()]);
|
|
},
|
|
[
|
|
'set_auto_start_on_lpr' => 'Set auto start on LPR',
|
|
'set_auto_start_on_lpr_other' => 'Set auto start on LPR for another user\'s vehicle',
|
|
]
|
|
);
|
|
|
|
$this->post('/vehicles/set-vehicle-type-id', function () {
|
|
global $response;
|
|
$auth = new authentication();
|
|
$user = $auth->get_user();
|
|
$permission_own = self::definePermission('set_vehicle_type_id', subusers_permission_node_key::VEHICLES_EDIT);
|
|
$permission_other = self::definePermission('set_vehicle_type_id_other');
|
|
self::requireParameters(['id', 'vehicleTypeId']);
|
|
$id = (int)self::getParameter('id');
|
|
self::requireType($id, self::type_int());
|
|
self::requireMinValue($id, 1);
|
|
self::requireMaxValue($id, 9999999999);
|
|
$vehicleTypeId = (string)self::getParameter('vehicleTypeId');
|
|
self::requireType($vehicleTypeId, self::type_string());
|
|
self::requireMinLength('vehicleTypeId', 1);
|
|
self::requireMaxLength('vehicleTypeId', 50);
|
|
$vehicle = (new customer_vehicles_o())->select($id);
|
|
if (!$vehicle->exists()) {
|
|
(new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'SET_VEHICLE_TYPE_ID', 'Vehicle not found');
|
|
$response->error('Vehicle not found', 404);
|
|
}
|
|
self::allowOwnOrDepartmentAccess(
|
|
$permission_own,
|
|
$permission_other,
|
|
(int)$vehicle->customer_id->value(),
|
|
null,
|
|
null,
|
|
'You are not allowed to edit vehicles from other users'
|
|
);
|
|
$customer = (new users_o())->getUserByCustomerNumber((int)$vehicle->customer_id->value());
|
|
if (!$vehicle->hasXLVask()) {
|
|
if (!$customer->hasXLVaskCustomerAccount()) {
|
|
(new logs_o())->add('vehicles', 'global', 1, (int)($user->id ?? 0), 'SET_VEHICLE_TYPE_ID', 'Attempt to set vehicle type ID without XL Vask registration and no customer account');
|
|
$response->error('Vehicle is not registered in the XL Vask system', 400);
|
|
} else {
|
|
$vehicle->createXLVaskVehicle($vehicleTypeId);
|
|
}
|
|
} else {
|
|
$vehicle->setVehicleTypeId($vehicleTypeId);
|
|
}
|
|
$response->success([...$vehicle->asArray()]);
|
|
},
|
|
[
|
|
'set_vehicle_type_id' => 'Set vehicle type ID',
|
|
'set_vehicle_type_id_other' => 'Set vehicle type ID for another user\'s vehicle',
|
|
]
|
|
);
|
|
|
|
|
|
$this->get('/superuser/users-with-vehicle-subscriptions', function () {
|
|
// Require the user to be logged in
|
|
global $response;
|
|
$this->requirePermission('list_users_with_vehicle_subscriptions');
|
|
// Get the user object
|
|
$user = (new authentication())->get_user();
|
|
// Check if the request was successful
|
|
if ($user) {
|
|
// Log the incident
|
|
(new logs_o())->add('vehicles', 'global', 1, $user->id, 'LIST_USERS_WITH_VEHICLE_SUBSCRIPTIONS', 'Successfully listed users with vehicle subscriptions');
|
|
// Return the list of users with vehicle subscriptions
|
|
$vehicles_o = new customer_vehicles_o();
|
|
$customer_ids = $vehicles_o->getFieldsWhere(
|
|
[
|
|
'wash_subscription' => 1,
|
|
],
|
|
['customer_id']
|
|
);
|
|
// Get all the customer_ids that has vehicle subscriptions
|
|
$customer_ids = array_map(function ($customer_id) {
|
|
return (int)$customer_id['customer_id'];
|
|
}, $customer_ids);
|
|
// Remove duplicates
|
|
$customer_ids = array_unique($customer_ids);
|
|
$response->success(
|
|
$customer_ids ? array_map(function ($customer_id) {
|
|
$tmp_user = new users_o();
|
|
$tmp_user->getUserByCustomerNumber((int)$customer_id);
|
|
return [...$tmp_user->asArray()];
|
|
}, $customer_ids) : []
|
|
);
|
|
} else {
|
|
// Log the incident
|
|
(new logs_o())->add('vehicles', 'global', 1, 0, 'LIST_USERS_WITH_VEHICLE_SUBSCRIPTIONS', 'No user found, or invalid session');
|
|
// Return an error
|
|
$response->error('Invalid session', 400);
|
|
}
|
|
},
|
|
[
|
|
'list_users_with_vehicle_subscriptions' => 'List users with vehicle subscriptions',
|
|
]
|
|
);
|
|
|
|
$this->get('/vehicles/status', function () {
|
|
// Require the user to be logged in
|
|
global $response;
|
|
$this->requirePermission('view_vehicle_status');
|
|
/** Define parameters */
|
|
// "reg" - The registration number to check the status of
|
|
self::requireParameters([
|
|
'reg',
|
|
]);
|
|
self::requireType((string)self::getParameter('reg'), self::type_string());
|
|
$reg = (string)self::getParameter('reg');
|
|
self::requireMinLength('reg', 1);
|
|
self::requireMaxLength('reg', 12);
|
|
// "department" - The department to check the status of (optional)
|
|
if (self::isParametersSet(['department'])) {
|
|
self::requireType((int)self::getParameter('department'), self::type_int());
|
|
$department = (int)self::getParameter('department');
|
|
self::requireMinValue($department, 1);
|
|
self::requireMaxValue($department, 999999);
|
|
// Check if the department exists
|
|
$department_o = new departments_o();
|
|
$department_o->select($department);
|
|
if (!$department_o->exists()) {
|
|
$response->error('Department not found', 404);
|
|
}
|
|
} else {
|
|
$department = null;
|
|
}
|
|
// Validate the registration number
|
|
$this->validateRegistrationNumber($reg, $response);
|
|
$customer_vehicles_o = new customer_vehicles_o();
|
|
/** Determine the statuses of the vehicle */
|
|
$isVerified = $customer_vehicles_o->countRowsWhere(
|
|
[
|
|
'reg' => $reg,
|
|
]
|
|
) > 0;
|
|
$isKnown = (new orders_o())->countRowsWhere(
|
|
[
|
|
'reg_1' => $reg,
|
|
]
|
|
) > 1; // We need to go above one, as the current order will also be counted
|
|
$isBooked = (new bookings_o())->countRowsWhere(
|
|
[
|
|
'regNrTraekker' => $reg,
|
|
'status' => 'pending',
|
|
...($department ? ['department' => $department] : []), // Filter by department if set
|
|
]
|
|
) > 0;
|
|
// Check if the customer is barred (only if the vehicle is verified)
|
|
$customer_number = $isVerified ? ($customer_vehicles_o->getFieldsWhere(
|
|
[
|
|
'reg' => $reg,
|
|
],
|
|
['customer_id']
|
|
)) : null;
|
|
$customer_number = $customer_number ? (int)$customer_number[0]['customer_id'] : null;
|
|
$cardPaymentRequired = ($customer_number && (new users_o())->isCustomerBarred((int)$customer_number));
|
|
$last_order = $customer_vehicles_o->getLastOrderByPlate($reg);
|
|
$response->success([
|
|
'department' => $department,
|
|
'reg' => $reg,
|
|
'verified' => $isVerified,
|
|
'known' => $isKnown,
|
|
'booked' => $isBooked,
|
|
'card' => $cardPaymentRequired,
|
|
'unknown' => !$customer_number && !$isKnown && !$isBooked,
|
|
'last_order_id' => $last_order?->id ?? null,
|
|
]);
|
|
},
|
|
[
|
|
'view_vehicle_status' => 'View vehicle status (Verified, Known, Unknown, Booked, Card payment required, etc.)',
|
|
]
|
|
);
|
|
|
|
$this->get('/vehicles/search', function () {
|
|
global $response;
|
|
$this->requirePermission('search_vehicles');
|
|
/**
|
|
* This endpoint allows searching for vehicles by registration number.
|
|
* This includes:
|
|
* - verified vehicles (in customer_vehicles)
|
|
* - known vehicles (in orders).
|
|
* - booked vehicles (in bookings).
|
|
* - unknown vehicles (not in any of the above).
|
|
*/
|
|
self::requireParameters(['search']);
|
|
self::requireType((string)self::getParameter('search'), self::type_string());
|
|
$search = (string)self::getParameter('search');
|
|
self::requireMinLength('search', 1);
|
|
self::requireMaxLength('search', 12);
|
|
$department = null;
|
|
// If department is set, validate it
|
|
if (self::isParametersSet(['department'])) {
|
|
$department = (int)self::getParameter('department');
|
|
self::requireType($department, self::type_int());
|
|
self::requireMinValue($department, 1);
|
|
self::requireMaxValue($department, 999999);
|
|
// Check if the department exists
|
|
$department_o = new departments_o();
|
|
$department_o->select($department);
|
|
if (!$department_o->exists()) {
|
|
$response->error('Department not found', 404);
|
|
}
|
|
self::requireDepartmentAccess($department);
|
|
}
|
|
// Strip the search term of whitespace
|
|
$search = preg_replace('/\s+/', '', $search);
|
|
/** Get the different lists of registration numbers */
|
|
$options = ['limit' => 10];
|
|
$booked_filters = [...($department ? ['department' => $department] : [])];
|
|
$booking_lookup_filters = ['order_id' => null, 'deleted_at' => null, ...$booked_filters];
|
|
$verified_regs = self::getVerifiedRegs($search, $options);
|
|
$known_regs = self::getKnownRegs($search, $options);
|
|
$booked_regs = self::getBookedRegs($search, $options, $booked_filters);
|
|
$unknown_regs = array_filter(array_unique(array_merge([...self::getUnknownRegs($search, $options), $search]))); // Add the search term as the first item, to ensure it is included in the results
|
|
/**
|
|
* Prevent overlaps between the lists
|
|
* - Booked vehicles should not be in known, unknown or verified
|
|
* - Verified vehicles should not be in known or booked
|
|
* - Known vehicles should not be in unknown
|
|
* - Unknown vehicles should not be in any other list
|
|
*/
|
|
$known_regs = array_filter($known_regs, function ($reg) use ($verified_regs, $booked_regs) {
|
|
return !in_array($reg, $verified_regs) && !in_array($reg, $booked_regs);
|
|
});
|
|
$verified_regs = array_filter($verified_regs, function ($reg) use ($booked_regs) {
|
|
return !in_array($reg, $booked_regs);
|
|
});
|
|
$unknown_regs = array_filter($unknown_regs, function ($reg) use ($verified_regs, $known_regs, $booked_regs) {
|
|
return !in_array($reg, $verified_regs) && !in_array($reg, $known_regs) && !in_array($reg, $booked_regs);
|
|
});
|
|
// Sort the lists alphabetically
|
|
sort($verified_regs);
|
|
sort($known_regs);
|
|
sort($booked_regs);
|
|
sort($unknown_regs);
|
|
// Define the custom relevance sorting function
|
|
$relevant = [
|
|
...array_values(array_unique($booked_regs)),
|
|
...array_values(array_unique($verified_regs)),
|
|
...array_values(array_unique($known_regs)),
|
|
...array_values(array_unique($unknown_regs)),
|
|
];
|
|
// Limit the results to 10 items total, while keeping the relevance order
|
|
$vehicles = array_slice($relevant, 0, 10);
|
|
/** Format the relevance results */
|
|
$vehicles = array_map(function ($reg) use ($verified_regs, $known_regs, $booked_regs, $unknown_regs, $booking_lookup_filters) {
|
|
// Determine the status of the vehicle
|
|
$status = null;
|
|
$isVerified = in_array($reg, $verified_regs);
|
|
$isKnown = in_array($reg, $known_regs);
|
|
$isBooked = in_array($reg, $booked_regs);
|
|
$isUnknown = in_array($reg, $unknown_regs);
|
|
// Initialize other variables
|
|
$customer_number = null; // Will be populated for verified / booked vehicles
|
|
$customer_name = null; // Will be populated for verified / booked vehicles
|
|
$last_order_id = (new customer_vehicles_o())->getLastOrderByPlate($reg)?->id ?? null;
|
|
// These variables are only for verified vehicles
|
|
$type = null;
|
|
$id = null;
|
|
$reference = null;
|
|
// Booking related variables
|
|
$booking_id = null;
|
|
$notes = null; // Booking notes
|
|
$booking_datetime = null;
|
|
|
|
// Set the status based on priority: booked > verified > known > unknown
|
|
if ($isBooked) {
|
|
$status = 'booked';
|
|
$booking = self::getPendingOrderBookingSummaryByPlate($reg, $booking_lookup_filters);
|
|
// Populate other variables
|
|
$booking_id = $booking ? (int)$booking['id'] : null;
|
|
$customer_number = $booking ? (int)$booking['customer_number'] : null;
|
|
$reference = $booking ? (string)$booking['reference'] : null;
|
|
$notes = $booking ? (string)$booking['note'] : null;
|
|
$booking_datetime = $booking ? (string)($booking['datetime'] ?? '') : null;
|
|
// If the booking has a customer number, check if the customer is barred
|
|
if ($customer_number && (new users_o())->isCustomerBarred((int)$customer_number)) {
|
|
$status = 'card';
|
|
}
|
|
} elseif ($isVerified) {
|
|
$status = 'verified';
|
|
// Get the vehicle
|
|
$customer_vehicles_o = new customer_vehicles_o();
|
|
$vehicle = $customer_vehicles_o->getFieldsWhere(['reg' => $reg], ['customer_id', 'type', 'id', 'reference']);
|
|
// Populate other variables
|
|
$customer_number = $vehicle ? (int)$vehicle[0]['customer_id'] : null;
|
|
$type = $vehicle ? (int)$vehicle[0]['type'] : null;
|
|
$id = $vehicle ? (int)$vehicle[0]['id'] : null;
|
|
$reference = $vehicle ? (string)$vehicle[0]['reference'] : null;
|
|
// Check if the customer is barred (only if verified)
|
|
if ($customer_number && (new users_o())->isCustomerBarred((int)$customer_number)) {
|
|
$status = 'card';
|
|
}
|
|
} elseif ($isKnown) {
|
|
$status = 'known';
|
|
// Check if the vehicle previously only has been set to a specific customer, and if so, get the customer name
|
|
$orders_o = new orders_o();
|
|
$order = $orders_o->getFieldsWhere([
|
|
'reg_1' => $reg,
|
|
'deleted_at' => null, // Only consider non-deleted orders
|
|
], ['customer_id']);
|
|
$customer_ids = array_unique(array_map(function ($row) {
|
|
return (int)$row['customer_id'];
|
|
}, $order));
|
|
if (count($customer_ids) === 1) {
|
|
$customer_number = $customer_ids[0];
|
|
// Check if the customer is barred (only if known)
|
|
if ($customer_number && (new users_o())->isCustomerBarred((int)$customer_number)) {
|
|
$status = 'unknown'; // If the customer is barred, we set the status to unknown (as we don't need to know about barred known vehicles)
|
|
$customer_number = null; // Clear the customer number as well
|
|
}
|
|
} else {
|
|
// Set the customer name to multiple known customers
|
|
$customer_name = 'Multiple known customers';
|
|
}
|
|
} elseif ($isUnknown) {
|
|
$status = 'unknown';
|
|
}
|
|
// Get the customer name if we have a customer number
|
|
if ($customer_number) {
|
|
$customer = (new users_o())->getUserByCustomerNumber((int)$customer_number);
|
|
if ($customer->exists()) {
|
|
$customer_name = (string)$customer->getCustomerName((int)$customer_number);
|
|
}
|
|
}
|
|
// Return the formatted vehicle
|
|
return [
|
|
'reg' => $reg,
|
|
'status' => $status,
|
|
'type' => $type,
|
|
'customer_id' => $customer_number,
|
|
'id' => $id,
|
|
'last_order_id' => $last_order_id,
|
|
'reference' => $reference,
|
|
'booking_id' => $booking_id,
|
|
'booking_datetime' => $booking_datetime,
|
|
'notes' => $notes,
|
|
'customer_name' => $customer_name,
|
|
'barred' => $status === 'card', // If the status is 'card', the customer is barred
|
|
];
|
|
}, $vehicles);
|
|
// Return the results
|
|
$response->success($vehicles);
|
|
},
|
|
[
|
|
'search_vehicles' => 'Search vehicles by registration number',
|
|
]
|
|
);
|
|
}
|
|
|
|
private static function getVerifiedRegs(string $search, array $options = ['limit' => null]): array
|
|
{
|
|
$customer_vehicles_o = new customer_vehicles_o();
|
|
$results = $customer_vehicles_o->getFieldsWhereContaining(
|
|
[
|
|
'reg' => $search
|
|
],
|
|
['reg'],
|
|
$options
|
|
);
|
|
return array_map(function ($row) {
|
|
return (string)$row['reg'];
|
|
}, $results);
|
|
}
|
|
|
|
private static function getKnownRegs(string $search, array $options = ['limit' => null]): array
|
|
{
|
|
$orders_o = new orders_o();
|
|
$results = $orders_o->getFieldsWhereContaining(
|
|
[
|
|
'reg_1' => $search
|
|
],
|
|
['reg_1'],
|
|
$options
|
|
);
|
|
return array_map(function ($row) {
|
|
return (string)$row['reg_1'];
|
|
}, $results);
|
|
}
|
|
|
|
private static function getBookedRegs(string $search, array $options = ['limit' => null], ?array $filters = []): array
|
|
{
|
|
$bookings_o = new order_bookings_o();
|
|
/**
|
|
* $bookings_o = new bookings_o();
|
|
*
|
|
* $results1 = $bookings_o->getFieldsWhereContaining(
|
|
* [
|
|
* 'regNrTraekker' => $search,
|
|
* 'status' => 'pending',
|
|
* ],
|
|
* ['regNrTraekker'],
|
|
* $options
|
|
* );
|
|
* $results2 = $bookings_o->getFieldsWhereContaining(
|
|
* [
|
|
* 'regNrTrailer' => $search,
|
|
* 'status' => 'pending',
|
|
* ],
|
|
* ['regNrTrailer'],
|
|
* $options
|
|
* );
|
|
*/
|
|
$results1 = $bookings_o->getFieldsWhereContaining(
|
|
[
|
|
'reg_1' => $search,
|
|
'order_id' => null,
|
|
'deleted_at' => null,
|
|
...$filters
|
|
],
|
|
['reg_1'],
|
|
$options
|
|
);
|
|
$results2 = $bookings_o->getFieldsWhereContaining(
|
|
[
|
|
'reg_2' => $search,
|
|
'order_id' => null,
|
|
'deleted_at' => null,
|
|
...$filters
|
|
],
|
|
['reg_2'],
|
|
$options
|
|
);
|
|
return array_unique(array_merge(
|
|
array_map(function ($row) {
|
|
return (string)$row['reg_1'];
|
|
}, $results1),
|
|
array_map(function ($row) {
|
|
return (string)$row['reg_2'];
|
|
}, $results2)
|
|
));
|
|
}
|
|
|
|
private static function getPendingOrderBookingSummaryByPlate(string $reg, array $filters = []): ?array
|
|
{
|
|
$bookings_o = new order_bookings_o();
|
|
$fields = ['id', 'customer_number', 'reference', 'note', 'datetime'];
|
|
$results1 = $bookings_o->getFieldsWhere([
|
|
'reg_1' => $reg,
|
|
...$filters
|
|
], $fields) ?: [];
|
|
$results2 = $bookings_o->getFieldsWhere([
|
|
'reg_2' => $reg,
|
|
...$filters
|
|
], $fields) ?: [];
|
|
|
|
$matches_by_key = [];
|
|
foreach (array_merge($results1, $results2) as $booking) {
|
|
if (!is_array($booking)) {
|
|
continue;
|
|
}
|
|
|
|
$booking_id = isset($booking['id']) ? (int)$booking['id'] : 0;
|
|
$dedupe_key = $booking_id > 0
|
|
? 'booking:' . $booking_id
|
|
: implode('|', [
|
|
(string)($booking['reference'] ?? ''),
|
|
(string)($booking['note'] ?? ''),
|
|
(string)($booking['datetime'] ?? ''),
|
|
]);
|
|
|
|
$matches_by_key[$dedupe_key] = $booking;
|
|
}
|
|
|
|
if (empty($matches_by_key)) {
|
|
return null;
|
|
}
|
|
|
|
$matches = array_values($matches_by_key);
|
|
usort($matches, function (array $left, array $right): int {
|
|
$left_has_datetime = self::hasPendingOrderBookingSummaryDatetime($left);
|
|
$right_has_datetime = self::hasPendingOrderBookingSummaryDatetime($right);
|
|
if ($left_has_datetime !== $right_has_datetime) {
|
|
return $right_has_datetime <=> $left_has_datetime;
|
|
}
|
|
|
|
$timestamp_difference = self::getPendingOrderBookingSummaryTimestamp($left) <=> self::getPendingOrderBookingSummaryTimestamp($right);
|
|
if ($timestamp_difference !== 0) {
|
|
return $timestamp_difference;
|
|
}
|
|
|
|
return ((int)($left['id'] ?? 0)) <=> ((int)($right['id'] ?? 0));
|
|
});
|
|
|
|
return $matches[0] ?? null;
|
|
}
|
|
|
|
private static function hasPendingOrderBookingSummaryDatetime(array $booking): bool
|
|
{
|
|
return trim((string)($booking['datetime'] ?? '')) !== '';
|
|
}
|
|
|
|
private static function getPendingOrderBookingSummaryTimestamp(array $booking): int
|
|
{
|
|
$raw_value = (string)($booking['datetime'] ?? '');
|
|
if ($raw_value !== '') {
|
|
$parsed_value = strtotime($raw_value);
|
|
if ($parsed_value !== false) {
|
|
return $parsed_value;
|
|
}
|
|
}
|
|
|
|
$fallback_id = (int)($booking['id'] ?? PHP_INT_MAX);
|
|
return $fallback_id > 0 ? $fallback_id : PHP_INT_MAX;
|
|
}
|
|
|
|
private static function getUnknownRegs(string $search, array $options = ['limit' => null]): array
|
|
{
|
|
// Unknown vehicles are vehicles that has been scanned by the LPR system, but are not in any of the other lists
|
|
$plate_scans_o = new plate_scans_o();
|
|
$results = $plate_scans_o->getFieldsWhereContaining(
|
|
[
|
|
'plate' => $search,
|
|
],
|
|
['plate'],
|
|
$options
|
|
);
|
|
return array_map(function ($row) {
|
|
return (string)$row['plate'];
|
|
}, $results);
|
|
}
|
|
|
|
private function getData(mixed $data, $response)
|
|
{
|
|
if (!isset($data['type'])) {
|
|
$response->error('Type is required', 400);
|
|
}
|
|
if (!isset($data['reg'])) {
|
|
$response->error('Registration number is required', 400);
|
|
}
|
|
if (!isset($data['notes'])) {
|
|
$response->error('Notes is required', 400);
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
private function validateRegistrationNumber(mixed $reg, $response): void
|
|
{
|
|
if (!preg_match('/^[A-Z0-9]{4,10}$/', $reg)) {
|
|
$response->error('Invalid registration number, it must be 4-10 characters long, and only contain uppercase letters and numbers', 400);
|
|
}
|
|
}
|
|
|
|
private function validateType(mixed $type, $response): void
|
|
{
|
|
// Make sure the type is more than 2 characters
|
|
if (strlen($type) < 2) {
|
|
$response->error('Type is too short, it must be at least 2 characters', 400);
|
|
}
|
|
// Make sure the type is less than 50 characters
|
|
if (strlen($type) > 50) {
|
|
$response->error('Type is too long, it must be less than 50 characters', 400);
|
|
}
|
|
}
|
|
|
|
private function validateNotes(mixed $notes, $response): void
|
|
{
|
|
// If the notes are set, make sure they are less than 250 characters
|
|
if (isset($notes) && strlen($notes) > 250) {
|
|
$response->error('Notes are too long, they must be less than 250 characters', 400);
|
|
}
|
|
}
|
|
}
|