@fleetbase/fleetops-engine 0.6.12 → 0.6.13

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 (42) hide show
  1. package/addon/components/customer/orders.js +6 -8
  2. package/addon/components/layout/fleet-ops-sidebar.js +8 -0
  3. package/addon/components/live-map.js +6 -8
  4. package/addon/components/order-config/fields-editor.js +1 -1
  5. package/addon/components/route-optimization-engine-select-button.hbs +25 -0
  6. package/addon/components/route-optimization-engine-select-button.js +13 -0
  7. package/addon/controllers/operations/orders/index/new.js +52 -150
  8. package/addon/controllers/operations/orders/index/view.js +8 -9
  9. package/addon/controllers/settings/routing.js +47 -0
  10. package/addon/engine.js +27 -0
  11. package/addon/routes/application.js +8 -0
  12. package/addon/routes/operations/orders/index/view.js +12 -3
  13. package/addon/routes/settings/routing.js +3 -0
  14. package/addon/routes.js +1 -0
  15. package/addon/services/leaflet-router-control.js +64 -0
  16. package/addon/services/osrm.js +46 -0
  17. package/addon/services/route-optimization-interface.js +9 -0
  18. package/addon/services/route-optimization.js +67 -0
  19. package/addon/templates/operations/orders/index/new.hbs +21 -16
  20. package/addon/templates/settings/routing.hbs +32 -0
  21. package/app/components/route-optimization-engine-select-button.js +1 -0
  22. package/app/controllers/settings/routing.js +1 -0
  23. package/app/routes/settings/routing.js +1 -0
  24. package/app/services/leaflet-router-control.js +1 -0
  25. package/app/services/osrm.js +1 -0
  26. package/app/services/route-optimization-interface.js +1 -0
  27. package/app/services/route-optimization.js +1 -0
  28. package/app/templates/settings/routing.js +1 -0
  29. package/composer.json +1 -1
  30. package/extension.json +1 -1
  31. package/package.json +2 -2
  32. package/server/config/api.php +10 -1
  33. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  34. package/server/src/Events/EntityActivityChanged.php +118 -0
  35. package/server/src/Events/EntityCompleted.php +118 -0
  36. package/server/src/Http/Controllers/Internal/v1/OrderController.php +1 -1
  37. package/server/src/Http/Controllers/Internal/v1/SettingController.php +30 -0
  38. package/server/src/Models/Order.php +6 -0
  39. package/server/src/Models/Payload.php +19 -8
  40. package/server/src/Models/Place.php +49 -13
  41. package/server/src/routes.php +2 -0
  42. package/translations/en-us.yaml +5 -0
@@ -0,0 +1,64 @@
1
+ import Service, { inject as service } from '@ember/service';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { underscore } from '@ember/string';
4
+
5
+ export class RouterControlRegistry {
6
+ @tracked routers = {};
7
+ }
8
+
9
+ export class RouterControl {
10
+ @tracked name;
11
+ @tracked router;
12
+ @tracked formatter;
13
+
14
+ constructor(init) {
15
+ const { name, router, formatter } = typeof init === 'function' ? init() : init;
16
+ this.name = name;
17
+ this.router = router;
18
+ this.formatter = formatter;
19
+ }
20
+ }
21
+
22
+ export default class LeafletRouterControlService extends Service {
23
+ @service universe;
24
+ registry = this.#initializeRegistry();
25
+
26
+ get availableEngines() {
27
+ return Object.keys(this.registry.routers).map(underscore);
28
+ }
29
+
30
+ get availableServices() {
31
+ return Object.entries(this.registry.routers).map(([key, control]) => ({
32
+ key,
33
+ name: control.name ?? key,
34
+ }));
35
+ }
36
+
37
+ register(name, getter) {
38
+ this.registry.routers = {
39
+ ...this.registry.routers,
40
+ [underscore(name)]: getter,
41
+ };
42
+ }
43
+
44
+ /* eslint-disable no-unused-vars */
45
+ unregister(name) {
46
+ let key = underscore(name);
47
+ let { [key]: _, ...rest } = this.registry.routers;
48
+ this.registry.routers = rest;
49
+ }
50
+
51
+ get(name) {
52
+ return this.registry.routers[underscore(name)];
53
+ }
54
+
55
+ #initializeRegistry() {
56
+ const registry = 'registry:router-controls';
57
+ const application = this.universe.getApplicationInstance();
58
+ if (!application.hasRegistration(registry)) {
59
+ application.register(registry, new RouterControlRegistry(), { instantiate: false });
60
+ }
61
+
62
+ return application.resolveRegistration(registry);
63
+ }
64
+ }
@@ -0,0 +1,46 @@
1
+ import RouteOptimizationInterfaceService from './route-optimization-interface';
2
+ import getRoutingHost from '@fleetbase/ember-core/utils/get-routing-host';
3
+ import polyline from '@fleetbase/ember-core/utils/polyline';
4
+ import { debug } from '@ember/debug';
5
+
6
+ export default class OsrmService extends RouteOptimizationInterfaceService {
7
+ name = 'OSRM';
8
+
9
+ async optimize({ order, payload, waypoints, coordinates: originalCoords }, options = {}) {
10
+ const driverAssigned = order.driver_assigned;
11
+ const driverPosition = driverAssigned?.location?.coordinates; // [lon,lat] | undefined
12
+ const coordinates = driverPosition ? [driverPosition, ...originalCoords] : [...originalCoords];
13
+ const hasDriverStart = Boolean(driverPosition);
14
+ const source = 'first';
15
+ const destination = 'any';
16
+ const roundtrip = false; // don’t loop back
17
+ const routingHost = getRoutingHost(payload, waypoints);
18
+
19
+ try {
20
+ const result = await this.fetch.routing(coordinates, { source, destination, roundtrip, annotations: true }, { host: routingHost, ...options });
21
+
22
+ // Pair each OSRM waypoint with its Waypoint model
23
+ const modelsByInputIndex = hasDriverStart ? [null, ...waypoints] : waypoints;
24
+ const pairs = result.waypoints.map((wp, idx) => ({
25
+ model: modelsByInputIndex[idx], // Ember model or null (driver)
26
+ wp,
27
+ }));
28
+
29
+ // Drop the driver start if present
30
+ const payloadPairs = hasDriverStart ? pairs.slice(1) : pairs;
31
+
32
+ // Sort by the optimised order
33
+ payloadPairs.sort((a, b) => a.wp.waypoint_index - b.wp.waypoint_index);
34
+
35
+ // Extract the Ember models (null-safe)
36
+ const sortedWaypoints = payloadPairs.map((p) => p.model).filter(Boolean);
37
+ const trip = result.trips?.[0];
38
+ const route = polyline.decode(trip.geometry);
39
+
40
+ return { sortedWaypoints, trip, route, result };
41
+ } catch (err) {
42
+ debug(`[OSRM] Error routing trip : ${err.message}`);
43
+ throw err;
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,9 @@
1
+ import Service, { inject as service } from '@ember/service';
2
+
3
+ export default class RouteOptimizationInterfaceService extends Service {
4
+ @service fetch;
5
+
6
+ optimize() {
7
+ throw new Error(`${this.constructor.name} must implement optimize(params, options)`);
8
+ }
9
+ }
@@ -0,0 +1,67 @@
1
+ import Service, { inject as service } from '@ember/service';
2
+ import { tracked } from '@glimmer/tracking';
3
+ import { underscore } from '@ember/string';
4
+
5
+ export class RouteOptimizationRegistry {
6
+ @tracked engines = {};
7
+ }
8
+
9
+ export default class RouteOptimizationService extends Service {
10
+ @service universe;
11
+ registry = this.#initializeRegistry();
12
+
13
+ get availableEngines() {
14
+ return Object.keys(this.registry.engines).map(underscore);
15
+ }
16
+
17
+ get availableServices() {
18
+ return Object.entries(this.registry.engines).map(([key, engine]) => ({
19
+ key,
20
+ name: engine.name ?? key,
21
+ }));
22
+ }
23
+
24
+ register(name, engine) {
25
+ if (typeof engine.optimize !== 'function') {
26
+ throw new Error(`Cannot register "${name}": missing optimize()`);
27
+ }
28
+
29
+ this.registry.engines = {
30
+ ...this.registry.engines,
31
+ [underscore(name)]: engine,
32
+ };
33
+ }
34
+
35
+ /* eslint-disable no-unused-vars */
36
+ unregister(name) {
37
+ let key = underscore(name);
38
+ let { [key]: _, ...rest } = this.registry.engines;
39
+ this.registry.engines = rest;
40
+ }
41
+
42
+ optimize(name, params = {}, options = {}) {
43
+ let engine = this.registry.engines[underscore(name)];
44
+ if (!engine) {
45
+ return Promise.reject(new Error(`No route optimization engine registered as "${name}"`));
46
+ }
47
+ return engine.optimize(params, options);
48
+ }
49
+
50
+ handler(name, context, data) {
51
+ let engine = this.registry.engines[underscore(name)];
52
+ if (!engine) {
53
+ throw new Error(`No route optimization engine registered as "${name}"`);
54
+ }
55
+ return engine.handler(context, data);
56
+ }
57
+
58
+ #initializeRegistry() {
59
+ const registry = 'registry:route-optimization-engines';
60
+ const application = this.universe.getApplicationInstance();
61
+ if (!application.hasRegistration(registry)) {
62
+ application.register(registry, new RouteOptimizationRegistry(), { instantiate: false });
63
+ }
64
+
65
+ return application.resolveRegistration(registry);
66
+ }
67
+ }
@@ -315,17 +315,21 @@
315
315
  </div>
316
316
  <div class="flex flex-1 justify-end space-x-2">
317
317
  {{#if this.isMultipleDropoffOrder}}
318
- <Button
319
- @type="magic"
320
- @icon="magic"
321
- @text={{t "fleet-ops.operations.orders.index.new.optimize-route"}}
322
- @size="sm"
323
- @onClick={{perform this.optimizeRoute}}
324
- @permission="fleet-ops optimize order"
325
- @helpText={{t "fleet-ops.operations.orders.index.new.optimize-route-help-text"}}
326
- @disabled={{lt this.waypoints.length 3}}
327
- @isLoading={{this.isOptimizingRoute}}
328
- />
318
+ {{#if this.hasRouteOptimizationEngines}}
319
+ <RouteOptimizationEngineSelectButton @onClick={{perform this.optimizeRouteWithService}} @isLoading={{this.optimizeRouteWithService.isRunning}} />
320
+ {{else}}
321
+ <Button
322
+ @type="magic"
323
+ @icon="magic"
324
+ @text={{t "fleet-ops.operations.orders.index.new.optimize-route"}}
325
+ @size="sm"
326
+ @onClick={{perform this.optimizeRoute}}
327
+ @permission="fleet-ops optimize order"
328
+ @helpText={{t "fleet-ops.operations.orders.index.new.optimize-route-help-text"}}
329
+ @disabled={{lt this.waypoints.length 3}}
330
+ @isLoading={{this.optimizeRoute.isRunning}}
331
+ />
332
+ {{/if}}
329
333
  <Button
330
334
  @icon="map-marked-alt"
331
335
  @text={{t "fleet-ops.operations.orders.index.new.add-waypoint"}}
@@ -630,11 +634,9 @@
630
634
  </div>
631
635
  </DropdownButton>
632
636
  {{/if}}
633
- {{#if this.renderableEntityInputComponents}}
634
- {{#each this.renderableEntityInputComponents as |renderableEntityInputComponent|}}
635
- {{component renderableEntityInputComponent order=this.order controller=this}}
636
- {{/each}}
637
- {{/if}}
637
+ <RegistryYield @registry="fleet-ops:template:operations:orders:new:entities-input" as |RegistryComponent|>
638
+ <RegistryComponent @order={{this.order}} @controller={{this}} />
639
+ </RegistryYield>
638
640
  </div>
639
641
  {{/if}}
640
642
  {{#if this.isCsvImportedOrder}}
@@ -709,6 +711,9 @@
709
711
  <div>
710
712
  <Input @value={{entity.sku}} @type="text" class="w-full form-input form-input-sm" placeholder={{t "fleet-ops.operations.orders.index.new.sku"}} />
711
713
  </div>
714
+ <RegistryYield @registry="fleet-ops:template:operations:orders:new:entities-input:entity" as |RegistryComponent|>
715
+ <RegistryComponent @entity={{entity}} @order={{this.order}} @controller={{this}} />
716
+ </RegistryYield>
712
717
  {{#if this.waypoints.length}}
713
718
  <Select
714
719
  @value={{entity.destination_uuid}}
@@ -0,0 +1,32 @@
1
+ <Layout::Section::Header @title={{t "fleet-ops.settings.routing.fleet-ops-routing-settings"}}>
2
+ <Button
3
+ @type="primary"
4
+ @size="sm"
5
+ @icon="save"
6
+ @text={{t "common.save-button-text"}}
7
+ @onClick={{perform this.saveSettings}}
8
+ @disabled={{this.saveSettings.isRunning}}
9
+ @isLoading={{or this.saveSettings.isRunning this.getSettings.isRunning}}
10
+ />
11
+ </Layout::Section::Header>
12
+
13
+ <Layout::Section::Body class="overflow-y-scroll h-full">
14
+ <div class="container mx-auto h-screen">
15
+ <div class="max-w-3xl my-10 mx-auto space-y-6">
16
+ <ContentPanel @title={{t "fleet-ops.settings.routing.configure-routing"}} @open={{true}} @pad={{true}} @panelBodyClass="bg-white dark:bg-gray-800">
17
+ <InputGroup @name="Routing Service" @helpText="Select the service which is responsible for calculating and plotting routes on the map.">
18
+ <Select
19
+ @value={{this.routerService}}
20
+ @options={{this.leafletRouterControl.availableServices}}
21
+ @optionLabel="name"
22
+ @optionValue="key"
23
+ @onSelect={{fn (mut this.routerService)}}
24
+ @placeholder="Select routing service..."
25
+ class="w-full"
26
+ />
27
+ </InputGroup>
28
+ </ContentPanel>
29
+ </div>
30
+ </div>
31
+ <Spacer @height="600px" />
32
+ </Layout::Section::Body>
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/components/route-optimization-engine-select-button';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/controllers/settings/routing';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/routes/settings/routing';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/services/leaflet-router-control';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/services/osrm';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/services/route-optimization-interface';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/services/route-optimization';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/fleetops-engine/templates/settings/routing';
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/fleetops-api",
3
- "version": "0.6.12",
3
+ "version": "0.6.13",
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.12",
3
+ "version": "0.6.13",
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.12",
3
+ "version": "0.6.13",
4
4
  "description": "Fleet & Transport Management Extension for Fleetbase",
5
5
  "fleetbase": {
6
6
  "route": "fleet-ops"
@@ -45,7 +45,7 @@
45
45
  "@fleetbase/ember-core": "latest",
46
46
  "@fleetbase/ember-ui": "latest",
47
47
  "@fleetbase/fleetops-data": "latest",
48
- "@fleetbase/leaflet-routing-machine": "^3.2.16",
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",
51
51
  "@fortawesome/free-brands-svg-icons": "6.4.0",
@@ -10,14 +10,15 @@ return [
10
10
  'events' => [
11
11
  // order events
12
12
  'order.created',
13
+ 'order.ready',
13
14
  'order.updated',
14
15
  'order.deleted',
15
16
  'order.dispatched',
16
17
  'order.dispatch_failed',
17
- 'order.completed',
18
18
  'order.failed',
19
19
  'order.driver_assigned',
20
20
  'order.completed',
21
+ 'order.canceled',
21
22
 
22
23
  // payload events
23
24
  'payload.created',
@@ -29,12 +30,19 @@ return [
29
30
  'entity.updated',
30
31
  'entity.deleted',
31
32
  'entity.driver_assigned',
33
+ 'entity.activity',
34
+ 'entity.completed',
35
+
36
+ // waypoint events
37
+ 'waypoint.activity',
38
+ 'waypoint.completed',
32
39
 
33
40
  // driver events
34
41
  'driver.created',
35
42
  'driver.updated',
36
43
  'driver.deleted',
37
44
  'driver.assigned',
45
+ 'driver.location_changed',
38
46
  // 'driver.entered_zone',
39
47
  // 'driver.exited_zone',
40
48
 
@@ -87,6 +95,7 @@ return [
87
95
  'vehicle.created',
88
96
  'vehicle.updated',
89
97
  'vehicle.deleted',
98
+ 'vehicle.location_changed',
90
99
 
91
100
  // vendor events
92
101
  'vendor.created',
@@ -120,6 +120,11 @@ class FleetOps
120
120
  'action' => [],
121
121
  'remove_actions' => ['export', 'create'],
122
122
  ],
123
+ [
124
+ 'name' => 'routing-settings',
125
+ 'action' => [],
126
+ 'remove_actions' => ['export', 'create'],
127
+ ],
123
128
  ];
124
129
 
125
130
  /**
@@ -0,0 +1,118 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Events;
4
+
5
+ use Fleetbase\FleetOps\Flow\Activity;
6
+ use Fleetbase\FleetOps\Models\Entity;
7
+ use Fleetbase\FleetOps\Models\Order;
8
+ use Illuminate\Broadcasting\Channel;
9
+ use Illuminate\Broadcasting\InteractsWithSockets;
10
+ use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
11
+ use Illuminate\Foundation\Events\Dispatchable;
12
+ use Illuminate\Queue\SerializesModels;
13
+ use Illuminate\Support\Carbon;
14
+
15
+ class EntityActivityChanged implements ShouldBroadcast
16
+ {
17
+ use Dispatchable;
18
+ use InteractsWithSockets;
19
+ use SerializesModels;
20
+
21
+ /**
22
+ * The entity which has new activity.
23
+ */
24
+ public Entity $entity;
25
+
26
+ /**
27
+ * The activity which triggered the waypoint completed.
28
+ */
29
+ public Activity $activity;
30
+
31
+ /**
32
+ * The event id.
33
+ *
34
+ * @var string
35
+ */
36
+ public $eventId;
37
+
38
+ /**
39
+ * The datetime instance the broadcast ws triggered.
40
+ *
41
+ * @var string
42
+ */
43
+ public $sentAt;
44
+
45
+ /**
46
+ * Create a new event instance.
47
+ *
48
+ * @return void
49
+ */
50
+ public function __construct(Entity $entity, Activity $activity)
51
+ {
52
+ $this->entity = $entity;
53
+ $this->activity = $activity;
54
+ $this->eventId = uniqid('event_');
55
+ $this->sentAt = Carbon::now()->toDateTimeString();
56
+ }
57
+
58
+ /**
59
+ * Get the channels the event should broadcast on.
60
+ *
61
+ * @return Channel|array
62
+ */
63
+ public function broadcastOn()
64
+ {
65
+ $channels = [
66
+ new Channel('api.' . session('api_credential')),
67
+ new Channel('entity.' . $this->entity->public_id),
68
+ new Channel('entity.' . $this->entity->uuid),
69
+ ];
70
+
71
+ $order = $this->getModelRecord();
72
+ if ($order) {
73
+ $channels[] = new Channel('company.' . session('company', data_get($order, 'company.uuid')));
74
+ $channels[] = new Channel('company.' . data_get($order, 'company.public_id'));
75
+ $channels[] = new Channel('order.' . $order->uuid);
76
+ $channels[] = new Channel('order.' . $order->public_id);
77
+ }
78
+
79
+ return $channels;
80
+ }
81
+
82
+ /**
83
+ * The event's broadcast name.
84
+ *
85
+ * @return string
86
+ */
87
+ public function broadcastAs()
88
+ {
89
+ return 'entity.activity';
90
+ }
91
+
92
+ /**
93
+ * Get the data to broadcast.
94
+ *
95
+ * @return array
96
+ */
97
+ public function broadcastWith()
98
+ {
99
+ return [
100
+ 'id' => $this->eventId,
101
+ 'api_version' => config('api.version'),
102
+ 'event' => $this->broadcastAs(),
103
+ 'created_at' => $this->sentAt,
104
+ 'data' => [
105
+ 'entity' => $this->entity->public_id,
106
+ 'activity' => $this->activity->toArray(),
107
+ ],
108
+ ];
109
+ }
110
+
111
+ /**
112
+ * Get the assosciated order model record for this waypoint.
113
+ */
114
+ public function getModelRecord(): ?Order
115
+ {
116
+ return Order::where('payload_uuid', $this->entity->payload_uuid)->first();
117
+ }
118
+ }
@@ -0,0 +1,118 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Events;
4
+
5
+ use Fleetbase\FleetOps\Flow\Activity;
6
+ use Fleetbase\FleetOps\Models\Entity;
7
+ use Fleetbase\FleetOps\Models\Order;
8
+ use Illuminate\Broadcasting\Channel;
9
+ use Illuminate\Broadcasting\InteractsWithSockets;
10
+ use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
11
+ use Illuminate\Foundation\Events\Dispatchable;
12
+ use Illuminate\Queue\SerializesModels;
13
+ use Illuminate\Support\Carbon;
14
+
15
+ class EntityCompleted implements ShouldBroadcast
16
+ {
17
+ use Dispatchable;
18
+ use InteractsWithSockets;
19
+ use SerializesModels;
20
+
21
+ /**
22
+ * The entity which is completed.
23
+ */
24
+ public Entity $entity;
25
+
26
+ /**
27
+ * The activity which triggered the waypoint completed.
28
+ */
29
+ public Activity $activity;
30
+
31
+ /**
32
+ * The event id.
33
+ *
34
+ * @var string
35
+ */
36
+ public $eventId;
37
+
38
+ /**
39
+ * The datetime instance the broadcast ws triggered.
40
+ *
41
+ * @var string
42
+ */
43
+ public $sentAt;
44
+
45
+ /**
46
+ * Create a new event instance.
47
+ *
48
+ * @return void
49
+ */
50
+ public function __construct(Entity $entity, Activity $activity)
51
+ {
52
+ $this->entity = $entity;
53
+ $this->activity = $activity;
54
+ $this->eventId = uniqid('event_');
55
+ $this->sentAt = Carbon::now()->toDateTimeString();
56
+ }
57
+
58
+ /**
59
+ * Get the channels the event should broadcast on.
60
+ *
61
+ * @return Channel|array
62
+ */
63
+ public function broadcastOn()
64
+ {
65
+ $channels = [
66
+ new Channel('api.' . session('api_credential')),
67
+ new Channel('entity.' . $this->entity->public_id),
68
+ new Channel('entity.' . $this->entity->uuid),
69
+ ];
70
+
71
+ $order = $this->getModelRecord();
72
+ if ($order) {
73
+ $channels[] = new Channel('company.' . session('company', data_get($order, 'company.uuid')));
74
+ $channels[] = new Channel('company.' . data_get($order, 'company.public_id'));
75
+ $channels[] = new Channel('order.' . $order->uuid);
76
+ $channels[] = new Channel('order.' . $order->public_id);
77
+ }
78
+
79
+ return $channels;
80
+ }
81
+
82
+ /**
83
+ * The event's broadcast name.
84
+ *
85
+ * @return string
86
+ */
87
+ public function broadcastAs()
88
+ {
89
+ return 'entity.completed';
90
+ }
91
+
92
+ /**
93
+ * Get the data to broadcast.
94
+ *
95
+ * @return array
96
+ */
97
+ public function broadcastWith()
98
+ {
99
+ return [
100
+ 'id' => $this->eventId,
101
+ 'api_version' => config('api.version'),
102
+ 'event' => $this->broadcastAs(),
103
+ 'created_at' => $this->sentAt,
104
+ 'data' => [
105
+ 'entity' => $this->entity->public_id,
106
+ 'activity' => $this->activity->toArray(),
107
+ ],
108
+ ];
109
+ }
110
+
111
+ /**
112
+ * Get the assosciated order model record for this waypoint.
113
+ */
114
+ public function getModelRecord(): ?Order
115
+ {
116
+ return Order::where('payload_uuid', $this->entity->payload_uuid)->first();
117
+ }
118
+ }
@@ -354,7 +354,7 @@ class OrderController extends FleetOpsController
354
354
  */
355
355
  public function bulkCancel(BulkActionRequest $request)
356
356
  {
357
- /** @var Order */
357
+ /** @var \Illuminate\Database\Eloquent\Collection $orders */
358
358
  $orders = Order::whereIn('uuid', $request->input('ids'))->get();
359
359
 
360
360
  $count = $orders->count();