@fleetbase/fleetops-engine 0.6.48 → 0.6.50

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 (266) hide show
  1. package/addon/components/customer/create-order-form.hbs +2 -2
  2. package/addon/components/customer/form.hbs +2 -2
  3. package/addon/components/customer/order-form.hbs +2 -2
  4. package/addon/components/customer/orders.hbs +2 -2
  5. package/addon/components/display-place.hbs +2 -2
  6. package/addon/components/driver/details.hbs +5 -1
  7. package/addon/components/driver/form.hbs +11 -1
  8. package/addon/components/fleet-listing-panel.hbs +2 -2
  9. package/addon/components/fleet-listing-panel.js +10 -0
  10. package/addon/components/map/google-live-map.js +18 -75
  11. package/addon/components/map/leaflet-live-map.hbs +187 -37
  12. package/addon/components/map/leaflet-live-map.js +184 -20
  13. package/addon/components/modals/order-change-destination.hbs +46 -0
  14. package/addon/components/modals/order-change-destination.js +106 -0
  15. package/addon/components/modals/stop-activity.hbs +28 -0
  16. package/addon/components/modals/stop-activity.js +52 -0
  17. package/addon/components/modals/update-order-activity.hbs +45 -3
  18. package/addon/components/modals/update-order-activity.js +143 -5
  19. package/addon/components/orchestrator/phase-builder.hbs +21 -0
  20. package/addon/components/orchestrator/phase-builder.js +13 -0
  21. package/addon/components/orchestrator/resource-panel.hbs +0 -10
  22. package/addon/components/orchestrator/resource-panel.js +2 -4
  23. package/addon/components/orchestrator-workbench.js +1 -0
  24. package/addon/components/order/activity-list.hbs +2 -2
  25. package/addon/components/order/activity-timeline.hbs +31 -29
  26. package/addon/components/order/details/activity.hbs +3 -3
  27. package/addon/components/order/details/activity.js +11 -0
  28. package/addon/components/order/details/documents.js +2 -0
  29. package/addon/components/order/details/purchase-rate.hbs +2 -2
  30. package/addon/components/order/details/route.hbs +11 -3
  31. package/addon/components/order/details/route.js +91 -14
  32. package/addon/components/order/details/tracking.hbs +101 -72
  33. package/addon/components/order/details/tracking.js +149 -2
  34. package/addon/components/order/form/details.hbs +3 -3
  35. package/addon/components/order/form/documents.js +11 -4
  36. package/addon/components/order/form/route.hbs +108 -104
  37. package/addon/components/order/form/route.js +37 -1
  38. package/addon/components/order/form/service-rate.hbs +2 -2
  39. package/addon/components/order/form.hbs +2 -2
  40. package/addon/components/order-config-manager/activity-flow.hbs +4 -1
  41. package/addon/components/order-config-manager/activity-flow.js +178 -12
  42. package/addon/components/place/address.js +1 -1
  43. package/addon/components/route-list.hbs +17 -17
  44. package/addon/components/route-list.js +42 -10
  45. package/addon/components/service-rate/details.hbs +41 -0
  46. package/addon/components/service-rate/form.hbs +118 -0
  47. package/addon/components/service-rate/form.js +55 -0
  48. package/addon/components/vehicle/form.js +1 -1
  49. package/addon/components/vehicle/pill.hbs +1 -1
  50. package/addon/components/widget/fleet-ops-key-metrics.hbs +19 -17
  51. package/addon/components/widget/fleet-ops-key-metrics.js +26 -0
  52. package/addon/components/widget/fuel-efficiency.hbs +26 -0
  53. package/addon/components/widget/fuel-efficiency.js +43 -0
  54. package/addon/components/widget/geofence-violations.hbs +44 -0
  55. package/addon/components/widget/geofence-violations.js +55 -0
  56. package/addon/components/widget/issues-insights.hbs +43 -0
  57. package/addon/components/widget/issues-insights.js +62 -0
  58. package/addon/components/widget/kpi-active-orders.hbs +10 -0
  59. package/addon/components/widget/kpi-active-orders.js +5 -0
  60. package/addon/components/widget/kpi-aov.hbs +10 -0
  61. package/addon/components/widget/kpi-aov.js +5 -0
  62. package/addon/components/widget/kpi-distance.hbs +10 -0
  63. package/addon/components/widget/kpi-distance.js +5 -0
  64. package/addon/components/widget/kpi-drivers-online.hbs +10 -0
  65. package/addon/components/widget/kpi-drivers-online.js +5 -0
  66. package/addon/components/widget/kpi-earnings.hbs +10 -0
  67. package/addon/components/widget/kpi-earnings.js +5 -0
  68. package/addon/components/widget/kpi-open-issues.hbs +11 -0
  69. package/addon/components/widget/kpi-open-issues.js +5 -0
  70. package/addon/components/widget/kpi-tile.hbs +59 -0
  71. package/addon/components/widget/kpi-tile.js +168 -0
  72. package/addon/components/widget/live-fleet.hbs +122 -0
  73. package/addon/components/widget/live-fleet.js +365 -0
  74. package/addon/components/widget/maintenance-overview.hbs +45 -0
  75. package/addon/components/widget/maintenance-overview.js +40 -0
  76. package/addon/components/widget/on-time-delivery.hbs +27 -0
  77. package/addon/components/widget/on-time-delivery.js +73 -0
  78. package/addon/components/widget/operations-pulse.hbs +51 -0
  79. package/addon/components/widget/operations-pulse.js +173 -0
  80. package/addon/components/widget/orders-by-status.hbs +26 -0
  81. package/addon/components/widget/orders-by-status.js +54 -0
  82. package/addon/components/widget/revenue-trend.hbs +34 -0
  83. package/addon/components/widget/revenue-trend.js +67 -0
  84. package/addon/components/widget/top-drivers.hbs +44 -0
  85. package/addon/components/widget/top-drivers.js +54 -0
  86. package/addon/controllers/management/vehicles/index/new.js +1 -1
  87. package/addon/controllers/operations/orders/index/details.js +4 -4
  88. package/addon/controllers/operations/orders/index.js +14 -0
  89. package/addon/controllers/settings/routing.js +26 -0
  90. package/addon/extension.js +172 -3
  91. package/addon/helpers/place-address-html.js +2 -2
  92. package/addon/routes/operations/service-rates/index.js +1 -1
  93. package/addon/services/geofence.js +163 -19
  94. package/addon/services/leaflet-contextmenu-manager.js +27 -0
  95. package/addon/services/map-adapter/google.js +13 -2
  96. package/addon/services/vehicle-actions.js +1 -1
  97. package/addon/styles/fleetops-engine.css +205 -88
  98. package/addon/templates/maintenance/equipment/index.hbs +1 -1
  99. package/addon/templates/maintenance/parts/index.hbs +1 -1
  100. package/addon/templates/maintenance/schedules/index.hbs +1 -1
  101. package/addon/templates/management/vehicles/index.hbs +1 -1
  102. package/addon/templates/settings/avatars.hbs +1 -1
  103. package/addon/templates/settings/custom-fields.hbs +1 -1
  104. package/addon/templates/settings/map.hbs +1 -1
  105. package/addon/templates/settings/navigator-app.hbs +1 -1
  106. package/addon/templates/settings/notifications.hbs +1 -1
  107. package/addon/templates/settings/orchestrator.hbs +1 -1
  108. package/addon/templates/settings/payments/index.hbs +1 -1
  109. package/addon/templates/settings/payments/onboard.hbs +1 -1
  110. package/addon/templates/settings/routing.hbs +22 -2
  111. package/addon/templates/settings/scheduling.hbs +1 -1
  112. package/addon/utils/fleet-ops-options.js +2 -1
  113. package/addon/utils/live-map-card-content.js +171 -0
  114. package/addon/utils/normalize-order-config-flow.js +41 -0
  115. package/addon/utils/place-address-html.js +3 -2
  116. package/addon/utils/route-visualization.js +54 -7
  117. package/app/components/modals/order-change-destination.js +1 -0
  118. package/app/components/modals/stop-activity.js +1 -0
  119. package/app/components/widget/fuel-efficiency.js +1 -0
  120. package/app/components/widget/geofence-violations.js +1 -0
  121. package/app/components/widget/issues-insights.js +1 -0
  122. package/app/components/widget/kpi-active-orders.js +1 -0
  123. package/app/components/widget/kpi-aov.js +1 -0
  124. package/app/components/widget/kpi-distance.js +1 -0
  125. package/app/components/widget/kpi-drivers-online.js +1 -0
  126. package/app/components/widget/kpi-earnings.js +1 -0
  127. package/app/components/widget/kpi-open-issues.js +1 -0
  128. package/app/components/widget/kpi-tile.js +1 -0
  129. package/app/components/widget/live-fleet.js +1 -0
  130. package/app/components/widget/maintenance-overview.js +1 -0
  131. package/app/components/widget/on-time-delivery.js +1 -0
  132. package/app/components/widget/operations-pulse.js +1 -0
  133. package/app/components/widget/orders-by-status.js +1 -0
  134. package/app/components/widget/revenue-trend.js +1 -0
  135. package/app/components/widget/top-drivers.js +1 -0
  136. package/app/utils/live-map-card-content.js +1 -0
  137. package/composer.json +1 -1
  138. package/extension.json +1 -1
  139. package/package.json +3 -3
  140. package/pnpm-workspace.yaml +1 -0
  141. package/server/config/telematics.php +111 -1
  142. package/server/migrations/2026_05_18_000001_add_license_expiry_to_drivers_table.php +25 -0
  143. package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
  144. package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
  145. package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
  146. package/server/resources/views/labels/default.php +57 -49
  147. package/server/resources/views/labels/waypoint-label.php +27 -20
  148. package/server/seeders/Testing/Concerns/SeedsTestingData.php +183 -0
  149. package/server/seeders/Testing/ConnectivitySeeder.php +213 -0
  150. package/server/seeders/Testing/FleetSeeder.php +341 -0
  151. package/server/seeders/Testing/MaintenanceSeeder.php +238 -0
  152. package/server/seeders/Testing/NetworkSeeder.php +250 -0
  153. package/server/seeders/Testing/OrdersSeeder.php +198 -0
  154. package/server/seeders/Testing/TestingSeeder.php +40 -0
  155. package/server/seeders/{VroomOrchestrationSeeder.php → Testing/VroomOrchestrationSeeder.php} +29 -22
  156. package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
  157. package/server/src/Console/Commands/ProcessOperationalAlerts.php +279 -0
  158. package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
  159. package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
  160. package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
  161. package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
  162. package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
  163. package/server/src/Http/Controllers/Api/v1/OrderController.php +98 -70
  164. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
  165. package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
  166. package/server/src/Http/Controllers/Internal/v1/AnalyticsController.php +119 -0
  167. package/server/src/Http/Controllers/Internal/v1/ContactController.php +19 -0
  168. package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
  169. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +82 -3
  170. package/server/src/Http/Controllers/Internal/v1/OrderController.php +441 -32
  171. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
  172. package/server/src/Http/Controllers/Internal/v1/SettingController.php +46 -1
  173. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
  174. package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
  175. package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
  176. package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
  177. package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
  178. package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
  179. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
  180. package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
  181. package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
  182. package/server/src/Http/Resources/v1/Customer.php +79 -0
  183. package/server/src/Http/Resources/v1/Driver.php +2 -0
  184. package/server/src/Http/Resources/v1/Fleet.php +4 -0
  185. package/server/src/Http/Resources/v1/Index/Driver.php +48 -0
  186. package/server/src/Http/Resources/v1/Index/Vehicle.php +74 -1
  187. package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
  188. package/server/src/Http/Resources/v1/Payload.php +23 -20
  189. package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
  190. package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
  191. package/server/src/Listeners/HandleOrderDispatched.php +2 -2
  192. package/server/src/Models/Contact.php +92 -15
  193. package/server/src/Models/Customer.php +64 -0
  194. package/server/src/Models/Driver.php +14 -3
  195. package/server/src/Models/Entity.php +2 -2
  196. package/server/src/Models/Order.php +29 -4
  197. package/server/src/Models/OrderConfig.php +14 -11
  198. package/server/src/Models/Payload.php +17 -1
  199. package/server/src/Models/ServiceRate.php +273 -1
  200. package/server/src/Models/ServiceRateFee.php +39 -9
  201. package/server/src/Models/TrackingNumber.php +8 -2
  202. package/server/src/Models/Vehicle.php +18 -2
  203. package/server/src/Models/Waypoint.php +2 -2
  204. package/server/src/Notifications/LateDeparture.php +48 -0
  205. package/server/src/Notifications/ProlongedStoppage.php +48 -0
  206. package/server/src/Notifications/RouteDeviation.php +48 -0
  207. package/server/src/Observers/ContactObserver.php +2 -0
  208. package/server/src/Observers/ServiceRateObserver.php +6 -6
  209. package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +29 -2
  210. package/server/src/Providers/FleetOpsServiceProvider.php +6 -0
  211. package/server/src/Providers/NotificationServiceProvider.php +3 -0
  212. package/server/src/Support/Analytics/AbstractAnalytics.php +77 -0
  213. package/server/src/Support/Analytics/FuelEfficiency.php +83 -0
  214. package/server/src/Support/Analytics/GeofenceViolations.php +63 -0
  215. package/server/src/Support/Analytics/IssuesInsights.php +67 -0
  216. package/server/src/Support/Analytics/LiveFleet.php +127 -0
  217. package/server/src/Support/Analytics/MaintenanceOverview.php +70 -0
  218. package/server/src/Support/Analytics/OnTimeDelivery.php +75 -0
  219. package/server/src/Support/Analytics/OperationsPulse.php +86 -0
  220. package/server/src/Support/Analytics/OrdersByStatus.php +64 -0
  221. package/server/src/Support/Analytics/RevenueTrend.php +78 -0
  222. package/server/src/Support/Analytics/TopDrivers.php +92 -0
  223. package/server/src/Support/CustomerAuth.php +98 -0
  224. package/server/src/Support/LabelPdf.php +52 -0
  225. package/server/src/Support/Metrics/AbstractMetric.php +170 -0
  226. package/server/src/Support/Metrics/ActiveLiveOrdersMetric.php +28 -0
  227. package/server/src/Support/Metrics/AvgOrderValueMetric.php +53 -0
  228. package/server/src/Support/Metrics/DriversOnlineMetric.php +30 -0
  229. package/server/src/Support/Metrics/EarningsMetric.php +30 -0
  230. package/server/src/Support/Metrics/FuelCostsMetric.php +30 -0
  231. package/server/src/Support/Metrics/MoneyMetric.php +30 -0
  232. package/server/src/Support/Metrics/OpenIssuesMetric.php +35 -0
  233. package/server/src/Support/Metrics/OrdersCanceledMetric.php +35 -0
  234. package/server/src/Support/Metrics/OrdersCompletedMetric.php +35 -0
  235. package/server/src/Support/Metrics/OrdersInProgressMetric.php +51 -0
  236. package/server/src/Support/Metrics/OrdersScheduledMetric.php +37 -0
  237. package/server/src/Support/Metrics/Registry.php +46 -0
  238. package/server/src/Support/Metrics/ResolvedIssuesMetric.php +35 -0
  239. package/server/src/Support/Metrics/TotalCustomersMetric.php +29 -0
  240. package/server/src/Support/Metrics/TotalDistanceTraveledMetric.php +35 -0
  241. package/server/src/Support/Metrics/TotalDriversMetric.php +28 -0
  242. package/server/src/Support/Metrics/TotalTimeTraveledMetric.php +35 -0
  243. package/server/src/Support/Metrics.php +46 -249
  244. package/server/src/Support/ResolvesOrderServiceStops.php +512 -0
  245. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +290 -0
  246. package/server/src/Support/Telematics/Providers/SafeeProvider.php +333 -0
  247. package/server/src/Tracking/TrackingContextBuilder.php +41 -38
  248. package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
  249. package/server/src/Tracking/TrackingStop.php +13 -11
  250. package/server/src/routes.php +50 -0
  251. package/server/tests/AnalyticsRoutesTest.php +41 -0
  252. package/server/tests/CapacityAllocationEngineTest.php +10 -10
  253. package/server/tests/CustomerContactIdentitySafetyTest.php +54 -0
  254. package/server/tests/CustomerEndpointTest.php +199 -0
  255. package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
  256. package/server/tests/LabelPdfRenderingTest.php +23 -0
  257. package/server/tests/LiveControllerViewportTest.php +60 -0
  258. package/server/tests/MetricsRegistryTest.php +84 -0
  259. package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
  260. package/server/tests/OrderConfigEndpointTest.php +86 -0
  261. package/server/tests/OrderFileAttachmentTest.php +20 -0
  262. package/server/tests/ServiceAreaZoneApiShapeTest.php +34 -0
  263. package/server/tests/VehicleDriverAssignmentTest.php +18 -0
  264. package/server/tests/VroomOrchestrationEngineTest.php +91 -0
  265. package/translations/en-us.yaml +38 -0
  266. package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
@@ -0,0 +1,341 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Seeders\Testing;
4
+
5
+ use Fleetbase\FleetOps\Models\Driver;
6
+ use Fleetbase\FleetOps\Models\Fleet;
7
+ use Fleetbase\FleetOps\Models\FleetDriver;
8
+ use Fleetbase\FleetOps\Models\FleetVehicle;
9
+ use Fleetbase\FleetOps\Models\FuelReport;
10
+ use Fleetbase\FleetOps\Models\Issue;
11
+ use Fleetbase\FleetOps\Models\Vehicle;
12
+ use Fleetbase\FleetOps\Seeders\Testing\Concerns\SeedsTestingData;
13
+ use Fleetbase\Models\Company;
14
+ use Fleetbase\Models\User;
15
+ use Illuminate\Database\Seeder;
16
+ use Illuminate\Support\Facades\DB;
17
+ use Illuminate\Support\Facades\Hash;
18
+
19
+ class FleetSeeder extends Seeder
20
+ {
21
+ use SeedsTestingData;
22
+
23
+ public function run(): void
24
+ {
25
+ $company = $this->prepareCompany();
26
+ if (!$company) {
27
+ return;
28
+ }
29
+
30
+ DB::transaction(function () use ($company) {
31
+ $this->purgeSeedData();
32
+ $users = $this->seedUsers($company);
33
+ $vehicles = $this->seedVehicles($company);
34
+ $drivers = $this->seedDrivers($company, $users, $vehicles);
35
+ $fleets = $this->seedFleets($company);
36
+ $this->seedFleetMemberships($fleets, $drivers, $vehicles);
37
+ $this->seedFuelReports($company, $users, $drivers, $vehicles);
38
+ $this->seedIssues($company, $users, $drivers, $vehicles);
39
+
40
+ $this->command?->info('Seeded FleetOps testing fleet fixtures for company: ' . $company->public_id);
41
+ });
42
+ }
43
+
44
+ public function purgeSeedData(): void
45
+ {
46
+ $this->purgeModel(Issue::class);
47
+ $this->purgeModel(FuelReport::class);
48
+ $this->purgeModel(FleetDriver::class);
49
+ $this->purgeModel(FleetVehicle::class);
50
+ $this->purgeModel(Driver::class);
51
+ $this->purgeModel(Vehicle::class);
52
+ $this->purgeModel(Fleet::class);
53
+ $this->purgeModel(User::class);
54
+ }
55
+
56
+ protected function seedUsers(Company $company): array
57
+ {
58
+ $users = [
59
+ 'driver_ava_user' => ['Ava Driver', 'ava.driver.testing@example.test', '+6581000001'],
60
+ 'driver_ken_user' => ['Ken Driver', 'ken.driver.testing@example.test', '+6581000002'],
61
+ 'driver_mira_user' => ['Mira Driver', 'mira.driver.testing@example.test', '+6581000003'],
62
+ 'dispatcher_user' => ['Testing Dispatcher', 'dispatcher.testing@example.test', '+6581000004'],
63
+ ];
64
+
65
+ $models = [];
66
+ foreach ($users as $seedId => [$name, $email, $phone]) {
67
+ $models[$seedId] = $this->createRecord(User::class, [
68
+ '_key' => $this->fixtureKey($seedId),
69
+ 'company_uuid' => $company->uuid,
70
+ 'name' => $name,
71
+ 'email' => $email,
72
+ 'phone' => $phone,
73
+ 'password' => Hash::make('fleetops-testing'),
74
+ 'type' => str_contains($seedId, 'driver') ? 'driver' : 'user',
75
+ 'status' => 'active',
76
+ 'timezone' => 'Asia/Singapore',
77
+ 'country' => 'SG',
78
+ 'meta' => $this->meta($seedId),
79
+ ]);
80
+ }
81
+
82
+ return $models;
83
+ }
84
+
85
+ protected function seedVehicles(Company $company): array
86
+ {
87
+ $vehicles = [
88
+ 'van_central' => ['CEN-01', 'Toyota', 'HiAce', 'GBB-1001', 1.2819, 103.8507, true, 'available', 250, 8, 2, 42],
89
+ 'van_east' => ['EAS-01', 'Nissan', 'NV200', 'GBB-1002', 1.3520, 103.9449, true, 'available', 160, 5, 1, 28],
90
+ 'truck_maintenance' => ['WRK-03', 'Isuzu', 'NPR', 'GBB-1003', 1.3347, 103.7423, false, 'maintenance', 900, 24, 8, 160],
91
+ 'bike_unassigned' => ['RCH-04', 'Honda', 'PCX', 'GBB-1004', 1.3039, 103.8520, false, 'inactive', 25, 1, 0, 6],
92
+ 'van_airport' => ['CGO-05', 'Mercedes-Benz', 'Vito', 'GBB-1005', 1.3644, 103.9915, true, 'available', 220, 7, 2, 36],
93
+ 'van_cold_chain' => ['CLD-06', 'Hyundai', 'H350', 'GBB-1006', 1.3525, 103.9447, true, 'available', 300, 10, 3, 48],
94
+ 'truck_west' => ['JUR-07', 'Hino', '300 Series', 'GBB-1007', 1.3345, 103.7420, true, 'available', 1200, 30, 10, 180],
95
+ 'van_linehaul' => ['LNH-08', 'Ford', 'Transit', 'GBB-1008', 1.3215, 103.7055, false, 'available', 450, 14, 4, 72],
96
+ 'van_returns' => ['RTN-09', 'Peugeot', 'Expert', 'GBB-1009', 1.3150, 103.7649, true, 'available', 180, 6, 1, 32],
97
+ 'van_central_2' => ['CEN-02', 'Toyota', 'HiAce', 'GBB-1010', 1.2871, 103.8550, true, 'available', 250, 8, 2, 42],
98
+ 'van_central_3' => ['CEN-03', 'Nissan', 'NV350', 'GBB-1011', 1.2950, 103.8468, true, 'available', 280, 9, 2, 45],
99
+ 'bike_rochor_2' => ['RCH-11', 'Yamaha', 'NMAX', 'GBB-1012', 1.3042, 103.8528, true, 'available', 25, 1, 0, 6],
100
+ 'bike_rochor_3' => ['RCH-12', 'Honda', 'ADV160', 'GBB-1013', 1.3017, 103.8509, true, 'available', 30, 1, 0, 7],
101
+ 'truck_alexandra_1' => ['ALX-14', 'Mitsubishi Fuso', 'Canter', 'GBB-1014', 1.2868, 103.8054, true, 'available', 850, 20, 6, 120],
102
+ 'van_alexandra_2' => ['ALX-15', 'Ford', 'Transit Custom', 'GBB-1015', 1.2881, 103.8079, false, 'available', 400, 12, 3, 64],
103
+ 'van_changi_2' => ['CGO-16', 'Mercedes-Benz', 'Sprinter', 'GBB-1016', 1.3658, 103.9871, true, 'available', 500, 16, 4, 80],
104
+ 'truck_changi_3' => ['CGO-17', 'Isuzu', 'NPR', 'GBB-1017', 1.3610, 103.9954, true, 'available', 1000, 26, 8, 150],
105
+ 'van_tampines_cold_2' => ['CLD-18', 'Hyundai', 'H350', 'GBB-1018', 1.3529, 103.9460, true, 'available', 320, 10, 3, 50],
106
+ 'van_tampines_cold_3' => ['CLD-19', 'Toyota', 'HiAce Refrigerated', 'GBB-1019', 1.3498, 103.9425, false, 'maintenance', 260, 8, 2, 40],
107
+ 'truck_jurong_2' => ['JUR-20', 'Hino', '500 Series', 'GBB-1020', 1.3340, 103.7398, true, 'available', 1400, 34, 12, 220],
108
+ 'van_jurong_3' => ['JUR-21', 'Nissan', 'Urvan', 'GBB-1021', 1.3362, 103.7451, true, 'available', 300, 9, 2, 52],
109
+ 'truck_linehaul_2' => ['LNH-22', 'Volvo', 'FL', 'GBB-1022', 1.3210, 103.7032, true, 'available', 1800, 42, 14, 260],
110
+ 'van_linehaul_3' => ['LNH-23', 'Ford', 'Transit', 'GBB-1023', 1.3234, 103.7088, true, 'available', 470, 14, 4, 72],
111
+ 'van_returns_2' => ['RTN-24', 'Peugeot', 'Expert', 'GBB-1024', 1.3168, 103.7660, true, 'available', 180, 6, 1, 32],
112
+ 'bike_returns_3' => ['RTN-25', 'Honda', 'PCX', 'GBB-1025', 1.3136, 103.7629, false, 'inactive', 25, 1, 0, 6],
113
+ ];
114
+
115
+ $models = [];
116
+ foreach ($vehicles as $seedId => [$name, $make, $model, $plate, $lat, $lng, $online, $status, $weight, $volume, $pallets, $parcels]) {
117
+ $models[$seedId] = $this->createRecord(Vehicle::class, [
118
+ '_key' => $this->fixtureKey($seedId),
119
+ 'company_uuid' => $company->uuid,
120
+ 'name' => $name,
121
+ 'make' => $make,
122
+ 'model' => $model,
123
+ 'year' => '2025',
124
+ 'type' => str_contains($seedId, 'bike') ? 'motorcycle' : 'van',
125
+ 'status' => $status,
126
+ 'online' => $online,
127
+ 'plate_number' => $plate,
128
+ 'location' => $this->point($lat, $lng),
129
+ 'odometer' => str_contains($seedId, 'maintenance') ? 90450 : 24600,
130
+ 'odometer_unit' => 'km',
131
+ 'fuel_type' => str_contains($seedId, 'bike') ? 'petrol' : 'diesel',
132
+ 'currency' => 'SGD',
133
+ 'payload_capacity' => $weight,
134
+ 'payload_capacity_volume' => $volume,
135
+ 'payload_capacity_pallets' => $pallets,
136
+ 'payload_capacity_parcels' => $parcels,
137
+ 'max_tasks' => str_contains($seedId, 'bike') ? 4 : 8,
138
+ 'return_to_depot' => false,
139
+ 'skills' => str_contains($seedId, 'east') ? ['cold_chain'] : [],
140
+ 'meta' => $this->meta($seedId),
141
+ ]);
142
+ }
143
+
144
+ return $models;
145
+ }
146
+
147
+ protected function seedDrivers(Company $company, array $users, array $vehicles): array
148
+ {
149
+ $drivers = [
150
+ 'driver_ava' => ['driver_ava_user', 'van_central', 'S1234567A', 1.2821, 103.8510, true, 'available', ['fragile']],
151
+ 'driver_ken' => ['driver_ken_user', 'van_east', 'S1234567K', 1.3522, 103.9442, true, 'on_duty', ['cold_chain']],
152
+ 'driver_mira' => ['driver_mira_user', null, 'S1234567M', 1.3341, 103.7421, false, 'off_duty', []],
153
+ ];
154
+
155
+ $models = [];
156
+ foreach ($drivers as $seedId => [$userSeedId, $vehicleSeedId, $license, $lat, $lng, $online, $status, $skills]) {
157
+ $models[$seedId] = $this->createRecord(Driver::class, [
158
+ '_key' => $this->fixtureKey($seedId),
159
+ 'company_uuid' => $company->uuid,
160
+ 'user_uuid' => $users[$userSeedId]?->uuid,
161
+ 'vehicle_uuid' => $vehicleSeedId ? $vehicles[$vehicleSeedId]?->uuid : null,
162
+ 'drivers_license_number' => $license,
163
+ 'license_expiry' => $this->timestamp()->addYears(2)->toDateString(),
164
+ 'location' => $this->point($lat, $lng),
165
+ 'country' => 'SG',
166
+ 'currency' => 'SGD',
167
+ 'city' => 'Singapore',
168
+ 'online' => $online,
169
+ 'current_status' => $status,
170
+ 'status' => 'available',
171
+ 'skills' => $skills,
172
+ 'max_travel_time' => 3600,
173
+ 'max_distance' => 60000,
174
+ 'meta' => $this->meta($seedId),
175
+ ]);
176
+ }
177
+
178
+ return $models;
179
+ }
180
+
181
+ protected function seedFleets(Company $company): array
182
+ {
183
+ $fleets = [
184
+ 'central_fleet' => ['CityOps', 'transport', '#2563eb', 'cbd_zone', null],
185
+ 'east_fleet' => ['EastLink', 'transport', '#059669', 'airport_zone', null],
186
+ 'west_fleet' => ['WestHub', 'transport', '#ea580c', 'warehouse_zone', null],
187
+ ];
188
+
189
+ $models = [];
190
+ foreach ($fleets as $seedId => [$name, $task, $color, $zoneSeedId, $parentSeedId]) {
191
+ $zone = $this->seededZone($zoneSeedId);
192
+ $models[$seedId] = $this->createRecord(Fleet::class, [
193
+ '_key' => $this->fixtureKey($seedId),
194
+ 'company_uuid' => $company->uuid,
195
+ 'service_area_uuid' => $zone?->service_area_uuid,
196
+ 'zone_uuid' => $zone?->uuid,
197
+ 'parent_fleet_uuid' => $parentSeedId ? $models[$parentSeedId]?->uuid : null,
198
+ 'name' => $name,
199
+ 'color' => $color,
200
+ 'task' => $task,
201
+ 'status' => 'active',
202
+ ]);
203
+ }
204
+
205
+ $subFleets = [
206
+ 'central_cbd_subfleet' => ['Marina AM', 'last_mile', '#1d4ed8', 'cbd_zone', 'central_fleet'],
207
+ 'central_warehouse_subfleet' => ['Alexandra PM', 'middle_mile', '#3b82f6', 'warehouse_zone', 'central_fleet'],
208
+ 'east_airport_subfleet' => ['Changi Cargo', 'airport', '#047857', 'airport_zone', 'east_fleet'],
209
+ 'east_cold_chain_subfleet' => ['Tampines Cold', 'cold_chain', '#10b981', 'airport_zone', 'east_fleet'],
210
+ 'west_linehaul_subfleet' => ['Jurong Linehaul', 'linehaul', '#c2410c', 'warehouse_zone', 'west_fleet'],
211
+ 'west_returns_subfleet' => ['Clementi Returns', 'returns', '#f97316', 'warehouse_zone', 'west_fleet'],
212
+ ];
213
+
214
+ foreach ($subFleets as $seedId => [$name, $task, $color, $zoneSeedId, $parentSeedId]) {
215
+ $zone = $this->seededZone($zoneSeedId);
216
+ $models[$seedId] = $this->createRecord(Fleet::class, [
217
+ '_key' => $this->fixtureKey($seedId),
218
+ 'company_uuid' => $company->uuid,
219
+ 'service_area_uuid' => $zone?->service_area_uuid,
220
+ 'zone_uuid' => $zone?->uuid,
221
+ 'parent_fleet_uuid' => $models[$parentSeedId]?->uuid,
222
+ 'name' => $name,
223
+ 'color' => $color,
224
+ 'task' => $task,
225
+ 'status' => 'active',
226
+ ]);
227
+ }
228
+
229
+ return $models;
230
+ }
231
+
232
+ protected function seedFleetMemberships(array $fleets, array $drivers, array $vehicles): void
233
+ {
234
+ $driverLinks = [
235
+ 'central_driver_ava' => ['central_fleet', 'driver_ava'],
236
+ 'central_subfleet_driver_ava' => ['central_cbd_subfleet', 'driver_ava'],
237
+ 'east_driver_ken' => ['east_fleet', 'driver_ken'],
238
+ 'east_subfleet_driver_ken' => ['east_cold_chain_subfleet', 'driver_ken'],
239
+ 'west_driver_mira' => ['west_fleet', 'driver_mira'],
240
+ 'west_subfleet_driver_mira' => ['west_linehaul_subfleet', 'driver_mira'],
241
+ ];
242
+
243
+ foreach ($driverLinks as $seedId => [$fleetSeedId, $driverSeedId]) {
244
+ $this->createRecord(FleetDriver::class, [
245
+ '_key' => $this->fixtureKey($seedId),
246
+ 'fleet_uuid' => $fleets[$fleetSeedId]?->uuid,
247
+ 'driver_uuid' => $drivers[$driverSeedId]?->uuid,
248
+ ]);
249
+ }
250
+
251
+ $vehicleLinks = [
252
+ 'central_vehicle_van' => ['central_fleet', 'van_central'],
253
+ 'central_vehicle_van_2' => ['central_fleet', 'van_central_2'],
254
+ 'central_vehicle_van_3' => ['central_fleet', 'van_central_3'],
255
+ 'central_cbd_vehicle_bike' => ['central_cbd_subfleet', 'bike_unassigned'],
256
+ 'central_cbd_vehicle_bike_2' => ['central_cbd_subfleet', 'bike_rochor_2'],
257
+ 'central_cbd_vehicle_bike_3' => ['central_cbd_subfleet', 'bike_rochor_3'],
258
+ 'central_warehouse_truck' => ['central_warehouse_subfleet', 'truck_maintenance'],
259
+ 'central_warehouse_truck_2' => ['central_warehouse_subfleet', 'truck_alexandra_1'],
260
+ 'central_warehouse_van_3' => ['central_warehouse_subfleet', 'van_alexandra_2'],
261
+ 'east_vehicle_van' => ['east_fleet', 'van_east'],
262
+ 'east_airport_vehicle_van' => ['east_airport_subfleet', 'van_airport'],
263
+ 'east_airport_vehicle_van_2' => ['east_airport_subfleet', 'van_changi_2'],
264
+ 'east_airport_vehicle_truck_3' => ['east_airport_subfleet', 'truck_changi_3'],
265
+ 'east_cold_chain_vehicle_van' => ['east_cold_chain_subfleet', 'van_cold_chain'],
266
+ 'east_cold_chain_vehicle_van_2' => ['east_cold_chain_subfleet', 'van_tampines_cold_2'],
267
+ 'east_cold_chain_vehicle_van_3' => ['east_cold_chain_subfleet', 'van_tampines_cold_3'],
268
+ 'west_vehicle_truck' => ['west_fleet', 'truck_west'],
269
+ 'west_vehicle_truck_2' => ['west_fleet', 'truck_jurong_2'],
270
+ 'west_vehicle_van_3' => ['west_fleet', 'van_jurong_3'],
271
+ 'west_linehaul_vehicle_van' => ['west_linehaul_subfleet', 'van_linehaul'],
272
+ 'west_linehaul_vehicle_truck_2' => ['west_linehaul_subfleet', 'truck_linehaul_2'],
273
+ 'west_linehaul_vehicle_van_3' => ['west_linehaul_subfleet', 'van_linehaul_3'],
274
+ 'west_returns_vehicle_van' => ['west_returns_subfleet', 'van_returns'],
275
+ 'west_returns_vehicle_van_2' => ['west_returns_subfleet', 'van_returns_2'],
276
+ 'west_returns_vehicle_bike_3' => ['west_returns_subfleet', 'bike_returns_3'],
277
+ ];
278
+
279
+ foreach ($vehicleLinks as $seedId => [$fleetSeedId, $vehicleSeedId]) {
280
+ $this->createRecord(FleetVehicle::class, [
281
+ '_key' => $this->fixtureKey($seedId),
282
+ 'fleet_uuid' => $fleets[$fleetSeedId]?->uuid,
283
+ 'vehicle_uuid' => $vehicles[$vehicleSeedId]?->uuid,
284
+ ]);
285
+ }
286
+ }
287
+
288
+ protected function seedFuelReports(Company $company, array $users, array $drivers, array $vehicles): void
289
+ {
290
+ $reports = [
291
+ 'fuel_van_central' => ['driver_ava', 'van_central', 'Diesel refill after morning route.', 25120, 8450, 42, 'approved', 1.2820, 103.8508],
292
+ 'fuel_van_east' => ['driver_ken', 'van_east', 'Cold chain route refill.', 18420, 6025, 31, 'pending', 1.3521, 103.9444],
293
+ ];
294
+
295
+ foreach ($reports as $seedId => [$driverSeedId, $vehicleSeedId, $report, $odometer, $amount, $volume, $status, $lat, $lng]) {
296
+ $this->createRecord(FuelReport::class, [
297
+ 'company_uuid' => $company->uuid,
298
+ 'driver_uuid' => $drivers[$driverSeedId]?->uuid,
299
+ 'vehicle_uuid' => $vehicles[$vehicleSeedId]?->uuid,
300
+ 'reported_by_uuid' => $users['dispatcher_user']?->uuid,
301
+ 'report' => $report,
302
+ 'odometer' => $odometer,
303
+ 'amount' => $amount,
304
+ 'currency' => 'SGD',
305
+ 'volume' => $volume,
306
+ 'metric_unit' => 'l',
307
+ 'location' => $this->point($lat, $lng),
308
+ 'status' => $status,
309
+ 'meta' => $this->meta($seedId),
310
+ ]);
311
+ }
312
+ }
313
+
314
+ protected function seedIssues(Company $company, array $users, array $drivers, array $vehicles): void
315
+ {
316
+ $issues = [
317
+ 'issue_low_tire' => ['driver_ava', 'van_central', 'Low tire pressure warning', 'maintenance', 'medium', 'pending', 1.2822, 103.8512],
318
+ 'issue_liftgate' => ['driver_mira', 'truck_maintenance', 'Liftgate inspection required', 'equipment', 'high', 'in-progress', 1.3346, 103.7424],
319
+ ];
320
+
321
+ foreach ($issues as $seedId => [$driverSeedId, $vehicleSeedId, $title, $category, $priority, $status, $lat, $lng]) {
322
+ $this->createRecord(Issue::class, [
323
+ 'company_uuid' => $company->uuid,
324
+ 'reported_by_uuid' => $users['dispatcher_user']?->uuid,
325
+ 'assigned_to_uuid' => $users['dispatcher_user']?->uuid,
326
+ 'driver_uuid' => $drivers[$driverSeedId]?->uuid,
327
+ 'vehicle_uuid' => $vehicles[$vehicleSeedId]?->uuid,
328
+ 'issue_id' => strtoupper($seedId),
329
+ 'location' => $this->point($lat, $lng),
330
+ 'category' => $category,
331
+ 'type' => 'inspection',
332
+ 'title' => $title,
333
+ 'report' => $title . ' created as a FleetOps testing fixture.',
334
+ 'tags' => ['fixture', 'fleetops-testing'],
335
+ 'priority' => $priority,
336
+ 'status' => $status,
337
+ 'meta' => $this->meta($seedId),
338
+ ]);
339
+ }
340
+ }
341
+ }
@@ -0,0 +1,238 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Seeders\Testing;
4
+
5
+ use Fleetbase\FleetOps\Models\Driver;
6
+ use Fleetbase\FleetOps\Models\Equipment;
7
+ use Fleetbase\FleetOps\Models\Maintenance;
8
+ use Fleetbase\FleetOps\Models\MaintenanceSchedule;
9
+ use Fleetbase\FleetOps\Models\Part;
10
+ use Fleetbase\FleetOps\Models\Vehicle;
11
+ use Fleetbase\FleetOps\Models\Vendor;
12
+ use Fleetbase\FleetOps\Models\WorkOrder;
13
+ use Fleetbase\FleetOps\Seeders\Testing\Concerns\SeedsTestingData;
14
+ use Fleetbase\Models\Company;
15
+ use Illuminate\Database\Seeder;
16
+ use Illuminate\Support\Facades\DB;
17
+
18
+ class MaintenanceSeeder 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
+ $equipment = $this->seedEquipment($company);
32
+ $this->seedParts($company);
33
+ $schedules = $this->seedSchedules($company);
34
+ $workOrders = $this->seedWorkOrders($company, $schedules);
35
+ $this->seedMaintenanceHistory($company, $equipment, $workOrders);
36
+
37
+ $this->command?->info('Seeded FleetOps testing maintenance fixtures for company: ' . $company->public_id);
38
+ });
39
+ }
40
+
41
+ public function purgeSeedData(): void
42
+ {
43
+ $this->purgeModel(Maintenance::class);
44
+ $this->purgeModel(WorkOrder::class);
45
+ $this->purgeModel(MaintenanceSchedule::class);
46
+ $this->purgeModel(Part::class);
47
+ $this->purgeModel(Equipment::class);
48
+ }
49
+
50
+ protected function seedEquipment(Company $company): array
51
+ {
52
+ $vehicle = $this->seededModel(Vehicle::class, 'van_east');
53
+ $equipment = [
54
+ 'equipment_refrigeration_unit' => ['Carrier X4-7500', 'EQ-REF-001', 'refrigeration', 'Carrier', 'X4-7500', $vehicle],
55
+ 'equipment_tail_lift' => ['Dhollandia DH-LM', 'EQ-LIFT-001', 'liftgate', 'Dhollandia', 'DH-LM', $this->seededModel(Vehicle::class, 'truck_maintenance')],
56
+ ];
57
+
58
+ $models = [];
59
+ foreach ($equipment as $seedId => [$name, $code, $type, $manufacturer, $model, $vehicle]) {
60
+ $models[$seedId] = $this->createRecord(Equipment::class, [
61
+ '_key' => $this->fixtureKey($seedId),
62
+ 'company_uuid' => $company->uuid,
63
+ 'name' => $name,
64
+ 'code' => $code,
65
+ 'type' => $type,
66
+ 'status' => 'active',
67
+ 'serial_number' => strtoupper($seedId),
68
+ 'manufacturer' => $manufacturer,
69
+ 'model' => $model,
70
+ 'equipable_type'=> Vehicle::class,
71
+ 'equipable_uuid'=> $vehicle?->uuid,
72
+ 'purchased_at' => $this->timestamp(-2400)->toDateString(),
73
+ 'purchase_price'=> $type === 'refrigeration' ? 1240000 : 740000,
74
+ 'currency' => 'SGD',
75
+ 'meta' => $this->meta($seedId),
76
+ ]);
77
+ }
78
+
79
+ return $models;
80
+ }
81
+
82
+ protected function seedParts(Company $company): void
83
+ {
84
+ $vendor = $this->seededModel(Vendor::class, 'supplier_parts');
85
+ $vehicle = $this->seededModel(Vehicle::class, 'truck_maintenance');
86
+ $parts = [
87
+ 'part_oil_filter' => ['OIL-FILTER-001', 'Donaldson P550', 'Donaldson', 'P550', 12, 1800, 'consumable', 'active'],
88
+ 'part_brake_pad' => ['BRAKE-PAD-001', 'Bendix CT-3', 'Bendix', 'CT-3', 3, 8600, 'brake', 'low_stock'],
89
+ ];
90
+
91
+ foreach ($parts as $seedId => [$sku, $name, $manufacturer, $model, $quantity, $unitCost, $type, $status]) {
92
+ $this->createRecord(Part::class, [
93
+ '_key' => $this->fixtureKey($seedId),
94
+ 'company_uuid' => $company->uuid,
95
+ 'vendor_uuid' => $vendor?->uuid,
96
+ 'sku' => $sku,
97
+ 'name' => $name,
98
+ 'manufacturer' => $manufacturer,
99
+ 'model' => $model,
100
+ 'description' => 'FleetOps testing fixture part.',
101
+ 'quantity_on_hand' => $quantity,
102
+ 'unit_cost' => $unitCost,
103
+ 'msrp' => $unitCost * 1.3,
104
+ 'currency' => 'SGD',
105
+ 'asset_type' => Vehicle::class,
106
+ 'asset_uuid' => $vehicle?->uuid,
107
+ 'type' => $type,
108
+ 'status' => $status,
109
+ 'specs' => ['fixture' => true],
110
+ 'meta' => $this->meta($seedId),
111
+ ]);
112
+ }
113
+ }
114
+
115
+ protected function seedSchedules(Company $company): array
116
+ {
117
+ $driver = $this->seededModel(Driver::class, 'driver_mira');
118
+ $schedules = [
119
+ 'schedule_due_soon' => ['Due Soon Inspection', 'inspection', 'active', 'date', 'months', 1, $this->seededModel(Vehicle::class, 'van_central'), $this->timestamp(168)],
120
+ 'schedule_overdue' => ['Overdue Service', 'service', 'active', 'odometer', 'km', 5000, $this->seededModel(Vehicle::class, 'truck_maintenance'), $this->timestamp(-72)],
121
+ ];
122
+
123
+ $models = [];
124
+ foreach ($schedules as $seedId => [$name, $type, $status, $intervalMethod, $intervalUnit, $intervalValue, $vehicle, $nextDueDate]) {
125
+ $models[$seedId] = $this->createRecord(MaintenanceSchedule::class, [
126
+ '_key' => $this->fixtureKey($seedId),
127
+ 'company_uuid' => $company->uuid,
128
+ 'subject_type' => Vehicle::class,
129
+ 'subject_uuid' => $vehicle?->uuid,
130
+ 'name' => $name,
131
+ 'type' => $type,
132
+ 'status' => $status,
133
+ 'interval_method' => $intervalMethod,
134
+ 'interval_type' => 'recurring',
135
+ 'interval_value' => $intervalValue,
136
+ 'interval_unit' => $intervalUnit,
137
+ 'last_service_odometer' => 85000,
138
+ 'last_service_date' => $this->timestamp(-720),
139
+ 'next_due_date' => $nextDueDate,
140
+ 'next_due_odometer' => $intervalMethod === 'odometer' ? 90500 : null,
141
+ 'default_priority' => $seedId === 'schedule_overdue' ? 'high' : 'medium',
142
+ 'default_assignee_type' => Driver::class,
143
+ 'default_assignee_uuid' => $driver?->uuid,
144
+ 'instructions' => 'FleetOps testing fixture maintenance schedule.',
145
+ 'reminder_offsets' => [7, 1],
146
+ 'meta' => $this->meta($seedId),
147
+ ]);
148
+ }
149
+
150
+ return $models;
151
+ }
152
+
153
+ protected function seedWorkOrders(Company $company, array $schedules): array
154
+ {
155
+ $driver = $this->seededModel(Driver::class, 'driver_mira');
156
+ $vehicle = $this->seededModel(Vehicle::class, 'truck_maintenance');
157
+ $workOrders = [
158
+ 'work_order_open' => ['WO-TEST-OPEN', 'Open Inspection', 'open', 'medium', $this->timestamp(-4), $this->timestamp(24), null, 'schedule_due_soon'],
159
+ 'work_order_overdue' => ['WO-TEST-OVERDUE', 'Overdue Service', 'open', 'high', $this->timestamp(-120), $this->timestamp(-24), null, 'schedule_overdue'],
160
+ 'work_order_completed' => ['WO-TEST-CLOSED', 'Completed Repair', 'completed', 'low', $this->timestamp(-240), $this->timestamp(-120), $this->timestamp(-96), 'schedule_overdue'],
161
+ ];
162
+
163
+ $models = [];
164
+ foreach ($workOrders as $seedId => [$code, $subject, $status, $priority, $openedAt, $dueAt, $closedAt, $scheduleSeedId]) {
165
+ $models[$seedId] = $this->createRecord(WorkOrder::class, [
166
+ '_key' => $this->fixtureKey($seedId),
167
+ 'company_uuid' => $company->uuid,
168
+ 'code' => $code,
169
+ 'subject' => $subject,
170
+ 'status' => $status,
171
+ 'priority' => $priority,
172
+ 'target_type' => Vehicle::class,
173
+ 'target_uuid' => $vehicle?->uuid,
174
+ 'assignee_type' => Driver::class,
175
+ 'assignee_uuid' => $driver?->uuid,
176
+ 'opened_at' => $openedAt,
177
+ 'due_at' => $dueAt,
178
+ 'closed_at' => $closedAt,
179
+ 'instructions' => 'FleetOps testing fixture work order.',
180
+ 'checklist' => [
181
+ ['label' => 'Inspect vehicle', 'completed' => $status === 'completed'],
182
+ ['label' => 'Record readings', 'completed' => $status === 'completed'],
183
+ ],
184
+ 'estimated_cost' => 25000,
185
+ 'approved_budget' => 30000,
186
+ 'actual_cost' => $status === 'completed' ? 22800 : null,
187
+ 'currency' => 'SGD',
188
+ 'cost_breakdown' => ['labor' => 12000, 'parts' => 10800],
189
+ 'cost_center' => 'testing-fixtures',
190
+ 'budget_code' => 'FB-TEST',
191
+ 'schedule_uuid' => $schedules[$scheduleSeedId]?->uuid,
192
+ 'meta' => $this->meta($seedId),
193
+ ]);
194
+ }
195
+
196
+ return $models;
197
+ }
198
+
199
+ protected function seedMaintenanceHistory(Company $company, array $equipment, array $workOrders): void
200
+ {
201
+ $driver = $this->seededModel(Driver::class, 'driver_mira');
202
+ $vehicle = $this->seededModel(Vehicle::class, 'truck_maintenance');
203
+ $records = [
204
+ 'maintenance_completed_vehicle' => [$vehicle, Vehicle::class, 'work_order_completed', 'Completed brake inspection', 'completed'],
205
+ 'maintenance_completed_equipment' => [$equipment['equipment_refrigeration_unit'] ?? null, Equipment::class, 'work_order_completed', 'Refrigeration unit calibration', 'completed'],
206
+ ];
207
+
208
+ foreach ($records as $seedId => [$maintainable, $maintainableType, $workOrderSeedId, $summary, $status]) {
209
+ $this->createRecord(Maintenance::class, [
210
+ '_key' => $this->fixtureKey($seedId),
211
+ 'company_uuid' => $company->uuid,
212
+ 'work_order_uuid' => $workOrders[$workOrderSeedId]?->uuid,
213
+ 'maintainable_type' => $maintainableType,
214
+ 'maintainable_uuid' => $maintainable?->uuid,
215
+ 'type' => 'service',
216
+ 'status' => $status,
217
+ 'priority' => 'medium',
218
+ 'scheduled_at' => $this->timestamp(-150),
219
+ 'started_at' => $this->timestamp(-120),
220
+ 'completed_at' => $this->timestamp(-96),
221
+ 'odometer' => 90450,
222
+ 'engine_hours' => 2200,
223
+ 'performed_by_type' => Driver::class,
224
+ 'performed_by_uuid' => $driver?->uuid,
225
+ 'summary' => $summary,
226
+ 'notes' => 'FleetOps testing fixture maintenance history.',
227
+ 'line_items' => [['description' => 'Labor', 'amount' => 12000], ['description' => 'Parts', 'amount' => 10800]],
228
+ 'labor_cost' => 12000,
229
+ 'parts_cost' => 10800,
230
+ 'tax' => 1824,
231
+ 'total_cost' => 24624,
232
+ 'currency' => 'SGD',
233
+ 'attachments' => [],
234
+ 'meta' => $this->meta($seedId),
235
+ ]);
236
+ }
237
+ }
238
+ }