@fleetbase/fleetops-engine 0.6.23 → 0.6.24

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.
@@ -27,7 +27,7 @@
27
27
  </div>
28
28
  </div>
29
29
 
30
- <div class="w-48 {{if this.isReplaying 'hidden' ''}}">
30
+ <div class="w-48 {{if (or this.isReplaying this.isPaused) 'hidden' ''}}">
31
31
  <DatePicker
32
32
  @value={{this.dateFilter}}
33
33
  @range={{true}}
@@ -34,7 +34,7 @@ export default class MapLeafletLiveMapComponent extends Component {
34
34
 
35
35
  /** tracked properties */
36
36
  @tracked ready = false;
37
- @tracked zoom = this.args.zoom ?? 14;
37
+ @tracked zoom = this.getValidZoom();
38
38
  @tracked latitude = this.location.getLatitude();
39
39
  @tracked longitude = this.location.getLongitude();
40
40
  @tracked contextmenuItems = [];
@@ -45,6 +45,29 @@ export default class MapLeafletLiveMapComponent extends Component {
45
45
  @tracked vehicles = [];
46
46
  @tracked places = [];
47
47
 
48
+ constructor() {
49
+ super(...arguments);
50
+
51
+ // Store bound function reference for proper cleanup
52
+ this._locationUpdateHandler = this.#handleLocationUpdate.bind(this);
53
+
54
+ // Listen for location updates from the location service
55
+ this.universe.on('user.located', this._locationUpdateHandler);
56
+
57
+ // Ensure we have valid coordinates on initialization
58
+ this.#updateCoordinatesFromLocation();
59
+ }
60
+
61
+ willDestroy() {
62
+ super.willDestroy();
63
+
64
+ // Clean up event listener using stored reference
65
+ if (this._locationUpdateHandler) {
66
+ this.universe.off('user.located', this._locationUpdateHandler);
67
+ this._locationUpdateHandler = null;
68
+ }
69
+ }
70
+
48
71
  @action didLoad({ target: map }) {
49
72
  this.#setMap(map);
50
73
  this.#createMapContextMenu(map);
@@ -189,6 +212,46 @@ export default class MapLeafletLiveMapComponent extends Component {
189
212
  return this.ready === true;
190
213
  }
191
214
 
215
+ /**
216
+ * Get valid zoom level for map initialization
217
+ * @returns {number} Valid zoom level between 1-20
218
+ */
219
+ getValidZoom() {
220
+ const zoom = this.args.zoom;
221
+ // Validate zoom is a valid number within Leaflet bounds (1-20)
222
+ if (typeof zoom === 'number' && !isNaN(zoom) && zoom >= 1 && zoom <= 20) {
223
+ return zoom;
224
+ }
225
+ // Return default zoom of 14 if invalid
226
+ return 14;
227
+ }
228
+
229
+ /**
230
+ * Handles location updates from the location service
231
+ * @param {Object} coordinates - The new coordinates
232
+ */
233
+ #handleLocationUpdate(coordinates) {
234
+ if (coordinates && typeof coordinates.latitude === 'number' && typeof coordinates.longitude === 'number') {
235
+ this.latitude = coordinates.latitude;
236
+ this.longitude = coordinates.longitude;
237
+
238
+ // Update map position if map is loaded
239
+ if (this.map && this.map.setView) {
240
+ this.map.setView([coordinates.latitude, coordinates.longitude], this.zoom);
241
+ }
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Updates coordinates from location service on initialization
247
+ */
248
+ #updateCoordinatesFromLocation() {
249
+ // Initial coordinates are already set via tracked properties
250
+ // This method ensures we have the latest location service values
251
+ this.latitude = this.location.getLatitude();
252
+ this.longitude = this.location.getLongitude();
253
+ }
254
+
192
255
  #setMap(map) {
193
256
  set(map, 'livemap', this);
194
257
  this.map = map;
@@ -417,6 +480,38 @@ export default class MapLeafletLiveMapComponent extends Component {
417
480
  return contextmenuRegistry;
418
481
  }
419
482
 
483
+ /**
484
+ * Safely gets a valid latitude value with fallback to default
485
+ * @returns {number} Valid latitude value
486
+ */
487
+ #getValidLatitude() {
488
+ const lat = this.location.getLatitude();
489
+
490
+ // Validate latitude is a number and within valid range (-90 to 90)
491
+ if (typeof lat === 'number' && !isNaN(lat) && lat >= -90 && lat <= 90) {
492
+ return lat;
493
+ }
494
+
495
+ // Fallback to default Singapore latitude
496
+ return 1.369;
497
+ }
498
+
499
+ /**
500
+ * Safely gets a valid longitude value with fallback to default
501
+ * @returns {number} Valid longitude value
502
+ */
503
+ #getValidLongitude() {
504
+ const lng = this.location.getLongitude();
505
+
506
+ // Validate longitude is a number and within valid range (-180 to 180)
507
+ if (typeof lng === 'number' && !isNaN(lng) && lng >= -180 && lng <= 180) {
508
+ return lng;
509
+ }
510
+
511
+ // Fallback to default Singapore longitude
512
+ return 103.8864;
513
+ }
514
+
420
515
  #changeTileSource(source) {
421
516
  switch (source) {
422
517
  case 'dark':
@@ -39,7 +39,6 @@ export default class ConnectivityEventsIndexController extends Controller {
39
39
  resizable: true,
40
40
  sortable: true,
41
41
  filterable: true,
42
- filterParam: 'name',
43
42
  filterComponent: 'filter/string',
44
43
  },
45
44
  {
@@ -1,21 +1,23 @@
1
1
  import Controller from '@ember/controller';
2
- import { tracked } from '@glimmer/tracking';
3
2
  import { inject as service } from '@ember/service';
4
3
  import { getOwner } from '@ember/application';
5
4
  import getCurrentNestedController from '@fleetbase/ember-core/utils/get-current-nested-controller';
6
5
 
7
6
  export default class ManagementContactsController extends Controller {
8
7
  @service hostRouter;
9
- @tracked tabs = [
10
- {
11
- route: 'management.contacts.index',
12
- label: 'Contacts',
13
- },
14
- {
15
- route: 'management.contacts.customers',
16
- label: 'Customers',
17
- },
18
- ];
8
+
9
+ get tabs() {
10
+ return [
11
+ {
12
+ route: 'management.contacts.index',
13
+ label: 'Contacts',
14
+ },
15
+ {
16
+ route: 'management.contacts.customers',
17
+ label: 'Customers',
18
+ },
19
+ ];
20
+ }
19
21
 
20
22
  get childController() {
21
23
  return getCurrentNestedController(getOwner(this), this.hostRouter.currentRouteName);
@@ -69,7 +69,11 @@ export default class LocationService extends Service {
69
69
  * @returns {number} The current latitude.
70
70
  */
71
71
  getLatitude() {
72
- return this.latitude;
72
+ // Ensure we always return a valid number within geographic bounds
73
+ if (typeof this.latitude === 'number' && !isNaN(this.latitude) && this.latitude >= -90 && this.latitude <= 90) {
74
+ return this.latitude;
75
+ }
76
+ return this.constructor.DEFAULT_LATITUDE;
73
77
  }
74
78
 
75
79
  /**
@@ -77,7 +81,11 @@ export default class LocationService extends Service {
77
81
  * @returns {number} The current longitude.
78
82
  */
79
83
  getLongitude() {
80
- return this.longitude;
84
+ // Ensure we always return a valid number within geographic bounds
85
+ if (typeof this.longitude === 'number' && !isNaN(this.longitude) && this.longitude >= -180 && this.longitude <= 180) {
86
+ return this.longitude;
87
+ }
88
+ return this.constructor.DEFAULT_LONGITUDE;
81
89
  }
82
90
 
83
91
  /**
@@ -9,6 +9,7 @@
9
9
  @onSearch={{perform this.childController.search}}
10
10
  @table={{this.childController.table}}
11
11
  @bulkActionIconOnly={{true}}
12
+ @controller={{this.childController}}
12
13
  />
13
14
  </div>
14
15
  </:actions>
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/fleetops-api",
3
- "version": "0.6.23",
3
+ "version": "0.6.24",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Fleet-Ops",
3
- "version": "0.6.23",
3
+ "version": "0.6.24",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/fleetops",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/fleetops-engine",
3
- "version": "0.6.23",
3
+ "version": "0.6.24",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "fleet-ops"
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "@babel/core": "^7.23.2",
45
45
  "@fleetbase/ember-core": "^0.3.6",
46
- "@fleetbase/ember-ui": "^0.3.8",
46
+ "@fleetbase/ember-ui": "^0.3.9",
47
47
  "@fleetbase/fleetops-data": "^0.1.21",
48
48
  "@fleetbase/leaflet-routing-machine": "^3.2.17",
49
49
  "@fortawesome/ember-fontawesome": "^2.0.0",
@@ -0,0 +1,245 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration {
8
+ /**
9
+ * Run the migrations.
10
+ */
11
+ public function up(): void
12
+ {
13
+ // ============================================================
14
+ // ORDERS TABLE INDEXES
15
+ // ============================================================
16
+ Schema::table('orders', function (Blueprint $table) {
17
+ /**
18
+ * Index 1: company_uuid + status.
19
+ *
20
+ * WHY: The most common query is "get all orders for company X with status Y"
21
+ * USED BY: OrderFilter status filtering, dashboard queries
22
+ * QUERY: WHERE company_uuid = ? AND status IN (?, ?, ?)
23
+ *
24
+ * This index allows MySQL to:
25
+ * 1. Jump to all rows for the company (first column)
26
+ * 2. Filter by status within that company (second column)
27
+ *
28
+ * Without this, MySQL would use company_uuid index, then scan all
29
+ * matching rows to filter by status (slow for large datasets).
30
+ */
31
+ if (!$this->indexExists('orders', 'idx_orders_company_status')) {
32
+ $table->index(['company_uuid', 'status'], 'idx_orders_company_status');
33
+ }
34
+
35
+ /**
36
+ * Index 2: company_uuid + driver_assigned_uuid.
37
+ *
38
+ * WHY: Finding unassigned orders is a critical operation
39
+ * USED BY: OrderFilter unassigned() method, driver assignment UI
40
+ * QUERY: WHERE company_uuid = ? AND driver_assigned_uuid IS NULL
41
+ *
42
+ * This index is particularly efficient for IS NULL checks because
43
+ * MySQL stores NULL values in indexes (unlike some databases).
44
+ */
45
+ if (!$this->indexExists('orders', 'idx_orders_company_driver')) {
46
+ $table->index(['company_uuid', 'driver_assigned_uuid'], 'idx_orders_company_driver');
47
+ }
48
+
49
+ /**
50
+ * Index 3: company_uuid + created_at.
51
+ *
52
+ * WHY: Date-based filtering and sorting is extremely common
53
+ * USED BY: Date range filters, "recent orders" queries
54
+ * QUERY: WHERE company_uuid = ? AND created_at >= ? AND created_at <= ?
55
+ * ORDER BY: created_at DESC
56
+ *
57
+ * This index supports both filtering AND sorting efficiently.
58
+ * The ORDER BY can use the index for sorting without a filesort operation.
59
+ */
60
+ if (!$this->indexExists('orders', 'idx_orders_company_created')) {
61
+ $table->index(['company_uuid', 'created_at'], 'idx_orders_company_created');
62
+ }
63
+
64
+ /**
65
+ * Index 4: company_uuid + scheduled_at.
66
+ *
67
+ * WHY: Scheduled orders need to be queried efficiently
68
+ * USED BY: Scheduler, upcoming deliveries view
69
+ * QUERY: WHERE company_uuid = ? AND scheduled_at >= ? AND scheduled_at <= ?
70
+ */
71
+ if (!$this->indexExists('orders', 'idx_orders_company_scheduled')) {
72
+ $table->index(['company_uuid', 'scheduled_at'], 'idx_orders_company_scheduled');
73
+ }
74
+
75
+ /**
76
+ * Index 5: company_uuid + dispatched + status.
77
+ *
78
+ * WHY: Finding dispatched orders with specific statuses
79
+ * USED BY: Active orders view, driver assignment
80
+ * QUERY: WHERE company_uuid = ? AND dispatched = 1 AND status IN (?, ?)
81
+ *
82
+ * This is a 3-column composite index. MySQL will use it for:
83
+ * - company_uuid alone
84
+ * - company_uuid + dispatched
85
+ * - company_uuid + dispatched + status (full index)
86
+ *
87
+ * But NOT for dispatched alone or status alone.
88
+ */
89
+ if (!$this->indexExists('orders', 'idx_orders_company_dispatched_status')) {
90
+ $table->index(['company_uuid', 'dispatched', 'status'], 'idx_orders_company_dispatched_status');
91
+ }
92
+
93
+ /**
94
+ * Index 6: company_uuid + tracking_number_uuid.
95
+ *
96
+ * WHY: Common join key and filtering key for tracking data.
97
+ * USED BY: OrderFilter, tracking joins
98
+ * QUERY: WHERE company_uuid = ? AND tracking_number_uuid = ?
99
+ */
100
+ if (!$this->indexExists('orders', 'idx_orders_company_tracking')) {
101
+ $table->index(['company_uuid', 'tracking_number_uuid'], 'idx_orders_company_tracking');
102
+ }
103
+ });
104
+
105
+ // ============================================================
106
+ // PAYLOADS TABLE INDEXES
107
+ // ============================================================
108
+ Schema::table('payloads', function (Blueprint $table) {
109
+ /**
110
+ * Index 6-8: Foreign key indexes.
111
+ *
112
+ * WHY: JOINs require indexes on both sides of the join condition
113
+ * USED BY: All queries that JOIN orders to payloads to places
114
+ *
115
+ * When we JOIN payloads to places:
116
+ * JOIN places ON places.uuid = payloads.pickup_uuid
117
+ *
118
+ * MySQL needs an index on payloads.pickup_uuid to efficiently
119
+ * find matching rows. Without it, MySQL does a full table scan
120
+ * of the payloads table for each place.
121
+ */
122
+ if (!$this->indexExists('payloads', 'pickup_uuid')) {
123
+ $table->index('pickup_uuid', 'idx_payloads_pickup');
124
+ }
125
+ if (!$this->indexExists('payloads', 'dropoff_uuid')) {
126
+ $table->index('dropoff_uuid', 'idx_payloads_dropoff');
127
+ }
128
+ if (!$this->indexExists('payloads', 'return_uuid')) {
129
+ $table->index('return_uuid', 'idx_payloads_return');
130
+ }
131
+ });
132
+
133
+ // ============================================================
134
+ // WAYPOINTS TABLE INDEXES
135
+ // ============================================================
136
+ Schema::table('waypoints', function (Blueprint $table) {
137
+ /**
138
+ * Index 9: payload_uuid.
139
+ *
140
+ * WHY: Loading waypoints for a payload is a 1-to-many relationship
141
+ * USED BY: Eager loading payload.waypoints
142
+ * QUERY: WHERE payload_uuid = ?
143
+ *
144
+ * Without this index, loading waypoints for 378 payloads would
145
+ * require 378 table scans of the waypoints table.
146
+ */
147
+ if (!$this->indexExists('waypoints', 'payload_uuid')) {
148
+ $table->index('payload_uuid', 'idx_waypoints_payload');
149
+ }
150
+
151
+ /**
152
+ * Index 10: Composite index for deleted waypoints.
153
+ *
154
+ * WHY: We frequently query non-deleted waypoints for a payload
155
+ * QUERY: WHERE payload_uuid = ? AND deleted_at IS NULL
156
+ *
157
+ * This composite index is more efficient than two separate indexes
158
+ * because it allows MySQL to filter on both conditions using one index.
159
+ */
160
+ if (!$this->indexExists('waypoints', 'idx_waypoints_payload_deleted')) {
161
+ $table->index(['payload_uuid', 'deleted_at'], 'idx_waypoints_payload_deleted');
162
+ }
163
+ });
164
+
165
+ // ============================================================
166
+ // TRACKING_STATUSES TABLE INDEXES
167
+ // ============================================================
168
+ Schema::table('tracking_statuses', function (Blueprint $table) {
169
+ /**
170
+ * Index 11: tracking_number_uuid.
171
+ *
172
+ * WHY: Loading tracking statuses for an order
173
+ * USED BY: Eager loading order.trackingStatuses
174
+ * QUERY: WHERE tracking_number_uuid = ?
175
+ */
176
+ if (!$this->indexExists('tracking_statuses', 'tracking_number_uuid')) {
177
+ $table->index('tracking_number_uuid', 'idx_tracking_statuses_tracking_number');
178
+ }
179
+ });
180
+
181
+ // ============================================================
182
+ // ENTITIES TABLE INDEXES
183
+ // ============================================================
184
+
185
+ Schema::table('entities', function (Blueprint $table) {
186
+ /**
187
+ * Index 12: payload_uuid.
188
+ *
189
+ * WHY: Loading entities for a payload
190
+ * USED BY: Eager loading payload.entities
191
+ * QUERY: WHERE payload_uuid = ?
192
+ */
193
+ if (!$this->indexExists('entities', 'payload_uuid')) {
194
+ $table->index('payload_uuid', 'idx_entities_payload');
195
+ }
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Reverse the migrations.
201
+ */
202
+ public function down(): void
203
+ {
204
+ Schema::table('orders', function (Blueprint $table) {
205
+ $table->dropIndex('idx_orders_company_status');
206
+ $table->dropIndex('idx_orders_company_driver');
207
+ $table->dropIndex('idx_orders_company_created');
208
+ $table->dropIndex('idx_orders_company_scheduled');
209
+ $table->dropIndex('idx_orders_company_dispatched_status');
210
+ $table->dropIndex('idx_orders_company_tracking');
211
+ });
212
+
213
+ Schema::table('payloads', function (Blueprint $table) {
214
+ $table->dropIndex('idx_payloads_pickup');
215
+ $table->dropIndex('idx_payloads_dropoff');
216
+ $table->dropIndex('idx_payloads_return');
217
+ });
218
+
219
+ Schema::table('waypoints', function (Blueprint $table) {
220
+ $table->dropIndex('idx_waypoints_payload');
221
+ $table->dropIndex('idx_waypoints_payload_deleted');
222
+ });
223
+
224
+ Schema::table('tracking_statuses', function (Blueprint $table) {
225
+ $table->dropIndex('idx_tracking_statuses_tracking_number');
226
+ });
227
+
228
+ Schema::table('entities', function (Blueprint $table) {
229
+ $table->dropIndex('idx_entities_payload');
230
+ });
231
+ }
232
+
233
+ /**
234
+ * Check if an index exists on a table.
235
+ *
236
+ * This helper method prevents errors when running migrations multiple times
237
+ * or when indexes already exist from previous manual additions.
238
+ */
239
+ private function indexExists(string $table, string $indexName): bool
240
+ {
241
+ $indexes = DB::select("SHOW INDEX FROM {$table} WHERE Key_name = ?", [$indexName]);
242
+
243
+ return count($indexes) > 0;
244
+ }
245
+ };
@@ -332,15 +332,14 @@ class DriverController extends Controller
332
332
  try {
333
333
  $driver = Driver::findRecordOrFail($id);
334
334
  } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
335
- return response()->json(
336
- [
337
- 'error' => 'Driver resource not found.',
338
- ],
339
- 404
340
- );
335
+ return response()->apiError('Driver resource not found.', 404);
336
+ }
337
+
338
+ // If no lat/lng provided, maintain compatibility and just return existing driver resource
339
+ if (empty($latitude) && empty($longitude)) {
340
+ return new DriverResource($driver);
341
341
  }
342
342
 
343
- // check if driver needs a geocoded update to set city and country they are currently in
344
343
  $isGeocodable = Carbon::parse($driver->updated_at)->diffInMinutes(Carbon::now(), false) > 10 || empty($driver->country) || empty($driver->city);
345
344
 
346
345
  $positionData = [
@@ -352,37 +351,38 @@ class DriverController extends Controller
352
351
  'speed' => $speed,
353
352
  ];
354
353
 
355
- // Append current order to data if applicable
356
- $order = $driver->getCurrentOrder();
357
- if ($order) {
354
+ if ($order = $driver->getCurrentOrder()) {
358
355
  $positionData['order_uuid'] = $order->uuid;
359
- // Get destination
360
- $destination = $order->payload?->getPickupOrCurrentWaypoint();
356
+ $destination = $order->payload?->getPickupOrCurrentWaypoint();
357
+
361
358
  if ($destination) {
362
359
  $positionData['destination_uuid'] = $destination->uuid;
363
360
  }
364
361
  }
365
362
 
366
- $driver->update($positionData);
363
+ $driver->updateQuietly($positionData);
367
364
  $driver->createPosition($positionData);
368
365
 
369
- // If vehicle is assigned to driver load it and sync position data
370
366
  $driver->loadMissing('vehicle');
371
- if ($driver->vehicle) {
372
- $driver->vehicle->update($positionData);
373
- $driver->vehicle->createPosition($positionData);
374
- broadcast(new VehicleLocationChanged($driver->vehicle, ['driver' => $driver->public_id]));
367
+ if ($vehicle = $driver->vehicle) {
368
+ $vehicle->updateQuietly($positionData);
369
+ $vehicle->createPosition($positionData);
370
+ broadcast(new VehicleLocationChanged($vehicle, ['driver' => $driver->public_id]));
375
371
  }
376
372
 
377
373
  if ($isGeocodable) {
378
- // attempt to geocode and fill country and city
379
- $geocoded = Geocoder::reverse($latitude, $longitude)->get()->first();
380
-
381
- if ($geocoded) {
382
- $driver->update([
383
- 'city' => $geocoded->getLocality(),
384
- 'country' => $geocoded->getCountry()->getCode(),
385
- ]);
374
+ try {
375
+ $geocoded = Geocoder::reverse($latitude, $longitude)->get()->first();
376
+ if ($geocoded) {
377
+ $driver->updateQuietly([
378
+ 'city' => $geocoded->getLocality(),
379
+ 'country' => $geocoded->getCountry()->getCode(),
380
+ ]);
381
+ }
382
+ } catch (\Throwable $e) {
383
+ if (app()->bound('sentry')) {
384
+ app('sentry')->captureException($e);
385
+ }
386
386
  }
387
387
  }
388
388
 
@@ -227,12 +227,12 @@ class VehicleController extends Controller
227
227
  try {
228
228
  $vehicle = Vehicle::findRecordOrFail($id);
229
229
  } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $exception) {
230
- return response()->json(
231
- [
232
- 'error' => 'Vehicle resource not found.',
233
- ],
234
- 404
235
- );
230
+ return response()->apiError('Vehicle resource not found.', 404);
231
+ }
232
+
233
+ // If no lat/lng provided, maintain compatibility and just return existing driver resource
234
+ if (empty($latitude) && empty($longitude)) {
235
+ return new VehicleResource($vehicle);
236
236
  }
237
237
 
238
238
  $positionData = [
@@ -260,7 +260,7 @@ class VehicleController extends Controller
260
260
  }
261
261
  }
262
262
 
263
- $vehicle->update($positionData);
263
+ $vehicle->updateQuietly($positionData);
264
264
  $vehicle->createPosition($positionData);
265
265
 
266
266
  broadcast(new VehicleLocationChanged($vehicle));
@@ -12,31 +12,46 @@ class OrderFilter extends Filter
12
12
  {
13
13
  public function queryForInternal()
14
14
  {
15
- $this->builder
16
- ->where('company_uuid', $this->request->session()->get('company'))
17
- ->whereHas(
18
- 'payload',
19
- function ($q) {
20
- $q->where(
21
- function ($q) {
22
- $q->whereHas('waypoints');
23
- $q->orWhereHas('pickup');
24
- $q->orWhereHas('dropoff');
25
- }
26
- );
27
- $q->with(['entities', 'waypoints', 'dropoff', 'pickup', 'return']);
28
- }
29
- )
30
- ->whereHas('trackingNumber')
31
- ->whereHas('trackingStatuses')
32
- ->with(
33
- [
34
- 'payload',
35
- 'trackingNumber',
36
- 'trackingStatuses',
37
- 'driverAssigned',
38
- ]
39
- );
15
+ $companyUuid = $this->request->session()->get('company');
16
+
17
+ // apply company scope first for indexed filtering
18
+ $this->builder->where('orders.company_uuid', $companyUuid);
19
+
20
+ // replace ambiguous whereRelation with qualified whereHas to avoid alias clashes
21
+ $this->builder->whereHas('payload', function ($payloadQuery) {
22
+ $payloadQuery->where(function ($q) {
23
+ $q->whereHas('waypoints', function ($w) {
24
+ $w->whereNotNull('waypoints.uuid');
25
+ });
26
+ $q->orWhereHas('pickup', function ($p) {
27
+ $p->whereNotNull('places.uuid');
28
+ });
29
+ $q->orWhereHas('dropoff', function ($d) {
30
+ $d->whereNotNull('places.uuid');
31
+ });
32
+ });
33
+ });
34
+
35
+ // ensure associated tracking data exists
36
+ $this->builder->whereHas('trackingNumber', function ($q) {
37
+ $q->select('uuid');
38
+ });
39
+
40
+ $this->builder->whereHas('trackingStatuses', function ($q) {
41
+ $q->select('uuid');
42
+ });
43
+
44
+ // eager load main relationships to reduce N+1 overhead
45
+ $this->builder->with([
46
+ 'payload.entities',
47
+ 'payload.waypoints',
48
+ 'payload.pickup',
49
+ 'payload.dropoff',
50
+ 'payload.return',
51
+ 'trackingNumber',
52
+ 'trackingStatuses',
53
+ 'driverAssigned',
54
+ ]);
40
55
  }
41
56
 
42
57
  public function queryForPublic()
@@ -108,6 +123,8 @@ class OrderFilter extends Filter
108
123
  $this->builder->whereNotIn('status', ['created', 'completed', 'expired', 'order_canceled', 'canceled', 'pending']);
109
124
  // remove the searchBuilder where clause
110
125
  $this->builder->removeWhereFromQuery('status', 'active');
126
+
127
+ return;
111
128
  }
112
129
 
113
130
  $status = Utils::arrayFrom($status);
@@ -38,6 +38,7 @@ class Payload extends FleetbaseResource
38
38
  'cod_amount' => $this->cod_amount ?? null,
39
39
  'cod_currency' => $this->cod_currency ?? null,
40
40
  'cod_payment_method' => $this->cod_payment_method ?? null,
41
+ 'payment_method' => $this->payment_method ?? null,
41
42
  'meta' => data_get($this, 'meta', Utils::createObject()),
42
43
  'updated_at' => $this->updated_at,
43
44
  'created_at' => $this->created_at,
@@ -70,7 +70,6 @@ class Vendor extends Model
70
70
  'meta',
71
71
  'callbacks',
72
72
  'phone',
73
- 'place_uuid',
74
73
  'country',
75
74
  'status',
76
75
  'notes',