@fleetbase/fleetops-engine 0.6.47 → 0.6.49

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 (223) hide show
  1. package/addon/components/admin/routing-settings.hbs +71 -0
  2. package/addon/components/admin/routing-settings.js +124 -0
  3. package/addon/components/customer/orders.js +1 -1
  4. package/addon/components/display-place.hbs +1 -1
  5. package/addon/components/driver/details.hbs +5 -1
  6. package/addon/components/driver/form.hbs +11 -1
  7. package/addon/components/fleet-listing-panel.hbs +2 -2
  8. package/addon/components/fleet-listing-panel.js +10 -0
  9. package/addon/components/map/google-live-map.js +12 -73
  10. package/addon/components/map/leaflet-live-map.hbs +149 -33
  11. package/addon/components/map/order-list-overlay/order.hbs +18 -3
  12. package/addon/components/map/toolbar.hbs +2 -2
  13. package/addon/components/map/toolbar.js +10 -2
  14. package/addon/components/orchestrator/phase-builder.hbs +49 -5
  15. package/addon/components/orchestrator/phase-builder.js +34 -1
  16. package/addon/components/orchestrator/resource-panel.hbs +19 -1
  17. package/addon/components/orchestrator/resource-panel.js +4 -0
  18. package/addon/components/orchestrator-workbench.js +4 -1
  19. package/addon/components/order/details/purchase-rate.hbs +19 -22
  20. package/addon/components/order/details/route.hbs +26 -1
  21. package/addon/components/order/details/route.js +50 -0
  22. package/addon/components/order/details/tracking.hbs +171 -27
  23. package/addon/components/order/details/tracking.js +457 -0
  24. package/addon/components/order/form.hbs +2 -2
  25. package/addon/components/order/kanban-card.hbs +18 -3
  26. package/addon/components/order-list-overlay/order.hbs +18 -3
  27. package/addon/components/order-progress-card.hbs +21 -6
  28. package/addon/components/order-tracking-lookup.hbs +7 -7
  29. package/addon/components/order-tracking-lookup.js +1 -1
  30. package/addon/components/route-list.hbs +78 -8
  31. package/addon/components/route-list.js +48 -6
  32. package/addon/components/tracking-stop-progress.hbs +45 -0
  33. package/addon/components/tracking-stop-progress.js +79 -0
  34. package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -17
  35. package/addon/components/widget/fleet-ops-key-metrics.js +26 -0
  36. package/addon/components/widget/fuel-efficiency.hbs +26 -0
  37. package/addon/components/widget/fuel-efficiency.js +43 -0
  38. package/addon/components/widget/geofence-violations.hbs +44 -0
  39. package/addon/components/widget/geofence-violations.js +55 -0
  40. package/addon/components/widget/issues-insights.hbs +43 -0
  41. package/addon/components/widget/issues-insights.js +62 -0
  42. package/addon/components/widget/kpi-active-orders.hbs +10 -0
  43. package/addon/components/widget/kpi-active-orders.js +5 -0
  44. package/addon/components/widget/kpi-aov.hbs +10 -0
  45. package/addon/components/widget/kpi-aov.js +5 -0
  46. package/addon/components/widget/kpi-distance.hbs +10 -0
  47. package/addon/components/widget/kpi-distance.js +5 -0
  48. package/addon/components/widget/kpi-drivers-online.hbs +10 -0
  49. package/addon/components/widget/kpi-drivers-online.js +5 -0
  50. package/addon/components/widget/kpi-earnings.hbs +10 -0
  51. package/addon/components/widget/kpi-earnings.js +5 -0
  52. package/addon/components/widget/kpi-open-issues.hbs +11 -0
  53. package/addon/components/widget/kpi-open-issues.js +5 -0
  54. package/addon/components/widget/kpi-tile.hbs +59 -0
  55. package/addon/components/widget/kpi-tile.js +168 -0
  56. package/addon/components/widget/live-fleet.hbs +122 -0
  57. package/addon/components/widget/live-fleet.js +365 -0
  58. package/addon/components/widget/maintenance-overview.hbs +45 -0
  59. package/addon/components/widget/maintenance-overview.js +40 -0
  60. package/addon/components/widget/on-time-delivery.hbs +27 -0
  61. package/addon/components/widget/on-time-delivery.js +73 -0
  62. package/addon/components/widget/operations-pulse.hbs +51 -0
  63. package/addon/components/widget/operations-pulse.js +173 -0
  64. package/addon/components/widget/orders-by-status.hbs +26 -0
  65. package/addon/components/widget/orders-by-status.js +54 -0
  66. package/addon/components/widget/revenue-trend.hbs +34 -0
  67. package/addon/components/widget/revenue-trend.js +67 -0
  68. package/addon/components/widget/top-drivers.hbs +44 -0
  69. package/addon/components/widget/top-drivers.js +54 -0
  70. package/addon/controllers/settings/routing.js +124 -1
  71. package/addon/extension.js +172 -3
  72. package/addon/helpers/format-duration.js +38 -0
  73. package/addon/routes/application.js +6 -0
  74. package/addon/services/map-adapter/google.js +13 -2
  75. package/addon/services/order-list-overlay.js +2 -2
  76. package/addon/styles/fleetops-engine.css +806 -46
  77. package/addon/templates/maintenance/equipment/index.hbs +1 -1
  78. package/addon/templates/maintenance/parts/index.hbs +1 -1
  79. package/addon/templates/maintenance/schedules/index.hbs +1 -1
  80. package/addon/templates/management/vehicles/index.hbs +1 -1
  81. package/addon/templates/settings/avatars.hbs +1 -1
  82. package/addon/templates/settings/custom-fields.hbs +1 -1
  83. package/addon/templates/settings/map.hbs +1 -1
  84. package/addon/templates/settings/navigator-app.hbs +1 -1
  85. package/addon/templates/settings/notifications.hbs +1 -1
  86. package/addon/templates/settings/orchestrator.hbs +1 -1
  87. package/addon/templates/settings/payments/index.hbs +1 -1
  88. package/addon/templates/settings/payments/onboard.hbs +1 -1
  89. package/addon/templates/settings/routing.hbs +82 -4
  90. package/addon/templates/settings/scheduling.hbs +1 -1
  91. package/addon/utils/live-map-card-content.js +171 -0
  92. package/app/components/tracking-stop-progress.js +1 -0
  93. package/app/components/widget/fuel-efficiency.js +1 -0
  94. package/app/components/widget/geofence-violations.js +1 -0
  95. package/app/components/widget/issues-insights.js +1 -0
  96. package/app/components/widget/kpi-active-orders.js +1 -0
  97. package/app/components/widget/kpi-aov.js +1 -0
  98. package/app/components/widget/kpi-distance.js +1 -0
  99. package/app/components/widget/kpi-drivers-online.js +1 -0
  100. package/app/components/widget/kpi-earnings.js +1 -0
  101. package/app/components/widget/kpi-open-issues.js +1 -0
  102. package/app/components/widget/kpi-tile.js +1 -0
  103. package/app/components/widget/live-fleet.js +1 -0
  104. package/app/components/widget/maintenance-overview.js +1 -0
  105. package/app/components/widget/on-time-delivery.js +1 -0
  106. package/app/components/widget/operations-pulse.js +1 -0
  107. package/app/components/widget/orders-by-status.js +1 -0
  108. package/app/components/widget/revenue-trend.js +1 -0
  109. package/app/components/widget/top-drivers.js +1 -0
  110. package/app/helpers/format-duration.js +1 -0
  111. package/app/utils/live-map-card-content.js +1 -0
  112. package/composer.json +1 -1
  113. package/extension.json +1 -1
  114. package/package.json +3 -3
  115. package/pnpm-workspace.yaml +1 -0
  116. package/server/config/fleetops.php +32 -11
  117. package/server/config/telematics.php +111 -1
  118. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +86 -0
  119. package/server/migrations/2026_05_18_000001_add_license_expiry_to_drivers_table.php +25 -0
  120. package/server/seeders/Testing/Concerns/SeedsTestingData.php +183 -0
  121. package/server/seeders/Testing/ConnectivitySeeder.php +212 -0
  122. package/server/seeders/Testing/FleetSeeder.php +341 -0
  123. package/server/seeders/Testing/MaintenanceSeeder.php +238 -0
  124. package/server/seeders/Testing/NetworkSeeder.php +250 -0
  125. package/server/seeders/Testing/OrdersSeeder.php +214 -0
  126. package/server/seeders/Testing/TestingSeeder.php +40 -0
  127. package/server/seeders/Testing/VroomOrchestrationSeeder.php +409 -0
  128. package/server/src/Console/Commands/ProcessOperationalAlerts.php +279 -0
  129. package/server/src/Http/Controllers/Api/v1/OrchestrationController.php +64 -0
  130. package/server/src/Http/Controllers/Api/v1/OrderController.php +26 -26
  131. package/server/src/Http/Controllers/Api/v1/PayloadController.php +10 -10
  132. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +1 -1
  133. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +119 -0
  134. package/server/src/Http/Controllers/Internal/v1/ContactController.php +41 -0
  135. package/server/src/Http/Controllers/Internal/v1/LiveController.php +6 -42
  136. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +82 -3
  137. package/server/src/Http/Controllers/Internal/v1/OrchestrationController.php +1 -1
  138. package/server/src/Http/Controllers/Internal/v1/OrderController.php +52 -21
  139. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  140. package/server/src/Http/Controllers/Internal/v1/SettingController.php +164 -12
  141. package/server/src/Http/Resources/v1/Driver.php +2 -0
  142. package/server/src/Http/Resources/v1/Fleet.php +4 -0
  143. package/server/src/Http/Resources/v1/Index/Driver.php +48 -0
  144. package/server/src/Http/Resources/v1/Index/Payload.php +2 -2
  145. package/server/src/Http/Resources/v1/Index/Vehicle.php +74 -1
  146. package/server/src/Models/Contact.php +122 -13
  147. package/server/src/Models/Driver.php +2 -0
  148. package/server/src/Models/Order.php +1 -1
  149. package/server/src/Models/OrderConfig.php +14 -11
  150. package/server/src/Models/Payload.php +8 -8
  151. package/server/src/Models/Place.php +0 -3
  152. package/server/src/Models/ServiceRate.php +1 -1
  153. package/server/src/Notifications/LateDeparture.php +48 -0
  154. package/server/src/Notifications/ProlongedStoppage.php +48 -0
  155. package/server/src/Notifications/RouteDeviation.php +48 -0
  156. package/server/src/Observers/ContactObserver.php +8 -0
  157. package/server/src/Orchestration/Engines/CapacityAllocationEngine.php +220 -0
  158. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +357 -38
  159. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +259 -51
  160. package/server/src/Providers/FleetOpsServiceProvider.php +32 -0
  161. package/server/src/Providers/NotificationServiceProvider.php +3 -0
  162. package/server/src/Support/Analytics/AbstractAnalytics.php +77 -0
  163. package/server/src/Support/Analytics/FuelEfficiency.php +83 -0
  164. package/server/src/Support/Analytics/GeofenceViolations.php +63 -0
  165. package/server/src/Support/Analytics/IssuesInsights.php +67 -0
  166. package/server/src/Support/Analytics/LiveFleet.php +127 -0
  167. package/server/src/Support/Analytics/MaintenanceOverview.php +70 -0
  168. package/server/src/Support/Analytics/OnTimeDelivery.php +75 -0
  169. package/server/src/Support/Analytics/OperationsPulse.php +86 -0
  170. package/server/src/Support/Analytics/OrdersByStatus.php +64 -0
  171. package/server/src/Support/Analytics/RevenueTrend.php +79 -0
  172. package/server/src/Support/Analytics/TopDrivers.php +92 -0
  173. package/server/src/Support/LiveOrderQuery.php +75 -0
  174. package/server/src/Support/Metrics/AbstractMetric.php +170 -0
  175. package/server/src/Support/Metrics/ActiveLiveOrdersMetric.php +28 -0
  176. package/server/src/Support/Metrics/AvgOrderValueMetric.php +53 -0
  177. package/server/src/Support/Metrics/DriversOnlineMetric.php +30 -0
  178. package/server/src/Support/Metrics/EarningsMetric.php +30 -0
  179. package/server/src/Support/Metrics/FuelCostsMetric.php +30 -0
  180. package/server/src/Support/Metrics/MoneyMetric.php +30 -0
  181. package/server/src/Support/Metrics/OpenIssuesMetric.php +35 -0
  182. package/server/src/Support/Metrics/OrdersCanceledMetric.php +35 -0
  183. package/server/src/Support/Metrics/OrdersCompletedMetric.php +35 -0
  184. package/server/src/Support/Metrics/OrdersInProgressMetric.php +51 -0
  185. package/server/src/Support/Metrics/OrdersScheduledMetric.php +37 -0
  186. package/server/src/Support/Metrics/Registry.php +46 -0
  187. package/server/src/Support/Metrics/ResolvedIssuesMetric.php +35 -0
  188. package/server/src/Support/Metrics/TotalCustomersMetric.php +29 -0
  189. package/server/src/Support/Metrics/TotalDistanceTraveledMetric.php +35 -0
  190. package/server/src/Support/Metrics/TotalDriversMetric.php +28 -0
  191. package/server/src/Support/Metrics/TotalTimeTraveledMetric.php +35 -0
  192. package/server/src/Support/Metrics.php +46 -234
  193. package/server/src/Support/OSRM.php +11 -6
  194. package/server/src/Support/OrderTracker.php +7 -582
  195. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +290 -0
  196. package/server/src/Support/Telematics/Providers/SafeeProvider.php +333 -0
  197. package/server/src/Support/Utils.php +1 -1
  198. package/server/src/Tracking/Contracts/TrackingProviderInterface.php +19 -0
  199. package/server/src/Tracking/Providers/CalculatedTrackingProvider.php +65 -0
  200. package/server/src/Tracking/Providers/GoogleRoutesTrackingProvider.php +118 -0
  201. package/server/src/Tracking/Providers/OsrmTrackingProvider.php +71 -0
  202. package/server/src/Tracking/README.md +25 -0
  203. package/server/src/Tracking/Support/FakeTrackingProvider.php +42 -0
  204. package/server/src/Tracking/TrackingContext.php +63 -0
  205. package/server/src/Tracking/TrackingContextBuilder.php +204 -0
  206. package/server/src/Tracking/TrackingIntelligenceService.php +180 -0
  207. package/server/src/Tracking/TrackingOptions.php +49 -0
  208. package/server/src/Tracking/TrackingProviderCapabilities.php +30 -0
  209. package/server/src/Tracking/TrackingProviderManager.php +89 -0
  210. package/server/src/Tracking/TrackingProviderRegistry.php +35 -0
  211. package/server/src/Tracking/TrackingProviderResult.php +20 -0
  212. package/server/src/Tracking/TrackingStop.php +59 -0
  213. package/server/src/routes.php +27 -0
  214. package/server/tests/AnalyticsRoutesTest.php +41 -0
  215. package/server/tests/CapacityAllocationEngineTest.php +108 -0
  216. package/server/tests/LiveOrderQueryTest.php +34 -0
  217. package/server/tests/MetricsRegistryTest.php +88 -0
  218. package/server/tests/OrchestrationPayloadBuilderTest.php +132 -0
  219. package/server/tests/OrchestratorConsumableApiTest.php +74 -0
  220. package/server/tests/PingDriverEndpointTest.php +19 -0
  221. package/server/tests/TrackingIntelligenceTest.php +176 -0
  222. package/server/tests/VroomOrchestrationEngineTest.php +250 -0
  223. package/translations/en-us.yaml +41 -0
@@ -0,0 +1,250 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Seeders\Testing;
4
+
5
+ use Fleetbase\FleetOps\Models\Contact;
6
+ use Fleetbase\FleetOps\Models\Place;
7
+ use Fleetbase\FleetOps\Models\ServiceArea;
8
+ use Fleetbase\FleetOps\Models\ServiceRate;
9
+ use Fleetbase\FleetOps\Models\ServiceRateFee;
10
+ use Fleetbase\FleetOps\Models\Vendor;
11
+ use Fleetbase\FleetOps\Models\Zone;
12
+ use Fleetbase\FleetOps\Seeders\Testing\Concerns\SeedsTestingData;
13
+ use Fleetbase\FleetOps\Support\FleetOps;
14
+ use Fleetbase\Models\Company;
15
+ use Illuminate\Database\Seeder;
16
+ use Illuminate\Support\Facades\DB;
17
+
18
+ class NetworkSeeder extends Seeder
19
+ {
20
+ use SeedsTestingData;
21
+
22
+ public function run(): void
23
+ {
24
+ $company = $this->prepareCompany();
25
+ if (!$company) {
26
+ return;
27
+ }
28
+
29
+ DB::transaction(function () use ($company) {
30
+ $this->purgeSeedData();
31
+ FleetOps::createTransportConfig($company);
32
+ $this->seedServiceAreas($company);
33
+ $this->seedZones($company);
34
+ $places = $this->seedPlaces($company);
35
+ $this->seedContacts($company, $places);
36
+ $vendors = $this->seedVendors($company, $places);
37
+ $this->seedServiceRates($company, $vendors);
38
+
39
+ $this->command?->info('Seeded FleetOps testing network fixtures for company: ' . $company->public_id);
40
+ });
41
+ }
42
+
43
+ public function purgeSeedData(): void
44
+ {
45
+ $this->purgeModel(ServiceRateFee::class);
46
+ $this->purgeModel(ServiceRate::class);
47
+ $this->purgeModel(Vendor::class);
48
+ $this->purgeModel(Contact::class);
49
+ $this->purgeModel(Place::class);
50
+ $this->purgeModel(Zone::class);
51
+ $this->purgeModel(ServiceArea::class);
52
+ }
53
+
54
+ protected function seedServiceAreas(Company $company): void
55
+ {
56
+ $areas = [
57
+ 'central_service_area' => [
58
+ 'name' => 'Singapore Central',
59
+ 'border' => [[1.2700, 103.8000], [1.2700, 103.9100], [1.3600, 103.9100], [1.3600, 103.8000]],
60
+ 'color' => '#2563eb',
61
+ ],
62
+ 'east_service_area' => [
63
+ 'name' => 'Singapore East',
64
+ 'border' => [[1.3050, 103.9000], [1.3050, 104.0200], [1.3950, 104.0200], [1.3950, 103.9000]],
65
+ 'color' => '#059669',
66
+ ],
67
+ ];
68
+
69
+ foreach ($areas as $seedId => $area) {
70
+ $this->createRecord(ServiceArea::class, [
71
+ '_key' => $this->fixtureKey($seedId),
72
+ 'company_uuid' => $company->uuid,
73
+ 'name' => $area['name'],
74
+ 'type' => 'city',
75
+ 'country' => 'SG',
76
+ 'border' => $this->multiPolygon($area['border']),
77
+ 'color' => $area['color'],
78
+ 'stroke_color' => $area['color'],
79
+ 'status' => 'active',
80
+ 'trigger_on_entry' => true,
81
+ 'trigger_on_exit' => true,
82
+ 'dwell_threshold_minutes' => 15,
83
+ 'speed_limit_kmh' => 50,
84
+ ]);
85
+ }
86
+ }
87
+
88
+ protected function seedZones(Company $company): void
89
+ {
90
+ $zones = [
91
+ 'cbd_zone' => [
92
+ 'service_area' => 'central_service_area',
93
+ 'name' => 'Marina Bay',
94
+ 'border' => [[1.2760, 103.8350], [1.2760, 103.8650], [1.3050, 103.8650], [1.3050, 103.8350]],
95
+ 'color' => '#7c3aed',
96
+ ],
97
+ 'airport_zone' => [
98
+ 'service_area' => 'east_service_area',
99
+ 'name' => 'Changi',
100
+ 'border' => [[1.3350, 103.9600], [1.3350, 104.0100], [1.3850, 104.0100], [1.3850, 103.9600]],
101
+ 'color' => '#dc2626',
102
+ ],
103
+ 'warehouse_zone' => [
104
+ 'service_area' => 'central_service_area',
105
+ 'name' => 'Jurong',
106
+ 'border' => [[1.3000, 103.7800], [1.3000, 103.8300], [1.3450, 103.8300], [1.3450, 103.7800]],
107
+ 'color' => '#ea580c',
108
+ ],
109
+ ];
110
+
111
+ foreach ($zones as $seedId => $zone) {
112
+ $serviceArea = $this->seededServiceArea($zone['service_area']);
113
+ $this->createRecord(Zone::class, [
114
+ '_key' => $this->fixtureKey($seedId),
115
+ 'company_uuid' => $company->uuid,
116
+ 'service_area_uuid' => $serviceArea?->uuid,
117
+ 'name' => $zone['name'],
118
+ 'description' => 'Fixture zone for FleetOps testing assertions.',
119
+ 'border' => $this->polygon($zone['border']),
120
+ 'color' => $zone['color'],
121
+ 'stroke_color' => $zone['color'],
122
+ 'status' => 'active',
123
+ 'trigger_on_entry' => true,
124
+ 'trigger_on_exit' => true,
125
+ 'dwell_threshold_minutes' => 10,
126
+ 'speed_limit_kmh' => 40,
127
+ ]);
128
+ }
129
+ }
130
+
131
+ protected function seedPlaces(Company $company): array
132
+ {
133
+ $places = [
134
+ 'central_depot' => ['Raffles Quay', '1 Raffles Quay', 'Singapore', 'SG', 1.2816, 103.8510],
135
+ 'west_depot' => ['Jurong East', '2 Jurong East Street 21', 'Singapore', 'SG', 1.3345, 103.7420],
136
+ 'airport_hub' => ['Changi Airfreight', '80 Airport Boulevard', 'Singapore', 'SG', 1.3644, 103.9915],
137
+ 'orchard_store' => ['ION Orchard', '2 Orchard Turn', 'Singapore', 'SG', 1.3048, 103.8318],
138
+ 'rochor_store' => ['Rochor Canal', '1 Rochor Canal Road', 'Singapore', 'SG', 1.3039, 103.8520],
139
+ 'tampines_store'=> ['Tampines Mall', '4 Tampines Central 5', 'Singapore', 'SG', 1.3525, 103.9447],
140
+ ];
141
+
142
+ $models = [];
143
+ foreach ($places as $seedId => [$name, $street1, $city, $country, $lat, $lng]) {
144
+ $models[$seedId] = $this->createRecord(Place::class, [
145
+ '_key' => $this->fixtureKey($seedId),
146
+ 'company_uuid' => $company->uuid,
147
+ 'name' => $name,
148
+ 'street1' => $street1,
149
+ 'city' => $city,
150
+ 'country' => $country,
151
+ 'location' => $this->point($lat, $lng),
152
+ 'meta' => $this->meta($seedId),
153
+ ]);
154
+ }
155
+
156
+ return $models;
157
+ }
158
+
159
+ protected function seedContacts(Company $company, array $places): void
160
+ {
161
+ $contacts = [
162
+ 'customer_alice' => ['Alice Tan', 'customer', 'alice.testing@example.test', '+6591000001', 'orchard_store'],
163
+ 'customer_ben' => ['Ben Lim', 'customer', 'ben.testing@example.test', '+6591000002', 'rochor_store'],
164
+ 'ops_manager' => ['Nadia Rahman', 'contact', 'nadia.testing@example.test', '+6591000003', 'central_depot'],
165
+ ];
166
+
167
+ foreach ($contacts as $seedId => [$name, $type, $email, $phone, $placeSeedId]) {
168
+ $this->createRecord(Contact::class, [
169
+ '_key' => $this->fixtureKey($seedId),
170
+ 'company_uuid' => $company->uuid,
171
+ 'place_uuid' => $places[$placeSeedId]?->uuid,
172
+ 'name' => $name,
173
+ 'title' => $type === 'customer' ? 'Receiving Contact' : 'Operations Manager',
174
+ 'email' => $email,
175
+ 'phone' => $phone,
176
+ 'type' => $type,
177
+ 'notes' => 'FleetOps testing fixture contact.',
178
+ 'meta' => $this->meta($seedId),
179
+ ]);
180
+ }
181
+ }
182
+
183
+ protected function seedVendors(Company $company, array $places): array
184
+ {
185
+ $vendors = [
186
+ 'facilitator_fastline' => ['Fastline Logistics', 'facilitator', 'ops@fastline.example.test', '+6592000001', 'central_depot'],
187
+ 'supplier_parts' => ['Apex Parts Supply', 'supplier', 'parts@example.test', '+6592000002', 'west_depot'],
188
+ ];
189
+
190
+ $models = [];
191
+ foreach ($vendors as $seedId => [$name, $type, $email, $phone, $placeSeedId]) {
192
+ $models[$seedId] = $this->createRecord(Vendor::class, [
193
+ '_key' => $this->fixtureKey($seedId),
194
+ 'company_uuid' => $company->uuid,
195
+ 'place_uuid' => $places[$placeSeedId]?->uuid,
196
+ 'name' => $name,
197
+ 'email' => $email,
198
+ 'phone' => $phone,
199
+ 'country' => 'SG',
200
+ 'status' => 'active',
201
+ 'type' => $type,
202
+ 'notes' => 'FleetOps testing fixture vendor.',
203
+ 'meta' => $this->meta($seedId),
204
+ ]);
205
+ }
206
+
207
+ return $models;
208
+ }
209
+
210
+ protected function seedServiceRates(Company $company, array $vendors): void
211
+ {
212
+ $orderConfig = FleetOps::createTransportConfig($company);
213
+ $rates = [
214
+ 'same_day_cbd' => ['Same Day CBD', 'same_day', 'central_service_area', 'cbd_zone', 1200, 120, 1],
215
+ 'airport_express' => ['Airport Express', 'express', 'east_service_area', 'airport_zone', 2800, 180, 0],
216
+ ];
217
+
218
+ foreach ($rates as $seedId => [$name, $type, $areaSeedId, $zoneSeedId, $baseFee, $perMeter, $estimatedDays]) {
219
+ $serviceRate = $this->createRecord(ServiceRate::class, [
220
+ '_key' => $this->fixtureKey($seedId),
221
+ 'company_uuid' => $company->uuid,
222
+ 'service_area_uuid' => $this->seededServiceArea($areaSeedId)?->uuid,
223
+ 'zone_uuid' => $this->seededZone($zoneSeedId)?->uuid,
224
+ 'order_config_uuid' => $orderConfig->uuid,
225
+ 'service_name' => $name,
226
+ 'service_type' => $type,
227
+ 'base_fee' => $baseFee,
228
+ 'per_meter_flat_rate_fee' => $perMeter,
229
+ 'per_meter_unit' => 'm',
230
+ 'algorithm' => 'fixed_meter',
231
+ 'rate_calculation_method' => 'fixed_meter',
232
+ 'currency' => 'SGD',
233
+ 'duration_terms' => 'days',
234
+ 'estimated_days' => $estimatedDays,
235
+ ]);
236
+
237
+ foreach ([['parcel', 1, 5, 400], ['parcel', 6, 20, 900]] as $index => [$unit, $min, $max, $fee]) {
238
+ $this->createRecord(ServiceRateFee::class, [
239
+ '_key' => $this->fixtureKey($seedId . '_fee_' . ($index + 1)),
240
+ 'service_rate_uuid' => $serviceRate->uuid,
241
+ 'min' => $min,
242
+ 'max' => $max,
243
+ 'unit' => $unit,
244
+ 'fee' => $fee,
245
+ 'currency' => 'SGD',
246
+ ]);
247
+ }
248
+ }
249
+ }
250
+ }
@@ -0,0 +1,214 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Seeders\Testing;
4
+
5
+ use Fleetbase\FleetOps\Models\Contact;
6
+ use Fleetbase\FleetOps\Models\Driver;
7
+ use Fleetbase\FleetOps\Models\Entity;
8
+ use Fleetbase\FleetOps\Models\Order;
9
+ use Fleetbase\FleetOps\Models\Payload;
10
+ use Fleetbase\FleetOps\Models\Place;
11
+ use Fleetbase\FleetOps\Models\TrackingNumber;
12
+ use Fleetbase\FleetOps\Models\TrackingStatus;
13
+ use Fleetbase\FleetOps\Models\Vehicle;
14
+ use Fleetbase\FleetOps\Models\Vendor;
15
+ use Fleetbase\FleetOps\Models\Waypoint;
16
+ use Fleetbase\FleetOps\Seeders\Testing\Concerns\SeedsTestingData;
17
+ use Fleetbase\FleetOps\Support\FleetOps;
18
+ use Fleetbase\Models\Company;
19
+ use Fleetbase\Models\User;
20
+ use Illuminate\Database\Seeder;
21
+ use Illuminate\Support\Facades\DB;
22
+
23
+ class OrdersSeeder extends Seeder
24
+ {
25
+ use SeedsTestingData;
26
+
27
+ public function run(): void
28
+ {
29
+ $company = $this->prepareCompany();
30
+ if (!$company) {
31
+ return;
32
+ }
33
+
34
+ DB::transaction(function () use ($company) {
35
+ $this->purgeSeedData();
36
+ $this->seedOrders($company);
37
+
38
+ $this->command?->info('Seeded FleetOps testing order fixtures for company: ' . $company->public_id);
39
+ });
40
+ }
41
+
42
+ public function purgeSeedData(): void
43
+ {
44
+ $this->purgeModel(TrackingStatus::class);
45
+ $this->purgeModel(Order::class);
46
+ $this->purgeModel(Entity::class);
47
+ $this->purgeModel(Waypoint::class);
48
+ $this->purgeModel(TrackingNumber::class);
49
+ $this->purgeModel(Payload::class);
50
+ }
51
+
52
+ protected function seedOrders(Company $company): void
53
+ {
54
+ $places = [
55
+ 'central_depot' => $this->seededModel(Place::class, 'central_depot'),
56
+ 'west_depot' => $this->seededModel(Place::class, 'west_depot'),
57
+ 'airport_hub' => $this->seededModel(Place::class, 'airport_hub'),
58
+ 'orchard_store' => $this->seededModel(Place::class, 'orchard_store'),
59
+ 'rochor_store' => $this->seededModel(Place::class, 'rochor_store'),
60
+ 'tampines_store' => $this->seededModel(Place::class, 'tampines_store'),
61
+ ];
62
+ $customers = [
63
+ 'customer_alice' => $this->seededModel(Contact::class, 'customer_alice'),
64
+ 'customer_ben' => $this->seededModel(Contact::class, 'customer_ben'),
65
+ ];
66
+ $drivers = [
67
+ 'driver_ava' => $this->seededModel(Driver::class, 'driver_ava'),
68
+ 'driver_ken' => $this->seededModel(Driver::class, 'driver_ken'),
69
+ ];
70
+ $vehicles = [
71
+ 'van_central' => $this->seededModel(Vehicle::class, 'van_central'),
72
+ 'van_east' => $this->seededModel(Vehicle::class, 'van_east'),
73
+ ];
74
+ $facilitator = $this->seededModel(Vendor::class, 'facilitator_fastline');
75
+ $dispatcher = $this->seededModel(User::class, 'dispatcher_user');
76
+ $orderConfig = FleetOps::createTransportConfig($company);
77
+
78
+ $orders = [
79
+ 'order_created_unassigned' => ['created', null, null, 'customer_alice', 'central_depot', 'orchard_store', [], 2, 1, null, false, false],
80
+ 'order_scheduled' => ['created', null, null, 'customer_ben', 'west_depot', 'rochor_store', [], 4, 1, $this->timestamp(30), false, false],
81
+ 'order_dispatched' => ['dispatched', 'driver_ava', 'van_central', 'customer_alice', 'central_depot', 'orchard_store', ['rochor_store'], 6, 2, null, true, false],
82
+ 'order_started' => ['started', 'driver_ken', 'van_east', 'customer_ben', 'airport_hub', 'tampines_store', [], 8, 1, null, true, true],
83
+ 'order_completed' => ['completed', 'driver_ava', 'van_central', 'customer_alice', 'central_depot', 'orchard_store', [], -4, 1, null, true, true],
84
+ 'order_canceled' => ['canceled', null, null, 'customer_ben', 'central_depot', 'rochor_store', [], -2, 1, null, false, false],
85
+ 'order_failed' => ['failed', 'driver_ken', 'van_east', 'customer_ben', 'airport_hub', 'tampines_store', [], -1, 1, null, true, true],
86
+ 'order_vehicle_assigned' => ['created', null, 'van_central', 'customer_alice', 'west_depot', 'airport_hub', ['orchard_store', 'rochor_store'], 10, 3, null, false, false],
87
+ ];
88
+
89
+ foreach ($orders as $seedId => [$status, $driverSeedId, $vehicleSeedId, $customerSeedId, $pickupSeedId, $dropoffSeedId, $waypointSeedIds, $hourOffset, $entityCount, $scheduledAt, $dispatched, $started]) {
90
+ $payload = $this->createPayload($company, $seedId, $places[$pickupSeedId], $places[$dropoffSeedId], array_map(fn ($placeSeedId) => $places[$placeSeedId], $waypointSeedIds));
91
+ $order = $this->createRecord(Order::class, [
92
+ '_key' => $this->fixtureKey($seedId),
93
+ 'internal_id' => 'TEST-' . strtoupper(str_replace('order_', '', $seedId)),
94
+ 'company_uuid' => $company->uuid,
95
+ 'payload_uuid' => $payload->uuid,
96
+ 'order_config_uuid' => $orderConfig->uuid,
97
+ 'customer_uuid' => $customers[$customerSeedId]?->uuid,
98
+ 'customer_type' => Contact::class,
99
+ 'facilitator_uuid' => $facilitator?->uuid,
100
+ 'facilitator_type' => Vendor::class,
101
+ 'driver_assigned_uuid' => $driverSeedId ? $drivers[$driverSeedId]?->uuid : null,
102
+ 'vehicle_assigned_uuid' => $vehicleSeedId ? $vehicles[$vehicleSeedId]?->uuid : null,
103
+ 'created_by_uuid' => $dispatcher?->uuid,
104
+ 'updated_by_uuid' => $dispatcher?->uuid,
105
+ 'scheduled_at' => $scheduledAt,
106
+ 'dispatched_at' => $dispatched ? $this->timestamp($hourOffset) : null,
107
+ 'dispatched' => $dispatched,
108
+ 'started' => $started,
109
+ 'started_at' => $started ? $this->timestamp($hourOffset + 1) : null,
110
+ 'type' => 'transport',
111
+ 'status' => $status,
112
+ 'notes' => 'FleetOps testing fixture order: ' . $seedId,
113
+ 'pod_method' => 'scan',
114
+ 'pod_required' => in_array($status, ['completed', 'failed'], true),
115
+ 'orchestrator_priority' => $status === 'failed' ? 90 : 50,
116
+ 'required_skills' => $vehicleSeedId === 'van_east' ? ['cold_chain'] : [],
117
+ 'meta' => $this->meta($seedId),
118
+ 'created_at' => $this->timestamp($hourOffset),
119
+ 'updated_at' => $this->timestamp($hourOffset + 1),
120
+ ]);
121
+
122
+ $trackingNumber = $this->createTrackingNumber($company, $order, $seedId, $status);
123
+ $order->forceFill(['tracking_number_uuid' => $trackingNumber->uuid])->save();
124
+ $this->seedEntities($company, $payload, $trackingNumber, $seedId, $entityCount, $places[$dropoffSeedId]);
125
+ }
126
+ }
127
+
128
+ protected function createPayload(Company $company, string $seedId, ?Place $pickup, ?Place $dropoff, array $waypoints): Payload
129
+ {
130
+ /** @var Payload $payload */
131
+ $payload = $this->createRecord(Payload::class, [
132
+ '_key' => $this->fixtureKey($seedId . '_payload'),
133
+ 'company_uuid' => $company->uuid,
134
+ 'pickup_uuid' => $pickup?->uuid,
135
+ 'dropoff_uuid' => $dropoff?->uuid,
136
+ 'type' => 'transport',
137
+ 'meta' => $this->meta($seedId . '_payload'),
138
+ ]);
139
+
140
+ foreach ($waypoints as $index => $place) {
141
+ $this->createRecord(Waypoint::class, [
142
+ '_key' => $this->fixtureKey($seedId . '_waypoint_' . ($index + 1)),
143
+ '_import_id' => $this->fixtureKey($seedId . ':waypoint:' . ($index + 1)),
144
+ 'company_uuid' => $company->uuid,
145
+ 'payload_uuid' => $payload->uuid,
146
+ 'place_uuid' => $place?->uuid,
147
+ 'type' => 'dropoff',
148
+ 'order' => $index + 1,
149
+ 'service_time' => 300,
150
+ 'notes' => 'FleetOps testing waypoint.',
151
+ ]);
152
+ }
153
+
154
+ return $payload;
155
+ }
156
+
157
+ protected function createTrackingNumber(Company $company, Order $order, string $seedId, string $status): TrackingNumber
158
+ {
159
+ /** @var TrackingNumber $trackingNumber */
160
+ $trackingNumber = $this->createRecord(TrackingNumber::class, [
161
+ '_key' => $this->fixtureKey($seedId . '_tracking'),
162
+ 'company_uuid' => $company->uuid,
163
+ 'owner_uuid' => $order->uuid,
164
+ 'owner_type' => Order::class,
165
+ 'tracking_number' => 'TEST-' . str_pad((string) crc32($seedId), 10, '0', STR_PAD_LEFT),
166
+ 'region' => 'SG',
167
+ ]);
168
+
169
+ $trackingStatus = $this->createRecord(TrackingStatus::class, [
170
+ '_key' => $this->fixtureKey($seedId . '_tracking_status'),
171
+ 'company_uuid' => $company->uuid,
172
+ 'tracking_number_uuid' => $trackingNumber->uuid,
173
+ 'status' => str_replace('_', ' ', $status),
174
+ 'details' => 'FleetOps testing tracking status for ' . $seedId,
175
+ 'code' => $status,
176
+ 'complete' => $status === 'completed',
177
+ 'city' => 'Singapore',
178
+ 'country' => 'SG',
179
+ 'location' => $this->point(1.3048, 103.8318),
180
+ 'meta' => $this->meta($seedId . '_tracking_status'),
181
+ ]);
182
+
183
+ $trackingNumber->forceFill(['status_uuid' => $trackingStatus->uuid])->save();
184
+
185
+ return $trackingNumber;
186
+ }
187
+
188
+ protected function seedEntities(Company $company, Payload $payload, TrackingNumber $trackingNumber, string $seedId, int $entityCount, ?Place $destination): void
189
+ {
190
+ for ($i = 1; $i <= $entityCount; $i++) {
191
+ $this->createRecord(Entity::class, [
192
+ '_key' => $this->fixtureKey($seedId . '_entity_' . $i),
193
+ '_import_id' => $this->fixtureKey($seedId . ':entity:' . $i),
194
+ 'company_uuid' => $company->uuid,
195
+ 'payload_uuid' => $payload->uuid,
196
+ 'tracking_number_uuid' => $trackingNumber->uuid,
197
+ 'destination_uuid' => $destination?->uuid,
198
+ 'internal_id' => 'TEST-ENTITY-' . strtoupper($seedId) . '-' . $i,
199
+ 'name' => 'Item ' . $i,
200
+ 'type' => 'parcel',
201
+ 'description' => 'FleetOps testing fixture entity.',
202
+ 'weight' => 5 + $i,
203
+ 'weight_unit' => 'kg',
204
+ 'length' => 40,
205
+ 'width' => 30,
206
+ 'height' => 20,
207
+ 'dimensions_unit' => 'cm',
208
+ 'currency' => 'SGD',
209
+ 'declared_value' => 1000 + ($i * 100),
210
+ 'meta' => $this->meta($seedId . '_entity_' . $i),
211
+ ]);
212
+ }
213
+ }
214
+ }
@@ -0,0 +1,40 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Seeders\Testing;
4
+
5
+ use Illuminate\Database\Seeder;
6
+ use Illuminate\Support\Facades\Schema;
7
+
8
+ class TestingSeeder extends Seeder
9
+ {
10
+ public function run(): void
11
+ {
12
+ Schema::disableForeignKeyConstraints();
13
+ try {
14
+ $this->purgeSeedData();
15
+ } finally {
16
+ Schema::enableForeignKeyConstraints();
17
+ }
18
+
19
+ $this->call([
20
+ NetworkSeeder::class,
21
+ FleetSeeder::class,
22
+ OrdersSeeder::class,
23
+ ConnectivitySeeder::class,
24
+ MaintenanceSeeder::class,
25
+ ]);
26
+ }
27
+
28
+ protected function purgeSeedData(): void
29
+ {
30
+ foreach ([
31
+ new MaintenanceSeeder(),
32
+ new ConnectivitySeeder(),
33
+ new OrdersSeeder(),
34
+ new FleetSeeder(),
35
+ new NetworkSeeder(),
36
+ ] as $seeder) {
37
+ $seeder->purgeSeedData();
38
+ }
39
+ }
40
+ }