Add /vehicles/search route for vehicle registration search

- Introduced a new route `/vehicles/search` to enable searching for vehicles by registration number across multiple categories (e.g., verified, known, booked, unknown).
- Added validation for search parameters, including length and format checks.
- Ensured proper classification of vehicles with clear prioritization and deduplication logic.
- Implemented helper methods to retrieve matching registrations from various sources like bookings, orders, and scanned plates.
- Enhanced response format to include vehicle status, customer details, and references for improved usability.
This commit is contained in:
Jeppe Bundgaard
2025-09-15 15:09:36 +02:00
parent 5b00e47276
commit 9ee486a49e
3 changed files with 265 additions and 0 deletions
+222
View File
@@ -9,6 +9,7 @@ use objects\customer_vehicles_o;
use objects\departments_o;
use objects\logs_o;
use objects\orders_o;
use objects\plate_scans_o;
use objects\products_o;
use objects\users_o;
use traits\route_t;
@@ -715,6 +716,227 @@ class vehiclesRoute
'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);
// Strip the search term of whitespace
$search = preg_replace('/\s+/', '', $search);
/** Get the different lists of registration numbers */
$options = ['limit' => 10];
$verified_regs = self::getVerifiedRegs($search, $options);
$known_regs = self::getKnownRegs($search, $options);
$booked_regs = self::getBookedRegs($search, $options);
$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) {
// 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
// Set the status based on priority: booked > verified > known > unknown
if ($isBooked) {
$status = 'booked';
// Get the booking
$booking = (new bookings_o())->getFieldsWhere([
'regNrTraekker' => $reg,
'status' => 'pending',
], ['id', 'customer_number', 'reference_number', 'notes']);
// If there is no booking with the tractor reg, check the trailer reg
if (!$booking) {
$booking = (new bookings_o())->getFieldsWhere([
'regNrTrailer' => $reg,
'status' => 'pending',
], ['id', 'customer_number', 'reference_number', 'notes']);
}
// Populate other variables
$booking_id = $booking ? (int)$booking[0]['id'] : null;
$customer_number = $booking ? (int)$booking[0]['customer_number'] : null;
$reference = $booking ? (string)$booking[0]['reference_number'] : null;
$notes = $booking ? (string)$booking[0]['notes'] : 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';
} 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,
'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
{
$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
);
return array_unique(array_merge(
array_map(function ($row) {
return (string)$row['regNrTraekker'];
}, $results1),
array_map(function ($row) {
return (string)$row['regNrTrailer'];
}, $results2)
));
}
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)