@fleetbase/fleetops-engine 0.6.42 → 0.6.44

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 (52) hide show
  1. package/addon/components/map/drawer/geofence-event-listing.hbs +123 -0
  2. package/addon/components/map/drawer/geofence-event-listing.js +100 -0
  3. package/addon/components/map/drawer.js +1 -0
  4. package/addon/components/map/leaflet-live-map.js +83 -0
  5. package/addon/components/modals/orchestrator-import.js +2 -78
  6. package/addon/components/service-area/form.hbs +59 -0
  7. package/addon/components/zone/form.hbs +60 -1
  8. package/addon/services/geofence-event-bus.js +223 -0
  9. package/app/components/{modals/set-password.js → map/drawer/geofence-event-listing.js} +1 -1
  10. package/app/services/geofence-event-bus.js +1 -0
  11. package/composer.json +1 -1
  12. package/extension.json +1 -1
  13. package/package.json +2 -2
  14. package/server/migrations/2026_04_05_000001_add_geofence_config_to_zones_table.php +48 -0
  15. package/server/migrations/2026_04_05_000002_add_geofence_config_to_service_areas_table.php +38 -0
  16. package/server/migrations/2026_04_05_000003_create_driver_geofence_states_table.php +62 -0
  17. package/server/migrations/2026_04_05_000004_create_geofence_events_log_table.php +66 -0
  18. package/server/migrations/2026_04_08_000001_add_orchestrator_columns_to_vehicles_table.php +3 -0
  19. package/server/migrations/2026_04_08_000004_add_orchestrator_columns_to_payloads_table.php +3 -0
  20. package/server/migrations/2026_04_14_000001_drop_redundant_capacity_columns_from_payloads_table.php +2 -0
  21. package/server/migrations/2026_04_14_000002_rename_capacity_columns_on_vehicles_table.php +2 -0
  22. package/server/migrations/2026_04_21_081353_add_internal_id_column_to_vehicles_table.php +27 -0
  23. package/server/migrations/2026_04_22_000001_add_vehicle_support_to_geofence_events_log.php +31 -0
  24. package/server/migrations/2026_04_22_000002_create_vehicle_geofence_states_table.php +39 -0
  25. package/server/src/Console/Commands/SimulateGeofenceEvents.php +278 -0
  26. package/server/src/Events/GeofenceDwelled.php +208 -0
  27. package/server/src/Events/GeofenceEntered.php +207 -0
  28. package/server/src/Events/GeofenceExited.php +213 -0
  29. package/server/src/Http/Controllers/Api/v1/DriverController.php +100 -0
  30. package/server/src/Http/Controllers/Api/v1/GeofenceController.php +263 -0
  31. package/server/src/Http/Controllers/Api/v1/OrderController.php +8 -3
  32. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
  33. package/server/src/Http/Controllers/Api/v1/VehicleController.php +88 -0
  34. package/server/src/Http/Controllers/Api/v1/ZoneController.php +2 -2
  35. package/server/src/Http/Controllers/Internal/v1/GeofenceController.php +7 -0
  36. package/server/src/Http/Requests/CreateServiceAreaRequest.php +8 -4
  37. package/server/src/Http/Requests/CreateZoneRequest.php +8 -4
  38. package/server/src/Jobs/CheckGeofenceDwell.php +139 -0
  39. package/server/src/Listeners/HandleGeofenceDwelled.php +56 -0
  40. package/server/src/Listeners/HandleGeofenceEntered.php +187 -0
  41. package/server/src/Listeners/HandleGeofenceExited.php +56 -0
  42. package/server/src/Models/GeofenceEventLog.php +155 -0
  43. package/server/src/Models/ServiceArea.php +21 -2
  44. package/server/src/Models/ServiceQuote.php +3 -1
  45. package/server/src/Models/Zone.php +20 -2
  46. package/server/src/Notifications/DriverArrivedAtGeofence.php +86 -0
  47. package/server/src/Orchestration/Support/OrchestrationPayloadBuilder.php +2 -2
  48. package/server/src/Providers/EventServiceProvider.php +19 -0
  49. package/server/src/Providers/FleetOpsServiceProvider.php +10 -0
  50. package/server/src/Support/GeofenceIntersectionService.php +206 -0
  51. package/server/src/routes.php +16 -0
  52. package/addon/components/modals/set-password.hbs +0 -27
@@ -0,0 +1,223 @@
1
+ import Service from '@ember/service';
2
+ import { inject as service } from '@ember/service';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { action } from '@ember/object';
5
+ import { debug } from '@ember/debug';
6
+
7
+ /**
8
+ * GeofenceEventBus
9
+ *
10
+ * A singleton service that subscribes to the company-level geofence WebSocket
11
+ * channel and maintains a live feed of geofence events (entered, exited, dwelled).
12
+ *
13
+ * Other components (e.g. the live map, the geofence events panel) inject this
14
+ * service and observe `this.events` to react to incoming events.
15
+ *
16
+ * Usage:
17
+ * @service geofenceEventBus;
18
+ *
19
+ * // In a template: {{#each this.geofenceEventBus.events as |event|}} ...
20
+ * // In JS: this.geofenceEventBus.on('geofence.entered', this.handleEntry);
21
+ */
22
+ export default class GeofenceEventBusService extends Service {
23
+ @service socket;
24
+ @service currentUser;
25
+ @service universe;
26
+
27
+ /**
28
+ * Live event feed — most recent events at the front.
29
+ * Capped at MAX_EVENTS to prevent unbounded memory growth.
30
+ *
31
+ * @type {Array}
32
+ */
33
+ @tracked events = [];
34
+
35
+ /**
36
+ * Whether the service has successfully subscribed to the socket channel.
37
+ *
38
+ * @type {boolean}
39
+ */
40
+ @tracked isSubscribed = false;
41
+
42
+ /**
43
+ * Maximum number of events to retain in the live feed.
44
+ */
45
+ MAX_EVENTS = 100;
46
+
47
+ /**
48
+ * The geofence event types to listen for.
49
+ */
50
+ GEOFENCE_EVENTS = ['geofence.entered', 'geofence.exited', 'geofence.dwelled'];
51
+
52
+ /**
53
+ * Internal map of event type → array of handler functions.
54
+ * Supports the simple pub/sub API.
55
+ *
56
+ * @type {Map}
57
+ */
58
+ #handlers = new Map();
59
+
60
+ /**
61
+ * The active socket channel subscription.
62
+ */
63
+ #channel = null;
64
+
65
+ /**
66
+ * Subscribe to the company geofence channel.
67
+ * Should be called once after the user session is established.
68
+ *
69
+ * @param {string} companyUuid - The company UUID to subscribe to
70
+ */
71
+ @action
72
+ async subscribe(companyUuid) {
73
+ if (this.isSubscribed || !companyUuid) {
74
+ return;
75
+ }
76
+
77
+ try {
78
+ const socketInstance = this.socket.instance();
79
+ const channelId = `company.${companyUuid}`;
80
+
81
+ this.#channel = socketInstance.subscribe(channelId);
82
+ if (this.#channel.state !== 'subscribed') {
83
+ await this.#channel.subscribe();
84
+ }
85
+
86
+ this.isSubscribed = true;
87
+ debug(`[GeofenceEventBus] Subscribed to channel: ${channelId}`);
88
+
89
+ // Start consuming events from the channel
90
+ (async () => {
91
+ for await (const output of this.#channel) {
92
+ const { event, data } = output;
93
+
94
+ if (this.GEOFENCE_EVENTS.includes(event)) {
95
+ this.#handleIncomingEvent(event, data);
96
+ }
97
+ }
98
+ })();
99
+ } catch (error) {
100
+ debug(`[GeofenceEventBus] Failed to subscribe: ${error.message}`);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Unsubscribe from the geofence channel and clear state.
106
+ * Called on user logout or session end.
107
+ */
108
+ @action
109
+ unsubscribe() {
110
+ if (this.#channel) {
111
+ this.#channel.close();
112
+ this.#channel = null;
113
+ }
114
+ this.isSubscribed = false;
115
+ this.events = [];
116
+ this.#handlers.clear();
117
+ debug('[GeofenceEventBus] Unsubscribed.');
118
+ }
119
+
120
+ /**
121
+ * Register a handler function for a specific geofence event type.
122
+ *
123
+ * @param {string} eventType - 'geofence.entered' | 'geofence.exited' | 'geofence.dwelled'
124
+ * @param {Function} handler - Called with the normalised event object
125
+ */
126
+ on(eventType, handler) {
127
+ if (!this.#handlers.has(eventType)) {
128
+ this.#handlers.set(eventType, []);
129
+ }
130
+ this.#handlers.get(eventType).push(handler);
131
+ }
132
+
133
+ /**
134
+ * Unregister a previously registered handler.
135
+ *
136
+ * @param {string} eventType
137
+ * @param {Function} handler
138
+ */
139
+ off(eventType, handler) {
140
+ if (!this.#handlers.has(eventType)) {
141
+ return;
142
+ }
143
+ const handlers = this.#handlers.get(eventType).filter((h) => h !== handler);
144
+ this.#handlers.set(eventType, handlers);
145
+ }
146
+
147
+ /**
148
+ * Clear the live event feed.
149
+ */
150
+ @action
151
+ clearFeed() {
152
+ this.events = [];
153
+ }
154
+
155
+ @action
156
+ seedEvents(events = []) {
157
+ this.events = Array.isArray(events) ? events.slice(0, this.MAX_EVENTS).map((event) => ({ ...event, isNew: false })) : [];
158
+ }
159
+
160
+ /**
161
+ * Process an incoming geofence event from the WebSocket channel.
162
+ *
163
+ * @param {string} eventType
164
+ * @param {Object} data
165
+ */
166
+ #handleIncomingEvent(eventType, data) {
167
+ const normalised = this.normalizeEvent(eventType, data);
168
+
169
+ // Prepend and cap
170
+ this.events = [normalised, ...this.events].slice(0, this.MAX_EVENTS);
171
+
172
+ setTimeout(() => {
173
+ this.events = this.events.map((event) => (event.id === normalised.id ? { ...event, isNew: false } : event));
174
+ }, 3000);
175
+
176
+ debug(`[GeofenceEventBus] ${eventType} — driver: ${normalised.driverName}, geofence: ${normalised.geofenceName}`);
177
+
178
+ // Notify registered handlers
179
+ const handlers = this.#handlers.get(eventType) ?? [];
180
+ handlers.forEach((handler) => {
181
+ try {
182
+ handler(normalised);
183
+ } catch (e) {
184
+ debug(`[GeofenceEventBus] Handler error for ${eventType}: ${e.message}`);
185
+ }
186
+ });
187
+
188
+ // Also emit on the universe bus so any component can react
189
+ this.universe.trigger(`fleet-ops.geofence.${eventType.replace('geofence.', '')}`, normalised);
190
+ }
191
+
192
+ /**
193
+ * Normalise a raw WebSocket payload into a consistent display object.
194
+ *
195
+ * @param {string} eventType
196
+ * @param {Object} raw
197
+ * @returns {Object}
198
+ */
199
+ normalizeEvent(eventType, raw) {
200
+ const subject = raw.subject ?? null;
201
+ const subjectType = subject?.type ?? (raw.vehicle && !raw.driver ? 'vehicle' : 'driver');
202
+ const subjectName = subject?.name ?? raw.driver?.name ?? raw.vehicle?.name ?? raw.vehicle?.plate ?? 'Unknown Subject';
203
+ return {
204
+ id: raw.id ?? raw.uuid ?? `${Date.now()}-${Math.random()}`,
205
+ eventType,
206
+ occurredAt: raw.occurred_at ?? new Date().toISOString(),
207
+ subjectType,
208
+ subjectName,
209
+ driverName: raw.driver?.name ?? null,
210
+ driverUuid: raw.driver?.uuid ?? null,
211
+ vehicleName: raw.vehicle?.name ?? null,
212
+ vehiclePlate: raw.vehicle?.plate ?? null,
213
+ geofenceName: raw.geofence?.name ?? 'Unknown Geofence',
214
+ geofenceUuid: raw.geofence?.uuid ?? null,
215
+ geofenceType: raw.geofence?.type ?? 'zone',
216
+ orderPublicId: raw.order?.id ?? null,
217
+ latitude: raw.location?.latitude ?? null,
218
+ longitude: raw.location?.longitude ?? null,
219
+ dwellMinutes: raw.dwell_duration_minutes ?? null,
220
+ isNew: true,
221
+ };
222
+ }
223
+ }
@@ -1 +1 @@
1
- export { default } from '@fleetbase/fleetops-engine/components/modals/set-password';
1
+ export { default } from '@fleetbase/fleetops-engine/components/map/drawer/geofence-event-listing';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/services/geofence-event-bus';
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/fleetops-api",
3
- "version": "0.6.42",
3
+ "version": "0.6.44",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Fleet-Ops",
3
- "version": "0.6.42",
3
+ "version": "0.6.44",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "repository": "https://github.com/fleetbase/fleetops",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/fleetops-engine",
3
- "version": "0.6.42",
3
+ "version": "0.6.44",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "fleet-ops"
@@ -44,7 +44,7 @@
44
44
  "@babel/core": "^7.23.2",
45
45
  "@fleetbase/ember-core": "^0.3.18",
46
46
  "@fleetbase/ember-ui": "^0.3.26",
47
- "@fleetbase/fleetops-data": "^0.1.28",
47
+ "@fleetbase/fleetops-data": "^0.1.29",
48
48
  "@fleetbase/leaflet-routing-machine": "^3.2.17",
49
49
  "@fortawesome/ember-fontawesome": "^2.0.0",
50
50
  "@fortawesome/fontawesome-svg-core": "6.4.0",
@@ -0,0 +1,48 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration {
8
+ /**
9
+ * Run the migrations.
10
+ *
11
+ * Adds geofence trigger configuration columns to the zones table,
12
+ * enabling each zone to be configured as an active geofence with
13
+ * entry/exit/dwell event triggers.
14
+ */
15
+ public function up(): void
16
+ {
17
+ Schema::table('zones', function (Blueprint $table) {
18
+ // Whether to fire a GeofenceEntered event when a driver enters this zone
19
+ $table->boolean('trigger_on_entry')->default(true)->after('stroke_color');
20
+
21
+ // Whether to fire a GeofenceExited event when a driver exits this zone
22
+ $table->boolean('trigger_on_exit')->default(true)->after('trigger_on_entry');
23
+
24
+ // Minutes a driver must remain inside before a GeofenceDwelled event fires.
25
+ // NULL disables dwell tracking for this zone.
26
+ $table->unsignedSmallInteger('dwell_threshold_minutes')->nullable()->after('trigger_on_exit');
27
+
28
+ // Optional speed limit (km/h) enforced within this zone.
29
+ // NULL means no speed limit is configured.
30
+ $table->unsignedSmallInteger('speed_limit_kmh')->nullable()->after('dwell_threshold_minutes');
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Reverse the migrations.
36
+ */
37
+ public function down(): void
38
+ {
39
+ Schema::table('zones', function (Blueprint $table) {
40
+ $table->dropColumn([
41
+ 'trigger_on_entry',
42
+ 'trigger_on_exit',
43
+ 'dwell_threshold_minutes',
44
+ 'speed_limit_kmh',
45
+ ]);
46
+ });
47
+ }
48
+ };
@@ -0,0 +1,38 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration {
8
+ /**
9
+ * Run the migrations.
10
+ *
11
+ * Adds geofence trigger configuration columns to the service_areas table,
12
+ * enabling each service area to act as an active geofence boundary.
13
+ */
14
+ public function up(): void
15
+ {
16
+ Schema::table('service_areas', function (Blueprint $table) {
17
+ $table->boolean('trigger_on_entry')->default(true)->after('stroke_color');
18
+ $table->boolean('trigger_on_exit')->default(true)->after('trigger_on_entry');
19
+ $table->unsignedSmallInteger('dwell_threshold_minutes')->nullable()->after('trigger_on_exit');
20
+ $table->unsignedSmallInteger('speed_limit_kmh')->nullable()->after('dwell_threshold_minutes');
21
+ });
22
+ }
23
+
24
+ /**
25
+ * Reverse the migrations.
26
+ */
27
+ public function down(): void
28
+ {
29
+ Schema::table('service_areas', function (Blueprint $table) {
30
+ $table->dropColumn([
31
+ 'trigger_on_entry',
32
+ 'trigger_on_exit',
33
+ 'dwell_threshold_minutes',
34
+ 'speed_limit_kmh',
35
+ ]);
36
+ });
37
+ }
38
+ };
@@ -0,0 +1,62 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration {
8
+ /**
9
+ * Run the migrations.
10
+ *
11
+ * Creates the driver_geofence_states table which acts as the state machine
12
+ * store for the geofencing engine. It tracks whether each driver is currently
13
+ * inside each geofence, enabling the system to detect transitions (entry/exit)
14
+ * rather than just evaluating current position in isolation.
15
+ *
16
+ * One record exists per driver-geofence pair. Records are upserted on each
17
+ * location update where a crossing is detected.
18
+ */
19
+ public function up(): void
20
+ {
21
+ Schema::create('driver_geofence_states', function (Blueprint $table) {
22
+ $table->id();
23
+ $table->uuid('driver_uuid')->index();
24
+ $table->uuid('geofence_uuid')->index();
25
+
26
+ // Discriminator: 'zone' or 'service_area'
27
+ $table->string('geofence_type', 50)->default('zone');
28
+
29
+ // Whether the driver is currently inside this geofence
30
+ $table->boolean('is_inside')->default(false)->index();
31
+
32
+ // Timestamp of when the driver most recently entered this geofence
33
+ $table->timestamp('entered_at')->nullable();
34
+
35
+ // Timestamp of when the driver most recently exited this geofence
36
+ $table->timestamp('exited_at')->nullable();
37
+
38
+ // Queue job ID of the pending CheckGeofenceDwell job.
39
+ // Stored so it can be cancelled if the driver exits before the dwell threshold.
40
+ $table->string('dwell_job_id')->nullable();
41
+
42
+ $table->timestamps();
43
+
44
+ // Composite unique key: one state record per driver-geofence pair
45
+ $table->unique(['driver_uuid', 'geofence_uuid'], 'driver_geofence_unique');
46
+
47
+ // Cascade delete when the driver is deleted
48
+ $table->foreign('driver_uuid')
49
+ ->references('uuid')
50
+ ->on('drivers')
51
+ ->onDelete('cascade');
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Reverse the migrations.
57
+ */
58
+ public function down(): void
59
+ {
60
+ Schema::dropIfExists('driver_geofence_states');
61
+ }
62
+ };
@@ -0,0 +1,66 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration {
8
+ /**
9
+ * Run the migrations.
10
+ *
11
+ * Creates the geofence_events_log table which provides a persistent,
12
+ * queryable audit trail of all geofence events (entered, exited, dwelled).
13
+ * This table powers the reporting dashboard, dwell time analytics, and
14
+ * geofence activity history views.
15
+ */
16
+ public function up(): void
17
+ {
18
+ Schema::create('geofence_events_log', function (Blueprint $table) {
19
+ $table->id();
20
+ $table->uuid('uuid')->unique()->index();
21
+ $table->uuid('company_uuid')->index();
22
+ $table->uuid('driver_uuid')->index();
23
+ $table->uuid('vehicle_uuid')->nullable()->index();
24
+ $table->uuid('order_uuid')->nullable()->index();
25
+ $table->uuid('geofence_uuid')->index();
26
+
27
+ // Discriminator: 'zone' or 'service_area'
28
+ $table->string('geofence_type', 50)->default('zone');
29
+
30
+ // Snapshot of the geofence name at the time of the event
31
+ $table->string('geofence_name')->nullable();
32
+
33
+ // The type of geofence event
34
+ $table->enum('event_type', ['entered', 'exited', 'dwelled'])->index();
35
+
36
+ // Driver location at the time of the event
37
+ $table->decimal('latitude', 10, 7)->nullable();
38
+ $table->decimal('longitude', 10, 7)->nullable();
39
+
40
+ // Driver speed at the time of the event (km/h)
41
+ $table->decimal('speed_kmh', 8, 2)->nullable();
42
+
43
+ // Populated for 'exited' and 'dwelled' events
44
+ $table->unsignedInteger('dwell_duration_minutes')->nullable();
45
+
46
+ // The precise time the event occurred (not the DB insert time)
47
+ $table->timestamp('occurred_at')->index();
48
+
49
+ $table->timestamps();
50
+
51
+ // Composite indexes for common query patterns
52
+ $table->index(['company_uuid', 'occurred_at'], 'gel_company_occurred_idx');
53
+ $table->index(['geofence_uuid', 'occurred_at'], 'gel_geofence_occurred_idx');
54
+ $table->index(['driver_uuid', 'occurred_at'], 'gel_driver_occurred_idx');
55
+ $table->index(['order_uuid', 'event_type'], 'gel_order_event_idx');
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Reverse the migrations.
61
+ */
62
+ public function down(): void
63
+ {
64
+ Schema::dropIfExists('geofence_events_log');
65
+ }
66
+ };
@@ -1,7 +1,9 @@
1
1
  <?php
2
+
2
3
  use Illuminate\Database\Migrations\Migration;
3
4
  use Illuminate\Database\Schema\Blueprint;
4
5
  use Illuminate\Support\Facades\Schema;
6
+
5
7
  /**
6
8
  * Add Orchestrator constraint columns to the vehicles table.
7
9
  *
@@ -34,6 +36,7 @@ return new class extends Migration {
34
36
  $table->boolean('return_to_depot')->default(true)->after('time_window_end');
35
37
  });
36
38
  }
39
+
37
40
  public function down(): void
38
41
  {
39
42
  Schema::table('vehicles', function (Blueprint $table) {
@@ -1,7 +1,9 @@
1
1
  <?php
2
+
2
3
  use Illuminate\Database\Migrations\Migration;
3
4
  use Illuminate\Database\Schema\Blueprint;
4
5
  use Illuminate\Support\Facades\Schema;
6
+
5
7
  /**
6
8
  * Add Orchestrator capacity columns to the payloads table.
7
9
  *
@@ -24,6 +26,7 @@ return new class extends Migration {
24
26
  $table->unsignedInteger('capacity_parcels')->nullable()->after('capacity_pallets');
25
27
  });
26
28
  }
29
+
27
30
  public function down(): void
28
31
  {
29
32
  Schema::table('payloads', function (Blueprint $table) {
@@ -1,7 +1,9 @@
1
1
  <?php
2
+
2
3
  use Illuminate\Database\Migrations\Migration;
3
4
  use Illuminate\Database\Schema\Blueprint;
4
5
  use Illuminate\Support\Facades\Schema;
6
+
5
7
  /**
6
8
  * Drop redundant denormalised capacity columns from the payloads table.
7
9
  *
@@ -1,7 +1,9 @@
1
1
  <?php
2
+
2
3
  use Illuminate\Database\Migrations\Migration;
3
4
  use Illuminate\Database\Schema\Blueprint;
4
5
  use Illuminate\Support\Facades\Schema;
6
+
5
7
  /**
6
8
  * Correct orchestrator capacity column names on the vehicles table.
7
9
  *
@@ -0,0 +1,27 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration {
8
+ public function up(): void
9
+ {
10
+ if (!Schema::hasColumn('vehicles', 'internal_id')) {
11
+ Schema::table('vehicles', function (Blueprint $table) {
12
+ $table->string('internal_id')
13
+ ->nullable()
14
+ ->after('public_id');
15
+ });
16
+ }
17
+ }
18
+
19
+ public function down(): void
20
+ {
21
+ if (Schema::hasColumn('vehicles', 'internal_id')) {
22
+ Schema::table('vehicles', function (Blueprint $table) {
23
+ $table->dropColumn('internal_id');
24
+ });
25
+ }
26
+ }
27
+ };
@@ -0,0 +1,31 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration {
8
+ /**
9
+ * Run the migrations.
10
+ */
11
+ public function up(): void
12
+ {
13
+ Schema::table('geofence_events_log', function (Blueprint $table) {
14
+ $table->uuid('subject_uuid')->nullable()->after('order_uuid')->index();
15
+ $table->string('subject_type', 50)->default('driver')->after('subject_uuid')->index();
16
+ $table->string('subject_name')->nullable()->after('subject_type');
17
+ $table->uuid('driver_uuid')->nullable()->change();
18
+ });
19
+ }
20
+
21
+ /**
22
+ * Reverse the migrations.
23
+ */
24
+ public function down(): void
25
+ {
26
+ Schema::table('geofence_events_log', function (Blueprint $table) {
27
+ $table->dropColumn(['subject_uuid', 'subject_type', 'subject_name']);
28
+ $table->uuid('driver_uuid')->nullable(false)->change();
29
+ });
30
+ }
31
+ };
@@ -0,0 +1,39 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration {
8
+ /**
9
+ * Run the migrations.
10
+ */
11
+ public function up(): void
12
+ {
13
+ Schema::create('vehicle_geofence_states', function (Blueprint $table) {
14
+ $table->id();
15
+ $table->uuid('vehicle_uuid')->index();
16
+ $table->uuid('geofence_uuid')->index();
17
+ $table->string('geofence_type', 50)->default('zone');
18
+ $table->boolean('is_inside')->default(false)->index();
19
+ $table->timestamp('entered_at')->nullable();
20
+ $table->timestamp('exited_at')->nullable();
21
+ $table->string('dwell_job_id')->nullable();
22
+ $table->timestamps();
23
+
24
+ $table->unique(['vehicle_uuid', 'geofence_uuid'], 'vehicle_geofence_unique');
25
+ $table->foreign('vehicle_uuid')
26
+ ->references('uuid')
27
+ ->on('vehicles')
28
+ ->onDelete('cascade');
29
+ });
30
+ }
31
+
32
+ /**
33
+ * Reverse the migrations.
34
+ */
35
+ public function down(): void
36
+ {
37
+ Schema::dropIfExists('vehicle_geofence_states');
38
+ }
39
+ };