@fleetbase/fleetops-engine 0.6.59 → 0.6.60
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 +36 -0
- package/addon/components/vehicle/details/schedules.hbs +2 -2
- package/addon/components/vehicle/details/schedules.js +7 -1
- package/addon/controllers/maintenance/schedules/index/edit.js +10 -1
- package/addon/services/vehicle-actions.js +4 -2
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/config/fleetops.php +44 -0
- package/server/config/geocoder.php +1 -1
- package/server/migrations/2026_08_08_000001_make_sku_and_provider_transaction_unique_indexes_soft_delete_aware.php +152 -0
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +50 -4
- package/server/src/Http/Controllers/Api/v1/DriverController.php +57 -2
- package/server/src/Http/Controllers/Api/v1/GeofenceController.php +34 -2
- package/server/src/Http/Controllers/Api/v1/NavigatorController.php +14 -13
- package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +10 -1
- package/server/src/Http/Controllers/Concerns/ResolvesReviewAccountBypass.php +62 -0
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +1 -1
- package/server/src/Http/Filter/MaintenanceScheduleFilter.php +40 -0
- package/server/src/Http/Requests/CreateCustomerRequest.php +9 -1
- package/server/src/Http/Requests/CreateFuelReportRequest.php +14 -3
- package/server/src/Http/Requests/CreateFuelTransactionRequest.php +64 -1
- package/server/src/Http/Requests/CreatePartRequest.php +39 -1
- package/server/src/Http/Resources/v1/TrackingNumber.php +33 -0
- package/server/src/Models/FuelReport.php +21 -0
- package/server/src/Models/Sensor.php +24 -0
- package/server/src/Support/FuelProviders/FuelProviderService.php +10 -1
- package/server/src/routes.php +1 -1
- package/server/tests/ApiCustomerControllerContractsTest.php +168 -0
- package/server/tests/ApiDriverControllerContractsTest.php +32 -0
- package/server/tests/CompactResourceSerializationTest.php +93 -0
- package/server/tests/CustomerEndpointTest.php +4 -1
- package/server/tests/Feature/Http/Api/DriverControllerAuthFlowsTest.php +81 -2
- package/server/tests/Feature/Http/Api/GeofenceControllerContractsTest.php +99 -2
- package/server/tests/Feature/Http/Api/TrackingNumberControllerHelpersTest.php +12 -6
- package/server/tests/Feature/Http/Internal/DriverControllerContractsTest.php +58 -0
- package/server/tests/FuelProviderTransactionControllerContractsTest.php +17 -0
- package/server/tests/RequestContractsTest.php +97 -5
- package/server/tests/SmallControllerContractsTest.php +28 -4
- package/server/tests/Unit/Http/Filter/MaintenanceScheduleFilterTest.php +69 -0
- package/server/tests/Unit/Http/Requests/CreateFuelReportRequestTest.php +21 -0
- package/server/tests/Unit/Jobs/GeofenceDwellAndBulkNotifyTest.php +7 -0
- package/server/tests/Unit/Models/SensorLastPositionDefaultTest.php +130 -0
|
@@ -10,25 +10,21 @@ use Illuminate\Http\JsonResponse;
|
|
|
10
10
|
class NavigatorController extends Controller
|
|
11
11
|
{
|
|
12
12
|
/**
|
|
13
|
-
* Retrieve the driver onboard settings.
|
|
13
|
+
* Retrieve the driver onboard settings for an organization.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* then fetches the saved driver onboard settings. If settings for the current company are found, they are returned,
|
|
18
|
-
* otherwise, default settings are provided.
|
|
19
|
-
*
|
|
20
|
-
* @return JsonResponse
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Retrieve driver onboard settings.
|
|
15
|
+
* The organization is resolved from its public id. An unknown public id is a client
|
|
16
|
+
* error, not a server error, so it answers 404 rather than dereferencing a null company.
|
|
25
17
|
*
|
|
26
18
|
* @return JsonResponse
|
|
27
19
|
*/
|
|
28
20
|
public function getDriverOnboardSettings($companyId)
|
|
29
21
|
{
|
|
30
|
-
$company
|
|
31
|
-
|
|
22
|
+
$company = $this->findCompanyByPublicId($companyId);
|
|
23
|
+
if (!$company) {
|
|
24
|
+
return $this->errorResponse('Organization not found.', 404);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
$driverOnboardSettings = $this->driverOnboardSetting($company->uuid);
|
|
32
28
|
if (!$driverOnboardSettings) {
|
|
33
29
|
$driverOnboardSettings = [];
|
|
34
30
|
}
|
|
@@ -50,4 +46,9 @@ class NavigatorController extends Controller
|
|
|
50
46
|
{
|
|
51
47
|
return response()->json($payload);
|
|
52
48
|
}
|
|
49
|
+
|
|
50
|
+
protected function errorResponse(string $message, int $statusCode): JsonResponse
|
|
51
|
+
{
|
|
52
|
+
return response()->error($message, $statusCode);
|
|
53
|
+
}
|
|
53
54
|
}
|
|
@@ -200,7 +200,16 @@ class TrackingNumberController extends Controller
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
// No raw-row fallback. Utils::findModel() was called with the ARRAY of table names
|
|
204
|
+
// and, when nothing matched, passed that array to DB::table() — which stringified
|
|
205
|
+
// it to the literal table name "Array" and threw SQLSTATE[42S02]. Every
|
|
206
|
+
// unresolvable code became a 500 instead of the 400 this method's caller already
|
|
207
|
+
// handles.
|
|
208
|
+
//
|
|
209
|
+
// The fallback could not have helped anyway: qrModelResource() serializes through
|
|
210
|
+
// a typed resource keyed on the model class, and a raw stdClass row has none. The
|
|
211
|
+
// mapped models above are the endpoint's whole contract.
|
|
212
|
+
return null;
|
|
204
213
|
}
|
|
205
214
|
|
|
206
215
|
protected function qrModelResource($model)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Http\Controllers\Concerns;
|
|
4
|
+
|
|
5
|
+
use Illuminate\Support\Facades\Log;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Accepts a fixed verification code, but only for explicitly designated accounts.
|
|
9
|
+
*
|
|
10
|
+
* App store reviewers cannot receive our SMS or email, so a fixed code has to keep
|
|
11
|
+
* working for them — including in production, which is where a review build is
|
|
12
|
+
* tested. Refusing the bypass outside production made review impossible; refusing
|
|
13
|
+
* it for everyone except named accounts keeps review working while removing the
|
|
14
|
+
* real hazard, which was that anyone holding the code could authenticate as any
|
|
15
|
+
* driver or customer.
|
|
16
|
+
*/
|
|
17
|
+
trait ResolvesReviewAccountBypass
|
|
18
|
+
{
|
|
19
|
+
/**
|
|
20
|
+
* @param string $codeKey config key holding the bypass code
|
|
21
|
+
* @param string $accountsKey config key holding the allowlisted identities
|
|
22
|
+
* @param string|null $identity identity being authenticated, already normalised
|
|
23
|
+
* @param string|null $code submitted verification code
|
|
24
|
+
* @param string $channel label for the audit log entry
|
|
25
|
+
*/
|
|
26
|
+
protected static function reviewAccountBypassMatches(
|
|
27
|
+
string $codeKey,
|
|
28
|
+
string $accountsKey,
|
|
29
|
+
?string $identity,
|
|
30
|
+
?string $code,
|
|
31
|
+
string $channel,
|
|
32
|
+
): bool {
|
|
33
|
+
$bypassCode = config($codeKey);
|
|
34
|
+
|
|
35
|
+
// `!== null && !== ''` rather than `!empty()`: `!empty('0')` is false, so a
|
|
36
|
+
// configured code of "0" would otherwise be silently ignored.
|
|
37
|
+
if ($bypassCode === null || $bypassCode === '' || $code === null || $code === '' || $identity === null || $identity === '') {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
$accounts = array_map(
|
|
42
|
+
static fn ($account) => strtolower(trim((string) $account)),
|
|
43
|
+
(array) config($accountsKey, [])
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (!in_array(strtolower(trim($identity)), $accounts, true)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Constant-time, so a wrong code cannot be recovered by timing the response.
|
|
51
|
+
if (!hash_equals((string) $bypassCode, (string) $code)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Log::warning('[Fleetbase] Verification bypass accepted for a review account.', [
|
|
56
|
+
'channel' => $channel,
|
|
57
|
+
'identity' => $identity,
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -665,7 +665,7 @@ class DriverController extends FleetOpsController
|
|
|
665
665
|
|
|
666
666
|
// Find and verify code
|
|
667
667
|
$verificationCode = static::verificationCodeExists($user, $code, $for);
|
|
668
|
-
if (!$verificationCode && $code
|
|
668
|
+
if (!$verificationCode && !ApiDriverController::verificationBypassMatches($identity, $code)) {
|
|
669
669
|
return response()->error('Invalid verification code!');
|
|
670
670
|
}
|
|
671
671
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\FleetOps\Http\Filter;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\FleetOps\Support\Utils;
|
|
6
|
+
use Fleetbase\Http\Filter\Filter;
|
|
7
|
+
use Illuminate\Support\Str;
|
|
8
|
+
|
|
9
|
+
class MaintenanceScheduleFilter extends Filter
|
|
10
|
+
{
|
|
11
|
+
public function queryForInternal(): void
|
|
12
|
+
{
|
|
13
|
+
$this->builder->where('company_uuid', $this->session->get('company'));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public function queryForPublic(): void
|
|
17
|
+
{
|
|
18
|
+
$this->queryForInternal();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public function subjectType(?string $subjectType): void
|
|
22
|
+
{
|
|
23
|
+
if (!$subjectType) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!Str::contains($subjectType, ['\\', ':'])) {
|
|
28
|
+
$subjectType = 'fleet-ops:' . $subjectType;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
$this->builder->where('subject_type', Utils::getMutationType($subjectType));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public function subjectUuid(?string $subjectUuid): void
|
|
35
|
+
{
|
|
36
|
+
if ($subjectUuid) {
|
|
37
|
+
$this->builder->where('subject_uuid', $subjectUuid);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -23,7 +23,15 @@ class CreateCustomerRequest extends FleetbaseRequest
|
|
|
23
23
|
{
|
|
24
24
|
return [
|
|
25
25
|
'identity' => 'required|string',
|
|
26
|
-
|
|
26
|
+
// Presence only — authorizing the code is the controller's job, and its
|
|
27
|
+
// check is strictly stronger: it matches code + for + meta->identity,
|
|
28
|
+
// whereas `exists:verification_codes,code` accepts any live code issued
|
|
29
|
+
// for any purpose to any user. The rule is also already unenforced on the
|
|
30
|
+
// proxy path, since verifyCode() with for=fleetops_create_customer calls
|
|
31
|
+
// create(CreateCustomerRequest::createFrom($request)), which never runs
|
|
32
|
+
// validateResolved(). Keeping it here only blocks the configured
|
|
33
|
+
// non-production testing bypass (fleetops.customers.verification_bypass_code).
|
|
34
|
+
'code' => 'required|string',
|
|
27
35
|
'name' => 'required|string',
|
|
28
36
|
'password' => 'required|string|min:8',
|
|
29
37
|
'email' => [
|
|
@@ -19,10 +19,21 @@ class CreateFuelReportRequest extends FleetbaseRequest
|
|
|
19
19
|
*/
|
|
20
20
|
public function rules(): array
|
|
21
21
|
{
|
|
22
|
+
// requiredIf(POST), not required: UpdateFuelReportRequest extends this class, so a
|
|
23
|
+
// flat `required` made every partial update fail — PUT /v1/fuel-reports/{id} with
|
|
24
|
+
// just {"status": "approved"} answered "The driver field is required."
|
|
25
|
+
//
|
|
26
|
+
// `driver` is the clearest case: the update action's $request->only() list does not
|
|
27
|
+
// even include it, so validation demanded a field the endpoint then discarded.
|
|
28
|
+
// CreateFuelTransactionRequest solves the same create/update inheritance with
|
|
29
|
+
// Rule::requiredIf(isMethod('POST')); a plain conditional is used here because
|
|
30
|
+
// illuminate/validation is not autoloadable in the package's test harness.
|
|
31
|
+
$requiredOnCreate = $this->isMethod('POST') ? ['required'] : ['sometimes'];
|
|
32
|
+
|
|
22
33
|
return [
|
|
23
|
-
'driver' =>
|
|
24
|
-
'odometer' =>
|
|
25
|
-
'volume' =>
|
|
34
|
+
'driver' => $requiredOnCreate,
|
|
35
|
+
'odometer' => $requiredOnCreate,
|
|
36
|
+
'volume' => $requiredOnCreate,
|
|
26
37
|
'metric_unit' => ['nullable'],
|
|
27
38
|
'location' => ['nullable'],
|
|
28
39
|
'amount' => ['nullable'],
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Requests;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Models\FuelProviderTransaction;
|
|
5
6
|
use Fleetbase\Http\Requests\FleetbaseRequest;
|
|
6
7
|
use Illuminate\Validation\Rule;
|
|
7
8
|
|
|
@@ -16,7 +17,7 @@ class CreateFuelTransactionRequest extends FleetbaseRequest
|
|
|
16
17
|
{
|
|
17
18
|
return [
|
|
18
19
|
'provider' => [Rule::requiredIf($this->isMethod('POST')), 'string'],
|
|
19
|
-
'provider_transaction_id' => [Rule::requiredIf($this->isMethod('POST')), 'string'],
|
|
20
|
+
'provider_transaction_id' => [Rule::requiredIf($this->isMethod('POST')), 'string', $this->uniqueProviderTransactionIdRule()],
|
|
20
21
|
'connection' => ['nullable', 'string'],
|
|
21
22
|
'fuel_report' => ['nullable', 'string'],
|
|
22
23
|
'vehicle' => ['nullable', 'string'],
|
|
@@ -47,4 +48,66 @@ class CreateFuelTransactionRequest extends FleetbaseRequest
|
|
|
47
48
|
'meta' => ['nullable', 'array'],
|
|
48
49
|
];
|
|
49
50
|
}
|
|
51
|
+
|
|
52
|
+
public function messages(): array
|
|
53
|
+
{
|
|
54
|
+
return [
|
|
55
|
+
'provider_transaction_id.unique' => 'A fuel transaction with this provider transaction id already exists for this provider.',
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Mirrors the fuel_provider_txn_company_provider_unique index.
|
|
61
|
+
*
|
|
62
|
+
* Provider transaction ids are natural idempotency keys, so a re-sent batch
|
|
63
|
+
* used to raise an unhandled UniqueConstraintViolationException — an HTTP 500
|
|
64
|
+
* rather than a duplicate signal the client could act on.
|
|
65
|
+
*
|
|
66
|
+
* @return \Illuminate\Validation\Rules\Unique
|
|
67
|
+
*/
|
|
68
|
+
protected function uniqueProviderTransactionIdRule()
|
|
69
|
+
{
|
|
70
|
+
$provider = $this->resolveProvider();
|
|
71
|
+
|
|
72
|
+
$rule = Rule::unique('fuel_provider_transactions', 'provider_transaction_id')
|
|
73
|
+
->where(function ($query) use ($provider) {
|
|
74
|
+
$query->where('company_uuid', session('company'));
|
|
75
|
+
$query->where('provider', $provider);
|
|
76
|
+
|
|
77
|
+
return $query->whereNull('deleted_at');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// On PUT the record re-sends its own provider_transaction_id. The v1 route
|
|
81
|
+
// parameter is a public_id (fuel_provider_transaction_xxxxx), not a uuid.
|
|
82
|
+
$id = $this->route('id');
|
|
83
|
+
if (!$this->isMethod('POST') && filled($id)) {
|
|
84
|
+
$rule->ignore($id, 'public_id');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return $rule;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The uniqueness scope is per provider, but `provider` is only required on
|
|
92
|
+
* POST — a PUT may change the transaction id while leaving the provider out of
|
|
93
|
+
* the body. Fall back to the stored value so the scope is never null, which
|
|
94
|
+
* would otherwise compare against the wrong set of rows.
|
|
95
|
+
*/
|
|
96
|
+
protected function resolveProvider(): ?string
|
|
97
|
+
{
|
|
98
|
+
if ($this->filled('provider')) {
|
|
99
|
+
return $this->input('provider');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
$id = $this->route('id');
|
|
103
|
+
if (blank($id)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return FuelProviderTransaction::where('company_uuid', session('company'))
|
|
108
|
+
->where(function ($query) use ($id) {
|
|
109
|
+
$query->where('public_id', $id)->orWhere('uuid', $id);
|
|
110
|
+
})
|
|
111
|
+
->value('provider');
|
|
112
|
+
}
|
|
50
113
|
}
|
|
@@ -15,7 +15,7 @@ class CreatePartRequest extends FleetbaseRequest
|
|
|
15
15
|
public function rules(): array
|
|
16
16
|
{
|
|
17
17
|
return [
|
|
18
|
-
'sku' => ['nullable', 'string'],
|
|
18
|
+
'sku' => ['nullable', 'string', $this->uniqueSkuRule()],
|
|
19
19
|
'name' => [Rule::requiredIf($this->isMethod('POST')), 'string'],
|
|
20
20
|
'manufacturer' => ['nullable', 'string'],
|
|
21
21
|
'model' => ['nullable', 'string'],
|
|
@@ -37,4 +37,42 @@ class CreatePartRequest extends FleetbaseRequest
|
|
|
37
37
|
'meta' => ['nullable', 'array'],
|
|
38
38
|
];
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
public function messages(): array
|
|
42
|
+
{
|
|
43
|
+
return [
|
|
44
|
+
'sku.unique' => 'A part with this SKU already exists.',
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Mirrors the parts_company_uuid_active_sku_unique index.
|
|
50
|
+
*
|
|
51
|
+
* Without this rule a duplicate SKU reached the driver and came back as an
|
|
52
|
+
* unhandled UniqueConstraintViolationException — an HTTP 500 with no field
|
|
53
|
+
* attribution, since public v1 controllers have no QueryException catch.
|
|
54
|
+
*
|
|
55
|
+
* `nullable` runs first, so this never fires on a null SKU; that matches the
|
|
56
|
+
* nullable column and MySQL's tolerance of repeated NULLs in a unique index.
|
|
57
|
+
*
|
|
58
|
+
* @return \Illuminate\Validation\Rules\Unique
|
|
59
|
+
*/
|
|
60
|
+
protected function uniqueSkuRule()
|
|
61
|
+
{
|
|
62
|
+
$rule = Rule::unique('parts', 'sku')
|
|
63
|
+
->where(function ($query) {
|
|
64
|
+
$query->where('company_uuid', session('company'));
|
|
65
|
+
|
|
66
|
+
return $query->whereNull('deleted_at');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// On PUT the record re-sends its own SKU, so it must not collide with
|
|
70
|
+
// itself. The v1 route parameter is a public_id (part_xxxxx), not a uuid.
|
|
71
|
+
$id = $this->route('id');
|
|
72
|
+
if (!$this->isMethod('POST') && filled($id)) {
|
|
73
|
+
$rule->ignore($id, 'public_id');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return $rule;
|
|
77
|
+
}
|
|
40
78
|
}
|
|
@@ -30,6 +30,17 @@ class TrackingNumber extends FleetbaseResource
|
|
|
30
30
|
'status' => $this->last_status,
|
|
31
31
|
'status_code' => $this->last_status_code,
|
|
32
32
|
'qr_code' => $this->qr_code,
|
|
33
|
+
// The raw text encoded inside qr_code, exposed ONLY in debug mode.
|
|
34
|
+
//
|
|
35
|
+
// qr_code is a base64 PNG generated from owner_uuid (TrackingNumber::newBarcode),
|
|
36
|
+
// and the endpoints that consume a scanned code match on that uuid. A client
|
|
37
|
+
// scans the image to obtain it; an automated contract run cannot, so debug
|
|
38
|
+
// builds publish the value beside the image rather than the API growing a
|
|
39
|
+
// decode endpoint or handing out uuids generally.
|
|
40
|
+
//
|
|
41
|
+
// `when()` omits the key entirely when false — it is absent, not null — so a
|
|
42
|
+
// production response is byte-identical to before.
|
|
43
|
+
'qr_code_content' => $this->when(static::exposesQrCodeContent(), fn () => $this->owner_uuid),
|
|
33
44
|
'barcode' => $this->barcode,
|
|
34
45
|
'url' => Utils::consoleUrl('track-order', ['order' => $this->tracking_number]),
|
|
35
46
|
'type' => Utils::getTypeFromClassName($this->owner_type),
|
|
@@ -38,6 +49,28 @@ class TrackingNumber extends FleetbaseResource
|
|
|
38
49
|
];
|
|
39
50
|
}
|
|
40
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Whether the QR code's decoded content may be published.
|
|
54
|
+
*
|
|
55
|
+
* Debug mode only, and fails closed: any failure to determine the debug state — no
|
|
56
|
+
* container, an application without the accessor — answers false rather than
|
|
57
|
+
* defaulting to exposure.
|
|
58
|
+
*/
|
|
59
|
+
protected static function exposesQrCodeContent(): bool
|
|
60
|
+
{
|
|
61
|
+
try {
|
|
62
|
+
$app = app();
|
|
63
|
+
|
|
64
|
+
if (!is_object($app) || !method_exists($app, 'hasDebugModeEnabled')) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (bool) $app->hasDebugModeEnabled();
|
|
69
|
+
} catch (\Throwable $e) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
41
74
|
/**
|
|
42
75
|
* Transform the resource into an webhook payload.
|
|
43
76
|
*
|
|
@@ -6,6 +6,7 @@ use Fleetbase\Casts\Json;
|
|
|
6
6
|
use Fleetbase\FleetOps\Casts\Point;
|
|
7
7
|
use Fleetbase\FleetOps\Support\Utils;
|
|
8
8
|
use Fleetbase\LaravelMysqlSpatial\Eloquent\SpatialTrait;
|
|
9
|
+
use Fleetbase\LaravelMysqlSpatial\Types\Point as SpatialPoint;
|
|
9
10
|
use Fleetbase\Models\Model;
|
|
10
11
|
use Fleetbase\Models\User;
|
|
11
12
|
use Fleetbase\Traits\HasApiModelBehavior;
|
|
@@ -149,6 +150,26 @@ class FuelReport extends Model
|
|
|
149
150
|
return $this->belongsTo(User::class, 'reported_by_uuid');
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Bootstrap the model.
|
|
155
|
+
*
|
|
156
|
+
* `location` is declared nullable in CreateFuelReportRequest but the column is
|
|
157
|
+
* POINT NOT NULL, so a report created without one failed outright with
|
|
158
|
+
* SQLSTATE[HY000] 1364 "Field 'location' doesn't have a default value" — a 500 for
|
|
159
|
+
* a request the validator had just accepted. Mirrors Device and Sensor, which
|
|
160
|
+
* default their spatial columns the same way.
|
|
161
|
+
*/
|
|
162
|
+
protected static function boot()
|
|
163
|
+
{
|
|
164
|
+
parent::boot();
|
|
165
|
+
|
|
166
|
+
static::creating(function (self $fuelReport) {
|
|
167
|
+
if (empty($fuelReport->location)) {
|
|
168
|
+
$fuelReport->location = new SpatialPoint(0, 0);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
152
173
|
/**
|
|
153
174
|
* Get the driver's name assigned to vehicle.
|
|
154
175
|
*
|
|
@@ -147,6 +147,30 @@ class Sensor extends Model
|
|
|
147
147
|
*/
|
|
148
148
|
protected $spatialFields = ['last_position'];
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Bootstrap the model.
|
|
152
|
+
*
|
|
153
|
+
* The same migration that made devices.last_position NOT NULL did it to
|
|
154
|
+
* sensors.last_position too, but only Device got a default. Creating a sensor
|
|
155
|
+
* without a position therefore failed outright:
|
|
156
|
+
*
|
|
157
|
+
* SQLSTATE[HY000]: General error: 1364 Field 'last_position' doesn't have a
|
|
158
|
+
* default value
|
|
159
|
+
*
|
|
160
|
+
* Position is not something a caller has when registering a sensor, so default
|
|
161
|
+
* it to POINT(0,0) on create, exactly as Device does.
|
|
162
|
+
*/
|
|
163
|
+
protected static function boot()
|
|
164
|
+
{
|
|
165
|
+
parent::boot();
|
|
166
|
+
|
|
167
|
+
static::creating(function (self $sensor) {
|
|
168
|
+
if (empty($sensor->last_position)) {
|
|
169
|
+
$sensor->last_position = new SpatialPoint(0, 0);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
150
174
|
/**
|
|
151
175
|
* The attributes that should be cast to native types.
|
|
152
176
|
*
|
|
@@ -173,8 +173,17 @@ class FuelProviderService
|
|
|
173
173
|
return DB::transaction(function () use ($connection, $payload) {
|
|
174
174
|
$provider = $payload['provider'] ?? $connection->provider;
|
|
175
175
|
$providerTransactionId = $payload['provider_transaction_id'];
|
|
176
|
+
// The match must include company_uuid. Provider transaction ids are only
|
|
177
|
+
// unique within the account they were issued for, so keying on
|
|
178
|
+
// (provider, provider_transaction_id) alone let one company's sync find
|
|
179
|
+
// and overwrite another company's transaction — reassigning its
|
|
180
|
+
// company_uuid. Mirrors fuel_provider_txn_company_provider_unique.
|
|
176
181
|
$transaction = FuelProviderTransaction::updateOrCreate(
|
|
177
|
-
[
|
|
182
|
+
[
|
|
183
|
+
'company_uuid' => $connection->company_uuid,
|
|
184
|
+
'provider' => $provider,
|
|
185
|
+
'provider_transaction_id' => $providerTransactionId,
|
|
186
|
+
],
|
|
178
187
|
array_merge($payload, [
|
|
179
188
|
'company_uuid' => $connection->company_uuid,
|
|
180
189
|
'fuel_provider_connection_uuid' => $connection->uuid,
|
package/server/src/routes.php
CHANGED
|
@@ -237,7 +237,7 @@ Route::prefix(config('fleetops.api.routing.prefix'))->namespace('Fleetbase\Fleet
|
|
|
237
237
|
$router->get('events', 'GeofenceController@events');
|
|
238
238
|
$router->get('inventory', 'GeofenceController@inventory');
|
|
239
239
|
$router->get('dwell-report', 'GeofenceController@dwellReport');
|
|
240
|
-
$router->get('driver/{
|
|
240
|
+
$router->get('driver/{driverId}/history', 'GeofenceController@driverHistory');
|
|
241
241
|
});
|
|
242
242
|
// service-rates routes
|
|
243
243
|
$router->group(['prefix' => 'service-rates'], function () use ($router) {
|