@fleetbase/fleetops-engine 0.6.44 → 0.6.46

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.
Files changed (172) hide show
  1. package/addon/components/admin/map-settings.hbs +26 -0
  2. package/addon/components/admin/map-settings.js +46 -0
  3. package/addon/components/admin/routing-settings.hbs +11 -0
  4. package/addon/components/cell/order-route-type.hbs +46 -0
  5. package/addon/components/cell/order-route-type.js +90 -0
  6. package/addon/components/cell/place-address.hbs +1 -0
  7. package/addon/components/display-place.hbs +45 -56
  8. package/addon/components/display-place.js +21 -0
  9. package/addon/components/entity/form.hbs +24 -0
  10. package/addon/components/entity/form.js +30 -0
  11. package/addon/components/layout/fleet-ops-sidebar/driver-listing.js +5 -5
  12. package/addon/components/layout/fleet-ops-sidebar/fleet-listing.js +5 -5
  13. package/addon/components/layout/fleet-ops-sidebar.js +8 -0
  14. package/addon/components/leaflet-draw-control.js +1 -1
  15. package/addon/components/maintenance-schedule/form.js +2 -1
  16. package/addon/components/map/drawer/driver-listing.js +5 -5
  17. package/addon/components/map/drawer/geofence-event-listing.js +5 -2
  18. package/addon/components/map/drawer/place-listing.js +5 -5
  19. package/addon/components/map/drawer/position-listing.js +102 -21
  20. package/addon/components/map/drawer/vehicle-listing.js +5 -5
  21. package/addon/components/map/google-live-map.hbs +9 -0
  22. package/addon/components/map/google-live-map.js +445 -0
  23. package/addon/components/map/leaflet-live-map.hbs +186 -142
  24. package/addon/components/map/leaflet-live-map.js +121 -41
  25. package/addon/components/map/toolbar/visibility-control-panel.js +2 -2
  26. package/addon/components/map/toolbar/zones-panel.hbs +9 -9
  27. package/addon/components/map/toolbar.hbs +3 -3
  28. package/addon/components/map/toolbar.js +9 -1
  29. package/addon/components/modals/map-layer-form.hbs +132 -63
  30. package/addon/components/modals/orchestrator-import.hbs +4 -11
  31. package/addon/components/modals/orchestrator-import.js +17 -1
  32. package/addon/components/modals/update-order-activity.hbs +1 -1
  33. package/addon/components/orchestrator/order-pool.js +3 -2
  34. package/addon/components/orchestrator-workbench.js +29 -41
  35. package/addon/components/order/details/detail.js +2 -2
  36. package/addon/components/order/details/payload.hbs +5 -5
  37. package/addon/components/order/details/payload.js +18 -1
  38. package/addon/components/order/details/route.hbs +2 -2
  39. package/addon/components/order/details/route.js +76 -3
  40. package/addon/components/order/form/payload.hbs +33 -0
  41. package/addon/components/order/form/payload.js +1 -0
  42. package/addon/components/order/form/route.hbs +8 -8
  43. package/addon/components/order/form/route.js +78 -18
  44. package/addon/components/order/form/service-rate.hbs +1 -1
  45. package/addon/components/order/form.js +2 -2
  46. package/addon/components/order/route-editor.hbs +209 -147
  47. package/addon/components/order/route-editor.js +143 -102
  48. package/addon/components/place/address.hbs +1 -0
  49. package/addon/components/place/address.js +12 -0
  50. package/addon/components/route-list.hbs +43 -55
  51. package/addon/components/route-list.js +47 -0
  52. package/addon/components/route-optimization-wizard-panel.js +3 -4
  53. package/addon/components/service-rate/details.hbs +6 -6
  54. package/addon/components/service-rate/form.hbs +28 -15
  55. package/addon/components/vehicle/card.hbs +4 -4
  56. package/addon/controllers/maintenance/schedules/index.js +2 -1
  57. package/addon/controllers/operations/orders/index/details/virtual.js +5 -0
  58. package/addon/controllers/operations/orders/index/details.js +113 -15
  59. package/addon/controllers/operations/orders/index/new.js +1 -1
  60. package/addon/controllers/operations/orders/index.js +8 -1
  61. package/addon/controllers/operations/service-rates/index/edit.js +1 -0
  62. package/addon/controllers/operations/service-rates/index/new.js +1 -0
  63. package/addon/controllers/settings/map.js +97 -0
  64. package/addon/controllers/settings/routing.js +47 -12
  65. package/addon/extension.js +11 -0
  66. package/addon/helpers/f-to-int.js +6 -0
  67. package/addon/helpers/place-address-html.js +6 -0
  68. package/addon/instance-initializers/register-google-routes.js +17 -0
  69. package/addon/instance-initializers/register-osrm.js +5 -0
  70. package/addon/routes/application.js +9 -3
  71. package/addon/routes/operations/orchestrator.js +13 -0
  72. package/addon/routes/operations/orders/index/details/virtual.js +1 -0
  73. package/addon/routes/operations/orders/index/details.js +47 -24
  74. package/addon/routes/operations/orders/index/new.js +9 -0
  75. package/addon/routes/operations/orders/index.js +0 -1
  76. package/addon/routes/settings/map.js +3 -0
  77. package/addon/routes.js +1 -0
  78. package/addon/services/entity-actions.js +1 -1
  79. package/addon/services/equipment-actions.js +4 -6
  80. package/addon/services/geofence-event-bus.js +2 -1
  81. package/addon/services/geofence.js +204 -168
  82. package/addon/services/google-routes.js +300 -0
  83. package/addon/services/leaflet-draw-restriction.js +2 -1
  84. package/addon/services/leaflet-map-manager.js +21 -0
  85. package/addon/services/maintenance-actions.js +4 -6
  86. package/addon/services/map-adapter/google.js +2205 -0
  87. package/addon/services/map-adapter/leaflet.js +1042 -0
  88. package/addon/services/map-adapter-interface.js +551 -0
  89. package/addon/services/map-manager.js +768 -0
  90. package/addon/services/map-settings.js +75 -0
  91. package/addon/services/movement-tracker.js +83 -32
  92. package/addon/services/order-actions.js +11 -1
  93. package/addon/services/osrm.js +79 -6
  94. package/addon/services/part-actions.js +4 -6
  95. package/addon/services/position-playback.js +135 -62
  96. package/addon/services/route-engine.js +83 -0
  97. package/addon/services/service-areas.js +8 -1
  98. package/addon/services/service-rate-actions.js +8 -3
  99. package/addon/styles/fleetops-engine.css +555 -0
  100. package/addon/templates/management/vehicles/index.hbs +0 -1
  101. package/addon/templates/operations/orders/index/details/virtual.hbs +7 -1
  102. package/addon/templates/operations/orders/index/details.hbs +0 -1
  103. package/addon/templates/settings/map.hbs +53 -0
  104. package/addon/templates/settings/routing.hbs +15 -4
  105. package/addon/utils/fleet-ops-options.js +34 -0
  106. package/addon/utils/has-valid-coordinates.js +6 -0
  107. package/addon/utils/map-provider-options.js +6 -0
  108. package/addon/utils/order-route-editing.js +86 -0
  109. package/addon/utils/order-route-summary.js +20 -0
  110. package/addon/utils/place-address-html.js +41 -0
  111. package/addon/utils/route-colors.js +43 -12
  112. package/addon/utils/route-visualization.js +93 -0
  113. package/addon/utils/serialize-payload.js +1 -1
  114. package/addon/utils/vendor-integration.js +2 -1
  115. package/app/components/admin/map-settings.js +1 -0
  116. package/app/components/admin/routing-settings.js +1 -0
  117. package/app/components/cell/order-route-type.js +1 -0
  118. package/app/components/cell/place-address.js +1 -0
  119. package/app/components/map/google-live-map.js +1 -0
  120. package/app/components/place/address.js +1 -0
  121. package/app/controllers/operations/orders/index/details/virtual.js +1 -0
  122. package/app/controllers/settings/map.js +1 -0
  123. package/app/helpers/f-to-int.js +1 -0
  124. package/app/helpers/place-address-html.js +1 -0
  125. package/app/routes/settings/map.js +1 -0
  126. package/app/services/map-adapter/google.js +1 -0
  127. package/app/services/map-adapter/leaflet.js +1 -0
  128. package/app/services/map-adapter-interface.js +1 -0
  129. package/app/services/map-manager.js +1 -0
  130. package/app/services/map-settings.js +1 -0
  131. package/app/templates/settings/map.js +1 -0
  132. package/app/utils/has-valid-coordinates.js +1 -0
  133. package/app/utils/order-route-editing.js +2 -0
  134. package/app/utils/order-route-summary.js +1 -0
  135. package/app/utils/place-address-html.js +1 -0
  136. package/app/utils/route-colors.js +1 -1
  137. package/app/utils/route-visualization.js +1 -0
  138. package/assets/images/drawing-controls-spritesheet.svg +156 -0
  139. package/composer.json +1 -1
  140. package/extension.json +1 -1
  141. package/package.json +3 -3
  142. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  143. package/server/src/Http/Controllers/Api/v1/OrderController.php +36 -8
  144. package/server/src/Http/Controllers/Api/v1/PayloadController.php +14 -1
  145. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +11 -3
  146. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -0
  147. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +7 -2
  148. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +6 -1
  149. package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -0
  150. package/server/src/Http/Controllers/Internal/v1/LiveController.php +2 -1
  151. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +7 -4
  152. package/server/src/Http/Controllers/Internal/v1/OrderController.php +61 -34
  153. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +3 -2
  154. package/server/src/Http/Controllers/Internal/v1/SettingController.php +106 -3
  155. package/server/src/Http/Filter/OrderFilter.php +2 -1
  156. package/server/src/Http/Requests/CreateServiceRateRequest.php +2 -2
  157. package/server/src/Http/Resources/v1/Index/Payload.php +9 -7
  158. package/server/src/Http/Resources/v1/Order.php +20 -2
  159. package/server/src/Http/Resources/v1/ServiceQuote.php +2 -2
  160. package/server/src/Models/Entity.php +7 -0
  161. package/server/src/Models/Order.php +81 -8
  162. package/server/src/Models/OrderConfig.php +15 -0
  163. package/server/src/Models/Payload.php +131 -27
  164. package/server/src/Models/Place.php +182 -48
  165. package/server/src/Models/ServiceRate.php +125 -32
  166. package/server/src/Observers/PurchaseRateObserver.php +3 -5
  167. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +59 -23
  168. package/server/src/Rules/ComputableAlgo.php +1 -3
  169. package/server/src/Support/Algo.php +87 -1
  170. package/server/src/Support/Utils.php +69 -0
  171. package/server/src/routes.php +4 -0
  172. package/translations/en-us.yaml +1937 -1916
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/fleetops-api",
3
- "version": "0.6.44",
3
+ "version": "0.6.46",
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.44",
3
+ "version": "0.6.46",
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.44",
3
+ "version": "0.6.46",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "fleet-ops"
@@ -43,8 +43,8 @@
43
43
  "dependencies": {
44
44
  "@babel/core": "^7.23.2",
45
45
  "@fleetbase/ember-core": "^0.3.18",
46
- "@fleetbase/ember-ui": "^0.3.26",
47
- "@fleetbase/fleetops-data": "^0.1.29",
46
+ "@fleetbase/ember-ui": "^0.3.29",
47
+ "@fleetbase/fleetops-data": "^0.1.31",
48
48
  "@fleetbase/leaflet-routing-machine": "^3.2.17",
49
49
  "@fortawesome/ember-fontawesome": "^2.0.0",
50
50
  "@fortawesome/fontawesome-svg-core": "6.4.0",
@@ -157,6 +157,11 @@ class FleetOps
157
157
  'action' => [],
158
158
  'remove_actions' => ['export', 'create'],
159
159
  ],
160
+ [
161
+ 'name' => 'map-settings',
162
+ 'action' => [],
163
+ 'remove_actions' => ['export', 'create'],
164
+ ],
160
165
  ];
161
166
 
162
167
  /**
@@ -100,6 +100,11 @@ class OrderController extends Controller
100
100
  $pickup = data_get($payloadInput, 'pickup');
101
101
  $dropoff = data_get($payloadInput, 'dropoff');
102
102
  $return = data_get($payloadInput, 'return');
103
+ $hasPickupField = array_key_exists('pickup', $payloadInput);
104
+ $hasDropoffField = array_key_exists('dropoff', $payloadInput);
105
+ $hasReturnField = array_key_exists('return', $payloadInput);
106
+ $hasWaypointsField = array_key_exists('waypoints', $payloadInput);
107
+ $hasRouteEndpointFields = $hasPickupField || $hasDropoffField || $hasReturnField;
103
108
 
104
109
  if ($pickup) {
105
110
  $payload->setPickup($pickup, [
@@ -321,7 +326,7 @@ class OrderController extends Controller
321
326
  }
322
327
 
323
328
  // load required relations
324
- $order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
329
+ $order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
325
330
 
326
331
  // Determine if order should be dispatched on creation
327
332
  $shouldDispatch = $request->boolean('dispatch') && $integratedVendorOrder === null;
@@ -364,7 +369,7 @@ class OrderController extends Controller
364
369
 
365
370
  // find for the order
366
371
  try {
367
- $order = Order::findRecordOrFail($id, ['trackingNumber', 'driverAssigned', 'purchaseRate', 'customer', 'facilitator']);
372
+ $order = Order::findRecordOrFail($id, ['trackingNumber', 'driverAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
368
373
  } catch (ModelNotFoundException $exception) {
369
374
  return response()->json(
370
375
  [
@@ -414,14 +419,21 @@ class OrderController extends Controller
414
419
  $payload->save();
415
420
 
416
421
  // set waypoints and entities after payload is saved
417
- if ($waypoints) {
422
+ if ($hasWaypointsField && is_array($waypoints) && count($waypoints)) {
418
423
  $payload->setWaypoints($waypoints);
424
+ } elseif ($hasWaypointsField || $hasRouteEndpointFields) {
425
+ $payload->removeWaypoints();
419
426
  }
420
427
 
421
428
  if ($entities) {
422
429
  $payload->setEntities($entities);
423
430
  }
424
431
 
432
+ $firstWaypoint = $payload->getPickupOrFirstWaypoint();
433
+ if ($firstWaypoint instanceof Place) {
434
+ $payload->setCurrentWaypoint($firstWaypoint);
435
+ }
436
+
425
437
  $input['payload_uuid'] = $payload->uuid;
426
438
  } elseif ($request->has('payload')) {
427
439
  $input['payload_uuid'] = Utils::getUuid('payloads', [
@@ -440,6 +452,11 @@ class OrderController extends Controller
440
452
  $pickup = data_get($payloadInput, 'pickup');
441
453
  $dropoff = data_get($payloadInput, 'dropoff');
442
454
  $return = data_get($payloadInput, 'return');
455
+ $hasPickupField = $request->exists('pickup');
456
+ $hasDropoffField = $request->exists('dropoff');
457
+ $hasReturnField = $request->exists('return');
458
+ $hasWaypointsField = $request->exists('waypoints');
459
+ $hasRouteEndpointFields = $hasPickupField || $hasDropoffField || $hasReturnField;
443
460
 
444
461
  // if no pickup and dropoff extract from waypoints
445
462
  if (empty($pickup) && empty($dropoff) && count($waypoints)) {
@@ -462,14 +479,21 @@ class OrderController extends Controller
462
479
  $payload->save();
463
480
 
464
481
  // set waypoints and entities after payload is saved
465
- if ($waypoints) {
482
+ if ($hasWaypointsField && is_array($waypoints) && count($waypoints)) {
466
483
  $payload->setWaypoints($waypoints);
484
+ } elseif ($hasWaypointsField || $hasRouteEndpointFields) {
485
+ $payload->removeWaypoints();
467
486
  }
468
487
 
469
488
  if ($entities) {
470
489
  $payload->setEntities($entities);
471
490
  }
472
491
 
492
+ $firstWaypoint = $payload->getPickupOrFirstWaypoint();
493
+ if ($firstWaypoint instanceof Place) {
494
+ $payload->setCurrentWaypoint($firstWaypoint);
495
+ }
496
+
473
497
  $input['payload_uuid'] = $payload->uuid;
474
498
  }
475
499
 
@@ -549,7 +573,7 @@ class OrderController extends Controller
549
573
  $order->flushAttributesCache();
550
574
 
551
575
  // load required relations
552
- $order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
576
+ $order->load(['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
553
577
 
554
578
  // response the order resource
555
579
  return new OrderResource($order);
@@ -759,7 +783,7 @@ class OrderController extends Controller
759
783
  {
760
784
  // find for the order
761
785
  try {
762
- $order = Order::findRecordOrFail($id, ['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
786
+ $order = Order::findRecordOrFail($id, ['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
763
787
  } catch (ModelNotFoundException $exception) {
764
788
  return response()->json(
765
789
  [
@@ -839,7 +863,7 @@ class OrderController extends Controller
839
863
  public function dispatchOrder(string $id)
840
864
  {
841
865
  try {
842
- $order = Order::findRecordOrFail($id, ['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate', 'customer', 'facilitator']);
866
+ $order = Order::findRecordOrFail($id, ['trackingNumber', 'trackingStatuses', 'driverAssigned', 'vehicleAssigned', 'purchaseRate.serviceQuote.items', 'customer', 'facilitator']);
843
867
  } catch (ModelNotFoundException $exception) {
844
868
  return response()->json(
845
869
  [
@@ -1159,7 +1183,11 @@ class OrderController extends Controller
1159
1183
  })->first();
1160
1184
  }
1161
1185
 
1162
- $activities = $order->config()->nextActivity($waypoint);
1186
+ $orderConfig = $order->ensureOrderConfig();
1187
+ if (!$orderConfig) {
1188
+ return response()->apiError('No order config found for order.');
1189
+ }
1190
+ $activities = $orderConfig->nextActivity($waypoint);
1163
1191
 
1164
1192
  // If activity is to complete order add proof of delivery properties if required
1165
1193
  // This is a temporary fix until activity is updated to handle POD on it's own
@@ -31,6 +31,11 @@ class PayloadController extends Controller
31
31
  $pickup = data_get($input, 'pickup');
32
32
  $dropoff = data_get($input, 'dropoff');
33
33
  $return = data_get($input, 'return');
34
+ $hasPickupField = array_key_exists('pickup', $input);
35
+ $hasDropoffField = array_key_exists('dropoff', $input);
36
+ $hasReturnField = array_key_exists('return', $input);
37
+ $hasWaypointsField = array_key_exists('waypoints', $input);
38
+ $hasRouteEndpointFields = $hasPickupField || $hasDropoffField || $hasReturnField;
34
39
 
35
40
  // make sure company is set
36
41
  $input['company_uuid'] = session('company');
@@ -116,8 +121,10 @@ class PayloadController extends Controller
116
121
  }
117
122
 
118
123
  // set waypoints
119
- if ($waypoints) {
124
+ if ($hasWaypointsField && is_array($waypoints) && count($waypoints)) {
120
125
  $payload->setWaypoints($waypoints);
126
+ } elseif ($hasWaypointsField || $hasRouteEndpointFields) {
127
+ $payload->removeWaypoints();
121
128
  }
122
129
 
123
130
  // set entities
@@ -131,6 +138,12 @@ class PayloadController extends Controller
131
138
  // save the payload
132
139
  $payload->save();
133
140
 
141
+ // set the first / current waypoint from the effective route shape
142
+ $firstWaypoint = $payload->getPickupOrFirstWaypoint();
143
+ if ($firstWaypoint instanceof Place) {
144
+ $payload->setCurrentWaypoint($firstWaypoint);
145
+ }
146
+
134
147
  // make sure entities and waypoints is loaded
135
148
  $payload->load(['entities', 'waypoints', 'pickup', 'dropoff', 'return']);
136
149
 
@@ -41,10 +41,19 @@ class PurchaseRateController extends Controller
41
41
 
42
42
  // order assignment
43
43
  if ($request->has('order')) {
44
- $input['order_uuid'] = Utils::getUuid('orders', [
44
+ $orderUuid = Utils::getUuid('orders', [
45
45
  'public_id' => $request->input('order'),
46
46
  'company_uuid' => session('company'),
47
47
  ]);
48
+
49
+ $order = Order::where('uuid', $orderUuid)->first();
50
+
51
+ if ($order instanceof Order) {
52
+ $input['payload_uuid'] = $order->payload_uuid;
53
+ $input['customer_uuid'] = $order->customer_uuid;
54
+ $input['customer_type'] = $order->customer_type;
55
+ $input['company_uuid'] = $order->company_uuid ?? $input['company_uuid'];
56
+ }
48
57
  } elseif ($createOrder) {
49
58
  // create order from service quote
50
59
  $serviceQuote = ServiceQuote::where('uuid', $input['service_quote_uuid'])->orWhere('public_id', $request->input('service_quote'))->first();
@@ -70,9 +79,8 @@ class PurchaseRateController extends Controller
70
79
  // create the purchaseRate
71
80
  $purchaseRate = PurchaseRate::create($input);
72
81
 
73
- // set purchase rate to order
74
82
  if ($order instanceof Order) {
75
- $order->update(['purchase_rate_uuid' => $purchaseRate->uuid]);
83
+ $order->attachPurchaseRate($purchaseRate);
76
84
  }
77
85
 
78
86
  // response the driver resource
@@ -65,6 +65,7 @@ class ServiceAreaController extends Controller
65
65
  // create the serviceArea
66
66
  try {
67
67
  $serviceArea = ServiceArea::create($input);
68
+ $serviceArea->refresh();
68
69
  } catch (\Throwable $e) {
69
70
  logger()->error('Unable to create service area.', ['error' => $e->getMessage()]);
70
71
 
@@ -135,6 +136,7 @@ class ServiceAreaController extends Controller
135
136
 
136
137
  // update the serviceArea
137
138
  $serviceArea->update($input);
139
+ $serviceArea->refresh();
138
140
 
139
141
  // response the serviceArea resource
140
142
  return new ServiceAreaResource($serviceArea);
@@ -98,6 +98,7 @@ class ServiceQuoteController extends Controller
98
98
  'amount' => $subTotal,
99
99
  'currency' => $serviceRate->currency,
100
100
  ]);
101
+ $quote->setRelation('serviceRate', $serviceRate);
101
102
 
102
103
  $items = $lines->map(function ($line) use ($quote) {
103
104
  return ServiceQuoteItem::create([
@@ -143,6 +144,7 @@ class ServiceQuoteController extends Controller
143
144
  'amount' => $subTotal,
144
145
  'currency' => $serviceRate->currency,
145
146
  ]);
147
+ $quote->setRelation('serviceRate', $serviceRate);
146
148
 
147
149
  $items = $lines->map(function ($line) use ($quote) {
148
150
  return ServiceQuoteItem::create([
@@ -230,6 +232,7 @@ class ServiceQuoteController extends Controller
230
232
 
231
233
  // should all be Place like
232
234
  $waypoints = collect([$pickup, ...$waypoints, $dropoff])->filter();
235
+ $endpointCount = (int) ($pickup instanceof Place) + (int) ($dropoff instanceof Place);
233
236
 
234
237
  // if facilitator is an integrated partner resolve service quotes from bridge
235
238
  if ($facilitator && Utils::isIntegratedVendorId($facilitator)) {
@@ -279,7 +282,7 @@ class ServiceQuoteController extends Controller
279
282
  $serviceQuotes = collect();
280
283
 
281
284
  if ($serviceRate) {
282
- [$subTotal, $lines] = $serviceRate->quoteFromPreliminaryData($entities, $waypoints, $totalDistance, $totalTime, $isCashOnDelivery);
285
+ [$subTotal, $lines] = $serviceRate->quoteFromPreliminaryData($entities, $waypoints, $totalDistance, $totalTime, $isCashOnDelivery, $endpointCount);
283
286
 
284
287
  $quote = ServiceQuote::create([
285
288
  'request_id' => $requestId,
@@ -288,6 +291,7 @@ class ServiceQuoteController extends Controller
288
291
  'amount' => $subTotal,
289
292
  'currency' => $serviceRate->currency,
290
293
  ]);
294
+ $quote->setRelation('serviceRate', $serviceRate);
291
295
 
292
296
  // set preliminary data to meta
293
297
  $quote->updateMeta('preliminary_data', $preliminaryData);
@@ -326,7 +330,7 @@ class ServiceQuoteController extends Controller
326
330
 
327
331
  // calculate quotes
328
332
  foreach ($serviceRates as $serviceRate) {
329
- [$subTotal, $lines] = $serviceRate->quoteFromPreliminaryData($entities, $waypoints, $totalDistance, $totalTime, $isCashOnDelivery);
333
+ [$subTotal, $lines] = $serviceRate->quoteFromPreliminaryData($entities, $waypoints, $totalDistance, $totalTime, $isCashOnDelivery, $endpointCount);
330
334
 
331
335
  $quote = ServiceQuote::create([
332
336
  'request_id' => $requestId,
@@ -335,6 +339,7 @@ class ServiceQuoteController extends Controller
335
339
  'amount' => $subTotal,
336
340
  'currency' => $serviceRate->currency,
337
341
  ]);
342
+ $quote->setRelation('serviceRate', $serviceRate);
338
343
 
339
344
  // set preliminary data to meta
340
345
  $quote->updateMeta('preliminary_data', $preliminaryData);
@@ -30,6 +30,8 @@ class ServiceRateController extends Controller
30
30
  'rate_calculation_method',
31
31
  'currency',
32
32
  'base_fee',
33
+ 'max_distance_unit',
34
+ 'max_distance',
33
35
  'per_meter_unit',
34
36
  'per_meter_flat_rate_fee',
35
37
  'meter_fees',
@@ -119,7 +121,10 @@ class ServiceRateController extends Controller
119
121
  'rate_calculation_method',
120
122
  'currency',
121
123
  'base_fee',
122
- 'per_km_flat_rate_fee',
124
+ 'max_distance_unit',
125
+ 'max_distance',
126
+ 'per_meter_unit',
127
+ 'per_meter_flat_rate_fee',
123
128
  'meter_fees',
124
129
  'meter_fees.*.distance',
125
130
  'meter_fees.*.fee',
@@ -69,6 +69,7 @@ class ZoneController extends Controller
69
69
 
70
70
  // create the zone
71
71
  $zone = Zone::create($input);
72
+ $zone->refresh();
72
73
 
73
74
  // response the zone resource
74
75
  return new ZoneResource($zone);
@@ -134,6 +135,7 @@ class ZoneController extends Controller
134
135
 
135
136
  // update the zone
136
137
  $zone->update($input);
138
+ $zone->refresh();
137
139
 
138
140
  // response the zone resource
139
141
  return new ZoneResource($zone);
@@ -128,10 +128,11 @@ class LiveController extends Controller
128
128
  ->applyDirectivesForPermissions('fleet-ops list order')
129
129
  ->with([
130
130
  'payload.entities',
131
- 'payload.waypoints',
132
131
  'payload.dropoff',
133
132
  'payload.pickup',
134
133
  'payload.return',
134
+ 'payload.firstWaypointMarker.place',
135
+ 'payload.lastWaypointMarker.place',
135
136
  'trackingNumber',
136
137
  'trackingStatuses',
137
138
  'driverAssigned' => function ($query) {
@@ -574,12 +574,15 @@ class OrchestrationController extends Controller
574
574
 
575
575
  // ── Resolve OrderConfig ───────────────────────────────────────
576
576
  $orderConfigUuid = null;
577
+ $resolvedOrderConfig = null;
577
578
  if (!empty($firstRow['type'])) {
578
- $orderConfig = OrderConfig::resolveFromIdentifier($firstRow['type']);
579
- if ($orderConfig) {
580
- $orderConfigUuid = $orderConfig->uuid;
579
+ $resolvedOrderConfig = OrderConfig::resolveFromIdentifier($firstRow['type']);
580
+ if ($resolvedOrderConfig) {
581
+ $orderConfigUuid = $resolvedOrderConfig->uuid;
581
582
  }
582
583
  }
584
+ $resolvedOrderConfig ??= OrderConfig::defaultOrCreate();
585
+ $orderConfigUuid = $resolvedOrderConfig?->uuid;
583
586
 
584
587
  // ── Resolve Customer ─────────────────────────────────────────
585
588
  $customerUuid = null;
@@ -660,7 +663,7 @@ class OrchestrationController extends Controller
660
663
  'driver_assigned_uuid' => $driverUuid,
661
664
  'internal_id' => $firstRow['internal_id'] ?? null,
662
665
  'status' => $firstRow['status'] ?? 'created',
663
- 'type' => $firstRow['type'] ?? 'default',
666
+ 'type' => $resolvedOrderConfig?->key ?? 'transport',
664
667
  'notes' => $firstRow['notes'] ?? null,
665
668
  'scheduled_at' => !empty($firstRow['scheduled_at'])
666
669
  ? Carbon::parse($firstRow['scheduled_at'])
@@ -109,9 +109,19 @@ class OrderController extends FleetOpsController
109
109
  $input['integrated_vendor_order'] = $integratedVendorOrder;
110
110
  }
111
111
 
112
- // if no type is set its default to default
113
- if (!isset($input['type'])) {
114
- $input['type'] = 'default';
112
+ // Normalize order config + type onto the company transport
113
+ // config when an explicit config was not provided.
114
+ $resolvedOrderConfig = null;
115
+ if (!empty($input['order_config_uuid']) || !empty($input['type'])) {
116
+ $resolvedOrderConfig = OrderConfig::resolveFromIdentifier([$input['order_config_uuid'] ?? null, $input['type'] ?? null]);
117
+ }
118
+ $resolvedOrderConfig ??= OrderConfig::defaultOrCreate();
119
+
120
+ if ($resolvedOrderConfig) {
121
+ $input['order_config_uuid'] = $resolvedOrderConfig->uuid;
122
+ $input['type'] = $resolvedOrderConfig->key;
123
+ } elseif (!isset($input['type'])) {
124
+ $input['type'] = 'transport';
115
125
  }
116
126
 
117
127
  // if no status is set its default to `created`
@@ -119,14 +129,6 @@ class OrderController extends FleetOpsController
119
129
  $input['status'] = 'created';
120
130
  }
121
131
 
122
- // Set order config
123
- if (!isset($input['order_config_uuid'])) {
124
- $defaultOrderConfig = OrderConfig::default();
125
- if ($defaultOrderConfig) {
126
- $input['order_config_uuid'] = $defaultOrderConfig->uuid;
127
- }
128
- }
129
-
130
132
  // Ensure orchestrator_priority is never null — the column is NOT NULL
131
133
  // and the DB default is bypassed when Eloquent receives an explicit null.
132
134
  if (!isset($input['orchestrator_priority']) || !is_numeric($input['orchestrator_priority'])) {
@@ -225,10 +227,14 @@ class OrderController extends FleetOpsController
225
227
  */
226
228
  public function editOrderRoute(string $id, Request $request)
227
229
  {
228
- $pickup = $request->input('pickup');
229
- $dropoff = $request->input('dropoff');
230
- $return = $request->input('return');
231
- $waypoints = $request->array('waypoints', []);
230
+ $pickup = $request->input('pickup');
231
+ $dropoff = $request->input('dropoff');
232
+ $return = $request->input('return');
233
+ $waypoints = $request->array('waypoints', []);
234
+ $hasPickupInput = $request->exists('pickup');
235
+ $hasDropoffInput = $request->exists('dropoff');
236
+ $hasReturnInput = $request->exists('return');
237
+ $hasWaypointsInput = $request->exists('waypoints');
232
238
 
233
239
  // Get the order
234
240
  $order = Order::where('uuid', $id)->with(['payload'])->first();
@@ -236,30 +242,51 @@ class OrderController extends FleetOpsController
236
242
  return response()->error('Unable to find order to update route for.');
237
243
  }
238
244
 
239
- // Handle update of multiple waypoints
240
- if ($waypoints) {
241
- $order->payload->updateWaypoints($waypoints);
242
- $order->payload->removePlace(['pickup', 'dropoff', 'return'], ['save' => true]);
243
- } else {
244
- // Update pickup
245
+ if ($hasPickupInput) {
245
246
  if ($pickup) {
246
247
  $order->payload->setPickup($pickup, ['save' => true]);
248
+ } else {
249
+ $order->payload->removePlace('pickup', ['save' => true]);
247
250
  }
251
+ }
248
252
 
249
- // Update dropoff
253
+ if ($hasDropoffInput) {
250
254
  if ($dropoff) {
251
255
  $order->payload->setDropoff($dropoff, ['save' => true]);
256
+ } else {
257
+ $order->payload->removePlace('dropoff', ['save' => true]);
252
258
  }
259
+ }
253
260
 
254
- // Update return
261
+ if ($hasReturnInput) {
255
262
  if ($return) {
256
- $order->payload->setDropoff($return, ['save' => true]);
263
+ $order->payload->setReturn($return, ['save' => true]);
264
+ } else {
265
+ $order->payload->removePlace('return', ['save' => true]);
257
266
  }
267
+ }
258
268
 
259
- // Remove waypoints if any
269
+ if ($hasWaypointsInput) {
270
+ if (!empty($waypoints)) {
271
+ $order->payload->updateWaypoints($waypoints);
272
+ } else {
273
+ $order->payload->removeWaypoints();
274
+ }
275
+ } elseif ($hasPickupInput || $hasDropoffInput || $hasReturnInput) {
260
276
  $order->payload->removeWaypoints();
261
277
  }
262
278
 
279
+ $startingDestination = $order->payload->getPickupOrFirstWaypoint();
280
+ if (!$startingDestination) {
281
+ $startingDestination = $order->payload->getDropoffOrLastWaypoint();
282
+ }
283
+
284
+ if ($startingDestination instanceof Place || $startingDestination instanceof Waypoint) {
285
+ $order->payload->setCurrentWaypoint($startingDestination);
286
+ }
287
+
288
+ $order->load(['payload.pickup', 'payload.dropoff', 'payload.return', 'payload.waypoints']);
289
+
263
290
  return ['order' => new $this->resource($order)];
264
291
  }
265
292
 
@@ -531,14 +558,10 @@ class OrderController extends FleetOpsController
531
558
  return response()->error('No order found to dispatch.');
532
559
  }
533
560
 
534
- // if order has no config set, set default config
535
- $order->loadMissing('orderConfig');
536
- if (!$order->orderConfig) {
537
- $defaultOrderConfig = OrderConfig::default();
538
- if ($defaultOrderConfig) {
539
- $order->update(['order_config_uuid' => $defaultOrderConfig->uuid]);
540
- $order->loadMissing('orderConfig');
541
- }
561
+ // Ensure the order is normalized onto a real order config before
562
+ // dispatching activities.
563
+ if (!$order->ensureOrderConfig()) {
564
+ return response()->error('No order config found for dispatch.');
542
565
  }
543
566
 
544
567
  if (!$order->hasDriverAssigned && !$order->adhoc) {
@@ -711,7 +734,11 @@ class OrderController extends FleetOpsController
711
734
  }
712
735
 
713
736
  $waypoint = $request->filled('waypoint') ? Waypoint::findByPlace($request->input('waypoint'), $order) : null;
714
- $activities = $order->config()->nextActivity($waypoint);
737
+ $orderConfig = $order->ensureOrderConfig();
738
+ if (!$orderConfig) {
739
+ return response()->error('No order config found for order.');
740
+ }
741
+ $activities = $orderConfig->nextActivity($waypoint);
715
742
 
716
743
  // If activity is to complete order add proof of delivery properties if required
717
744
  // This is a temporary fix until activity is updated to handle POD on it's own
@@ -227,6 +227,7 @@ class ServiceQuoteController extends FleetOpsController
227
227
 
228
228
  // should all be Place like
229
229
  $waypoints = collect([$pickup, ...$waypoints, $dropoff])->filter();
230
+ $endpointCount = (int) ($pickup instanceof Place) + (int) ($dropoff instanceof Place);
230
231
 
231
232
  // if facilitator is an integrated partner resolve service quotes from bridge
232
233
  if ($facilitator && Str::startsWith($facilitator, 'integrated_vendor')) {
@@ -274,7 +275,7 @@ class ServiceQuoteController extends FleetOpsController
274
275
  $serviceQuotes = collect();
275
276
 
276
277
  if ($serviceRate) {
277
- [$subTotal, $lines] = $serviceRate->quoteFromPreliminaryData($entities, $waypoints, $totalDistance, $totalTime, $isCashOnDelivery);
278
+ [$subTotal, $lines] = $serviceRate->quoteFromPreliminaryData($entities, $waypoints, $totalDistance, $totalTime, $isCashOnDelivery, $endpointCount);
278
279
 
279
280
  $quote = ServiceQuote::create([
280
281
  'request_id' => $requestId,
@@ -322,7 +323,7 @@ class ServiceQuoteController extends FleetOpsController
322
323
 
323
324
  // calculate quotes
324
325
  foreach ($serviceRates as $serviceRate) {
325
- [$subTotal, $lines] = $serviceRate->quoteFromPreliminaryData($entities, $waypoints, $totalDistance, $totalTime, $isCashOnDelivery);
326
+ [$subTotal, $lines] = $serviceRate->quoteFromPreliminaryData($entities, $waypoints, $totalDistance, $totalTime, $isCashOnDelivery, $endpointCount);
326
327
 
327
328
  $quote = ServiceQuote::create([
328
329
  'request_id' => $requestId,