@fleetbase/fleetops-engine 0.6.11 → 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 (48) 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/leaflet-tracking-marker.js +19 -0
  4. package/addon/components/live-map.hbs +5 -0
  5. package/addon/components/live-map.js +6 -8
  6. package/addon/components/order-config/fields-editor.js +1 -1
  7. package/addon/components/route-optimization-engine-select-button.hbs +25 -0
  8. package/addon/components/route-optimization-engine-select-button.js +13 -0
  9. package/addon/controllers/operations/orders/index/new.js +78 -171
  10. package/addon/controllers/operations/orders/index/view.js +30 -29
  11. package/addon/controllers/settings/routing.js +47 -0
  12. package/addon/engine.js +27 -0
  13. package/addon/routes/application.js +8 -0
  14. package/addon/routes/operations/orders/index/view.js +12 -3
  15. package/addon/routes/settings/routing.js +3 -0
  16. package/addon/routes.js +1 -0
  17. package/addon/services/leaflet-router-control.js +64 -0
  18. package/addon/services/osrm.js +46 -0
  19. package/addon/services/route-optimization-interface.js +9 -0
  20. package/addon/services/route-optimization.js +67 -0
  21. package/addon/templates/operations/orders/index/new.hbs +21 -16
  22. package/addon/templates/settings/routing.hbs +32 -0
  23. package/app/components/route-optimization-engine-select-button.js +1 -0
  24. package/app/controllers/settings/routing.js +1 -0
  25. package/app/routes/settings/routing.js +1 -0
  26. package/app/services/leaflet-router-control.js +1 -0
  27. package/app/services/osrm.js +1 -0
  28. package/app/services/route-optimization-interface.js +1 -0
  29. package/app/services/route-optimization.js +1 -0
  30. package/app/templates/settings/routing.js +1 -0
  31. package/composer.json +1 -1
  32. package/extension.json +1 -1
  33. package/package.json +2 -2
  34. package/server/config/api.php +10 -1
  35. package/server/src/Auth/Schemas/FleetOps.php +5 -0
  36. package/server/src/Console/Commands/DebugOrderTracker.php +5 -5
  37. package/server/src/Events/EntityActivityChanged.php +118 -0
  38. package/server/src/Events/EntityCompleted.php +118 -0
  39. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +3 -1
  40. package/server/src/Http/Controllers/Internal/v1/OrderController.php +16 -13
  41. package/server/src/Http/Controllers/Internal/v1/SettingController.php +31 -1
  42. package/server/src/Http/Resources/v1/Waypoint.php +1 -1
  43. package/server/src/Models/Order.php +44 -8
  44. package/server/src/Models/Payload.php +29 -10
  45. package/server/src/Models/Place.php +49 -13
  46. package/server/src/Support/Geocoding.php +0 -2
  47. package/server/src/routes.php +2 -0
  48. package/translations/en-us.yaml +5 -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
  /**
@@ -29,11 +29,11 @@ class DebugOrderTracker extends Command
29
29
  */
30
30
  public function handle()
31
31
  {
32
- $order = Order::where('public_id', 'order_n227274')->first();
33
- if ($order) {
34
- $tracker = new OrderTracker($order);
35
- dd($tracker->getOrderProgressPercentage());
36
- }
32
+ // $order = Order::where('public_id', 'order_n227274')->first();
33
+ // if ($order) {
34
+ // $tracker = new OrderTracker($order);
35
+ // dd($tracker->getOrderProgressPercentage());
36
+ // }
37
37
 
38
38
  return Command::SUCCESS;
39
39
  }
@@ -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
+ }
@@ -66,7 +66,9 @@ class ServiceAreaController extends Controller
66
66
  try {
67
67
  $serviceArea = ServiceArea::create($input);
68
68
  } catch (\Throwable $e) {
69
- dd($e->getMessage());
69
+ logger()->error('Unable to create service area.', ['error' => $e->getMessage()]);
70
+
71
+ return response()->apiError('Failed to create service area.');
70
72
  }
71
73
 
72
74
  // response the driver resource
@@ -167,25 +167,28 @@ class OrderController extends FleetOpsController
167
167
  );
168
168
  }
169
169
 
170
- // notify driver if assigned
171
- $order->notifyDriverAssigned();
170
+ // Run background processes on queue
171
+ dispatch(function () use ($order, $serviceQuote): void {
172
+ // notify driver if assigned
173
+ $order->notifyDriverAssigned();
172
174
 
173
- // set driving distance and time
174
- $order->setPreliminaryDistanceAndTime();
175
+ // set driving distance and time
176
+ $order->setPreliminaryDistanceAndTime();
175
177
 
176
- // if service quote attached purchase
177
- $order->purchaseServiceQuote($serviceQuote);
178
+ // if service quote attached purchase
179
+ $order->purchaseServiceQuote($serviceQuote);
178
180
 
179
- // dispatch if flagged true
180
- $order->firstDispatchWithActivity();
181
+ // dispatch if flagged true
182
+ $order->firstDispatchWithActivity();
181
183
 
182
- // load tracking number
183
- $order->load(['trackingNumber']);
184
+ // Trigger order created event
185
+ event(new OrderReady($order));
186
+ })->afterCommit();
184
187
  }
185
188
  );
186
189
 
187
- // Trigger order created event
188
- event(new OrderReady($record));
190
+ // Reload payload and tracking number
191
+ $record->load(['payload', 'trackingNumber']);
189
192
 
190
193
  return ['order' => new $this->resource($record)];
191
194
  } catch (\Exception $e) {
@@ -351,7 +354,7 @@ class OrderController extends FleetOpsController
351
354
  */
352
355
  public function bulkCancel(BulkActionRequest $request)
353
356
  {
354
- /** @var Order */
357
+ /** @var \Illuminate\Database\Eloquent\Collection $orders */
355
358
  $orders = Order::whereIn('uuid', $request->input('ids'))->get();
356
359
 
357
360
  $count = $orders->count();
@@ -150,7 +150,7 @@ class SettingController extends Controller
150
150
  if (!is_array($notificationSettings)) {
151
151
  throw new \Exception('Invalid notification settings data.');
152
152
  }
153
- $currentNotificationSettings = Setting::lookupCompany('notification_settings');
153
+ $currentNotificationSettings = Setting::lookupCompany('notification_settings', []);
154
154
  Setting::configureCompany('notification_settings', array_merge($currentNotificationSettings, $notificationSettings));
155
155
 
156
156
  return response()->json([
@@ -174,4 +174,34 @@ class SettingController extends Controller
174
174
  'notificationSettings' => $notificationSettings,
175
175
  ]);
176
176
  }
177
+
178
+ /**
179
+ * Save routing settings.
180
+ *
181
+ * @param Request $request the HTTP request object containing the routing settings data
182
+ *
183
+ * @return \Illuminate\Http\JsonResponse a JSON response
184
+ */
185
+ public function saveRoutingSettings(Request $request)
186
+ {
187
+ $router = $request->input('router');
188
+ Setting::configureCompany('routing', ['router' => $router]);
189
+
190
+ return response()->json([
191
+ 'status' => 'ok',
192
+ 'message' => 'Routing settings succesfully saved.',
193
+ ]);
194
+ }
195
+
196
+ /**
197
+ * Retrieve and return the routing settings.
198
+ *
199
+ * @return \Illuminate\Http\JsonResponse
200
+ */
201
+ public function getRoutingSettings()
202
+ {
203
+ $routingSettings = Setting::lookupCompany('routing', ['router' => 'osrm']);
204
+
205
+ return response()->json($routingSettings);
206
+ }
177
207
  }
@@ -58,7 +58,7 @@ class Waypoint extends FleetbaseResource
58
58
  'owner' => $this->when(!Http::isInternalRequest(), Resolve::resourceForMorph($this->owner_type, $this->owner_uuid)),
59
59
  'tracking_number' => $this->whenLoaded('trackingNumber', $waypoint->trackingNumber),
60
60
  'customer' => $this->setCustomerType(Resolve::resourceForMorph($waypoint->customer_type, $waypoint->customer_uuid)),
61
- 'type' => $this->type,
61
+ 'type' => $waypoint->type ?? $this->type,
62
62
  'meta' => data_get($this, 'meta', Utils::createObject()),
63
63
  'eta' => $this->eta,
64
64
  'updated_at' => $this->updated_at,
@@ -1173,6 +1173,12 @@ class Order extends Model
1173
1173
  {
1174
1174
  $this->status = 'canceled';
1175
1175
 
1176
+ // if saving update the status and add the cancel activity
1177
+ $this->loadMissing('orderConfig');
1178
+ $canceledActivity = $this->orderConfig->getCanceledActivity();
1179
+ $this->updateActivity($canceledActivity);
1180
+
1181
+ // trigger integrated vendor cancelation
1176
1182
  if ($this->isIntegratedVendorOrder()) {
1177
1183
  $api = $this->facilitator->api();
1178
1184
 
@@ -1395,8 +1401,6 @@ class Order extends Model
1395
1401
  $activity = $flow->firstWhere('code', $code);
1396
1402
  }
1397
1403
 
1398
- // dd($activity);
1399
-
1400
1404
  if (!Utils::isActivity($activity)) {
1401
1405
  return false;
1402
1406
  }
@@ -1760,23 +1764,55 @@ class Order extends Model
1760
1764
  */
1761
1765
  public function resolveDynamicProperty(string $property)
1762
1766
  {
1763
- $snakedProperty = Str::snake($property);
1767
+ // Special payload shortcuts
1768
+ $root = Str::before($property, '.'); // e.g. "pickup" in "pickup.address"
1769
+ $payloadMap = [
1770
+ 'pickup' => 'payload.pickup',
1771
+ 'dropoff' => 'payload.dropoff',
1772
+ 'waypoint' => 'payload.currentWaypointMarker',
1773
+ 'currentWaypoint' => 'payload.currentWaypointMarker',
1774
+ 'currentWaypointMarker' => 'payload.currentWaypointMarker',
1775
+ ];
1776
+
1777
+ if (isset($payloadMap[$root])) {
1778
+ $this->loadMissing($payloadMap[$root]);
1779
+ $target = data_get($this, $payloadMap[$root]);
1780
+
1781
+ // “pickup”, “dropoff”, or “currentWaypoint” on their own
1782
+ if ($property === $root) {
1783
+ return $target;
1784
+ }
1785
+
1786
+ // e.g. "address.city" part of "pickup.address.city"
1787
+ $subKey = Str::after($property, $root . '.');
1788
+
1789
+ // if waypoint we can do "waypoint.place.address" or "waypoint.address" for resolution
1790
+ $isWaypointMarker = Str::startsWith($root, 'currentWaypoint') || $root === 'waypoint';
1791
+ if ($isWaypointMarker && $target instanceof Waypoint) {
1792
+ $target->loadMissing('place');
1793
+
1794
+ return data_get($target, $subKey) ?? data_get($target->place, $subKey);
1795
+ }
1796
+
1797
+ return data_get($target, $subKey);
1798
+ }
1764
1799
 
1765
- // check if existing property
1766
- if ($this->{$snakedProperty}) {
1767
- return $this->{$snakedProperty};
1800
+ // Direct attribute on the model
1801
+ $snake = Str::snake($property);
1802
+ if (array_key_exists($snake, $this->getAttributes())) {
1803
+ return $this->getAttribute($snake);
1768
1804
  }
1769
1805
 
1770
- // Check if custom field property
1806
+ // Custom-field / meta look-ups
1771
1807
  if ($this->isCustomField($property)) {
1772
1808
  return $this->getCustomFieldValueByKey($property);
1773
1809
  }
1774
1810
 
1775
- // Check if meta attribute
1776
1811
  if ($this->hasMeta($property)) {
1777
1812
  return $this->getMeta($property);
1778
1813
  }
1779
1814
 
1815
+ // Fallback deep-path access
1780
1816
  return data_get($this, $property);
1781
1817
  }
1782
1818
 
@@ -3,6 +3,8 @@
3
3
  namespace Fleetbase\FleetOps\Models;
4
4
 
5
5
  use Fleetbase\Casts\Json;
6
+ use Fleetbase\FleetOps\Events\EntityActivityChanged;
7
+ use Fleetbase\FleetOps\Events\EntityCompleted;
6
8
  use Fleetbase\FleetOps\Events\WaypointActivityChanged;
7
9
  use Fleetbase\FleetOps\Events\WaypointCompleted;
8
10
  use Fleetbase\FleetOps\Flow\Activity;
@@ -185,6 +187,14 @@ class Payload extends Model
185
187
  return $this->belongsTo(Place::class, 'current_waypoint_uuid')->withoutGlobalScopes();
186
188
  }
187
189
 
190
+ /**
191
+ * The current waypoint of the payload in progress.
192
+ */
193
+ public function currentWaypointMarker()
194
+ {
195
+ return $this->belongsTo(Waypoint::class, 'current_waypoint_uuid', 'place_uuid')->withoutGlobalScopes();
196
+ }
197
+
188
198
  /**
189
199
  * Waypoints between start and end.
190
200
  *
@@ -305,7 +315,7 @@ class Payload extends Model
305
315
  }
306
316
 
307
317
  foreach ($waypoints as $index => $attributes) {
308
- $waypoint = ['payload_uuid' => $this->payload_uuid];
318
+ $waypoint = ['payload_uuid' => $this->payload_uuid, 'type' => data_get($attributes, 'type', 'dropoff')];
309
319
 
310
320
  if (Utils::isset($attributes, 'place') && is_array(Utils::get($attributes, 'place'))) {
311
321
  $attributes = Utils::get($attributes, 'place');
@@ -329,11 +339,13 @@ class Payload extends Model
329
339
  }
330
340
 
331
341
  // Handle customer assosciation for waypoint
332
- if (is_array($attributes) && Utils::notEmpty($attributes['customer_uuid']) && Utils::notEmpty($attributes['customer_type'])) {
333
- $customerTypeNamespace = Utils::getMutationType($attributes['customer_type']);
334
- $customerExists = app($customerTypeNamespace)->where('uuid', $attributes['customer_uuid'])->exists();
342
+ $customerId = data_get($attributes, 'customer_uuid');
343
+ $customerType = data_get($attributes, 'customer_type', 'fleetops:contact');
344
+ if ($customerId && $customerType) {
345
+ $customerTypeNamespace = Utils::getMutationType($customerType);
346
+ $customerExists = app($customerTypeNamespace)->where('uuid', $customerId)->exists();
335
347
  if ($customerExists) {
336
- $waypoint['customer_uuid'] = $attributes['customer_uuid'];
348
+ $waypoint['customer_uuid'] = $customerId;
337
349
  $waypoint['customer_type'] = $customerTypeNamespace;
338
350
  }
339
351
  }
@@ -355,7 +367,7 @@ class Payload extends Model
355
367
  }
356
368
 
357
369
  foreach ($waypoints as $index => $attributes) {
358
- $waypoint = ['payload_uuid' => $this->uuid, 'order' => $index];
370
+ $waypoint = ['payload_uuid' => $this->uuid, 'order' => $index, 'type' => data_get($attributes, 'type', 'dropoff')];
359
371
 
360
372
  if (Utils::isset($attributes, 'place') && is_array(Utils::get($attributes, 'place'))) {
361
373
  $placeAttributes = Utils::get($attributes, 'place');
@@ -384,11 +396,13 @@ class Payload extends Model
384
396
  }
385
397
 
386
398
  // Handle customer assosciation for waypoint
387
- if (is_array($attributes) && Utils::notEmpty($attributes['customer_uuid']) && Utils::notEmpty($attributes['customer_type'])) {
388
- $customerTypeNamespace = Utils::getMutationType($attributes['customer_type']);
389
- $customerExists = app($customerTypeNamespace)->where('uuid', $attributes['customer_uuid'])->exists();
399
+ $customerId = data_get($attributes, 'customer_uuid');
400
+ $customerType = data_get($attributes, 'customer_type', 'fleetops:contact');
401
+ if ($customerId && $customerType) {
402
+ $customerTypeNamespace = Utils::getMutationType($customerType);
403
+ $customerExists = app($customerTypeNamespace)->where('uuid', $customerId)->exists();
390
404
  if ($customerExists) {
391
- $waypoint['customer_uuid'] = $attributes['customer_uuid'];
405
+ $waypoint['customer_uuid'] = $customerId;
392
406
  $waypoint['customer_type'] = $customerTypeNamespace;
393
407
  }
394
408
  }
@@ -784,6 +798,11 @@ class Payload extends Model
784
798
  $entities = $this->entities->where('destination_uuid', $this->current_waypoint_uuid);
785
799
  foreach ($entities as $entity) {
786
800
  $entity->insertActivity($activity, $location, $proof);
801
+ if ($activity && $activity->complete()) {
802
+ event(new EntityCompleted($entity, $activity));
803
+ } else {
804
+ event(new EntityActivityChanged($entity, $activity));
805
+ }
787
806
  }
788
807
 
789
808
  // if this activity completes the waypoint notify waypoint customer