@fleetbase/fleetops-engine 0.6.61 → 0.6.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/RELEASE.md +23 -8
- package/addon/services/map-settings.js +4 -1
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/migrations/2026_09_05_000001_add_unique_indexes_to_fleet_membership_pivots.php +129 -0
- package/server/src/Exceptions/PublicRelationNotFoundException.php +47 -0
- package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesFleetOpsApiResources.php +42 -7
- package/server/src/Http/Controllers/Api/v1/Concerns/ResolvesPublicExpansions.php +187 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +93 -57
- package/server/src/Http/Controllers/Api/v1/EntityController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/FleetController.php +370 -33
- package/server/src/Http/Controllers/Api/v1/FuelReportController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/IssueController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/OrderController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +134 -35
- package/server/src/Http/Controllers/Api/v1/ZoneController.php +1 -1
- package/server/src/Http/Filter/Concerns/ResolvesPublicRelationUuids.php +54 -0
- package/server/src/Http/Filter/DriverFilter.php +71 -15
- package/server/src/Http/Filter/FleetFilter.php +32 -33
- package/server/src/Http/Filter/VehicleFilter.php +57 -19
- package/server/src/Http/Requests/Concerns/ScopesPublicRelationRules.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +100 -9
- package/server/src/Http/Requests/CreateFleetRequest.php +30 -2
- package/server/src/Http/Requests/CreateServiceRateRequest.php +7 -2
- package/server/src/Http/Requests/CreateVehicleRequest.php +150 -31
- package/server/src/Http/Resources/v1/Concerns/ResolvesPublicRelationFields.php +110 -0
- package/server/src/Http/Resources/v1/Driver.php +22 -2
- package/server/src/Http/Resources/v1/Fleet.php +38 -25
- package/server/src/Http/Resources/v1/Vehicle.php +46 -4
- package/server/src/Models/Driver.php +2 -2
- package/server/src/routes.php +8 -0
- package/server/tests/ApiDriverControllerContractsTest.php +284 -13
- package/server/tests/ApiEntityControllerContractsTest.php +1 -1
- package/server/tests/ApiEquipmentControllerContractsTest.php +2 -2
- package/server/tests/ApiFleetControllerContractsTest.php +354 -21
- package/server/tests/ApiFuelReportControllerContractsTest.php +1 -1
- package/server/tests/ApiIssueControllerContractsTest.php +1 -1
- package/server/tests/ApiOrderControllerContractsTest.php +1 -1
- package/server/tests/ApiPartControllerContractsTest.php +2 -2
- package/server/tests/ApiServiceAreaZoneControllerContractsTest.php +2 -2
- package/server/tests/ApiTrackingStatusControllerContractsTest.php +1 -1
- package/server/tests/ApiVehicleControllerContractsTest.php +175 -8
- package/server/tests/ControllerFilterContractsTest.php +132 -13
- package/server/tests/ControllerHelperContractsTest.php +6 -3
- package/server/tests/DriverFilterExecutionTest.php +130 -6
- package/server/tests/ExceptionContractsTest.php +15 -0
- package/server/tests/Feature/Http/Api/DeviceControllerContractsTest.php +1 -1
- package/server/tests/Feature/Http/Api/FleetMembershipMigrationTest.php +140 -0
- package/server/tests/Feature/Http/Api/FleetMembershipTest.php +333 -0
- package/server/tests/Feature/Http/Api/FleetPublicContractTest.php +439 -0
- package/server/tests/Feature/Http/Api/FuelTransactionControllerContractsTest.php +2 -2
- package/server/tests/Feature/Http/Api/NullRelationshipInputTest.php +191 -0
- package/server/tests/Feature/Http/Api/PublicIdentifierFilterTest.php +293 -0
- package/server/tests/Feature/Http/Api/SmallApiControllerHelpersTest.php +7 -1
- package/server/tests/Feature/Http/Api/VehicleControllerTrackingTest.php +3 -1
- package/server/tests/Feature/Http/Api/VehiclePublicContractTest.php +454 -0
- package/server/tests/Feature/Http/Api/WorkOrderControllerContractsTest.php +1 -1
- package/server/tests/FleetHierarchyResourceTest.php +56 -8
- package/server/tests/RequestContractsTest.php +100 -7
- package/server/tests/Unit/Http/Resources/FleetResourceTest.php +50 -6
- package/server/tests/VehicleControllerHelperContractsTest.php +1 -1
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Filter;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Http\Filter\Concerns\ResolvesPublicRelationUuids;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
7
|
+
use Fleetbase\FleetOps\Models\Fleet;
|
|
5
8
|
use Fleetbase\FleetOps\Models\Vehicle;
|
|
6
9
|
use Fleetbase\FleetOps\Models\Vendor;
|
|
7
10
|
use Fleetbase\FleetOps\Support\Utils;
|
|
@@ -10,6 +13,8 @@ use Fleetbase\Support\Http;
|
|
|
10
13
|
|
|
11
14
|
class VehicleFilter extends Filter
|
|
12
15
|
{
|
|
16
|
+
use ResolvesPublicRelationUuids;
|
|
17
|
+
|
|
13
18
|
public function queryForInternal()
|
|
14
19
|
{
|
|
15
20
|
$this->builder->where('company_uuid', $this->session->get('company'));
|
|
@@ -30,14 +35,56 @@ class VehicleFilter extends Filter
|
|
|
30
35
|
$this->builder->searchWhere(['year', 'make', 'model', 'plate_number'], $display_name);
|
|
31
36
|
}
|
|
32
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Match a vehicle by the identifier the operator's own system uses.
|
|
40
|
+
*
|
|
41
|
+
* `internal_id` is the column an importer keys on to decide whether a
|
|
42
|
+
* vehicle already exists, and it was the one identifier the filter did not
|
|
43
|
+
* support — so every lookup fell through to a create.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Match a vehicle by the identifier the operator's own system uses.
|
|
47
|
+
*
|
|
48
|
+
* Two callers, two meanings. The Fleet-Ops console types into a search box
|
|
49
|
+
* and expects `VEH-10` to find `VEH-100`; an importer asks whether `VEH-10`
|
|
50
|
+
* exists and must not be told yes because `VEH-100` does. A partial match
|
|
51
|
+
* there produces a duplicate on every rerun.
|
|
52
|
+
*
|
|
53
|
+
* Core's ordinary fillable-column filtering is already equality — this
|
|
54
|
+
* method exists only because the console needs the looser behaviour, so it
|
|
55
|
+
* is the console that gets the exception. An unknown request context is
|
|
56
|
+
* treated as public: exact is the safe default of the two.
|
|
57
|
+
*/
|
|
58
|
+
public function internalId(?string $internalId)
|
|
59
|
+
{
|
|
60
|
+
if (Http::isInternalRequest($this->request)) {
|
|
61
|
+
$this->builder->searchWhere('internal_id', $internalId);
|
|
62
|
+
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
$this->builder->where('internal_id', '=', $internalId);
|
|
67
|
+
}
|
|
68
|
+
|
|
33
69
|
public function vin(?string $vin)
|
|
34
70
|
{
|
|
35
71
|
$this->builder->searchWhere('vin', $vin);
|
|
36
72
|
}
|
|
37
73
|
|
|
74
|
+
/**
|
|
75
|
+
* A public id identifies exactly one record, so a public lookup matches it
|
|
76
|
+
* exactly. The console keeps the partial search its id column filter box
|
|
77
|
+
* has always had.
|
|
78
|
+
*/
|
|
38
79
|
public function publicId(?string $publicIc)
|
|
39
80
|
{
|
|
40
|
-
$this->
|
|
81
|
+
if (Http::isInternalRequest($this->request)) {
|
|
82
|
+
$this->builder->searchWhere('public_id', $publicIc);
|
|
83
|
+
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
$this->builder->where('public_id', '=', $publicIc);
|
|
41
88
|
}
|
|
42
89
|
|
|
43
90
|
public function plateNumber(?string $plateNumber)
|
|
@@ -68,10 +115,12 @@ class VehicleFilter extends Filter
|
|
|
68
115
|
return;
|
|
69
116
|
}
|
|
70
117
|
|
|
118
|
+
$driverUuids = $this->resolvePublicRelationUuids(Driver::class, $driverId);
|
|
119
|
+
|
|
71
120
|
$this->builder->whereHas(
|
|
72
121
|
'driver',
|
|
73
|
-
function ($query) use ($
|
|
74
|
-
$query->
|
|
122
|
+
function ($query) use ($driverUuids) {
|
|
123
|
+
$query->whereIn('uuid', $driverUuids);
|
|
75
124
|
}
|
|
76
125
|
);
|
|
77
126
|
}
|
|
@@ -82,20 +131,7 @@ class VehicleFilter extends Filter
|
|
|
82
131
|
return;
|
|
83
132
|
}
|
|
84
133
|
|
|
85
|
-
$this->builder->whereIn('vendor_uuid', Vendor::
|
|
86
|
-
->where('company_uuid', $this->session->get('company'))
|
|
87
|
-
->where(function ($query) use ($vendor) {
|
|
88
|
-
$query->where('public_id', $vendor);
|
|
89
|
-
|
|
90
|
-
if (in_array('internal_id', (new Vendor())->getFillable())) {
|
|
91
|
-
$query->orWhere('internal_id', $vendor);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (Http::isInternalRequest($this->request)) {
|
|
95
|
-
$query->orWhere('uuid', $vendor);
|
|
96
|
-
}
|
|
97
|
-
})
|
|
98
|
-
->pluck('uuid'));
|
|
134
|
+
$this->builder->whereIn('vendor_uuid', $this->resolvePublicRelationUuids(Vendor::class, $vendor));
|
|
99
135
|
}
|
|
100
136
|
|
|
101
137
|
public function driverUuid(?string $driverId)
|
|
@@ -132,10 +168,12 @@ class VehicleFilter extends Filter
|
|
|
132
168
|
|
|
133
169
|
public function fleet(string $fleet)
|
|
134
170
|
{
|
|
171
|
+
$fleetUuids = $this->resolvePublicRelationUuids(Fleet::class, $fleet);
|
|
172
|
+
|
|
135
173
|
$this->builder->whereHas(
|
|
136
174
|
'fleets',
|
|
137
|
-
function ($q) use ($
|
|
138
|
-
$q->
|
|
175
|
+
function ($q) use ($fleetUuids) {
|
|
176
|
+
$q->whereIn('fleet_uuid', $fleetUuids);
|
|
139
177
|
}
|
|
140
178
|
);
|
|
141
179
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Http\Requests\Concerns;
|
|
4
|
+
|
|
5
|
+
use Illuminate\Validation\Rule;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Builds `exists` rules for public relationship inputs that are scoped to the
|
|
9
|
+
* authenticated company.
|
|
10
|
+
*
|
|
11
|
+
* An unscoped `exists:vendors,public_id` accepts another organization's public
|
|
12
|
+
* id, and the controller then resolves it to nothing — so the request looks
|
|
13
|
+
* accepted while the relationship is silently dropped. Scoping the rule turns
|
|
14
|
+
* that into an explicit validation failure, and keeps a public id from
|
|
15
|
+
* confirming or denying the existence of a resource in someone else's company.
|
|
16
|
+
*/
|
|
17
|
+
trait ScopesPublicRelationRules
|
|
18
|
+
{
|
|
19
|
+
/**
|
|
20
|
+
* An `exists` rule limited to the session company and to rows that are not
|
|
21
|
+
* soft deleted.
|
|
22
|
+
*
|
|
23
|
+
* No return type: the pest harness substitutes its own `Illuminate\Validation\Rule`
|
|
24
|
+
* stand-in, which is not an instance of the real `Rules\Exists`.
|
|
25
|
+
*/
|
|
26
|
+
protected function existsInCompany(string $table, string $column = 'public_id')
|
|
27
|
+
{
|
|
28
|
+
$companyUuid = session('company');
|
|
29
|
+
|
|
30
|
+
return Rule::exists($table, $column)->where(function ($query) use ($companyUuid) {
|
|
31
|
+
$query->where('company_uuid', $companyUuid);
|
|
32
|
+
$query->whereNull('deleted_at');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The full rule set for an optional public relationship input.
|
|
38
|
+
*
|
|
39
|
+
* `nullable` is deliberate: sending `null` is how a caller clears an
|
|
40
|
+
* existing assignment, and `exists` is skipped for a null value.
|
|
41
|
+
*
|
|
42
|
+
* @return array<int, mixed>
|
|
43
|
+
*/
|
|
44
|
+
protected function publicRelationRules(string $table, string $column = 'public_id'): array
|
|
45
|
+
{
|
|
46
|
+
return ['nullable', 'string', $this->existsInCompany($table, $column)];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Requests;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Http\Requests\Concerns\ScopesPublicRelationRules;
|
|
6
|
+
use Fleetbase\FleetOps\Models\Driver;
|
|
5
7
|
use Fleetbase\FleetOps\Rules\ResolvablePoint;
|
|
6
8
|
use Fleetbase\Http\Requests\FleetbaseRequest;
|
|
7
9
|
use Illuminate\Validation\Rule;
|
|
8
10
|
|
|
9
11
|
class CreateDriverRequest extends FleetbaseRequest
|
|
10
12
|
{
|
|
13
|
+
use ScopesPublicRelationRules;
|
|
14
|
+
|
|
11
15
|
/**
|
|
12
16
|
* Determine if the user is authorized to make this request.
|
|
13
17
|
*/
|
|
@@ -24,23 +28,110 @@ class CreateDriverRequest extends FleetbaseRequest
|
|
|
24
28
|
$isCreating = $this->isMethod('POST');
|
|
25
29
|
|
|
26
30
|
return [
|
|
27
|
-
'name'
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
'name' => [Rule::requiredIf($isCreating), 'nullable', 'string', 'max:191'],
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Email and phone are optional.
|
|
35
|
+
*
|
|
36
|
+
* An operational fleet record often has neither: a subcontracted or
|
|
37
|
+
* yard-only driver may have no company mailbox and no handset. The
|
|
38
|
+
* previous contract forced both, and the only way to satisfy it was
|
|
39
|
+
* to invent an address or a number — which then sits in the tenant's
|
|
40
|
+
* user table looking real, can be mailed to, and blocks the genuine
|
|
41
|
+
* value later. Both are still validated and still unique when they
|
|
42
|
+
* are supplied; a driver created without them simply cannot sign in
|
|
43
|
+
* to Navigator until credentials are added.
|
|
44
|
+
*/
|
|
45
|
+
'email' => ['nullable', Rule::when($this->filled('email'), ['email']), $this->uniqueAmongUsers()],
|
|
46
|
+
'phone' => ['nullable', 'string', $this->uniqueAmongUsers()],
|
|
47
|
+
'password' => 'nullable|string',
|
|
48
|
+
'timezone' => 'nullable|string|max:64',
|
|
49
|
+
|
|
50
|
+
// Identity
|
|
51
|
+
'internal_id' => 'nullable|string|max:191',
|
|
52
|
+
'drivers_license_number' => 'nullable|string|max:191',
|
|
53
|
+
'license_expiry' => 'nullable|date',
|
|
54
|
+
'photo' => 'nullable|string',
|
|
55
|
+
|
|
56
|
+
// Operational
|
|
31
57
|
'country' => 'nullable|size:2',
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
58
|
+
'currency' => 'nullable|string|size:3',
|
|
59
|
+
'city' => 'nullable|string|max:191',
|
|
60
|
+
'online' => 'nullable|boolean',
|
|
61
|
+
'current_status' => 'nullable|string|max:64',
|
|
35
62
|
'status' => 'nullable|string|in:active,available,inactive',
|
|
36
|
-
'
|
|
37
|
-
'
|
|
63
|
+
'heading' => 'nullable|numeric',
|
|
64
|
+
'bearing' => 'nullable|numeric',
|
|
65
|
+
'altitude' => 'nullable|numeric',
|
|
66
|
+
'speed' => 'nullable|numeric',
|
|
38
67
|
'location' => ['nullable', new ResolvablePoint()],
|
|
39
68
|
'latitude' => ['nullable', 'required_with:longitude'],
|
|
40
69
|
'longitude' => ['nullable', 'required_with:latitude'],
|
|
70
|
+
|
|
71
|
+
// Structured / orchestrator
|
|
72
|
+
'meta' => 'nullable|array',
|
|
73
|
+
'skills' => 'nullable|array',
|
|
74
|
+
'skills.*' => 'string',
|
|
75
|
+
'max_travel_time' => 'nullable|integer|min:0',
|
|
76
|
+
'max_distance' => 'nullable|integer|min:0',
|
|
77
|
+
'time_window_start' => 'nullable|date_format:H:i,H:i:s',
|
|
78
|
+
'time_window_end' => 'nullable|date_format:H:i,H:i:s',
|
|
79
|
+
|
|
80
|
+
// Relationships, resolved from public ids inside the caller's company
|
|
81
|
+
'vehicle' => ['nullable', 'string', 'starts_with:vehicle_', $this->existsInCompany('vehicles')],
|
|
82
|
+
'vendor' => $this->publicRelationRules('vendors'),
|
|
83
|
+
'job' => $this->publicRelationRules('orders'),
|
|
41
84
|
];
|
|
42
85
|
}
|
|
43
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Uniqueness against every other live user account.
|
|
89
|
+
*
|
|
90
|
+
* Update used to skip this check entirely, so one driver could be given an
|
|
91
|
+
* address another driver already signs in with — and the two would then be
|
|
92
|
+
* indistinguishable to every identity lookup, including the password reset
|
|
93
|
+
* that matches on it. The driver's own linked user is ignored by uuid, so
|
|
94
|
+
* resending an unchanged address or number still succeeds.
|
|
95
|
+
*
|
|
96
|
+
* No return type: the pest harness substitutes its own
|
|
97
|
+
* `Illuminate\Validation\Rule`, which is not an instance of `Rules\Unique`.
|
|
98
|
+
*/
|
|
99
|
+
protected function uniqueAmongUsers()
|
|
100
|
+
{
|
|
101
|
+
$rule = Rule::unique('users')->whereNull('deleted_at');
|
|
102
|
+
|
|
103
|
+
$ownUserUuid = $this->linkedUserUuid();
|
|
104
|
+
|
|
105
|
+
return $ownUserUuid ? $rule->ignore($ownUserUuid, 'uuid') : $rule;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The uuid of the user behind the driver this request is updating.
|
|
110
|
+
*
|
|
111
|
+
* Null on create, and null when there is no resolvable route parameter —
|
|
112
|
+
* which is also what keeps this from querying during rule construction in
|
|
113
|
+
* contexts that have no route bound.
|
|
114
|
+
*/
|
|
115
|
+
protected function linkedUserUuid(): ?string
|
|
116
|
+
{
|
|
117
|
+
if ($this->isMethod('POST')) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
$id = $this->route('id');
|
|
122
|
+
|
|
123
|
+
if (!is_string($id) || $id === '') {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return Driver::withoutGlobalScopes()
|
|
128
|
+
->where('company_uuid', session('company'))
|
|
129
|
+
->where(function ($query) use ($id) {
|
|
130
|
+
$query->where('public_id', $id)->orWhere('internal_id', $id);
|
|
131
|
+
})
|
|
132
|
+
->value('user_uuid');
|
|
133
|
+
}
|
|
134
|
+
|
|
44
135
|
/**
|
|
45
136
|
* Get custom attributes for validator errors.
|
|
46
137
|
*/
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Requests;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Http\Requests\Concerns\ScopesPublicRelationRules;
|
|
5
6
|
use Fleetbase\Http\Requests\FleetbaseRequest;
|
|
6
7
|
use Illuminate\Validation\Rule;
|
|
7
8
|
|
|
8
9
|
class CreateFleetRequest extends FleetbaseRequest
|
|
9
10
|
{
|
|
11
|
+
use ScopesPublicRelationRules;
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
14
|
* Determine if the user is authorized to make this request.
|
|
12
15
|
*/
|
|
@@ -21,8 +24,33 @@ class CreateFleetRequest extends FleetbaseRequest
|
|
|
21
24
|
public function rules(): array
|
|
22
25
|
{
|
|
23
26
|
return [
|
|
24
|
-
'name'
|
|
25
|
-
'
|
|
27
|
+
'name' => [Rule::requiredIf($this->isMethod('POST')), 'nullable', 'string', 'max:191'],
|
|
28
|
+
'color' => 'nullable|string|max:64',
|
|
29
|
+
'task' => 'nullable|string|max:191',
|
|
30
|
+
/*
|
|
31
|
+
* Fleet status is not a closed set on this model: the console, the
|
|
32
|
+
* spreadsheet importer and existing integrations all write free-form
|
|
33
|
+
* values, and `active`/`disabled`/`decommissioned` are the console's
|
|
34
|
+
* options rather than a schema constraint. Restricting the public API
|
|
35
|
+
* to a subset would leave it unable to express a state the console can.
|
|
36
|
+
*/
|
|
37
|
+
'status' => 'nullable|string|max:64',
|
|
38
|
+
'service_area' => $this->publicRelationRules('service_areas'),
|
|
39
|
+
'zone' => $this->publicRelationRules('zones'),
|
|
40
|
+
'vendor' => $this->publicRelationRules('vendors'),
|
|
41
|
+
'parent_fleet' => $this->publicRelationRules('fleets'),
|
|
42
|
+
'photo' => 'nullable|string',
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get custom attributes for validator errors.
|
|
48
|
+
*/
|
|
49
|
+
public function attributes(): array
|
|
50
|
+
{
|
|
51
|
+
return [
|
|
52
|
+
'service_area' => 'service area',
|
|
53
|
+
'parent_fleet' => 'parent fleet',
|
|
26
54
|
];
|
|
27
55
|
}
|
|
28
56
|
}
|
|
@@ -25,8 +25,13 @@ class CreateServiceRateRequest extends FleetbaseRequest
|
|
|
25
25
|
return [
|
|
26
26
|
'service_name' => [Rule::requiredIf($this->isMethod('POST')), 'string'],
|
|
27
27
|
'service_type' => [Rule::requiredIf($this->isMethod('POST')), 'string'],
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// `nullable` so an empty relationship is treated as absent rather than
|
|
29
|
+
// as an invalid one. Both are optional, but without it `exists` runs
|
|
30
|
+
// against the null that ConvertEmptyStringsToNull produced and answers
|
|
31
|
+
// "The selected service area is invalid" for a field the caller simply
|
|
32
|
+
// did not fill in.
|
|
33
|
+
'service_area' => ['nullable', Rule::exists('service_areas', 'public_id')->whereNull('deleted_at')],
|
|
34
|
+
'zone' => ['nullable', Rule::exists('zones', 'public_id')->whereNull('deleted_at')],
|
|
30
35
|
'rate_calculation_method' => [Rule::requiredIf($this->isMethod('POST')), 'string', 'in:fixed_meter,fixed_rate,per_meter,per_drop,algo,parcel'],
|
|
31
36
|
'currency' => ['required', 'size:3'],
|
|
32
37
|
'base_fee' => ['numeric'],
|
|
@@ -2,12 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Requests;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Http\Requests\Concerns\ScopesPublicRelationRules;
|
|
5
6
|
use Fleetbase\FleetOps\Rules\ResolvablePoint;
|
|
6
7
|
use Fleetbase\Http\Requests\FleetbaseRequest;
|
|
7
8
|
use Illuminate\Validation\Rule;
|
|
8
9
|
|
|
9
10
|
class CreateVehicleRequest extends FleetbaseRequest
|
|
10
11
|
{
|
|
12
|
+
use ScopesPublicRelationRules;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The statuses the public API accepts for a vehicle.
|
|
16
|
+
*
|
|
17
|
+
* `active` is retained even though the model rewrites it to `available`, so
|
|
18
|
+
* clients written against the original contract keep working.
|
|
19
|
+
*
|
|
20
|
+
* @var array<int, string>
|
|
21
|
+
*/
|
|
22
|
+
public const STATUSES = [
|
|
23
|
+
'active',
|
|
24
|
+
'available',
|
|
25
|
+
'in_use',
|
|
26
|
+
'maintenance',
|
|
27
|
+
'out_of_service',
|
|
28
|
+
'reserved',
|
|
29
|
+
'retired',
|
|
30
|
+
'staging',
|
|
31
|
+
'on_route',
|
|
32
|
+
'idle',
|
|
33
|
+
'cleaning',
|
|
34
|
+
'awaiting_parts',
|
|
35
|
+
'inspection_due',
|
|
36
|
+
'inspection_failed',
|
|
37
|
+
'accident',
|
|
38
|
+
'compliance_hold',
|
|
39
|
+
'stolen',
|
|
40
|
+
'operational',
|
|
41
|
+
'decommissioned',
|
|
42
|
+
];
|
|
43
|
+
|
|
11
44
|
/**
|
|
12
45
|
* Determine if the user is authorized to make this request.
|
|
13
46
|
*/
|
|
@@ -22,40 +55,126 @@ class CreateVehicleRequest extends FleetbaseRequest
|
|
|
22
55
|
public function rules(): array
|
|
23
56
|
{
|
|
24
57
|
return [
|
|
25
|
-
'status'
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
]),
|
|
48
|
-
],
|
|
58
|
+
'status' => ['nullable', Rule::in(static::STATUSES)],
|
|
59
|
+
|
|
60
|
+
// Identity and description
|
|
61
|
+
'internal_id' => 'nullable|string|max:191',
|
|
62
|
+
'name' => 'nullable|string|max:191',
|
|
63
|
+
'description' => 'nullable|string',
|
|
64
|
+
'make' => 'nullable|string|max:191',
|
|
65
|
+
'model' => 'nullable|string|max:191',
|
|
66
|
+
'model_type' => 'nullable|string|max:191',
|
|
67
|
+
'year' => 'nullable|integer|min:1900|max:2100',
|
|
68
|
+
'trim' => 'nullable|string|max:191',
|
|
69
|
+
'color' => 'nullable|string|max:64',
|
|
70
|
+
'type' => 'nullable|string|max:191',
|
|
71
|
+
'class' => 'nullable|string|max:191',
|
|
72
|
+
'plate_number' => 'nullable|string|max:191',
|
|
73
|
+
'vin' => 'nullable|string|max:64',
|
|
74
|
+
'serial_number' => 'nullable|string|max:191',
|
|
75
|
+
'call_sign' => 'nullable|string|max:191',
|
|
76
|
+
'fuel_card_number' => 'nullable|string|max:191',
|
|
77
|
+
|
|
78
|
+
// Measurement and operation.
|
|
79
|
+
//
|
|
49
80
|
// Validated rather than merely accepted: the model casts odometer
|
|
50
81
|
// to an integer, so an unchecked string would be stored as 0 — a
|
|
51
82
|
// vehicle reporting zero miles rather than an error.
|
|
52
|
-
'odometer'
|
|
53
|
-
'odometer_unit'
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
'
|
|
83
|
+
'odometer' => 'nullable|numeric|min:0',
|
|
84
|
+
'odometer_unit' => 'nullable|string|max:12',
|
|
85
|
+
'odometer_at_purchase' => 'nullable|numeric|min:0',
|
|
86
|
+
'measurement_system' => 'nullable|string|max:32',
|
|
87
|
+
'fuel_type' => 'nullable|string|max:64',
|
|
88
|
+
'fuel_volume_unit' => 'nullable|string|max:12',
|
|
89
|
+
'online' => 'nullable|boolean',
|
|
90
|
+
|
|
91
|
+
// Body, capacity and dimensions
|
|
92
|
+
'transmission' => 'nullable|string|max:64',
|
|
93
|
+
'body_type' => 'nullable|string|max:191',
|
|
94
|
+
'body_sub_type' => 'nullable|string|max:191',
|
|
95
|
+
'usage_type' => 'nullable|string|max:64',
|
|
96
|
+
'ownership_type' => 'nullable|string|max:64',
|
|
97
|
+
'cargo_volume' => 'nullable|numeric|min:0',
|
|
98
|
+
'passenger_volume' => 'nullable|numeric|min:0',
|
|
99
|
+
'interior_volume' => 'nullable|numeric|min:0',
|
|
100
|
+
'weight' => 'nullable|numeric|min:0',
|
|
101
|
+
'width' => 'nullable|numeric|min:0',
|
|
102
|
+
'length' => 'nullable|numeric|min:0',
|
|
103
|
+
'height' => 'nullable|numeric|min:0',
|
|
104
|
+
'towing_capacity' => 'nullable|numeric|min:0',
|
|
105
|
+
'payload_capacity' => 'nullable|numeric|min:0',
|
|
106
|
+
'seating_capacity' => 'nullable|integer|min:0',
|
|
107
|
+
'ground_clearance' => 'nullable|numeric|min:0',
|
|
108
|
+
'bed_length' => 'nullable|numeric|min:0',
|
|
109
|
+
'fuel_capacity' => 'nullable|numeric|min:0',
|
|
110
|
+
|
|
111
|
+
// Lifecycle and financing
|
|
112
|
+
'financing_status' => 'nullable|string|max:64',
|
|
113
|
+
'loan_number_of_payments' => 'nullable|integer|min:0',
|
|
114
|
+
'loan_first_payment' => 'nullable|date',
|
|
115
|
+
'loan_amount' => 'nullable|numeric|min:0',
|
|
116
|
+
'estimated_service_life_distance_unit' => 'nullable|string|max:12',
|
|
117
|
+
'estimated_service_life_distance' => 'nullable|integer|min:0',
|
|
118
|
+
'estimated_service_life_months' => 'nullable|integer|min:0',
|
|
119
|
+
'insurance_value' => 'nullable|numeric|min:0',
|
|
120
|
+
'depreciation_rate' => 'nullable|numeric',
|
|
121
|
+
'current_value' => 'nullable|numeric|min:0',
|
|
122
|
+
'acquisition_cost' => 'nullable|numeric|min:0',
|
|
123
|
+
'currency' => 'nullable|string|size:3',
|
|
124
|
+
'purchased_at' => 'nullable|date',
|
|
125
|
+
'lease_expires_at' => 'nullable|date',
|
|
126
|
+
|
|
127
|
+
// Regulatory and engine specifications
|
|
128
|
+
'emission_standard' => 'nullable|string|max:64',
|
|
129
|
+
'dpf_equipped' => 'nullable|boolean',
|
|
130
|
+
'scr_equipped' => 'nullable|boolean',
|
|
131
|
+
'gvwr' => 'nullable|numeric|min:0',
|
|
132
|
+
'gcwr' => 'nullable|numeric|min:0',
|
|
133
|
+
'engine_number' => 'nullable|string|max:191',
|
|
134
|
+
'engine_model' => 'nullable|string|max:191',
|
|
135
|
+
'engine_make' => 'nullable|string|max:191',
|
|
136
|
+
'engine_family' => 'nullable|string|max:191',
|
|
137
|
+
'engine_configuration' => 'nullable|string|max:191',
|
|
138
|
+
'engine_displacement' => 'nullable|numeric|min:0',
|
|
139
|
+
'engine_size' => 'nullable|numeric|min:0',
|
|
140
|
+
'horsepower' => 'nullable|numeric|min:0',
|
|
141
|
+
'horsepower_rpm' => 'nullable|integer|min:0',
|
|
142
|
+
'torque' => 'nullable|numeric|min:0',
|
|
143
|
+
'torque_rpm' => 'nullable|integer|min:0',
|
|
144
|
+
'number_of_cylinders' => 'nullable|integer|min:0',
|
|
145
|
+
'cylinder_arrangement' => 'nullable|string|max:64',
|
|
146
|
+
|
|
147
|
+
// Structured and descriptive fields
|
|
148
|
+
'specs' => 'nullable|array',
|
|
149
|
+
'details' => 'nullable|array',
|
|
150
|
+
'notes' => 'nullable|string',
|
|
151
|
+
'meta' => 'nullable|array',
|
|
152
|
+
|
|
153
|
+
// Orchestrator
|
|
154
|
+
'skills' => 'nullable|array',
|
|
155
|
+
'skills.*' => 'string',
|
|
156
|
+
'payload_capacity_volume' => 'nullable|numeric|min:0',
|
|
157
|
+
'payload_capacity_pallets' => 'nullable|integer|min:0',
|
|
158
|
+
'payload_capacity_parcels' => 'nullable|integer|min:0',
|
|
159
|
+
'max_tasks' => 'nullable|integer|min:0',
|
|
160
|
+
'time_window_start' => 'nullable|date_format:H:i,H:i:s',
|
|
161
|
+
'time_window_end' => 'nullable|date_format:H:i,H:i:s',
|
|
162
|
+
'return_to_depot' => 'nullable|boolean',
|
|
163
|
+
|
|
164
|
+
// Relationships, resolved from public ids inside the caller's company
|
|
165
|
+
'vendor' => $this->publicRelationRules('vendors'),
|
|
166
|
+
'driver' => $this->publicRelationRules('drivers'),
|
|
167
|
+
'category' => $this->publicRelationRules('categories'),
|
|
168
|
+
'warranty' => $this->publicRelationRules('warranties'),
|
|
169
|
+
'photo' => 'nullable|string',
|
|
170
|
+
|
|
171
|
+
// Location
|
|
172
|
+
'location' => ['nullable', new ResolvablePoint()],
|
|
173
|
+
'latitude' => ['nullable', 'required_with:longitude'],
|
|
174
|
+
'longitude' => ['nullable', 'required_with:latitude'],
|
|
175
|
+
'altitude' => 'nullable|numeric',
|
|
176
|
+
'heading' => 'nullable|numeric',
|
|
177
|
+
'speed' => 'nullable|numeric',
|
|
59
178
|
];
|
|
60
179
|
}
|
|
61
180
|
}
|