@fleetbase/fleetops-engine 0.6.33 → 0.6.35
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/addon/components/order/details/proof.js +28 -1
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +1 -1
- package/server/src/Console/Commands/TestEmail.php +7 -7
- package/server/src/Http/Controllers/Api/v1/ContactController.php +16 -37
- package/server/src/Http/Controllers/Api/v1/DriverController.php +8 -29
- package/server/src/Http/Controllers/Api/v1/OrderController.php +12 -15
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/DriverController.php +1 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +6 -6
- package/server/src/Http/Resources/v1/Driver.php +2 -2
- package/server/src/Http/Resources/v1/Index/Driver.php +2 -1
- package/server/src/Http/Resources/v1/Index/Place.php +1 -1
- package/server/src/Http/Resources/v1/Index/Vehicle.php +2 -2
- package/server/src/Http/Resources/v1/Place.php +1 -1
- package/server/src/Http/Resources/v1/Vehicle.php +2 -3
- package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +2 -3
- package/server/src/Models/Payload.php +26 -0
- package/server/src/Models/ServiceRate.php +111 -48
- package/server/src/Support/Utils.php +18 -0
|
@@ -1,3 +1,30 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
|
+
import { tracked } from '@glimmer/tracking';
|
|
3
|
+
import { inject as service } from '@ember/service';
|
|
4
|
+
import { task } from 'ember-concurrency-decorators';
|
|
2
5
|
|
|
3
|
-
export default class OrderDetailsProofComponent extends Component {
|
|
6
|
+
export default class OrderDetailsProofComponent extends Component {
|
|
7
|
+
@service fetch;
|
|
8
|
+
@tracked proofs = [];
|
|
9
|
+
|
|
10
|
+
constructor(owner, { resource }) {
|
|
11
|
+
super(...arguments);
|
|
12
|
+
this.loadOrderProofs.perform(resource);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@task *loadOrderProofs(order) {
|
|
16
|
+
const proofs = yield this.fetch.get(`orders/${order.id}/proofs`);
|
|
17
|
+
|
|
18
|
+
this.proofs = proofs.map((proof) => ({
|
|
19
|
+
...proof,
|
|
20
|
+
type: this.#getTypeFromRemarks(proof.remarks),
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#getTypeFromRemarks(remarks = '') {
|
|
25
|
+
if (remarks.endsWith('Photo')) return 'photo';
|
|
26
|
+
if (remarks.endsWith('Scan')) return 'scan';
|
|
27
|
+
if (remarks.endsWith('Signature')) return 'signature';
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/composer.json
CHANGED
package/extension.json
CHANGED
package/package.json
CHANGED
|
@@ -33,7 +33,7 @@ class TestEmail extends Command
|
|
|
33
33
|
public function handle()
|
|
34
34
|
{
|
|
35
35
|
$email = $this->argument('email');
|
|
36
|
-
$type
|
|
36
|
+
$type = $this->option('type');
|
|
37
37
|
|
|
38
38
|
$this->info('Sending test email...');
|
|
39
39
|
$this->info("Type: {$type}");
|
|
@@ -47,40 +47,40 @@ class TestEmail extends Command
|
|
|
47
47
|
|
|
48
48
|
default:
|
|
49
49
|
$this->error("Unknown email type: {$type}");
|
|
50
|
+
|
|
50
51
|
return Command::FAILURE;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
$this->info('✓ Test email sent successfully!');
|
|
55
|
+
|
|
54
56
|
return Command::SUCCESS;
|
|
55
57
|
} catch (\Exception $e) {
|
|
56
58
|
$this->error('Failed to send test email: ' . $e->getMessage());
|
|
59
|
+
|
|
57
60
|
return Command::FAILURE;
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
/**
|
|
62
65
|
* Send a test customer credentials email.
|
|
63
|
-
*
|
|
64
|
-
* @param string $email
|
|
65
|
-
* @return void
|
|
66
66
|
*/
|
|
67
67
|
private function sendCustomerCredentialsEmail(string $email): void
|
|
68
68
|
{
|
|
69
69
|
// Create a mock user
|
|
70
70
|
$user = new User([
|
|
71
|
-
'name'
|
|
71
|
+
'name' => 'Test Customer',
|
|
72
72
|
'email' => $email,
|
|
73
73
|
]);
|
|
74
74
|
|
|
75
75
|
// Create a mock company
|
|
76
76
|
$company = new Company([
|
|
77
|
-
'name'
|
|
77
|
+
'name' => 'Test Company',
|
|
78
78
|
'public_id' => 'test_company_123',
|
|
79
79
|
]);
|
|
80
80
|
|
|
81
81
|
// Create a mock customer
|
|
82
82
|
$customer = new Contact([
|
|
83
|
-
'name'
|
|
83
|
+
'name' => 'Test Customer',
|
|
84
84
|
'email' => $email,
|
|
85
85
|
'phone' => '+1234567890',
|
|
86
86
|
]);
|
|
@@ -8,7 +8,6 @@ use Fleetbase\FleetOps\Http\Resources\v1\Contact as ContactResource;
|
|
|
8
8
|
use Fleetbase\FleetOps\Http\Resources\v1\DeletedResource;
|
|
9
9
|
use Fleetbase\FleetOps\Models\Contact;
|
|
10
10
|
use Fleetbase\Http\Controllers\Controller;
|
|
11
|
-
use Fleetbase\Models\File;
|
|
12
11
|
use Fleetbase\Models\User;
|
|
13
12
|
use Fleetbase\Support\Utils;
|
|
14
13
|
use Illuminate\Http\Request;
|
|
@@ -29,24 +28,13 @@ class ContactController extends Controller
|
|
|
29
28
|
$input['phone'] = is_string($input['phone']) ? Utils::formatPhoneNumber($input['phone']) : $input['phone'];
|
|
30
29
|
$input['type'] = empty($input['type']) ? 'contact' : $input['type'];
|
|
31
30
|
|
|
32
|
-
// Handle photo
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (Utils::isPublicId($photo)) {
|
|
37
|
-
$file = File::where('public_id', $photo)->first();
|
|
38
|
-
if ($file) {
|
|
39
|
-
$input['photo_uuid'] = $file->uuid;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
31
|
+
// Handle photo upload using FileResolverService
|
|
32
|
+
if ($request->has('photo')) {
|
|
33
|
+
$path = 'uploads/' . session('company') . '/contacts';
|
|
34
|
+
$file = app(\Fleetbase\Services\FileResolverService::class)->resolve($request->input('photo'), $path);
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
$path = implode('/', ['uploads', session('company'), 'contacts']);
|
|
46
|
-
$file = File::createFromBase64($photo, null, $path);
|
|
47
|
-
if ($file) {
|
|
48
|
-
$input['photo_uuid'] = $file->uuid;
|
|
49
|
-
}
|
|
36
|
+
if ($file) {
|
|
37
|
+
$input['photo_uuid'] = $file->uuid;
|
|
50
38
|
}
|
|
51
39
|
}
|
|
52
40
|
|
|
@@ -101,29 +89,20 @@ class ContactController extends Controller
|
|
|
101
89
|
]);
|
|
102
90
|
}
|
|
103
91
|
|
|
104
|
-
// Handle photo
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// Handle photo being a file id
|
|
108
|
-
if (Utils::isPublicId($photo)) {
|
|
109
|
-
$file = File::where('public_id', $photo)->first();
|
|
110
|
-
if ($file) {
|
|
111
|
-
$input['photo_uuid'] = $file->uuid;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Handle the photo being base64 data string
|
|
116
|
-
if (Utils::isBase64String($photo)) {
|
|
117
|
-
$path = implode('/', ['uploads', session('company'), 'customers']);
|
|
118
|
-
$file = File::createFromBase64($photo, null, $path);
|
|
119
|
-
if ($file) {
|
|
120
|
-
$input['photo_uuid'] = $file->uuid;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
92
|
+
// Handle photo upload using FileResolverService
|
|
93
|
+
if ($request->has('photo')) {
|
|
94
|
+
$photo = $request->input('photo');
|
|
123
95
|
|
|
124
96
|
// Handle removal key
|
|
125
97
|
if ($photo === 'REMOVE') {
|
|
126
98
|
$input['photo_uuid'] = null;
|
|
99
|
+
} else {
|
|
100
|
+
$path = 'uploads/' . session('company') . '/contacts';
|
|
101
|
+
$file = app(\Fleetbase\Services\FileResolverService::class)->resolve($photo, $path);
|
|
102
|
+
|
|
103
|
+
if ($file) {
|
|
104
|
+
$input['photo_uuid'] = $file->uuid;
|
|
105
|
+
}
|
|
127
106
|
}
|
|
128
107
|
}
|
|
129
108
|
|
|
@@ -20,7 +20,6 @@ use Fleetbase\Http\Resources\Organization;
|
|
|
20
20
|
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
21
21
|
use Fleetbase\Models\Company;
|
|
22
22
|
use Fleetbase\Models\CompanyUser;
|
|
23
|
-
use Fleetbase\Models\File;
|
|
24
23
|
use Fleetbase\Models\User;
|
|
25
24
|
use Fleetbase\Models\UserDevice;
|
|
26
25
|
use Fleetbase\Models\VerificationCode;
|
|
@@ -124,20 +123,10 @@ class DriverController extends Controller
|
|
|
124
123
|
// create the driver
|
|
125
124
|
$driver = Driver::create($input);
|
|
126
125
|
|
|
127
|
-
// Handle photo
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
$file =
|
|
131
|
-
// Handle photo being a file id
|
|
132
|
-
if (Utils::isPublicId($photo)) {
|
|
133
|
-
$file = File::where('public_id', $photo)->first();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Handle the photo being base64 data string
|
|
137
|
-
if (Utils::isBase64String($photo)) {
|
|
138
|
-
$path = implode('/', ['uploads', session('company'), 'drivers']);
|
|
139
|
-
$file = File::createFromBase64($photo, null, $path);
|
|
140
|
-
}
|
|
126
|
+
// Handle photo upload using FileResolverService
|
|
127
|
+
if ($request->has('photo')) {
|
|
128
|
+
$path = 'uploads/' . $company->uuid . '/drivers';
|
|
129
|
+
$file = app(\Fleetbase\Services\FileResolverService::class)->resolve($request->input('photo'), $path);
|
|
141
130
|
|
|
142
131
|
if ($file) {
|
|
143
132
|
$user->update(['photo_uuid' => $file->uuid]);
|
|
@@ -218,20 +207,10 @@ class DriverController extends Controller
|
|
|
218
207
|
$driver->update($input);
|
|
219
208
|
$driver->flushAttributesCache();
|
|
220
209
|
|
|
221
|
-
// Handle photo
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
$file =
|
|
225
|
-
// Handle photo being a file id
|
|
226
|
-
if (Utils::isPublicId($photo)) {
|
|
227
|
-
$file = File::where('public_id', $photo)->first();
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Handle the photo being base64 data string
|
|
231
|
-
if (Utils::isBase64String($photo)) {
|
|
232
|
-
$path = implode('/', ['uploads', session('company'), 'drivers']);
|
|
233
|
-
$file = File::createFromBase64($photo, null, $path);
|
|
234
|
-
}
|
|
210
|
+
// Handle photo upload using FileResolverService
|
|
211
|
+
if ($request->has('photo')) {
|
|
212
|
+
$path = 'uploads/' . session('company') . '/drivers';
|
|
213
|
+
$file = app(\Fleetbase\Services\FileResolverService::class)->resolve($request->input('photo'), $path);
|
|
235
214
|
|
|
236
215
|
if ($file) {
|
|
237
216
|
$driver->user->update(['photo_uuid' => $file->uuid]);
|
|
@@ -308,7 +308,7 @@ class OrderController extends Controller
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
// load required relations
|
|
311
|
-
$order->load(['trackingNumber', 'driverAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
311
|
+
$order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
312
312
|
|
|
313
313
|
// Determine if order should be dispatched on creation
|
|
314
314
|
$shouldDispatch = $request->boolean('dispatch') && $integratedVendorOrder === null;
|
|
@@ -352,7 +352,7 @@ class OrderController extends Controller
|
|
|
352
352
|
|
|
353
353
|
// find for the order
|
|
354
354
|
try {
|
|
355
|
-
$order = Order::findRecordOrFail($id);
|
|
355
|
+
$order = Order::findRecordOrFail($id, ['trackingNumber', 'driverAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
356
356
|
} catch (ModelNotFoundException $exception) {
|
|
357
357
|
return response()->json(
|
|
358
358
|
[
|
|
@@ -518,6 +518,9 @@ class OrderController extends Controller
|
|
|
518
518
|
$order->update($input);
|
|
519
519
|
$order->flushAttributesCache();
|
|
520
520
|
|
|
521
|
+
// load required relations
|
|
522
|
+
$order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
523
|
+
|
|
521
524
|
// response the order resource
|
|
522
525
|
return new OrderResource($order);
|
|
523
526
|
}
|
|
@@ -532,6 +535,7 @@ class OrderController extends Controller
|
|
|
532
535
|
set_time_limit(180);
|
|
533
536
|
|
|
534
537
|
$results = Order::queryWithRequest($request, function (&$query, $request) {
|
|
538
|
+
$query->with(['trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'customer', 'facilitator']);
|
|
535
539
|
$query->where('company_uuid', session('company'));
|
|
536
540
|
$query->whereNotNull('payload_uuid');
|
|
537
541
|
|
|
@@ -725,7 +729,7 @@ class OrderController extends Controller
|
|
|
725
729
|
{
|
|
726
730
|
// find for the order
|
|
727
731
|
try {
|
|
728
|
-
$order = Order::findRecordOrFail($id);
|
|
732
|
+
$order = Order::findRecordOrFail($id, ['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
729
733
|
} catch (ModelNotFoundException $exception) {
|
|
730
734
|
return response()->json(
|
|
731
735
|
[
|
|
@@ -805,7 +809,7 @@ class OrderController extends Controller
|
|
|
805
809
|
public function dispatchOrder(string $id)
|
|
806
810
|
{
|
|
807
811
|
try {
|
|
808
|
-
$order = Order::findRecordOrFail($id);
|
|
812
|
+
$order = Order::findRecordOrFail($id, ['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
|
|
809
813
|
} catch (ModelNotFoundException $exception) {
|
|
810
814
|
return response()->json(
|
|
811
815
|
[
|
|
@@ -887,7 +891,7 @@ class OrderController extends Controller
|
|
|
887
891
|
$assignAdhocDriver = $request->input('assign');
|
|
888
892
|
|
|
889
893
|
try {
|
|
890
|
-
$order = Order::findRecordOrFail($id, ['payload.waypoints'], []);
|
|
894
|
+
$order = Order::findRecordOrFail($id, ['payload.waypoints', 'driverAssigned'], []);
|
|
891
895
|
} catch (ModelNotFoundException $exception) {
|
|
892
896
|
return response()->json(
|
|
893
897
|
[
|
|
@@ -1220,14 +1224,11 @@ class OrderController extends Controller
|
|
|
1220
1224
|
public function setDestination(string $id, string $placeId)
|
|
1221
1225
|
{
|
|
1222
1226
|
try {
|
|
1223
|
-
$order = Order::findRecordOrFail($id);
|
|
1227
|
+
$order = Order::findRecordOrFail($id, ['payload.waypoints', 'payload.pickup', 'payload.dropoff', 'driverAssigned', 'vehicleAssigned', 'customer', 'facilitator']);
|
|
1224
1228
|
} catch (ModelNotFoundException $exception) {
|
|
1225
1229
|
return response()->apiError('Order resource not found.', 404);
|
|
1226
1230
|
}
|
|
1227
1231
|
|
|
1228
|
-
// Load required relations
|
|
1229
|
-
$order->loadMissing(['payload.waypoints', 'payload.pickup', 'payload.dropoff']);
|
|
1230
|
-
|
|
1231
1232
|
// Get the order payload
|
|
1232
1233
|
$payload = $order->payload;
|
|
1233
1234
|
|
|
@@ -1567,12 +1568,8 @@ class OrderController extends Controller
|
|
|
1567
1568
|
*
|
|
1568
1569
|
* @return \Feetbase\Models\File
|
|
1569
1570
|
*/
|
|
1570
|
-
protected function storeProofPhoto(
|
|
1571
|
-
|
|
1572
|
-
UploadedFile|string $photo,
|
|
1573
|
-
string $disk,
|
|
1574
|
-
string $bucket,
|
|
1575
|
-
): File {
|
|
1571
|
+
protected function storeProofPhoto(Proof $proof, UploadedFile|string $photo, string $disk, string $bucket): File
|
|
1572
|
+
{
|
|
1576
1573
|
$isFile = $photo instanceof UploadedFile;
|
|
1577
1574
|
$contents = $isFile
|
|
1578
1575
|
? file_get_contents($photo->getRealPath())
|
|
@@ -27,7 +27,7 @@ class VehicleController extends Controller
|
|
|
27
27
|
{
|
|
28
28
|
// get request input
|
|
29
29
|
$input = $request->only(['status', 'make', 'model', 'year', 'trim', 'type', 'plate_number', 'vin', 'meta', 'online', 'location', 'altitude', 'heading', 'speed']);
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
// make sure company is set
|
|
32
32
|
$input['company_uuid'] = session('company');
|
|
33
33
|
|
|
@@ -136,7 +136,7 @@ class DriverController extends FleetOpsController
|
|
|
136
136
|
|
|
137
137
|
if ($input->has('user_uuid')) {
|
|
138
138
|
$user = User::where('uuid', $input->get('user_uuid'))->first();
|
|
139
|
-
|
|
139
|
+
|
|
140
140
|
// If user doesn't exist with provided UUID, create new user
|
|
141
141
|
if (!$user) {
|
|
142
142
|
$userInput = $input
|
|
@@ -26,8 +26,8 @@ class CreateDriverRequest extends CreateDriverApiRequest
|
|
|
26
26
|
*/
|
|
27
27
|
public function rules()
|
|
28
28
|
{
|
|
29
|
-
$isCreating
|
|
30
|
-
$isCreatingWithUser
|
|
29
|
+
$isCreating = $this->isMethod('POST');
|
|
30
|
+
$isCreatingWithUser = $this->filled('driver.user_uuid');
|
|
31
31
|
$shouldValidateUserAttributes = $isCreating && !$isCreatingWithUser;
|
|
32
32
|
|
|
33
33
|
return [
|
|
@@ -36,13 +36,13 @@ class CreateDriverRequest extends CreateDriverApiRequest
|
|
|
36
36
|
'email' => [
|
|
37
37
|
Rule::requiredIf($shouldValidateUserAttributes),
|
|
38
38
|
Rule::when($this->filled('email'), ['email']),
|
|
39
|
-
Rule::when($shouldValidateUserAttributes, [Rule::unique('users')->whereNull('deleted_at')])
|
|
39
|
+
Rule::when($shouldValidateUserAttributes, [Rule::unique('users')->whereNull('deleted_at')]),
|
|
40
40
|
],
|
|
41
41
|
'phone' => [
|
|
42
42
|
Rule::requiredIf($shouldValidateUserAttributes),
|
|
43
|
-
Rule::when($shouldValidateUserAttributes, [Rule::unique('users')->whereNull('deleted_at')])
|
|
43
|
+
Rule::when($shouldValidateUserAttributes, [Rule::unique('users')->whereNull('deleted_at')]),
|
|
44
44
|
],
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
// Optional fields
|
|
47
47
|
'password' => 'nullable|string|min:8',
|
|
48
48
|
'drivers_license_number' => 'nullable|string|max:255',
|
|
@@ -56,7 +56,7 @@ class CreateDriverRequest extends CreateDriverApiRequest
|
|
|
56
56
|
'location' => ['nullable', new ResolvablePoint()],
|
|
57
57
|
'latitude' => ['nullable', 'required_with:longitude', 'numeric'],
|
|
58
58
|
'longitude' => ['nullable', 'required_with:latitude', 'numeric'],
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
// Photo/avatar
|
|
61
61
|
'photo_uuid' => 'nullable|exists:files,uuid',
|
|
62
62
|
'avatar_uuid' => 'nullable|exists:files,uuid',
|
|
@@ -50,7 +50,7 @@ class Driver extends FleetbaseResource
|
|
|
50
50
|
'jobs' => $this->whenLoaded('jobs', fn () => $this->getJobs()),
|
|
51
51
|
'vendor' => $this->whenLoaded('vendor', fn () => new Vendor($this->vendor)),
|
|
52
52
|
'fleets' => $this->whenLoaded('fleets', fn () => Fleet::collection($this->fleets()->without('drivers')->get())),
|
|
53
|
-
'location' => $this->wasRecentlyCreated ? new Point(0, 0) :
|
|
53
|
+
'location' => $this->wasRecentlyCreated ? new Point(0, 0) : Utils::castPoint($this->location),
|
|
54
54
|
'heading' => (int) data_get($this, 'heading', 0),
|
|
55
55
|
'altitude' => (int) data_get($this, 'altitude', 0),
|
|
56
56
|
'speed' => (int) data_get($this, 'speed', 0),
|
|
@@ -96,7 +96,7 @@ class Driver extends FleetbaseResource
|
|
|
96
96
|
'vehicle' => data_get($this, 'vehicle.public_id'),
|
|
97
97
|
'current_job' => data_get($this, 'currentJob.public_id'),
|
|
98
98
|
'vendor' => data_get($this, 'vendor.public_id'),
|
|
99
|
-
'location' =>
|
|
99
|
+
'location' => Utils::castPoint($this->location),
|
|
100
100
|
'heading' => (int) data_get($this, 'heading', 0),
|
|
101
101
|
'altitude' => (int) data_get($this, 'altitude', 0),
|
|
102
102
|
'speed' => (int) data_get($this, 'speed', 0),
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Resources\v1\Index;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Support\Utils;
|
|
5
6
|
use Fleetbase\Http\Resources\FleetbaseResource;
|
|
6
7
|
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
7
8
|
use Fleetbase\Support\Http;
|
|
@@ -35,7 +36,7 @@ class Driver extends FleetbaseResource
|
|
|
35
36
|
'phone' => $this->phone,
|
|
36
37
|
'photo_url' => $this->photo_url,
|
|
37
38
|
'status' => $this->status,
|
|
38
|
-
'location' => $this->wasRecentlyCreated ? new Point(0, 0) :
|
|
39
|
+
'location' => $this->wasRecentlyCreated ? new Point(0, 0) : Utils::castPoint($this->location),
|
|
39
40
|
'heading' => (int) data_get($this, 'heading', 0),
|
|
40
41
|
'altitude' => (int) data_get($this, 'altitude', 0),
|
|
41
42
|
'speed' => (int) data_get($this, 'speed', 0),
|
|
@@ -34,7 +34,7 @@ class Place extends FleetbaseResource
|
|
|
34
34
|
'city' => $this->city,
|
|
35
35
|
'country' => $this->country,
|
|
36
36
|
'avatar_url' => $this->avatar_url,
|
|
37
|
-
'location' => Utils::
|
|
37
|
+
'location' => Utils::castPoint($this->location),
|
|
38
38
|
|
|
39
39
|
// Meta flag to indicate this is an index resource
|
|
40
40
|
'meta' => [
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Resources\v1\Index;
|
|
4
4
|
|
|
5
|
+
use Fleetbase\FleetOps\Support\Utils;
|
|
5
6
|
use Fleetbase\Http\Resources\FleetbaseResource;
|
|
6
|
-
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
7
7
|
use Fleetbase\Support\Http;
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -35,7 +35,7 @@ class Vehicle extends FleetbaseResource
|
|
|
35
35
|
'year' => $this->year,
|
|
36
36
|
'photo_url' => $this->photo_url,
|
|
37
37
|
'status' => $this->status,
|
|
38
|
-
'location' =>
|
|
38
|
+
'location' => Utils::castPoint($this->location),
|
|
39
39
|
'heading' => (int) data_get($this, 'heading', 0),
|
|
40
40
|
'altitude' => (int) data_get($this, 'altitude', 0),
|
|
41
41
|
'speed' => (int) data_get($this, 'speed', 0),
|
|
@@ -28,7 +28,7 @@ class Place extends FleetbaseResource
|
|
|
28
28
|
'owner_uuid' => $this->when(Http::isInternalRequest(), $this->owner_uuid),
|
|
29
29
|
'owner_type' => $this->when(Http::isInternalRequest(), $this->owner_type ? Utils::toEmberResourceType($this->owner_type) : null),
|
|
30
30
|
'name' => $this->name,
|
|
31
|
-
'location' => Utils::
|
|
31
|
+
'location' => Utils::castPoint($this->location),
|
|
32
32
|
'address' => $this->address,
|
|
33
33
|
'address_html' => $this->when(Http::isInternalRequest(), $this->address_html),
|
|
34
34
|
'avatar_url' => $this->avatar_url,
|
|
@@ -4,7 +4,6 @@ namespace Fleetbase\FleetOps\Http\Resources\v1;
|
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Support\Utils;
|
|
6
6
|
use Fleetbase\Http\Resources\FleetbaseResource;
|
|
7
|
-
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
8
7
|
use Fleetbase\Support\Http;
|
|
9
8
|
|
|
10
9
|
class Vehicle extends FleetbaseResource
|
|
@@ -132,7 +131,7 @@ class Vehicle extends FleetbaseResource
|
|
|
132
131
|
'updated_at' => $this->updated_at,
|
|
133
132
|
'created_at' => $this->created_at,
|
|
134
133
|
// Location & telematics
|
|
135
|
-
'location' =>
|
|
134
|
+
'location' => Utils::castPoint($this->location),
|
|
136
135
|
'heading' => (int) data_get($this, 'heading', 0),
|
|
137
136
|
'altitude' => (int) data_get($this, 'altitude', 0),
|
|
138
137
|
'speed' => (int) data_get($this, 'speed', 0),
|
|
@@ -248,7 +247,7 @@ class Vehicle extends FleetbaseResource
|
|
|
248
247
|
'updated_at' => $this->updated_at,
|
|
249
248
|
'created_at' => $this->created_at,
|
|
250
249
|
// Location & telematics
|
|
251
|
-
'location' =>
|
|
250
|
+
'location' => Utils::castPoint($this->location),
|
|
252
251
|
'heading' => (int) data_get($this, 'heading', 0),
|
|
253
252
|
'altitude' => (int) data_get($this, 'altitude', 0),
|
|
254
253
|
'speed' => (int) data_get($this, 'speed', 0),
|
|
@@ -4,7 +4,6 @@ namespace Fleetbase\FleetOps\Http\Resources\v1;
|
|
|
4
4
|
|
|
5
5
|
use Fleetbase\FleetOps\Support\Utils;
|
|
6
6
|
use Fleetbase\Http\Resources\FleetbaseResource;
|
|
7
|
-
use Fleetbase\LaravelMysqlSpatial\Types\Point;
|
|
8
7
|
use Fleetbase\Support\Http;
|
|
9
8
|
|
|
10
9
|
class VehicleWithoutDriver extends FleetbaseResource
|
|
@@ -131,7 +130,7 @@ class VehicleWithoutDriver extends FleetbaseResource
|
|
|
131
130
|
'updated_at' => $this->updated_at,
|
|
132
131
|
'created_at' => $this->created_at,
|
|
133
132
|
// Location & telematics
|
|
134
|
-
'location' =>
|
|
133
|
+
'location' => Utils::castPoint($this->location),
|
|
135
134
|
'heading' => (int) data_get($this, 'heading', 0),
|
|
136
135
|
'altitude' => (int) data_get($this, 'altitude', 0),
|
|
137
136
|
'speed' => (int) data_get($this, 'speed', 0),
|
|
@@ -245,7 +244,7 @@ class VehicleWithoutDriver extends FleetbaseResource
|
|
|
245
244
|
'updated_at' => $this->updated_at,
|
|
246
245
|
'created_at' => $this->created_at,
|
|
247
246
|
// Location & telematics
|
|
248
|
-
'location' =>
|
|
247
|
+
'location' => Utils::castPoint($this->location),
|
|
249
248
|
'heading' => (int) data_get($this, 'heading', 0),
|
|
250
249
|
'altitude' => (int) data_get($this, 'altitude', 0),
|
|
251
250
|
'speed' => (int) data_get($this, 'speed', 0),
|
|
@@ -245,6 +245,19 @@ class Payload extends Model
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
// Handle entity photo upload
|
|
249
|
+
if (isset($attributes['photo'])) {
|
|
250
|
+
$path = 'uploads/' . session('company') . '/entities';
|
|
251
|
+
$file = app(\Fleetbase\Services\FileResolverService::class)->resolve($attributes['photo'], $path);
|
|
252
|
+
|
|
253
|
+
if ($file) {
|
|
254
|
+
$attributes['photo_uuid'] = $file->uuid;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Clean up raw photo data
|
|
258
|
+
unset($attributes['photo']);
|
|
259
|
+
}
|
|
260
|
+
|
|
248
261
|
$entity = new Entity($attributes);
|
|
249
262
|
$this->entities()->save($entity);
|
|
250
263
|
}
|
|
@@ -286,6 +299,19 @@ class Payload extends Model
|
|
|
286
299
|
}
|
|
287
300
|
}
|
|
288
301
|
|
|
302
|
+
// Handle entity photo upload
|
|
303
|
+
if (isset($attributes['photo'])) {
|
|
304
|
+
$path = 'uploads/' . session('company') . '/entities';
|
|
305
|
+
$file = app(\Fleetbase\Services\FileResolverService::class)->resolve($attributes['photo'], $path);
|
|
306
|
+
|
|
307
|
+
if ($file) {
|
|
308
|
+
$attributes['photo_uuid'] = $file->uuid;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Clean up raw photo data
|
|
312
|
+
unset($attributes['photo']);
|
|
313
|
+
}
|
|
314
|
+
|
|
289
315
|
Entity::insertGetUuid($attributes, $this);
|
|
290
316
|
}
|
|
291
317
|
|
|
@@ -12,7 +12,6 @@ use Fleetbase\Traits\HasPublicId;
|
|
|
12
12
|
use Fleetbase\Traits\HasUuid;
|
|
13
13
|
use Fleetbase\Traits\SendsWebhooks;
|
|
14
14
|
use Fleetbase\Traits\TracksApiCredential;
|
|
15
|
-
use Illuminate\Support\Facades\DB;
|
|
16
15
|
|
|
17
16
|
class ServiceRate extends Model
|
|
18
17
|
{
|
|
@@ -469,12 +468,11 @@ class ServiceRate extends Model
|
|
|
469
468
|
*/
|
|
470
469
|
public static function getServicableForPlaces($places = [], $service = null, $currency = null, ?\Closure $queryCallback = null): array
|
|
471
470
|
{
|
|
472
|
-
$reader
|
|
473
|
-
$
|
|
474
|
-
$serviceRatesQuery = static::with(['zone', 'serviceArea', 'rateFees', 'parcelFees']);
|
|
471
|
+
$reader = new GeoJSONReader();
|
|
472
|
+
$serviceRatesQuery = static::with(['zone', 'serviceArea', 'rateFees', 'parcelFees']);
|
|
475
473
|
|
|
476
474
|
if ($currency) {
|
|
477
|
-
$serviceRatesQuery->
|
|
475
|
+
$serviceRatesQuery->whereRaw('lower(currency) = ?', [strtolower($currency)]);
|
|
478
476
|
}
|
|
479
477
|
|
|
480
478
|
if ($service) {
|
|
@@ -487,44 +485,115 @@ class ServiceRate extends Model
|
|
|
487
485
|
|
|
488
486
|
$serviceRates = $serviceRatesQuery->get();
|
|
489
487
|
|
|
490
|
-
$waypoints = collect($places)
|
|
491
|
-
|
|
488
|
+
$waypoints = collect($places)
|
|
489
|
+
->map(function ($place) {
|
|
490
|
+
$place = Place::createFromMixed($place);
|
|
491
|
+
|
|
492
|
+
if (!$place instanceof Place) {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
492
495
|
|
|
493
|
-
if ($place instanceof Place) {
|
|
494
496
|
$point = $place->getLocationAsPoint();
|
|
495
497
|
|
|
496
|
-
//
|
|
497
|
-
return \Brick\Geo\Point::fromText(
|
|
498
|
+
// Brick point: X=lng, Y=lat (WKT order)
|
|
499
|
+
return \Brick\Geo\Point::fromText(
|
|
500
|
+
sprintf('POINT (%F %F)', $point->getLng(), $point->getLat()),
|
|
501
|
+
4326
|
|
502
|
+
);
|
|
503
|
+
})
|
|
504
|
+
->filter()
|
|
505
|
+
->values();
|
|
506
|
+
|
|
507
|
+
if ($waypoints->isEmpty()) {
|
|
508
|
+
return [];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Convert a casted spatial geometry (Zone::border / ServiceArea::border)
|
|
513
|
+
* into a Brick geometry using GeoJSONReader.
|
|
514
|
+
*/
|
|
515
|
+
$toBrickGeometry = function ($spatialGeometry) use ($reader) {
|
|
516
|
+
if (!$spatialGeometry) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Most Fleetbase spatial casts/types implement toJson()
|
|
521
|
+
if (is_object($spatialGeometry) && method_exists($spatialGeometry, 'toJson')) {
|
|
522
|
+
$json = $spatialGeometry->toJson();
|
|
523
|
+
$json = is_string($json) ? trim($json) : null;
|
|
524
|
+
|
|
525
|
+
if ($json) {
|
|
526
|
+
return $reader->read($json);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Fallback if the cast ever returns array/object
|
|
533
|
+
if (is_array($spatialGeometry) || is_object($spatialGeometry)) {
|
|
534
|
+
$json = json_encode($spatialGeometry, JSON_UNESCAPED_UNICODE);
|
|
535
|
+
if ($json && $json !== 'null') {
|
|
536
|
+
return $reader->read($json);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Fallback if it’s a raw JSON string
|
|
541
|
+
if (is_string($spatialGeometry) && trim($spatialGeometry) !== '') {
|
|
542
|
+
return $reader->read($spatialGeometry);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return null;
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Ensure ALL waypoints are inside the given Brick geometry.
|
|
550
|
+
*/
|
|
551
|
+
$containsAllWaypoints = function ($brickGeometry) use ($waypoints): bool {
|
|
552
|
+
if (!$brickGeometry) {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
foreach ($waypoints as $waypoint) {
|
|
557
|
+
if (!$brickGeometry->contains($waypoint)) {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
498
560
|
}
|
|
499
|
-
|
|
561
|
+
|
|
562
|
+
return true;
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
$applicableServiceRates = [];
|
|
500
566
|
|
|
501
567
|
foreach ($serviceRates as $serviceRate) {
|
|
568
|
+
// If a service area exists, all waypoints must be inside its border
|
|
502
569
|
if ($serviceRate->hasServiceArea()) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
570
|
+
$serviceAreaBorder = $serviceRate->serviceArea?->border;
|
|
571
|
+
|
|
572
|
+
$serviceAreaGeom = null;
|
|
573
|
+
try {
|
|
574
|
+
$serviceAreaGeom = $toBrickGeometry($serviceAreaBorder);
|
|
575
|
+
} catch (\Throwable $e) {
|
|
576
|
+
continue; // invalid geojson / geometry -> reject this rate
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (!$containsAllWaypoints($serviceAreaGeom)) {
|
|
580
|
+
continue;
|
|
514
581
|
}
|
|
515
582
|
}
|
|
516
583
|
|
|
584
|
+
// If a zone exists, all waypoints must be inside its border
|
|
517
585
|
if ($serviceRate->hasZone()) {
|
|
518
|
-
|
|
519
|
-
foreach ($serviceRate->zone->border as $polygon) {
|
|
520
|
-
$polygon = $reader->read($polygon->toJson());
|
|
586
|
+
$zoneBorder = $serviceRate->zone?->border;
|
|
521
587
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
588
|
+
$zoneGeom = null;
|
|
589
|
+
try {
|
|
590
|
+
$zoneGeom = $toBrickGeometry($zoneBorder);
|
|
591
|
+
} catch (\Throwable $e) {
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (!$containsAllWaypoints($zoneGeom)) {
|
|
596
|
+
continue;
|
|
528
597
|
}
|
|
529
598
|
}
|
|
530
599
|
|
|
@@ -933,29 +1002,23 @@ class ServiceRate extends Model
|
|
|
933
1002
|
*/
|
|
934
1003
|
public function findServiceRateFeeByDistance(int $totalDistance): ?ServiceRateFee
|
|
935
1004
|
{
|
|
936
|
-
$this->
|
|
1005
|
+
$this->loadMissing('rateFees');
|
|
937
1006
|
|
|
938
|
-
|
|
939
|
-
$
|
|
1007
|
+
// Convert meters to kilometers WITHOUT rounding up
|
|
1008
|
+
$distanceInKm = $totalDistance / 1000;
|
|
940
1009
|
|
|
941
|
-
|
|
942
|
-
|
|
1010
|
+
// Ensure predictable order
|
|
1011
|
+
$rateFees = $this->rateFees->sortBy('distance');
|
|
943
1012
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
} else {
|
|
949
|
-
$distanceFee = $rateFee;
|
|
1013
|
+
// Find the first tier that covers the distance
|
|
1014
|
+
foreach ($rateFees as $rateFee) {
|
|
1015
|
+
if ($distanceInKm <= $rateFee->distance) {
|
|
1016
|
+
return $rateFee;
|
|
950
1017
|
}
|
|
951
1018
|
}
|
|
952
1019
|
|
|
953
|
-
//
|
|
954
|
-
|
|
955
|
-
$distanceFee = $this->rateFees->sortByDesc('distance')->first();
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
return $distanceFee;
|
|
1020
|
+
// If distance exceeds all tiers, use the largest tier
|
|
1021
|
+
return $rateFees->last();
|
|
959
1022
|
}
|
|
960
1023
|
|
|
961
1024
|
/**
|
|
@@ -334,6 +334,24 @@ class Utils extends FleetbaseUtils
|
|
|
334
334
|
return new Point((float) $latitude, (float) $longitude);
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Always return spatial point.
|
|
339
|
+
*
|
|
340
|
+
* @param [type] $mixed
|
|
341
|
+
*
|
|
342
|
+
* @return void
|
|
343
|
+
*/
|
|
344
|
+
public static function castPoint($mixed)
|
|
345
|
+
{
|
|
346
|
+
try {
|
|
347
|
+
$point = static::getPointFromMixed($mixed);
|
|
348
|
+
|
|
349
|
+
return $point;
|
|
350
|
+
} catch (\Throwable $e) {
|
|
351
|
+
return new Point(0, 0);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
337
355
|
/**
|
|
338
356
|
* Determines if the given coordinates strictly represent a Point object.
|
|
339
357
|
* These will explude resolvable coordinates from records.
|