@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.
- package/addon/components/customer/orders.js +6 -8
- package/addon/components/layout/fleet-ops-sidebar.js +8 -0
- package/addon/components/live-map.js +6 -8
- package/addon/components/order-config/fields-editor.js +1 -1
- package/addon/components/route-optimization-engine-select-button.hbs +25 -0
- package/addon/components/route-optimization-engine-select-button.js +13 -0
- package/addon/controllers/operations/orders/index/new.js +52 -150
- package/addon/controllers/operations/orders/index/view.js +8 -9
- package/addon/controllers/settings/routing.js +47 -0
- package/addon/engine.js +27 -0
- package/addon/routes/application.js +8 -0
- package/addon/routes/operations/orders/index/view.js +12 -3
- package/addon/routes/settings/routing.js +3 -0
- package/addon/routes.js +1 -0
- package/addon/services/leaflet-router-control.js +64 -0
- package/addon/services/osrm.js +46 -0
- package/addon/services/route-optimization-interface.js +9 -0
- package/addon/services/route-optimization.js +67 -0
- package/addon/templates/operations/orders/index/new.hbs +21 -16
- package/addon/templates/settings/routing.hbs +32 -0
- package/app/components/route-optimization-engine-select-button.js +1 -0
- package/app/controllers/settings/routing.js +1 -0
- package/app/routes/settings/routing.js +1 -0
- package/app/services/leaflet-router-control.js +1 -0
- package/app/services/osrm.js +1 -0
- package/app/services/route-optimization-interface.js +1 -0
- package/app/services/route-optimization.js +1 -0
- package/app/templates/settings/routing.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +2 -2
- package/server/config/api.php +10 -1
- package/server/src/Auth/Schemas/FleetOps.php +5 -0
- package/server/src/Events/EntityActivityChanged.php +118 -0
- package/server/src/Events/EntityCompleted.php +118 -0
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +30 -0
- package/server/src/Models/Order.php +6 -0
- package/server/src/Models/Payload.php +19 -8
- package/server/src/Models/Place.php +49 -13
- package/server/src/routes.php +2 -0
- 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
|
-
|
|
319
|
-
@
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
634
|
-
{{
|
|
635
|
-
|
|
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
package/extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetbase/fleetops-engine",
|
|
3
|
-
"version": "0.6.
|
|
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.
|
|
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",
|
package/server/config/api.php
CHANGED
|
@@ -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',
|
|
@@ -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
|
|
357
|
+
/** @var \Illuminate\Database\Eloquent\Collection $orders */
|
|
358
358
|
$orders = Order::whereIn('uuid', $request->input('ids'))->get();
|
|
359
359
|
|
|
360
360
|
$count = $orders->count();
|