Enhance vehicle summary retrieval for customers and update OpenAPI schema for subuser management grants
This commit is contained in:
@@ -16908,6 +16908,41 @@ components:
|
||||
$ref: '#/components/schemas/SubuserPermissionGroup'
|
||||
grant_created_at: { type: string, format: date-time, nullable: true }
|
||||
grant_updated_at: { type: string, format: date-time, nullable: true }
|
||||
grants:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SubuserManagementGrant'
|
||||
grant_count: { type: integer }
|
||||
customer_numbers:
|
||||
type: array
|
||||
items: { type: integer }
|
||||
access_state:
|
||||
type: string
|
||||
enum: [active, pending_setup, disabled, inactive]
|
||||
|
||||
SubuserManagementGrant:
|
||||
type: object
|
||||
properties:
|
||||
grant_id: { type: integer }
|
||||
customer_number: { type: integer }
|
||||
customer_name: { type: string, nullable: true }
|
||||
grant_enabled: { type: boolean }
|
||||
grant_note: { type: string, nullable: true }
|
||||
grant_permissions:
|
||||
type: array
|
||||
items: { type: string }
|
||||
permissions:
|
||||
type: array
|
||||
items: { type: string }
|
||||
permission_template_key:
|
||||
type: string
|
||||
enum: [deactivated, driver, booking_coordinator, fleet_admin, custom]
|
||||
permission_groups:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SubuserPermissionGroup'
|
||||
grant_created_at: { type: string, format: date-time, nullable: true }
|
||||
grant_updated_at: { type: string, format: date-time, nullable: true }
|
||||
access_state:
|
||||
type: string
|
||||
enum: [active, pending_setup, disabled, inactive]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16919,6 +16919,41 @@ components:
|
||||
$ref: '#/components/schemas/SubuserPermissionGroup'
|
||||
grant_created_at: { type: string, format: date-time, nullable: true }
|
||||
grant_updated_at: { type: string, format: date-time, nullable: true }
|
||||
grants:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SubuserManagementGrant'
|
||||
grant_count: { type: integer }
|
||||
customer_numbers:
|
||||
type: array
|
||||
items: { type: integer }
|
||||
access_state:
|
||||
type: string
|
||||
enum: [active, pending_setup, disabled, inactive]
|
||||
|
||||
SubuserManagementGrant:
|
||||
type: object
|
||||
properties:
|
||||
grant_id: { type: integer }
|
||||
customer_number: { type: integer }
|
||||
customer_name: { type: string, nullable: true }
|
||||
grant_enabled: { type: boolean }
|
||||
grant_note: { type: string, nullable: true }
|
||||
grant_permissions:
|
||||
type: array
|
||||
items: { type: string }
|
||||
permissions:
|
||||
type: array
|
||||
items: { type: string }
|
||||
permission_template_key:
|
||||
type: string
|
||||
enum: [deactivated, driver, booking_coordinator, fleet_admin, custom]
|
||||
permission_groups:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SubuserPermissionGroup'
|
||||
grant_created_at: { type: string, format: date-time, nullable: true }
|
||||
grant_updated_at: { type: string, format: date-time, nullable: true }
|
||||
access_state:
|
||||
type: string
|
||||
enum: [active, pending_setup, disabled, inactive]
|
||||
|
||||
@@ -82,10 +82,15 @@ class vehiclesRoute
|
||||
|
||||
private function buildVehicleSummaryForCustomer(int $customerNumber): array
|
||||
{
|
||||
$rows = (new customer_vehicles_o())->getFieldsWhere([
|
||||
$vehicles = new customer_vehicles_o();
|
||||
$filters = [
|
||||
'customer_id' => $customerNumber,
|
||||
'deleted_at' => null,
|
||||
], [
|
||||
];
|
||||
if ($vehicles->columnsExist(['deleted_at'])) {
|
||||
$filters['deleted_at'] = null;
|
||||
}
|
||||
|
||||
$rows = $vehicles->getFieldsWhere($filters, [
|
||||
'id',
|
||||
'wash_subscription',
|
||||
]);
|
||||
|
||||
@@ -228,6 +228,11 @@ it('lists chauffeur grants across customers for superusers', function (): void {
|
||||
(int)$secondCustomer['customer_number'],
|
||||
['BOOKINGS_LIST']
|
||||
);
|
||||
$sharedGrantId = api_fixtures()->grantSubuser(
|
||||
(int)$firstSubuser['id'],
|
||||
(int)$secondCustomer['customer_number'],
|
||||
['ORDERS_LIST']
|
||||
);
|
||||
|
||||
$response = api_client()->get('/superuser/subusers?page=1&limit=20&search=Driver', $session['headers']);
|
||||
|
||||
@@ -239,22 +244,26 @@ it('lists chauffeur grants across customers for superusers', function (): void {
|
||||
$rows = array_values(array_filter(
|
||||
is_array($response->data()) ? $response->data() : [],
|
||||
static fn (mixed $item): bool => is_array($item)
|
||||
&& in_array((int)($item['grant_id'] ?? 0), [$firstGrantId, $secondGrantId], true)
|
||||
&& in_array((int)($item['id'] ?? 0), [(int)$firstSubuser['id'], (int)$secondSubuser['id']], true)
|
||||
));
|
||||
|
||||
expect($rows)->toHaveCount(2);
|
||||
|
||||
$byGrantId = [];
|
||||
$bySubuserId = [];
|
||||
foreach ($rows as $row) {
|
||||
$byGrantId[(int)$row['grant_id']] = $row;
|
||||
$bySubuserId[(int)$row['id']] = $row;
|
||||
}
|
||||
|
||||
expect($byGrantId[$firstGrantId]['customer_number'])->toBe((int)$firstCustomer['customer_number']);
|
||||
expect($byGrantId[$firstGrantId]['customer_name'])->toBe('Fleet Customer Alpha');
|
||||
expect($byGrantId[$firstGrantId]['grant_permissions'])->toBe(['VEHICLES_LIST', 'SUBUSERS_LIST']);
|
||||
expect($byGrantId[$secondGrantId]['customer_number'])->toBe((int)$secondCustomer['customer_number']);
|
||||
expect($byGrantId[$secondGrantId]['customer_name'])->toBe('Fleet Customer Beta');
|
||||
expect($byGrantId[$secondGrantId]['grant_permissions'])->toBe(['BOOKINGS_LIST']);
|
||||
expect($bySubuserId[(int)$firstSubuser['id']]['grant_count'])->toBe(2);
|
||||
expect(array_column($bySubuserId[(int)$firstSubuser['id']]['grants'], 'grant_id'))
|
||||
->toContain($firstGrantId)
|
||||
->toContain($sharedGrantId);
|
||||
expect($bySubuserId[(int)$firstSubuser['id']]['customer_numbers'])
|
||||
->toContain((int)$firstCustomer['customer_number'])
|
||||
->toContain((int)$secondCustomer['customer_number']);
|
||||
expect($bySubuserId[(int)$secondSubuser['id']]['grant_count'])->toBe(1);
|
||||
expect($bySubuserId[(int)$secondSubuser['id']]['grants'][0]['grant_id'])->toBe($secondGrantId);
|
||||
expect($bySubuserId[(int)$secondSubuser['id']]['grants'][0]['customer_name'])->toBe('Fleet Customer Beta');
|
||||
});
|
||||
|
||||
it('lets superusers invite chauffeurs for a selected customer', function (): void {
|
||||
|
||||
@@ -4,6 +4,28 @@ declare(strict_types=1);
|
||||
|
||||
usesApiSuite();
|
||||
|
||||
function vehicles_without_customer_vehicles_deleted_at(callable $callback): void
|
||||
{
|
||||
$db = api_test_runtime()->db();
|
||||
$column = $db->query("SHOW COLUMNS FROM `customer_vehicles` LIKE 'deleted_at'");
|
||||
if ($column === false) {
|
||||
throw new RuntimeException('Unable to inspect customer_vehicles.deleted_at test column.');
|
||||
}
|
||||
|
||||
$hadColumn = (int)$column->num_rows > 0;
|
||||
if ($hadColumn) {
|
||||
$db->query('ALTER TABLE `customer_vehicles` DROP COLUMN `deleted_at`');
|
||||
}
|
||||
|
||||
try {
|
||||
$callback();
|
||||
} finally {
|
||||
if ($hadColumn) {
|
||||
$db->query('ALTER TABLE `customer_vehicles` ADD COLUMN `deleted_at` DATETIME NULL');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it('defaults wash subscriptions to false when a customer creates a vehicle without the field', function (): void {
|
||||
api_test_covers('POST /vehicles', 'happy');
|
||||
|
||||
@@ -253,6 +275,72 @@ it('lists and summarizes vehicles through the user-scoped superuser route', func
|
||||
]);
|
||||
});
|
||||
|
||||
it('lists and summarizes user-scoped superuser vehicles when customer vehicles has no deleted at column', function (): void {
|
||||
vehicles_without_customer_vehicles_deleted_at(function (): void {
|
||||
api_test_covers('GET /superuser/users/{user_id}/vehicles', 'happy');
|
||||
api_test_covers('GET /superuser/users/{user_id}/vehicles/summary', 'happy');
|
||||
|
||||
$customerVehiclesDeletedAtColumn = api_test_runtime()->db()->query(
|
||||
"SHOW COLUMNS FROM `customer_vehicles` LIKE 'deleted_at'"
|
||||
);
|
||||
expect($customerVehiclesDeletedAtColumn)->not->toBeFalse();
|
||||
expect((int)$customerVehiclesDeletedAtColumn->num_rows)->toBe(0);
|
||||
|
||||
$targetUser = api_fixtures()->createUser(['display_name' => 'Scoped Vehicle Legacy Schema Customer']);
|
||||
$otherUser = api_fixtures()->createUser(['display_name' => 'Other Vehicle Legacy Schema Customer']);
|
||||
$vehicle = api_fixtures()->createVehicle([
|
||||
'customer_id' => $targetUser['customer_number'],
|
||||
'type' => 53,
|
||||
'reg' => 'LEGACYV1',
|
||||
'wash_subscription' => 1,
|
||||
'reference' => 'Legacy schema fleet',
|
||||
]);
|
||||
api_fixtures()->createVehicle([
|
||||
'customer_id' => $otherUser['customer_number'],
|
||||
'type' => 53,
|
||||
'reg' => 'LEGACYOTHER',
|
||||
'wash_subscription' => 1,
|
||||
]);
|
||||
$session = api_fixtures()->createUserSession(['list_vehicles_other']);
|
||||
|
||||
$response = api_client()->get(
|
||||
'/superuser/users/' . $targetUser['id'] . '/vehicles?page=1&limit=20',
|
||||
$session['headers']
|
||||
);
|
||||
$summary = api_client()->get(
|
||||
'/superuser/users/' . $targetUser['id'] . '/vehicles/summary',
|
||||
$session['headers']
|
||||
);
|
||||
|
||||
$response
|
||||
->assertStatus(200)
|
||||
->assertEnvelope()
|
||||
->assertSuccess();
|
||||
$summary
|
||||
->assertStatus(200)
|
||||
->assertEnvelope()
|
||||
->assertSuccess();
|
||||
|
||||
$rows = $response->data();
|
||||
expect(array_column($rows, 'reg'))->toContain('LEGACYV1');
|
||||
expect(array_column($rows, 'reg'))->not->toContain('LEGACYOTHER');
|
||||
expect($rows[0])->toMatchArray([
|
||||
'id' => $vehicle['id'],
|
||||
'customer_id' => $targetUser['customer_number'],
|
||||
'reg' => 'LEGACYV1',
|
||||
]);
|
||||
expect($response->meta()['user_context'])->toMatchArray([
|
||||
'user_id' => $targetUser['id'],
|
||||
'customer_number' => $targetUser['customer_number'],
|
||||
]);
|
||||
expect($summary->data())->toMatchArray([
|
||||
'total' => 1,
|
||||
'wash_subscription' => 1,
|
||||
'self_service' => 0,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('creates vehicles through the user-scoped superuser route and rejects mismatched customer ids', function (): void {
|
||||
api_test_covers('POST /superuser/users/{user_id}/vehicles', 'happy');
|
||||
api_test_covers('POST /superuser/users/{user_id}/vehicles', 'failure');
|
||||
|
||||
Reference in New Issue
Block a user