@fleetbase/fleetops-engine 0.6.52 → 0.6.54

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 (151) hide show
  1. package/addon/components/device/details.hbs +106 -135
  2. package/addon/components/device/details.js +174 -1
  3. package/addon/components/device/panel-header.hbs +2 -2
  4. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.hbs +201 -0
  5. package/addon/components/layout/fleet-ops-sidebar/operations-monitor.js +654 -0
  6. package/addon/components/layout/fleet-ops-sidebar.hbs +19 -233
  7. package/addon/components/layout/fleet-ops-sidebar.js +364 -395
  8. package/addon/components/maintenance-schedule/form.js +1 -0
  9. package/addon/components/order/form/details.hbs +1 -43
  10. package/addon/components/order/form/details.js +18 -53
  11. package/addon/components/order/form/orchestrator-constraints.hbs +40 -0
  12. package/addon/components/order/form/orchestrator-constraints.js +61 -0
  13. package/addon/components/order/form/payload.js +50 -2
  14. package/addon/components/order/form/route.js +15 -0
  15. package/addon/components/order/form/service-rate.hbs +62 -11
  16. package/addon/components/order/form/service-rate.js +124 -2
  17. package/addon/components/order/form.hbs +2 -0
  18. package/addon/components/telematic/details.hbs +7 -1
  19. package/addon/components/telematic/form.js +16 -8
  20. package/addon/components/work-order/details.hbs +5 -1
  21. package/addon/components/work-order/form.hbs +26 -4
  22. package/addon/components/work-order/form.js +5 -1
  23. package/addon/controllers/connectivity/devices/index/details/events.js +244 -1
  24. package/addon/controllers/connectivity/devices/index/details/sensors.js +199 -0
  25. package/addon/controllers/connectivity/devices/index/details/vehicle.js +74 -0
  26. package/addon/controllers/connectivity/devices/index/details.js +12 -0
  27. package/addon/controllers/connectivity/devices/index.js +4 -1
  28. package/addon/controllers/connectivity/telematics/details/attachments.js +220 -54
  29. package/addon/controllers/connectivity/telematics/details/devices.js +90 -41
  30. package/addon/controllers/connectivity/telematics/details/events.js +39 -11
  31. package/addon/controllers/connectivity/telematics/details/sensors.js +39 -19
  32. package/addon/controllers/maintenance/index.js +100 -0
  33. package/addon/controllers/maintenance/work-orders/index.js +18 -1
  34. package/addon/controllers/management/fuel-transactions/index.js +1 -1
  35. package/addon/controllers/management/index.js +88 -0
  36. package/addon/controllers/settings/index.js +142 -0
  37. package/addon/extension.js +21 -0
  38. package/addon/routes/analytics/index.js +3 -0
  39. package/addon/routes/connectivity/devices/index/details/events.js +37 -1
  40. package/addon/routes/connectivity/devices/index/details/sensors.js +35 -0
  41. package/addon/routes/connectivity/devices/index/details/vehicle.js +7 -0
  42. package/addon/routes/connectivity/devices/index.js +3 -0
  43. package/addon/routes/connectivity/telematics/details/attachments.js +72 -9
  44. package/addon/routes/connectivity/telematics/details/devices.js +4 -0
  45. package/addon/routes/connectivity/telematics/details/sensors.js +1 -0
  46. package/addon/routes/connectivity/telematics/details.js +1 -1
  47. package/addon/routes/connectivity/telematics/edit.js +1 -1
  48. package/addon/routes/maintenance/index.js +3 -0
  49. package/addon/routes/maintenance/work-orders/index.js +3 -0
  50. package/addon/routes/settings/index.js +3 -0
  51. package/addon/routes.js +2 -0
  52. package/addon/services/order-creation.js +54 -1
  53. package/addon/services/order-validation.js +3 -2
  54. package/addon/services/service-rate-actions.js +1 -0
  55. package/addon/styles/fleetops-engine.css +1821 -6
  56. package/addon/templates/analytics/index.hbs +14 -0
  57. package/addon/templates/analytics/reports/index.hbs +4 -4
  58. package/addon/templates/connectivity/devices/index/details/events.hbs +85 -1
  59. package/addon/templates/connectivity/devices/index/details/sensors.hbs +73 -0
  60. package/addon/templates/connectivity/devices/index/details/vehicle.hbs +84 -0
  61. package/addon/templates/connectivity/telematics/details/attachments.hbs +198 -121
  62. package/addon/templates/connectivity/telematics/details/devices.hbs +4 -1
  63. package/addon/templates/connectivity/telematics/details/events.hbs +4 -1
  64. package/addon/templates/connectivity/telematics/details/sensors.hbs +3 -0
  65. package/addon/templates/maintenance/index.hbs +113 -0
  66. package/addon/templates/management/fuel-transactions/index/details.hbs +1 -1
  67. package/addon/templates/management/index.hbs +109 -73
  68. package/addon/templates/management/vehicles/index/edit.hbs +1 -1
  69. package/addon/templates/operations/orders/index.hbs +1 -1
  70. package/addon/templates/operations/service-rates/index.hbs +1 -1
  71. package/addon/templates/settings/index.hbs +83 -0
  72. package/addon/templates/settings/routing.hbs +2 -2
  73. package/addon/utils/fleet-ops-options.js +38 -0
  74. package/app/components/order/form/orchestrator-constraints.js +1 -0
  75. package/app/controllers/connectivity/devices/index/details/sensors.js +1 -0
  76. package/app/controllers/connectivity/devices/index/details/vehicle.js +1 -0
  77. package/app/controllers/maintenance/index.js +1 -0
  78. package/app/controllers/management/index.js +1 -0
  79. package/app/controllers/settings/index.js +1 -0
  80. package/app/routes/analytics/index.js +1 -0
  81. package/app/routes/connectivity/devices/index/details/sensors.js +1 -0
  82. package/app/routes/connectivity/devices/index/details/vehicle.js +1 -0
  83. package/app/routes/maintenance/index.js +1 -0
  84. package/app/routes/settings/index.js +1 -0
  85. package/app/templates/analytics/index.js +1 -0
  86. package/app/templates/connectivity/devices/index/details/sensors.js +1 -0
  87. package/app/templates/connectivity/devices/index/details/vehicle.js +1 -0
  88. package/app/templates/maintenance/index.js +1 -0
  89. package/app/templates/management/index.js +1 -0
  90. package/app/templates/settings/index.js +1 -0
  91. package/composer.json +1 -1
  92. package/extension.json +1 -1
  93. package/package.json +3 -3
  94. package/server/migrations/2025_08_28_054930_create_work_orders_table.php +1 -0
  95. package/server/migrations/2026_06_15_000001_normalize_device_attachable_vehicle_morph_types.php +30 -0
  96. package/server/migrations/2026_06_18_000001_add_category_to_work_orders_table.php +32 -0
  97. package/server/seeders/Testing/MaintenanceSeeder.php +5 -4
  98. package/server/src/Console/Commands/FixInvalidPolymorphicRelationTypeNamespaces.php +1 -0
  99. package/server/src/Console/Commands/ProcessMaintenanceTriggers.php +1 -0
  100. package/server/src/Console/Commands/SyncTelematics.php +89 -0
  101. package/server/src/Exceptions/TelematicProviderException.php +13 -2
  102. package/server/src/Http/Controllers/Api/v1/OrderController.php +6 -21
  103. package/server/src/Http/Controllers/Internal/v1/DeviceController.php +112 -11
  104. package/server/src/Http/Controllers/Internal/v1/DeviceEventController.php +16 -5
  105. package/server/src/Http/Controllers/Internal/v1/FleetController.php +9 -0
  106. package/server/src/Http/Controllers/Internal/v1/HubController.php +300 -0
  107. package/server/src/Http/Controllers/Internal/v1/LiveController.php +177 -0
  108. package/server/src/Http/Controllers/Internal/v1/MaintenanceScheduleController.php +1 -0
  109. package/server/src/Http/Controllers/Internal/v1/OrderController.php +4 -32
  110. package/server/src/Http/Controllers/Internal/v1/SearchController.php +506 -0
  111. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +4 -4
  112. package/server/src/Http/Controllers/Internal/v1/TelematicController.php +1 -1
  113. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +94 -10
  114. package/server/src/Http/Filter/DeviceEventFilter.php +44 -0
  115. package/server/src/Http/Filter/DeviceFilter.php +131 -0
  116. package/server/src/Http/Filter/DriverFilter.php +6 -0
  117. package/server/src/Http/Filter/SensorFilter.php +125 -0
  118. package/server/src/Http/Filter/VehicleFilter.php +19 -0
  119. package/server/src/Http/Requests/CreateDriverRequest.php +14 -13
  120. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -0
  121. package/server/src/Http/Resources/v1/Driver.php +11 -2
  122. package/server/src/Http/Resources/v1/WorkOrder.php +1 -0
  123. package/server/src/Jobs/FinalizeApiOrderCreation.php +47 -0
  124. package/server/src/Jobs/FinalizeInternalOrderCreation.php +35 -0
  125. package/server/src/Jobs/NotifyBulkAssignedDriver.php +47 -0
  126. package/server/src/Jobs/SyncTelematicDevicesJob.php +181 -66
  127. package/server/src/Models/Device.php +15 -1
  128. package/server/src/Models/DeviceEvent.php +1 -1
  129. package/server/src/Models/Driver.php +17 -1
  130. package/server/src/Models/Sensor.php +15 -2
  131. package/server/src/Models/Telematic.php +1 -1
  132. package/server/src/Models/Vehicle.php +5 -1
  133. package/server/src/Models/WorkOrder.php +5 -2
  134. package/server/src/Observers/DriverObserver.php +3 -3
  135. package/server/src/Observers/FleetObserver.php +23 -0
  136. package/server/src/Observers/VehicleObserver.php +3 -3
  137. package/server/src/Providers/FleetOpsServiceProvider.php +12 -0
  138. package/server/src/Support/LiveCacheService.php +1 -1
  139. package/server/src/Support/Telematics/Providers/AfaqyProvider.php +188 -28
  140. package/server/src/Support/Telematics/Providers/SafeeProvider.php +44 -3
  141. package/server/src/Support/Telematics/TelematicService.php +330 -30
  142. package/server/src/routes.php +10 -0
  143. package/server/tests/DeviceFilterTest.php +36 -0
  144. package/server/tests/HubRoutesTest.php +62 -0
  145. package/server/tests/OperationsMonitorEndpointTest.php +38 -0
  146. package/server/tests/OrderQueuedClosureRegressionTest.php +36 -0
  147. package/server/tests/TelematicsHardeningTest.php +455 -3
  148. package/server/tests/VehicleDriverAssignmentTest.php +27 -0
  149. package/translations/ar-ae.yml +1 -1
  150. package/translations/en-us.yaml +1 -0
  151. package/translations/vi-vn.yaml +1 -1
@@ -0,0 +1,300 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
4
+
5
+ use Fleetbase\FleetOps\Models\Contact;
6
+ use Fleetbase\FleetOps\Models\Device;
7
+ use Fleetbase\FleetOps\Models\Driver;
8
+ use Fleetbase\FleetOps\Models\Equipment;
9
+ use Fleetbase\FleetOps\Models\Fleet;
10
+ use Fleetbase\FleetOps\Models\FuelProviderTransaction;
11
+ use Fleetbase\FleetOps\Models\FuelReport;
12
+ use Fleetbase\FleetOps\Models\Issue;
13
+ use Fleetbase\FleetOps\Models\Maintenance;
14
+ use Fleetbase\FleetOps\Models\MaintenanceSchedule;
15
+ use Fleetbase\FleetOps\Models\Part;
16
+ use Fleetbase\FleetOps\Models\Place;
17
+ use Fleetbase\FleetOps\Models\Vehicle;
18
+ use Fleetbase\FleetOps\Models\Vendor;
19
+ use Fleetbase\FleetOps\Models\WorkOrder;
20
+ use Fleetbase\Http\Controllers\Controller;
21
+ use Illuminate\Database\Eloquent\Builder;
22
+ use Illuminate\Http\Request;
23
+ use Illuminate\Support\Carbon;
24
+
25
+ class HubController extends Controller
26
+ {
27
+ public function resources(Request $request)
28
+ {
29
+ $company = $this->companyUuid($request);
30
+
31
+ $counts = [
32
+ 'drivers' => $this->count(Driver::query(), $company),
33
+ 'vehicles' => $this->count(Vehicle::query(), $company),
34
+ 'fleets' => $this->count(Fleet::query()->whereNull('parent_fleet_uuid'), $company),
35
+ 'vendors' => $this->count(Vendor::query(), $company),
36
+ 'contacts' => $this->count(Contact::query(), $company),
37
+ 'places' => $this->count(Place::query(), $company),
38
+ 'issues' => $this->count(Issue::query()->whereNotIn('status', ['resolved', 'closed']), $company),
39
+ 'drivers_without_vehicles' => $this->count(Driver::query()->whereNull('vehicle_uuid'), $company),
40
+ 'vehicles_without_drivers' => $this->count(Vehicle::query()->whereDoesntHave('driver'), $company),
41
+ 'vehicles_without_devices' => $this->count(Vehicle::query()->whereDoesntHave('devices'), $company),
42
+ 'unattached_devices' => $this->count(Device::query()->whereNull('attachable_uuid'), $company),
43
+ 'resource_issues' => $this->count(Issue::query()->whereNotIn('status', ['resolved', 'closed'])->where(function ($query) {
44
+ $query->whereNotNull('vehicle_uuid')->orWhereNotNull('driver_uuid');
45
+ }), $company),
46
+ 'overdue_vehicle_schedules' => $this->count(MaintenanceSchedule::query()->where('subject_type', Vehicle::class)->where('status', 'active')->whereNotNull('next_due_date')->where('next_due_date', '<', Carbon::now()), $company),
47
+ 'upcoming_vehicle_schedules' => $this->count(MaintenanceSchedule::query()->where('subject_type', Vehicle::class)->where('status', 'active')->whereBetween('next_due_date', [Carbon::now(), Carbon::now()->addDays(7)]), $company),
48
+ 'open_resource_work_orders' => $this->count(WorkOrder::query()->whereIn('target_type', [Vehicle::class, Equipment::class])->whereIn('status', ['open', 'in_progress']), $company),
49
+ 'overdue_resource_work_orders' => $this->count(WorkOrder::query()->whereIn('target_type', [Vehicle::class, Equipment::class])->whereNotIn('status', ['closed', 'canceled'])->whereNotNull('due_at')->where('due_at', '<', Carbon::now()), $company),
50
+ 'low_stock_parts' => $this->count(Part::query()->where('quantity_on_hand', '>', 0)->where('quantity_on_hand', '<=', 5), $company),
51
+ 'unmatched_fuel_transactions' => $this->count(FuelProviderTransaction::query()->where('sync_status', 'unmatched'), $company),
52
+ 'fuel_reports' => $this->count(FuelReport::query(), $company),
53
+ 'fuel_transactions' => $this->count(FuelProviderTransaction::query(), $company),
54
+ ];
55
+
56
+ $fuelRecords = $counts['fuel_reports'] + $counts['fuel_transactions'];
57
+
58
+ return response()->json([
59
+ 'kpis' => [
60
+ $this->kpi('drivers', 'Drivers', $counts['drivers'], $counts['drivers'] > 0 ? 'Driver profiles available for dispatch.' : 'Add driver profiles before dispatching work.', 'blue', 'id-card', 'management.drivers'),
61
+ $this->kpi('vehicles', 'Vehicles', $counts['vehicles'], $counts['vehicles'] > 0 ? 'Vehicles ready for assignment and tracking.' : 'Add vehicles so dispatch has assignable assets.', 'green', 'truck', 'management.vehicles'),
62
+ $this->kpi('issues', 'Open Issues', $counts['issues'], $counts['issues'] > 0 ? 'Issues need review before they affect service quality.' : 'No open issues are visible right now.', $counts['issues'] > 0 ? 'rose' : 'green', 'triangle-exclamation', 'management.issues'),
63
+ $this->kpi('fuel_records', 'Fuel Records', $fuelRecords, $fuelRecords > 0 ? 'Fuel activity is available for review.' : 'Fuel records will appear as reports or transactions arrive.', 'amber', 'gas-pump', $counts['fuel_transactions'] > $counts['fuel_reports'] ? 'management.fuel-transactions' : 'management.fuel-reports'),
64
+ ],
65
+ 'actions' => $this->resourceActions($counts, $fuelRecords),
66
+ 'sections' => [
67
+ [
68
+ 'key' => 'people_assets',
69
+ 'title' => 'People And Assets',
70
+ 'description' => 'Keep dispatchable drivers, vehicles, and fleet groupings ready for live work.',
71
+ 'links' => [
72
+ $this->link('Drivers', 'management.drivers', 'id-card', $counts['drivers'], 'Manage driver profiles, assignments, and schedules.'),
73
+ $this->link('Vehicles', 'management.vehicles', 'truck', $counts['vehicles'], 'Track vehicles, device links, equipment, and service context.'),
74
+ $this->link('Fleets', 'management.fleets', 'user-group', $counts['fleets'], 'Group drivers and vehicles by team, region, or service type.'),
75
+ ],
76
+ ],
77
+ [
78
+ 'key' => 'network',
79
+ 'title' => 'Operating Network',
80
+ 'description' => 'Maintain the partners, contacts, and places used while creating and dispatching orders.',
81
+ 'links' => [
82
+ $this->link('Vendors', 'management.vendors', 'warehouse', $counts['vendors'], 'Service partners connected to operational work.'),
83
+ $this->link('Contacts', 'management.contacts', 'address-book', $counts['contacts'], 'Customer and contact records for order workflows.'),
84
+ $this->link('Places', 'management.places', 'location-dot', $counts['places'], 'Reusable locations, zones, and address context.'),
85
+ ],
86
+ ],
87
+ [
88
+ 'key' => 'exceptions',
89
+ 'title' => 'Fuel And Exceptions',
90
+ 'description' => 'Review fuel activity and operational issues before they affect service quality.',
91
+ 'links' => [
92
+ $this->link('Fuel Reports', 'management.fuel-reports', 'gas-pump', $counts['fuel_reports'], 'Review reported fuel activity and costs.'),
93
+ $this->link('Fuel Transactions', 'management.fuel-transactions', 'credit-card', $counts['fuel_transactions'], 'Inspect transaction-level fueling data and matching context.'),
94
+ $this->link('Issues', 'management.issues', 'triangle-exclamation', $counts['issues'], 'Track incidents, service issues, and follow-up work.'),
95
+ ],
96
+ ],
97
+ ],
98
+ 'docs' => [
99
+ $this->doc('Drivers', 'id-card', 'fleet-ops/resources/drivers/overview', 'Drivers guide', 'Create driver profiles, app access, assignment context, and operating status.'),
100
+ $this->doc('Vehicles', 'truck', 'fleet-ops/resources/vehicles/overview', 'Vehicles guide', 'Track fleet assets, assignment readiness, and vehicle operating details.'),
101
+ $this->doc('Fleets', 'user-group', 'fleet-ops/resources/fleets/overview', 'Fleets guide', 'Group drivers and vehicles by team, region, or service coverage.'),
102
+ $this->doc('Contacts', 'address-book', 'fleet-ops/resources/contacts/overview', 'Contacts guide', 'Manage people and organizations used across orders and operational records.'),
103
+ $this->doc('Places', 'location-dot', 'fleet-ops/resources/places/overview', 'Places guide', 'Manage reusable pickup, dropoff, hub, and facility locations.'),
104
+ $this->doc('Issues', 'triangle-exclamation', 'fleet-ops/resources/issues/overview', 'Issues guide', 'Track incidents, service exceptions, and operational follow-up.'),
105
+ ],
106
+ ]);
107
+ }
108
+
109
+ public function maintenance(Request $request)
110
+ {
111
+ $company = $this->companyUuid($request);
112
+ $now = Carbon::now();
113
+ $next7 = Carbon::now()->addDays(7);
114
+
115
+ $overdueSchedules = $this->count(MaintenanceSchedule::query()->where('status', 'active')->whereNotNull('next_due_date')->where('next_due_date', '<', $now), $company);
116
+ $upcomingSchedules = $this->count(MaintenanceSchedule::query()->where('status', 'active')->whereBetween('next_due_date', [$now, $next7]), $company);
117
+ $openWorkOrders = $this->count(WorkOrder::query()->whereIn('status', ['open', 'in_progress']), $company);
118
+ $overdueWorkOrders = $this->count(WorkOrder::query()->whereNotIn('status', ['closed', 'canceled'])->whereNotNull('due_at')->where('due_at', '<', $now), $company);
119
+ $openMaintenance = $this->count(Maintenance::query()->whereNotIn('status', ['completed', 'canceled']), $company);
120
+ $highPriorityMaintenance = $this->count(Maintenance::query()->whereNotIn('status', ['completed', 'canceled'])->whereIn('priority', ['high', 'urgent', 'critical']), $company);
121
+ $lowStockParts = $this->count(Part::query()->where('quantity_on_hand', '>', 0)->where('quantity_on_hand', '<=', 5), $company);
122
+ $equipment = $this->count(Equipment::query(), $company);
123
+
124
+ return response()->json([
125
+ 'kpis' => [
126
+ $this->kpi('overdue_schedules', 'Overdue Schedules', $overdueSchedules, $overdueSchedules > 0 ? 'Recurring service needs attention.' : 'No recurring service is overdue.', $overdueSchedules > 0 ? 'rose' : 'green', 'calendar-xmark', 'maintenance.schedules'),
127
+ $this->kpi('upcoming_schedules', 'Due This Week', $upcomingSchedules, 'Maintenance schedules due in the next 7 days.', 'blue', 'calendar-day', 'maintenance.schedules'),
128
+ $this->kpi('open_work_orders', 'Open Work Orders', $openWorkOrders, $openWorkOrders > 0 ? 'Active work needs assignment or closure.' : 'No open work orders right now.', $openWorkOrders > 0 ? 'amber' : 'green', 'clipboard-list', 'maintenance.work-orders'),
129
+ $this->kpi('low_stock_parts', 'Low Stock Parts', $lowStockParts, $lowStockParts > 0 ? 'Parts inventory may need replenishment.' : 'No low-stock parts detected.', $lowStockParts > 0 ? 'amber' : 'green', 'cog', 'maintenance.parts'),
130
+ ],
131
+ 'actions' => $this->maintenanceActions($overdueSchedules, $upcomingSchedules, $openWorkOrders, $overdueWorkOrders, $highPriorityMaintenance, $lowStockParts, $equipment),
132
+ 'sections' => [
133
+ [
134
+ 'key' => 'planning',
135
+ 'title' => 'Planning',
136
+ 'description' => 'Schedule recurring service before work turns urgent.',
137
+ 'links' => [
138
+ $this->link('Schedules', 'maintenance.schedules', 'calendar-alt', $upcomingSchedules + $overdueSchedules, 'Recurring maintenance intervals and service windows.'),
139
+ $this->link('Work Orders', 'maintenance.work-orders', 'clipboard-list', $openWorkOrders, 'Repair tasks, assignments, vendor work, and closure.'),
140
+ ],
141
+ ],
142
+ [
143
+ 'key' => 'records',
144
+ 'title' => 'Records',
145
+ 'description' => 'Keep the maintenance history and serviceable asset catalog current.',
146
+ 'links' => [
147
+ $this->link('Maintenances', 'maintenance.maintenances', 'history', $openMaintenance, 'Maintenance records and service history by asset.'),
148
+ $this->link('Equipment', 'maintenance.equipment', 'trailer', $equipment, 'Assigned equipment and serviceable operational assets.'),
149
+ $this->link('Parts', 'maintenance.parts', 'cog', $lowStockParts, 'Parts inventory visibility for repair planning.'),
150
+ ],
151
+ ],
152
+ ],
153
+ 'docs' => [
154
+ $this->doc('Schedules', 'calendar-alt', 'fleet-ops/maintenance/schedules/overview', 'Maintenance schedules guide', 'Plan recurring service windows and convert due schedules into work orders.'),
155
+ $this->doc('Work Orders', 'clipboard-list', 'fleet-ops/maintenance/work-orders/overview', 'Work orders guide', 'Coordinate assigned maintenance work, vendors, due dates, and completion.'),
156
+ $this->doc('Equipment', 'trailer', 'fleet-ops/maintenance/equipment/overview', 'Equipment guide', 'Track serviceable equipment that participates in maintenance operations.'),
157
+ $this->doc('Parts', 'cog', 'fleet-ops/maintenance/parts/overview', 'Parts guide', 'Manage parts inventory and restocking signals used by maintenance teams.'),
158
+ ],
159
+ ]);
160
+ }
161
+
162
+ protected function resourceActions(array $counts, int $fuelRecords): array
163
+ {
164
+ $actions = [];
165
+
166
+ if ($counts['drivers'] === 0) {
167
+ $actions[] = $this->action('add_drivers', 'Add driver profiles', 'Drivers are required before dispatch can assign live orders.', 'warning', 'id-card', 'management.drivers');
168
+ }
169
+
170
+ if ($counts['vehicles'] === 0) {
171
+ $actions[] = $this->action('add_vehicles', 'Add vehicles', 'Vehicles make assignment, tracking, and service context usable.', 'warning', 'truck', 'management.vehicles');
172
+ }
173
+
174
+ if ($counts['drivers_without_vehicles'] > 0 && $counts['vehicles'] > 0) {
175
+ $actions[] = $this->action('assign_vehicles_to_drivers', 'Assign vehicles to drivers', "{$counts['drivers_without_vehicles']} driver" . ($counts['drivers_without_vehicles'] === 1 ? ' does' : 's do') . ' not have an assigned vehicle.', 'warning', 'id-card', 'management.drivers', ['vehicle' => 'unassigned']);
176
+ }
177
+
178
+ if ($counts['vehicles_without_drivers'] > 0 && $counts['drivers'] > 0) {
179
+ $actions[] = $this->action('assign_drivers_to_vehicles', 'Assign drivers to vehicles', "{$counts['vehicles_without_drivers']} vehicle" . ($counts['vehicles_without_drivers'] === 1 ? ' does' : 's do') . ' not have an assigned driver.', 'warning', 'truck', 'management.vehicles', ['driver' => 'unassigned']);
180
+ }
181
+
182
+ if ($counts['vehicles_without_devices'] > 0 && $counts['unattached_devices'] > 0) {
183
+ $actions[] = $this->action('attach_devices_to_vehicles', 'Attach devices to vehicles', "{$counts['unattached_devices']} device" . ($counts['unattached_devices'] === 1 ? ' is' : 's are') . ' ready to attach to vehicles without telemetry hardware.', 'info', 'satellite-dish', 'connectivity.devices', ['attachment_state' => 'unattached']);
184
+ }
185
+
186
+ if ($counts['resource_issues'] > 0) {
187
+ $actions[] = $this->action('review_resource_issues', 'Review vehicle and driver issues', "{$counts['resource_issues']} issue" . ($counts['resource_issues'] === 1 ? ' is' : 's are') . ' linked to drivers or vehicles.', 'warning', 'triangle-exclamation', 'management.issues', ['status' => 'open']);
188
+ } elseif ($counts['issues'] > 0) {
189
+ $actions[] = $this->action('review_issues', 'Review open issues', "{$counts['issues']} issue" . ($counts['issues'] === 1 ? ' needs' : 's need') . ' follow-up.', 'warning', 'triangle-exclamation', 'management.issues', ['status' => 'open']);
190
+ }
191
+
192
+ if (($counts['overdue_vehicle_schedules'] + $counts['upcoming_vehicle_schedules']) > 0) {
193
+ $serviceCount = $counts['overdue_vehicle_schedules'] + $counts['upcoming_vehicle_schedules'];
194
+ $actions[] = $this->action('prepare_vehicle_maintenance', 'Prepare upcoming maintenance', "{$serviceCount} vehicle schedule" . ($serviceCount === 1 ? ' needs' : 's need') . ' maintenance planning.', $counts['overdue_vehicle_schedules'] > 0 ? 'warning' : 'info', 'calendar-day', 'maintenance.schedules', ['status' => 'active']);
195
+ }
196
+
197
+ if ($counts['overdue_resource_work_orders'] > 0) {
198
+ $actions[] = $this->action('close_overdue_work_orders', 'Close overdue work orders', "{$counts['overdue_resource_work_orders']} resource work order" . ($counts['overdue_resource_work_orders'] === 1 ? ' is' : 's are') . ' past due.', 'warning', 'clipboard-list', 'maintenance.work-orders', ['status' => 'open']);
199
+ } elseif ($counts['open_resource_work_orders'] > 0) {
200
+ $actions[] = $this->action('review_open_work_orders', 'Review open work orders', "{$counts['open_resource_work_orders']} resource work order" . ($counts['open_resource_work_orders'] === 1 ? ' is' : 's are') . ' open for vehicles or equipment.', 'info', 'clipboard-list', 'maintenance.work-orders', ['status' => 'open']);
201
+ }
202
+
203
+ if ($counts['low_stock_parts'] > 0) {
204
+ $actions[] = $this->action('replenish_low_stock_parts', 'Replenish low-stock parts', "{$counts['low_stock_parts']} stocked part" . ($counts['low_stock_parts'] === 1 ? ' is' : 's are') . ' at or below the default threshold.', 'warning', 'cog', 'maintenance.parts', ['status' => 'in_stock']);
205
+ }
206
+
207
+ if ($counts['unmatched_fuel_transactions'] > 0) {
208
+ $actions[] = $this->action('review_unmatched_fuel_transactions', 'Review unmatched fuel transactions', "{$counts['unmatched_fuel_transactions']} fuel transaction" . ($counts['unmatched_fuel_transactions'] === 1 ? ' needs' : 's need') . ' resource matching.', 'warning', 'gas-pump', 'management.fuel-transactions', ['sync_status' => 'unmatched']);
209
+ }
210
+
211
+ if ($counts['fleets'] === 0 && $counts['drivers'] > 0 && $counts['vehicles'] > 0) {
212
+ $actions[] = $this->action('create_fleets', 'Create fleet groups', 'Group active drivers and vehicles by team, region, or service type.', 'info', 'user-group', 'management.fleets');
213
+ }
214
+
215
+ if ($counts['places'] === 0) {
216
+ $actions[] = $this->action('add_operating_places', 'Add operating places', 'Places keep recurring pickup, dropoff, facility, and service locations ready.', 'info', 'location-dot', 'management.places');
217
+ }
218
+
219
+ if ($counts['vendors'] === 0) {
220
+ $actions[] = $this->action('add_service_vendors', 'Add vendors for service work', 'Vendors connect maintenance, service work, and operating partners to resources.', 'info', 'warehouse', 'management.vendors');
221
+ }
222
+
223
+ if ($counts['contacts'] === 0) {
224
+ $actions[] = $this->action('add_contacts', 'Add operating contacts', 'Contacts connect customers, partners, and service people to order workflows.', 'info', 'address-book', 'management.contacts');
225
+ }
226
+
227
+ if ($fuelRecords === 0) {
228
+ $actions[] = $this->action('review_fuel', 'Open fuel records', 'Fuel activity will appear here once reports or transactions are captured.', 'info', 'gas-pump', 'management.fuel-reports');
229
+ }
230
+
231
+ return $actions ? array_slice($actions, 0, 6) : [$this->action('ready', 'Core resources look ready', 'Drivers, vehicles, issues, and supporting records are in a healthy operating posture.', 'success', 'check-circle', null)];
232
+ }
233
+
234
+ protected function maintenanceActions(int $overdueSchedules, int $upcomingSchedules, int $openWorkOrders, int $overdueWorkOrders, int $highPriorityMaintenance, int $lowStockParts, int $equipment): array
235
+ {
236
+ $actions = [];
237
+
238
+ if ($overdueSchedules > 0) {
239
+ $actions[] = $this->action('overdue_schedules', 'Review overdue schedules', "{$overdueSchedules} recurring service schedule" . ($overdueSchedules === 1 ? ' is' : 's are') . ' overdue.', 'warning', 'calendar-xmark', 'maintenance.schedules');
240
+ }
241
+
242
+ if ($overdueWorkOrders > 0) {
243
+ $actions[] = $this->action('overdue_work_orders', 'Close overdue work orders', "{$overdueWorkOrders} work order" . ($overdueWorkOrders === 1 ? ' is' : 's are') . ' past due.', 'warning', 'clipboard-list', 'maintenance.work-orders');
244
+ }
245
+
246
+ if ($highPriorityMaintenance > 0) {
247
+ $actions[] = $this->action('high_priority_maintenance', 'Prioritize critical maintenance', "{$highPriorityMaintenance} high-priority maintenance record" . ($highPriorityMaintenance === 1 ? ' needs' : 's need') . ' review.', 'warning', 'wrench', 'maintenance.maintenances');
248
+ }
249
+
250
+ if ($upcomingSchedules > 0) {
251
+ $actions[] = $this->action('upcoming_service', 'Prepare upcoming service', "{$upcomingSchedules} schedule" . ($upcomingSchedules === 1 ? ' is' : 's are') . ' due in the next 7 days.', 'info', 'calendar-day', 'maintenance.schedules');
252
+ }
253
+
254
+ if ($openWorkOrders === 0) {
255
+ $actions[] = $this->action('no_open_work_orders', 'No open work orders', 'Create work orders when scheduled service or asset issues need assignment.', 'success', 'check-circle', 'maintenance.work-orders');
256
+ }
257
+
258
+ if ($lowStockParts > 0) {
259
+ $actions[] = $this->action('low_stock_parts', 'Replenish low-stock parts', "{$lowStockParts} stocked part" . ($lowStockParts === 1 ? ' is' : 's are') . ' at or below the default threshold.', 'warning', 'cog', 'maintenance.parts');
260
+ }
261
+
262
+ if ($equipment === 0) {
263
+ $actions[] = $this->action('add_equipment', 'Add serviceable equipment', 'Equipment records make maintenance planning more complete.', 'info', 'trailer', 'maintenance.equipment');
264
+ }
265
+
266
+ return array_slice($actions, 0, 5);
267
+ }
268
+
269
+ protected function count(Builder $query, ?string $companyUuid): int
270
+ {
271
+ return (int) $query->when($companyUuid, fn ($query) => $query->where('company_uuid', $companyUuid))->count();
272
+ }
273
+
274
+ protected function companyUuid(Request $request): ?string
275
+ {
276
+ return session('company') ?? $request->user()?->company_uuid ?? $request->user()?->company?->uuid;
277
+ }
278
+
279
+ protected function kpi(string $key, string $label, int $value, string $caption, string $tone, string $icon, string $route): array
280
+ {
281
+ return compact('key', 'label', 'value', 'caption', 'tone', 'icon', 'route');
282
+ }
283
+
284
+ protected function action(string $key, string $label, string $description, string $tone, string $icon, ?string $route, array $query = []): array
285
+ {
286
+ $query = (object) $query;
287
+
288
+ return compact('key', 'label', 'description', 'tone', 'icon', 'route', 'query');
289
+ }
290
+
291
+ protected function link(string $label, string $route, string $icon, int $count, string $description): array
292
+ {
293
+ return compact('label', 'route', 'icon', 'count', 'description');
294
+ }
295
+
296
+ protected function doc(string $label, string $icon, string $slug, string $title, string $description = ''): array
297
+ {
298
+ return compact('label', 'icon', 'slug', 'title', 'description');
299
+ }
300
+ }
@@ -8,12 +8,16 @@ use Fleetbase\FleetOps\Http\Resources\v1\Index\Order as OrderIndexResource;
8
8
  use Fleetbase\FleetOps\Http\Resources\v1\Index\Place as PlaceIndexResource;
9
9
  use Fleetbase\FleetOps\Http\Resources\v1\Index\Vehicle as VehicleIndexResource;
10
10
  use Fleetbase\FleetOps\Models\Driver;
11
+ use Fleetbase\FleetOps\Models\Fleet;
12
+ use Fleetbase\FleetOps\Models\FleetDriver;
13
+ use Fleetbase\FleetOps\Models\FleetVehicle;
11
14
  use Fleetbase\FleetOps\Models\Order;
12
15
  use Fleetbase\FleetOps\Models\Place;
13
16
  use Fleetbase\FleetOps\Models\Route;
14
17
  use Fleetbase\FleetOps\Models\Vehicle;
15
18
  use Fleetbase\FleetOps\Support\LiveCacheService;
16
19
  use Fleetbase\FleetOps\Support\LiveOrderQuery;
20
+ use Fleetbase\FleetOps\Support\Utils;
17
21
  use Fleetbase\Http\Controllers\Controller;
18
22
  use Illuminate\Http\Request;
19
23
 
@@ -186,6 +190,179 @@ class LiveController extends Controller
186
190
  });
187
191
  }
188
192
 
193
+ /**
194
+ * Get the complete resource snapshot used by the operations sidebar monitor.
195
+ *
196
+ * @return array
197
+ */
198
+ public function operationsMonitor()
199
+ {
200
+ return LiveCacheService::remember('operations-monitor', [], function () {
201
+ $drivers = Driver::where(['company_uuid' => session('company')])
202
+ ->with(['user', 'vehicle'])
203
+ ->applyDirectivesForPermissions('fleet-ops list driver')
204
+ ->orderBy('id')
205
+ ->get();
206
+
207
+ $vehicles = Vehicle::where(['company_uuid' => session('company')])
208
+ ->with(['driver'])
209
+ ->applyDirectivesForPermissions('fleet-ops list vehicle')
210
+ ->orderBy('id')
211
+ ->get();
212
+
213
+ $fleets = Fleet::where(['company_uuid' => session('company')])
214
+ ->applyDirectivesForPermissions('fleet-ops list fleet')
215
+ ->orderBy('name')
216
+ ->orderBy('id')
217
+ ->get();
218
+
219
+ $driverIdsByUuid = $drivers->pluck('uuid', 'uuid');
220
+ $vehicleIdsByUuid = $vehicles->pluck('uuid', 'uuid');
221
+ $onlineDriverUuids = $drivers->where('online', true)->pluck('uuid')->flip();
222
+ $onlineVehicleUuids = $vehicles->where('online', true)->pluck('uuid')->flip();
223
+ $fleetUuids = $fleets->pluck('uuid');
224
+
225
+ $driverMemberships = FleetDriver::whereIn('fleet_uuid', $fleetUuids)
226
+ ->whereIn('driver_uuid', $driverIdsByUuid->keys())
227
+ ->get()
228
+ ->groupBy('fleet_uuid');
229
+
230
+ $vehicleMemberships = FleetVehicle::whereIn('fleet_uuid', $fleetUuids)
231
+ ->whereIn('vehicle_uuid', $vehicleIdsByUuid->keys())
232
+ ->get()
233
+ ->groupBy('fleet_uuid');
234
+
235
+ $fleetNodes = $fleets->mapWithKeys(function (Fleet $fleet) use ($driverMemberships, $vehicleMemberships, $driverIdsByUuid, $vehicleIdsByUuid, $onlineDriverUuids, $onlineVehicleUuids) {
236
+ $fleetDriverUuids = $driverMemberships->get($fleet->uuid, collect())->pluck('driver_uuid')->unique()->values();
237
+ $fleetVehicleUuids = $vehicleMemberships->get($fleet->uuid, collect())->pluck('vehicle_uuid')->unique()->values();
238
+ $driverIds = $fleetDriverUuids->map(fn ($uuid) => $driverIdsByUuid->get($uuid))->filter()->values();
239
+ $vehicleIds = $fleetVehicleUuids->map(fn ($uuid) => $vehicleIdsByUuid->get($uuid))->filter()->values();
240
+
241
+ return [
242
+ $fleet->uuid => [
243
+ 'id' => $fleet->uuid,
244
+ 'uuid' => $fleet->uuid,
245
+ 'public_id' => $fleet->public_id,
246
+ 'name' => $fleet->name,
247
+ 'task' => $fleet->task,
248
+ 'status' => $fleet->status,
249
+ 'slug' => $fleet->slug,
250
+ 'parent_fleet_uuid' => $fleet->parent_fleet_uuid,
251
+ 'drivers_count' => $driverIds->count(),
252
+ 'drivers_online_count' => $fleetDriverUuids->filter(fn ($uuid) => $onlineDriverUuids->has($uuid))->count(),
253
+ 'vehicles_count' => $vehicleIds->count(),
254
+ 'vehicles_online_count' => $fleetVehicleUuids->filter(fn ($uuid) => $onlineVehicleUuids->has($uuid))->count(),
255
+ 'driver_ids' => $driverIds->all(),
256
+ 'vehicle_ids' => $vehicleIds->all(),
257
+ 'subfleets' => [],
258
+ 'updated_at' => $fleet->updated_at,
259
+ 'created_at' => $fleet->created_at,
260
+ ],
261
+ ];
262
+ });
263
+
264
+ return [
265
+ 'drivers' => $drivers->map(fn (Driver $driver) => $this->serializeMonitorDriver($driver))->values()->all(),
266
+ 'vehicles' => $vehicles->map(fn (Vehicle $vehicle) => $this->serializeMonitorVehicle($vehicle))->values()->all(),
267
+ 'fleets' => $this->buildOperationsMonitorFleetTree($fleetNodes),
268
+ 'meta' => [
269
+ 'generated_at' => now()->toISOString(),
270
+ 'ttl' => LiveCacheService::DEFAULT_TTL,
271
+ 'drivers_count' => $drivers->count(),
272
+ 'vehicles_count' => $vehicles->count(),
273
+ 'fleets_count' => $fleets->count(),
274
+ ],
275
+ ];
276
+ });
277
+ }
278
+
279
+ protected function serializeMonitorDriver(Driver $driver): array
280
+ {
281
+ return [
282
+ 'id' => $driver->uuid,
283
+ 'uuid' => $driver->uuid,
284
+ 'public_id' => $driver->public_id,
285
+ 'company_uuid' => $driver->company_uuid,
286
+ 'user_uuid' => $driver->user_uuid,
287
+ 'vehicle_uuid' => $driver->vehicle_uuid,
288
+ 'vendor_uuid' => $driver->vendor_uuid,
289
+ 'current_job_uuid' => $driver->current_job_uuid,
290
+ 'name' => $driver->name,
291
+ 'email' => $driver->email,
292
+ 'phone' => $driver->phone,
293
+ 'photo_url' => $driver->photo_url,
294
+ 'avatar_url' => $driver->photo_url,
295
+ 'vehicle_name' => $driver->vehicle_name,
296
+ 'status' => $driver->status,
297
+ 'location' => Utils::castPoint($driver->location),
298
+ 'heading' => (int) data_get($driver, 'heading', 0),
299
+ 'altitude' => (int) data_get($driver, 'altitude', 0),
300
+ 'speed' => (int) data_get($driver, 'speed', 0),
301
+ 'online' => (bool) data_get($driver, 'online', false),
302
+ 'assigned_orders_count' => null,
303
+ 'meta' => [
304
+ '_index_resource' => true,
305
+ ],
306
+ 'updated_at' => $driver->updated_at,
307
+ 'created_at' => $driver->created_at,
308
+ ];
309
+ }
310
+
311
+ protected function serializeMonitorVehicle(Vehicle $vehicle): array
312
+ {
313
+ return [
314
+ 'id' => $vehicle->uuid,
315
+ 'uuid' => $vehicle->uuid,
316
+ 'public_id' => $vehicle->public_id,
317
+ 'company_uuid' => $vehicle->company_uuid,
318
+ 'vendor_uuid' => $vehicle->vendor_uuid,
319
+ 'photo_uuid' => $vehicle->photo_uuid,
320
+ 'internal_id' => $vehicle->internal_id,
321
+ 'name' => $vehicle->name,
322
+ 'display_name' => $vehicle->display_name,
323
+ 'driver_name' => $vehicle->driver_name,
324
+ 'plate_number' => $vehicle->plate_number,
325
+ 'serial_number' => $vehicle->serial_number,
326
+ 'fuel_card_number' => $vehicle->fuel_card_number,
327
+ 'vin' => $vehicle->vin,
328
+ 'make' => $vehicle->make,
329
+ 'model' => $vehicle->model,
330
+ 'year' => $vehicle->year,
331
+ 'photo_url' => $vehicle->photo_url,
332
+ 'avatar_url' => $vehicle->avatar_url,
333
+ 'status' => $vehicle->status,
334
+ 'location' => Utils::castPoint($vehicle->location),
335
+ 'heading' => (int) data_get($vehicle, 'heading', 0),
336
+ 'altitude' => (int) data_get($vehicle, 'altitude', 0),
337
+ 'speed' => (int) data_get($vehicle, 'speed', 0),
338
+ 'online' => (bool) data_get($vehicle, 'online', false),
339
+ 'assigned_orders_count' => null,
340
+ 'meta' => [
341
+ '_index_resource' => true,
342
+ ],
343
+ 'updated_at' => $vehicle->updated_at,
344
+ 'created_at' => $vehicle->created_at,
345
+ ];
346
+ }
347
+
348
+ protected function buildOperationsMonitorFleetTree($fleetNodes): array
349
+ {
350
+ $nodes = $fleetNodes->map(fn ($node) => $node)->all();
351
+
352
+ foreach ($nodes as $uuid => $node) {
353
+ $parentUuid = data_get($node, 'parent_fleet_uuid');
354
+
355
+ if ($parentUuid && isset($nodes[$parentUuid])) {
356
+ $nodes[$parentUuid]['subfleets'][] = &$nodes[$uuid];
357
+ }
358
+ }
359
+
360
+ return collect($nodes)
361
+ ->filter(fn ($node) => empty(data_get($node, 'parent_fleet_uuid')) || !isset($nodes[data_get($node, 'parent_fleet_uuid')]))
362
+ ->values()
363
+ ->all();
364
+ }
365
+
189
366
  /**
190
367
  * Get places based on filters for the current company.
191
368
  *
@@ -102,6 +102,7 @@ class MaintenanceScheduleController extends FleetOpsController
102
102
  'company_uuid' => $schedule->company_uuid,
103
103
  'schedule_uuid' => $schedule->uuid,
104
104
  'subject' => $schedule->name,
105
+ 'category' => 'preventive_maintenance',
105
106
  'status' => 'open',
106
107
  'priority' => $schedule->default_priority ?? 'normal',
107
108
  'target_type' => $schedule->subject_type,
@@ -6,7 +6,6 @@ use Fleetbase\Exceptions\FleetbaseRequestValidationException;
6
6
  use Fleetbase\FleetOps\Events\EntityActivityChanged;
7
7
  use Fleetbase\FleetOps\Events\EntityCompleted;
8
8
  use Fleetbase\FleetOps\Events\OrderDispatchFailed;
9
- use Fleetbase\FleetOps\Events\OrderReady;
10
9
  use Fleetbase\FleetOps\Events\OrderStarted;
11
10
  use Fleetbase\FleetOps\Events\WaypointActivityChanged;
12
11
  use Fleetbase\FleetOps\Events\WaypointCompleted;
@@ -20,6 +19,8 @@ use Fleetbase\FleetOps\Http\Resources\v1\Index\Order as OrderIndexResource;
20
19
  use Fleetbase\FleetOps\Http\Resources\v1\Order as OrderResource;
21
20
  use Fleetbase\FleetOps\Http\Resources\v1\Proof as ProofResource;
22
21
  use Fleetbase\FleetOps\Imports\OrdersImport;
22
+ use Fleetbase\FleetOps\Jobs\FinalizeInternalOrderCreation;
23
+ use Fleetbase\FleetOps\Jobs\NotifyBulkAssignedDriver;
23
24
  use Fleetbase\FleetOps\Models\Driver;
24
25
  use Fleetbase\FleetOps\Models\Entity;
25
26
  use Fleetbase\FleetOps\Models\Order;
@@ -210,14 +211,7 @@ class OrderController extends FleetOpsController
210
211
  // if service quote attached purchase
211
212
  $order->purchaseServiceQuote($serviceQuote);
212
213
 
213
- // Run background processes on queue
214
- dispatch(function () use ($order): void {
215
- // notify driver if assigned
216
- $order->notifyDriverAssigned();
217
-
218
- // Trigger order created event
219
- event(new OrderReady($order));
220
- })->afterCommit();
214
+ FinalizeInternalOrderCreation::dispatch($order->uuid)->afterCommit();
221
215
  }
222
216
  );
223
217
 
@@ -527,29 +521,7 @@ class OrderController extends FleetOpsController
527
521
 
528
522
  // Queue Per‑Order Notifications
529
523
  if (!$request->boolean('silent')) {
530
- dispatch(function () use ($orderUuids, $driver): void {
531
- // Re‑hydrate Driver To Avoid Serializing The Full Model
532
- $driver = Driver::whereUuid($driver->uuid)->first();
533
-
534
- // Stream Orders To Keep Memory Footprint Low
535
- Order::whereIn('uuid', $orderUuids)
536
- ->cursor()
537
- ->each(function (Order $order) use ($driver): void {
538
- // Synchronize In‑Memory Model
539
- $order->setRelation('driverAssigned', $driver);
540
- $order->driver_assigned_uuid = $driver->uuid;
541
-
542
- try {
543
- $order->notifyDriverAssigned();
544
- } catch (\Throwable $e) {
545
- logger()->warning(
546
- 'Failed notifying driver on order ' . $order->uuid,
547
- ['error' => $e->getMessage()]
548
- );
549
- }
550
- });
551
- })
552
- ->afterCommit();
524
+ NotifyBulkAssignedDriver::dispatch($orderUuids->all(), $driver->uuid)->afterCommit();
553
525
  }
554
526
 
555
527
  return response()->json([