@fleetbase/fleetops-engine 0.6.49 → 0.6.51
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/README.md +185 -67
- package/addon/components/contact/panel-header.hbs +11 -48
- package/addon/components/customer/create-order-form.hbs +2 -2
- package/addon/components/customer/details.hbs +1 -2
- package/addon/components/customer/form.hbs +62 -50
- package/addon/components/customer/form.js +27 -1
- package/addon/components/customer/order-form.hbs +2 -2
- package/addon/components/customer/orders.hbs +50 -2
- package/addon/components/customer/orders.js +67 -5
- package/addon/components/display-place.hbs +2 -2
- package/addon/components/driver/form.hbs +1 -1
- package/addon/components/home/getting-started-guidance.hbs +113 -0
- package/addon/components/home/getting-started-guidance.js +107 -0
- package/addon/components/issue/details/comments.hbs +3 -0
- package/addon/components/issue/details/comments.js +3 -0
- package/addon/components/issue/details/correspondence.hbs +9 -0
- package/addon/components/issue/details/correspondence.js +83 -0
- package/addon/components/issue/details/documents.hbs +69 -0
- package/addon/components/issue/details/documents.js +85 -0
- package/addon/components/issue/details.hbs +220 -59
- package/addon/components/issue/details.js +245 -0
- package/addon/components/issue/form.hbs +19 -1
- package/addon/components/issue/panel-header.hbs +43 -0
- package/addon/components/issue/panel-header.js +22 -0
- package/addon/components/issue/timeline.hbs +40 -0
- package/addon/components/issue/timeline.js +45 -0
- package/addon/components/leaflet-tracking-marker.js +3 -0
- package/addon/components/map/drawer/position-listing.js +3 -0
- package/addon/components/map/google-live-map.js +6 -2
- package/addon/components/map/leaflet-live-map.hbs +48 -14
- package/addon/components/map/leaflet-live-map.js +201 -20
- package/addon/components/map/order-list-overlay.hbs +4 -4
- package/addon/components/modals/convert-customer-to-vendor.hbs +30 -0
- package/addon/components/modals/convert-customer-to-vendor.js +55 -0
- package/addon/components/modals/issue-assign.hbs +20 -0
- package/addon/components/modals/issue-assign.js +49 -0
- package/addon/components/modals/issue-close.hbs +30 -0
- package/addon/components/modals/issue-close.js +73 -0
- package/addon/components/modals/issue-status.hbs +20 -0
- package/addon/components/modals/issue-status.js +43 -0
- package/addon/components/modals/order-change-destination.hbs +46 -0
- package/addon/components/modals/order-change-destination.js +106 -0
- package/addon/components/modals/reset-customer-credentials.js +12 -6
- package/addon/components/modals/stop-activity.hbs +28 -0
- package/addon/components/modals/stop-activity.js +52 -0
- package/addon/components/modals/update-order-activity.hbs +78 -2
- package/addon/components/modals/update-order-activity.js +197 -5
- package/addon/components/orchestrator/resource-panel.hbs +0 -10
- package/addon/components/orchestrator/resource-panel.js +2 -4
- package/addon/components/order/activity-list.hbs +2 -2
- package/addon/components/order/activity-timeline.hbs +31 -29
- package/addon/components/order/details/activity.hbs +3 -3
- package/addon/components/order/details/activity.js +13 -2
- package/addon/components/order/details/documents.js +2 -0
- package/addon/components/order/details/proof.hbs +40 -38
- package/addon/components/order/details/proof.js +31 -4
- package/addon/components/order/details/purchase-rate.hbs +2 -2
- package/addon/components/order/details/route.hbs +11 -3
- package/addon/components/order/details/route.js +91 -14
- package/addon/components/order/details/tracking.hbs +101 -72
- package/addon/components/order/details/tracking.js +149 -2
- package/addon/components/order/details.hbs +5 -5
- package/addon/components/order/details.js +19 -1
- package/addon/components/order/form/details.hbs +3 -3
- package/addon/components/order/form/documents.js +11 -4
- package/addon/components/order/form/route.hbs +108 -104
- package/addon/components/order/form/route.js +37 -1
- package/addon/components/order/form/service-rate.hbs +2 -2
- package/addon/components/order/header.hbs +1 -1
- package/addon/components/order-config-manager/activity-flow.hbs +4 -1
- package/addon/components/order-config-manager/activity-flow.js +178 -12
- package/addon/components/place/address.js +1 -1
- package/addon/components/route-list.hbs +17 -29
- package/addon/components/route-list.js +42 -10
- package/addon/components/service-rate/details.hbs +42 -1
- package/addon/components/service-rate/form.hbs +119 -1
- package/addon/components/service-rate/form.js +59 -0
- package/addon/components/vehicle/form.js +1 -1
- package/addon/components/vehicle/pill.hbs +1 -1
- package/addon/components/vendor/personnel-manager.hbs +166 -0
- package/addon/components/vendor/personnel-manager.js +143 -0
- package/addon/components/widget/live-fleet.hbs +1 -0
- package/addon/components/widget/live-fleet.js +2 -7
- package/addon/components/widget/revenue-trend.hbs +1 -1
- package/addon/components/widget/revenue-trend.js +75 -2
- package/addon/controllers/management/contacts/customers/details.js +31 -13
- package/addon/controllers/management/contacts/customers.js +16 -5
- package/addon/controllers/management/contacts/index/details.js +31 -13
- package/addon/controllers/management/contacts/index.js +7 -0
- package/addon/controllers/management/issues/index/details.js +52 -6
- package/addon/controllers/management/vehicles/index/new.js +1 -1
- package/addon/controllers/management/vendors/index/details.js +4 -0
- package/addon/controllers/operations/orders/index/details.js +15 -7
- package/addon/controllers/operations/orders/index.js +15 -1
- package/addon/extension.js +19 -12
- package/addon/helpers/format-duration.js +1 -38
- package/addon/helpers/place-address-html.js +2 -2
- package/addon/initializers/load-leaflet-assets.js +5 -6
- package/addon/initializers/patch-ember-leaflet-tooltip-layer.js +51 -0
- package/addon/routes/management/issues/index/details.js +1 -1
- package/addon/routes/operations/service-rates/index.js +1 -1
- package/addon/routes.js +1 -0
- package/addon/services/contact-actions.js +231 -1
- package/addon/services/geofence.js +163 -19
- package/addon/services/getting-started.js +44 -0
- package/addon/services/issue-actions.js +71 -0
- package/addon/services/leaflet-contextmenu-manager.js +27 -0
- package/addon/services/leaflet-map-manager.js +2 -0
- package/addon/services/map-adapter/leaflet.js +19 -0
- package/addon/services/service-areas.js +3 -0
- package/addon/services/vehicle-actions.js +1 -1
- package/addon/styles/fleetops-engine.css +1127 -1
- package/addon/templates/management/contacts/customers/details.hbs +1 -1
- package/addon/templates/management/contacts/index/details.hbs +1 -1
- package/addon/templates/management/contacts.hbs +2 -2
- package/addon/templates/management/issues/index/details/index.hbs +3 -1
- package/addon/templates/management/issues/index/details.hbs +3 -2
- package/addon/templates/management/vendors/index/details/personnel.hbs +1 -2
- package/addon/templates/operations/orders/index/details/index.hbs +2 -2
- package/addon/templates/operations/orders/index.hbs +1 -1
- package/addon/templates/operations/service-rates/index/edit.hbs +1 -0
- package/addon/templates/operations/service-rates/index/new.hbs +1 -0
- package/addon/templates/settings/payments/onboard.hbs +8 -8
- package/addon/utils/fleet-ops-options.js +2 -1
- package/addon/utils/leaflet-draw-namespace-guard.js +22 -0
- package/addon/utils/leaflet-plugin-loader.js +219 -0
- package/addon/utils/normalize-order-config-flow.js +41 -0
- package/addon/utils/place-address-html.js +3 -2
- package/addon/utils/route-visualization.js +54 -7
- package/addon/utils/setup-customer-portal.js +4 -64
- package/app/components/home/getting-started-guidance.js +1 -0
- package/app/components/issue/details/comments.js +1 -0
- package/app/components/issue/details/correspondence.js +1 -0
- package/app/components/issue/details/documents.js +1 -0
- package/app/components/issue/panel-header.js +1 -0
- package/app/components/modals/convert-customer-to-vendor.js +1 -0
- package/app/components/modals/issue-assign.js +1 -0
- package/app/components/modals/issue-close.js +1 -0
- package/app/components/modals/issue-status.js +1 -0
- package/app/components/modals/order-change-destination.js +1 -0
- package/app/components/modals/stop-activity.js +1 -0
- package/app/components/vendor/personnel-manager.js +1 -0
- package/app/helpers/format-duration.js +1 -1
- package/app/services/getting-started.js +1 -0
- package/app/utils/leaflet-draw-namespace-guard.js +1 -0
- package/app/utils/leaflet-plugin-loader.js +1 -0
- package/composer.json +1 -1
- package/extension.json +1 -1
- package/package.json +5 -5
- package/server/migrations/2026_05_21_000001_add_geographic_rules_to_service_rate_fees_table.php +37 -0
- package/server/migrations/2026_05_25_000001_add_endpoint_tracking_refs_to_payloads_table.php +50 -0
- package/server/migrations/2026_05_27_000001_default_driver_vehicle_statuses_to_available.php +48 -0
- package/server/migrations/2026_05_31_000001_add_portal_fields_to_vendor_personnels_table.php +30 -0
- package/server/migrations/2026_06_03_000000_ensure_tags_column_on_issues_table.php +31 -0
- package/server/migrations/2026_06_03_000001_add_order_uuid_to_issues_table.php +25 -0
- package/server/migrations/2026_06_04_000001_backfill_order_transaction_subjects.php +39 -0
- package/server/migrations/2026_06_04_000002_backfill_order_transaction_directions.php +24 -0
- package/server/resources/views/labels/default.php +57 -49
- package/server/resources/views/labels/waypoint-label.php +27 -20
- package/server/resources/views/mail/customer-credentials.blade.php +16 -13
- package/server/seeders/Testing/ConnectivitySeeder.php +1 -0
- package/server/seeders/Testing/FleetSeeder.php +22 -22
- package/server/seeders/Testing/OrdersSeeder.php +6 -22
- package/server/seeders/Testing/VroomOrchestrationSeeder.php +21 -21
- package/server/src/Console/Commands/AuditCustomerUserConflicts.php +75 -0
- package/server/src/Exceptions/CustomerUserConflictException.php +13 -0
- package/server/src/Expansions/UserFilterExpansion.php +28 -0
- package/server/src/Http/Controllers/Api/v1/ContactController.php +17 -2
- package/server/src/Http/Controllers/Api/v1/CustomerController.php +876 -0
- package/server/src/Http/Controllers/Api/v1/DriverController.php +1 -1
- package/server/src/Http/Controllers/Api/v1/IssueController.php +2 -0
- package/server/src/Http/Controllers/Api/v1/OrderConfigController.php +54 -0
- package/server/src/Http/Controllers/Api/v1/OrderController.php +109 -80
- package/server/src/Http/Controllers/Api/v1/PlaceController.php +10 -58
- package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +2 -2
- package/server/src/Http/Controllers/Api/v1/VehicleController.php +20 -3
- package/server/src/Http/Controllers/Internal/v1/ContactController.php +154 -0
- package/server/src/Http/Controllers/Internal/v1/CustomerController.php +116 -6
- package/server/src/Http/Controllers/Internal/v1/GettingStartedController.php +17 -0
- package/server/src/Http/Controllers/Internal/v1/IssueController.php +251 -0
- package/server/src/Http/Controllers/Internal/v1/LiveController.php +101 -66
- package/server/src/Http/Controllers/Internal/v1/OrderController.php +530 -52
- package/server/src/Http/Controllers/Internal/v1/PlaceController.php +10 -68
- package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +1 -1
- package/server/src/Http/Controllers/Internal/v1/SettingController.php +2 -2
- package/server/src/Http/Controllers/Internal/v1/VehicleController.php +54 -1
- package/server/src/Http/Controllers/Internal/v1/VendorController.php +89 -0
- package/server/src/Http/Middleware/AuthenticateCustomerToken.php +34 -0
- package/server/src/Http/Requests/CreateCustomerOrderRequest.php +63 -0
- package/server/src/Http/Requests/CreateCustomerRequest.php +48 -0
- package/server/src/Http/Requests/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/CreateIssueRequest.php +4 -0
- package/server/src/Http/Requests/CreateVehicleRequest.php +25 -1
- package/server/src/Http/Requests/Internal/CreateDriverRequest.php +1 -1
- package/server/src/Http/Requests/UpdateIssueRequest.php +4 -0
- package/server/src/Http/Requests/VerifyCreateCustomerRequest.php +29 -0
- package/server/src/Http/Resources/Internal/v1/OrderConfig.php +41 -0
- package/server/src/Http/Resources/v1/Customer.php +79 -0
- package/server/src/Http/Resources/v1/Index/Order.php +12 -9
- package/server/src/Http/Resources/v1/Issue.php +25 -0
- package/server/src/Http/Resources/v1/Order.php +20 -18
- package/server/src/Http/Resources/v1/OrderConfig.php +74 -0
- package/server/src/Http/Resources/v1/Payload.php +23 -20
- package/server/src/Http/Resources/v1/ServiceArea.php +33 -19
- package/server/src/Http/Resources/v1/ServiceRateFee.php +7 -0
- package/server/src/Listeners/HandleOrderDispatched.php +2 -2
- package/server/src/Mail/CustomerCredentialsMail.php +5 -6
- package/server/src/Models/Contact.php +122 -66
- package/server/src/Models/Customer.php +64 -0
- package/server/src/Models/Driver.php +12 -3
- package/server/src/Models/Entity.php +2 -2
- package/server/src/Models/Issue.php +47 -6
- package/server/src/Models/Order.php +62 -10
- package/server/src/Models/Payload.php +17 -1
- package/server/src/Models/ServiceArea.php +1 -1
- package/server/src/Models/ServiceRate.php +273 -1
- package/server/src/Models/ServiceRateFee.php +39 -9
- package/server/src/Models/TrackingNumber.php +8 -2
- package/server/src/Models/Vehicle.php +18 -2
- package/server/src/Models/VendorPersonnel.php +10 -1
- package/server/src/Models/Waypoint.php +2 -2
- package/server/src/Models/Zone.php +1 -1
- package/server/src/Notifications/LateDeparture.php +2 -2
- package/server/src/Notifications/ProlongedStoppage.php +2 -2
- package/server/src/Notifications/RouteDeviation.php +2 -2
- package/server/src/Observers/ContactObserver.php +2 -0
- package/server/src/Observers/PurchaseRateObserver.php +20 -0
- package/server/src/Observers/ServiceRateObserver.php +6 -6
- package/server/src/Observers/ZoneObserver.php +56 -0
- package/server/src/Orchestration/Engines/VroomOrchestrationEngine.php +1 -1
- package/server/src/Providers/FleetOpsServiceProvider.php +2 -0
- package/server/src/Support/Analytics/AbstractAnalytics.php +6 -6
- package/server/src/Support/Analytics/FuelEfficiency.php +2 -2
- package/server/src/Support/Analytics/OrdersByStatus.php +1 -1
- package/server/src/Support/Analytics/RevenueTrend.php +0 -1
- package/server/src/Support/Analytics/TopDrivers.php +10 -2
- package/server/src/Support/CustomerAuth.php +98 -0
- package/server/src/Support/GettingStarted.php +144 -0
- package/server/src/Support/LabelPdf.php +52 -0
- package/server/src/Support/Metrics/AbstractMetric.php +4 -4
- package/server/src/Support/PlaceSearch.php +282 -0
- package/server/src/Support/ResolvesOrderServiceStops.php +522 -0
- package/server/src/Tracking/TrackingContextBuilder.php +41 -38
- package/server/src/Tracking/TrackingIntelligenceService.php +109 -9
- package/server/src/Tracking/TrackingStop.php +13 -11
- package/server/src/routes.php +76 -10
- package/server/tests/AnalyticsRoutesTest.php +8 -0
- package/server/tests/CapacityAllocationEngineTest.php +10 -10
- package/server/tests/CustomerContactIdentitySafetyTest.php +95 -0
- package/server/tests/CustomerEndpointTest.php +199 -0
- package/server/tests/DriverVehicleStatusDefaultTest.php +39 -0
- package/server/tests/GettingStartedStatusTest.php +21 -0
- package/server/tests/LabelPdfRenderingTest.php +23 -0
- package/server/tests/LiveControllerViewportTest.php +60 -0
- package/server/tests/MetricsRegistryTest.php +4 -8
- package/server/tests/OrchestrationPayloadBuilderTest.php +2 -2
- package/server/tests/OrderActivityFlowRegressionTest.php +116 -0
- package/server/tests/OrderConfigEndpointTest.php +86 -0
- package/server/tests/OrderFileAttachmentTest.php +20 -0
- package/server/tests/ServiceAreaZoneApiShapeTest.php +78 -0
- package/server/tests/VehicleDriverAssignmentTest.php +18 -0
- package/translations/ar-ae.yml +9 -0
- package/translations/bg-bg.yaml +9 -0
- package/translations/en-us.yaml +12 -0
- package/translations/es-pa.yaml +9 -0
- package/translations/fr-fr.yaml +9 -0
- package/translations/mn-mn.yaml +9 -0
- package/translations/pt-br.yaml +9 -0
- package/translations/ru-ru.yaml +9 -0
- package/translations/vi-vn.yaml +9 -0
- package/server/migrations/2026_05_13_000001_repair_customer_contact_user_types.php +0 -86
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
namespace Fleetbase\FleetOps\Http\Controllers\Internal\v1;
|
|
4
4
|
|
|
5
5
|
use Fleetbase\Exceptions\FleetbaseRequestValidationException;
|
|
6
|
+
use Fleetbase\FleetOps\Events\EntityActivityChanged;
|
|
7
|
+
use Fleetbase\FleetOps\Events\EntityCompleted;
|
|
6
8
|
use Fleetbase\FleetOps\Events\OrderDispatchFailed;
|
|
7
9
|
use Fleetbase\FleetOps\Events\OrderReady;
|
|
8
10
|
use Fleetbase\FleetOps\Events\OrderStarted;
|
|
11
|
+
use Fleetbase\FleetOps\Events\WaypointActivityChanged;
|
|
12
|
+
use Fleetbase\FleetOps\Events\WaypointCompleted;
|
|
9
13
|
use Fleetbase\FleetOps\Exports\OrderExport;
|
|
10
14
|
use Fleetbase\FleetOps\Flow\Activity;
|
|
11
15
|
use Fleetbase\FleetOps\Http\Controllers\FleetOpsController;
|
|
@@ -27,6 +31,7 @@ use Fleetbase\FleetOps\Models\ServiceQuote;
|
|
|
27
31
|
use Fleetbase\FleetOps\Models\TrackingStatus;
|
|
28
32
|
use Fleetbase\FleetOps\Models\Waypoint;
|
|
29
33
|
use Fleetbase\FleetOps\Notifications\OrderPing;
|
|
34
|
+
use Fleetbase\FleetOps\Support\ResolvesOrderServiceStops;
|
|
30
35
|
use Fleetbase\FleetOps\Support\Utils;
|
|
31
36
|
use Fleetbase\Http\Requests\ExportRequest;
|
|
32
37
|
use Fleetbase\Http\Requests\Internal\BulkActionRequest;
|
|
@@ -37,13 +42,17 @@ use Fleetbase\Support\TemplateString;
|
|
|
37
42
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
38
43
|
use Illuminate\Database\QueryException;
|
|
39
44
|
use Illuminate\Http\Request;
|
|
45
|
+
use Illuminate\Http\UploadedFile;
|
|
40
46
|
use Illuminate\Support\Facades\DB;
|
|
47
|
+
use Illuminate\Support\Facades\Storage;
|
|
41
48
|
use Illuminate\Support\Facades\Validator;
|
|
42
49
|
use Illuminate\Support\Str;
|
|
43
50
|
use Maatwebsite\Excel\Facades\Excel;
|
|
44
51
|
|
|
45
52
|
class OrderController extends FleetOpsController
|
|
46
53
|
{
|
|
54
|
+
use ResolvesOrderServiceStops;
|
|
55
|
+
|
|
47
56
|
/**
|
|
48
57
|
* The resource to query.
|
|
49
58
|
*
|
|
@@ -63,6 +72,11 @@ class OrderController extends FleetOpsController
|
|
|
63
72
|
*/
|
|
64
73
|
public function onAfterUpdate($request, $order)
|
|
65
74
|
{
|
|
75
|
+
$uploads = $request->array('order.files');
|
|
76
|
+
if ($uploads) {
|
|
77
|
+
$order->attachFiles($uploads);
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
$waypoints = $request->array('order.payload.waypoints');
|
|
67
81
|
if ($waypoints) {
|
|
68
82
|
$order->loadMissing('payload');
|
|
@@ -165,14 +179,7 @@ class OrderController extends FleetOpsController
|
|
|
165
179
|
->insertEntities($entities);
|
|
166
180
|
|
|
167
181
|
// If order creation includes files assosciate each to this order
|
|
168
|
-
|
|
169
|
-
$ids = collect($uploads)->pluck('uuid');
|
|
170
|
-
$files = File::whereIn('uuid', $ids)->get();
|
|
171
|
-
|
|
172
|
-
foreach ($files as $file) {
|
|
173
|
-
$file->setKey($order);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
182
|
+
$order->attachFiles($uploads);
|
|
176
183
|
|
|
177
184
|
// save custom field values
|
|
178
185
|
if (is_array($customFieldValues)) {
|
|
@@ -668,28 +675,9 @@ class OrderController extends FleetOpsController
|
|
|
668
675
|
// get the next order activity
|
|
669
676
|
$flow = $activity = $order->config()->nextFirstActivity();
|
|
670
677
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
$location = $order->getLastLocation();
|
|
675
|
-
|
|
676
|
-
// if multi drop order set first destination
|
|
677
|
-
if ($payload->isMultipleDropOrder) {
|
|
678
|
-
$firstDestination = $payload->waypoints->first();
|
|
679
|
-
|
|
680
|
-
if ($firstDestination) {
|
|
681
|
-
$payload->current_waypoint_uuid = $firstDestination->uuid;
|
|
682
|
-
$payload->save();
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
// update activity for each waypoint and entity
|
|
686
|
-
foreach ($payload->waypointMarkers as $waypointMarker) {
|
|
687
|
-
$waypointMarker->insertActivity($activity, $location);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
foreach ($payload->entities as $entity) {
|
|
691
|
-
$entity->insertActivity($activity, $location);
|
|
692
|
-
}
|
|
678
|
+
// if multi-stop route set first service stop destination
|
|
679
|
+
if ($this->payloadUsesServiceStopActivity($payload)) {
|
|
680
|
+
$this->ensurePayloadCurrentServiceStop($payload);
|
|
693
681
|
}
|
|
694
682
|
|
|
695
683
|
// update order activity
|
|
@@ -706,13 +694,31 @@ class OrderController extends FleetOpsController
|
|
|
706
694
|
*/
|
|
707
695
|
public function updateActivity(string $id, Request $request)
|
|
708
696
|
{
|
|
709
|
-
$order = Order::findById($id, [
|
|
697
|
+
$order = Order::findById($id, [
|
|
698
|
+
'driverAssigned',
|
|
699
|
+
'payload.entities',
|
|
700
|
+
'payload.pickup',
|
|
701
|
+
'payload.dropoff',
|
|
702
|
+
'payload.return',
|
|
703
|
+
'payload.currentWaypoint',
|
|
704
|
+
'payload.waypoints',
|
|
705
|
+
'payload.waypointMarkers.place',
|
|
706
|
+
'payload.waypointMarkers.trackingNumber.status',
|
|
707
|
+
]);
|
|
710
708
|
if (!$order) {
|
|
711
709
|
return response()->error('No order found.');
|
|
712
710
|
}
|
|
713
711
|
|
|
714
|
-
$
|
|
715
|
-
$
|
|
712
|
+
$proof = $request->input('proof');
|
|
713
|
+
$bypassProof = $request->boolean('bypass_proof');
|
|
714
|
+
$activity = $request->array('activity');
|
|
715
|
+
$activity = new Activity($activity, $order->getConfigFlow());
|
|
716
|
+
|
|
717
|
+
$requiresProof = Utils::isActivity($activity)
|
|
718
|
+
&& ($activity->get('require_pod') || ($activity->completesOrder() && $order->pod_required));
|
|
719
|
+
if ($requiresProof && !$proof && !$bypassProof) {
|
|
720
|
+
return response()->error('Proof of delivery is required for this activity or must be explicitly bypassed.', 422);
|
|
721
|
+
}
|
|
716
722
|
|
|
717
723
|
// Handle pickup/dropoff order activity update as normal
|
|
718
724
|
if (Utils::isActivity($activity) && $activity->is('dispatched')) {
|
|
@@ -731,29 +737,65 @@ class OrderController extends FleetOpsController
|
|
|
731
737
|
/**
|
|
732
738
|
* @var \Fleetbase\LaravelMysqlSpatial\Types\Point
|
|
733
739
|
*/
|
|
734
|
-
$location
|
|
735
|
-
$
|
|
736
|
-
$
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
$
|
|
740
|
+
$location = $order->getLastLocation();
|
|
741
|
+
$usesServiceStopActivity = $this->payloadUsesServiceStopActivity($order->payload);
|
|
742
|
+
$isLifecycleActivity = Utils::isActivity($activity) && in_array($activity->code, ['created', 'dispatched', 'started'], true);
|
|
743
|
+
|
|
744
|
+
if (!$usesServiceStopActivity || $isLifecycleActivity) {
|
|
745
|
+
$order->updateActivity($activity, $proof);
|
|
746
|
+
|
|
747
|
+
if (!$usesServiceStopActivity) {
|
|
748
|
+
foreach ($order->payload->entities as $entity) {
|
|
749
|
+
$entity->insertActivity($activity, $location, $proof);
|
|
750
|
+
}
|
|
743
751
|
}
|
|
752
|
+
|
|
753
|
+
if ($usesServiceStopActivity && $activity->is('started')) {
|
|
754
|
+
$this->ensurePayloadCurrentServiceStop($order->payload);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Handle order completed
|
|
758
|
+
if (Utils::isActivity($activity) && $activity->completesOrder() && $order->driverAssigned) {
|
|
759
|
+
// unset from driver current job
|
|
760
|
+
$order->driverAssigned->unassignCurrentOrder();
|
|
761
|
+
$order->complete($this->resolveProof($proof));
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return new OrderResource($order->refresh());
|
|
744
765
|
}
|
|
745
766
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
// unset from driver current job
|
|
749
|
-
$order->driverAssigned->unassignCurrentOrder();
|
|
750
|
-
$order->complete();
|
|
767
|
+
if (!$order->started && in_array($order->status, ['created', 'dispatched'])) {
|
|
768
|
+
return response()->error('Order must be started before waypoint activity can be updated.', 422);
|
|
751
769
|
}
|
|
752
770
|
|
|
753
|
-
|
|
754
|
-
$activity->
|
|
771
|
+
$currentStop = $this->ensurePayloadCurrentServiceStop($order->payload);
|
|
772
|
+
$isCompletingCurrentStop = Utils::isActivity($activity) && $activity->complete();
|
|
773
|
+
|
|
774
|
+
$this->updateCurrentServiceStopActivity($order, $activity, $location, $proof);
|
|
775
|
+
|
|
776
|
+
if (Utils::isActivity($activity) && $activity->is('canceled')) {
|
|
777
|
+
if ($order->driverAssigned) {
|
|
778
|
+
$order->driverAssigned->unassignCurrentOrder();
|
|
779
|
+
}
|
|
755
780
|
|
|
756
|
-
|
|
781
|
+
$order->cancel();
|
|
782
|
+
|
|
783
|
+
return new OrderResource($order->refresh());
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (Utils::isActivity($activity) && $activity->completesOrder()) {
|
|
787
|
+
$nextStop = $isCompletingCurrentStop ? $this->advanceCurrentServiceStopDestination($order, $order->payload) : null;
|
|
788
|
+
|
|
789
|
+
if (!$nextStop || !$currentStop) {
|
|
790
|
+
if ($order->driverAssigned) {
|
|
791
|
+
$order->driverAssigned->unassignCurrentOrder();
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
$order->complete($this->resolveProof($proof));
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return new OrderResource($order->refresh());
|
|
757
799
|
}
|
|
758
800
|
|
|
759
801
|
/**
|
|
@@ -769,12 +811,27 @@ class OrderController extends FleetOpsController
|
|
|
769
811
|
return response()->error('No order found.');
|
|
770
812
|
}
|
|
771
813
|
|
|
772
|
-
$
|
|
814
|
+
$order->loadMissing([
|
|
815
|
+
'payload.pickup',
|
|
816
|
+
'payload.dropoff',
|
|
817
|
+
'payload.waypoints',
|
|
818
|
+
'payload.waypointMarkers.place',
|
|
819
|
+
'payload.waypointMarkers.trackingNumber.status',
|
|
820
|
+
]);
|
|
821
|
+
|
|
822
|
+
$canUpdateWaypointActivity = $this->payloadUsesServiceStopActivity($order->payload)
|
|
823
|
+
&& ($order->started || $order->started_at || !in_array($order->status, ['created', 'dispatched']));
|
|
824
|
+
$stop = null;
|
|
825
|
+
if ($canUpdateWaypointActivity) {
|
|
826
|
+
$stop = $request->filled('waypoint')
|
|
827
|
+
? $this->resolveServiceStopFromKey($order->payload, $request->input('waypoint'))
|
|
828
|
+
: $this->payloadCurrentServiceStop($order->payload);
|
|
829
|
+
}
|
|
773
830
|
$orderConfig = $order->ensureOrderConfig();
|
|
774
831
|
if (!$orderConfig) {
|
|
775
832
|
return response()->error('No order config found for order.');
|
|
776
833
|
}
|
|
777
|
-
$activities = $orderConfig->nextActivity(
|
|
834
|
+
$activities = $stop ? $this->nextActivitiesForServiceStop($order, $order->payload, $stop) : $orderConfig->nextActivity();
|
|
778
835
|
|
|
779
836
|
// If activity is to complete order add proof of delivery properties if required
|
|
780
837
|
// This is a temporary fix until activity is updated to handle POD on it's own
|
|
@@ -794,6 +851,427 @@ class OrderController extends FleetOpsController
|
|
|
794
851
|
return response()->json($activities);
|
|
795
852
|
}
|
|
796
853
|
|
|
854
|
+
/**
|
|
855
|
+
* Mark a waypoint as the current destination for a multi-waypoint order.
|
|
856
|
+
*
|
|
857
|
+
* @return \Fleetbase\Http\Resources\v1\Order
|
|
858
|
+
*/
|
|
859
|
+
public function setDestination(string $id, string $placeId)
|
|
860
|
+
{
|
|
861
|
+
$order = Order::findById($id, [
|
|
862
|
+
'payload.pickup',
|
|
863
|
+
'payload.dropoff',
|
|
864
|
+
'payload.return',
|
|
865
|
+
'payload.waypoints',
|
|
866
|
+
'payload.waypointMarkers.place',
|
|
867
|
+
'payload.waypointMarkers.trackingNumber.status',
|
|
868
|
+
'driverAssigned',
|
|
869
|
+
'vehicleAssigned',
|
|
870
|
+
'customer',
|
|
871
|
+
'facilitator',
|
|
872
|
+
]);
|
|
873
|
+
if (!$order) {
|
|
874
|
+
return response()->error('No order found.');
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (!$this->payloadUsesServiceStopActivity($order->payload)) {
|
|
878
|
+
return response()->error('Destination can only be changed for multi-waypoint orders.', 422);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
$stop = $this->resolveServiceStopFromKey($order->payload, $placeId);
|
|
882
|
+
if (!$stop) {
|
|
883
|
+
return response()->error('Place resource is not a valid route destination.', 422);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
$this->setPayloadCurrentServiceStop($order->payload, $stop);
|
|
887
|
+
|
|
888
|
+
return new OrderResource($order->refresh());
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Capture one or more photos for order/waypoint proof of delivery.
|
|
893
|
+
*
|
|
894
|
+
* @return ProofResource|\Illuminate\Http\Response
|
|
895
|
+
*/
|
|
896
|
+
public function capturePhoto(Request $request, string $id, ?string $subjectId = null)
|
|
897
|
+
{
|
|
898
|
+
$incoming = $this->collectProofPhotoInputs($request);
|
|
899
|
+
$metadata = $request->input('data', []);
|
|
900
|
+
$metadata = is_array($metadata) ? $metadata : [];
|
|
901
|
+
$validator = Validator::make([
|
|
902
|
+
'photos' => $incoming,
|
|
903
|
+
'remarks' => $request->input('remarks'),
|
|
904
|
+
'data' => $metadata,
|
|
905
|
+
], [
|
|
906
|
+
'photos' => 'required|array|min:1',
|
|
907
|
+
'photos.*' => [
|
|
908
|
+
function ($attribute, $value, $fail) {
|
|
909
|
+
if ($value instanceof UploadedFile) {
|
|
910
|
+
$extension = strtolower($value->getClientOriginalExtension() ?: $value->extension());
|
|
911
|
+
if (
|
|
912
|
+
!$value->isValid()
|
|
913
|
+
|| !in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'webp'])
|
|
914
|
+
|| $value->getSize() > 10 * 1024 * 1024
|
|
915
|
+
) {
|
|
916
|
+
$fail("{$attribute} must be a valid image file <= 10 MB.");
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if ($this->isValidBase64ProofPhoto($value)) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
$fail("{$attribute} must be an image file or a valid Base64 string.");
|
|
927
|
+
},
|
|
928
|
+
],
|
|
929
|
+
'remarks' => 'sometimes|string|max:255',
|
|
930
|
+
'data' => 'sometimes|array',
|
|
931
|
+
]);
|
|
932
|
+
|
|
933
|
+
if ($validator->fails()) {
|
|
934
|
+
$errorMessage = collect($validator->errors()->all())->first();
|
|
935
|
+
|
|
936
|
+
return response()->error($errorMessage, 422);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
$order = Order::findById($id, ['payload.pickup', 'payload.dropoff', 'payload.return', 'payload.waypoints', 'payload.waypointMarkers.place']);
|
|
940
|
+
if (!$order) {
|
|
941
|
+
return response()->error('No order found.');
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
$subject = $this->resolveProofSubject($order, $subjectId);
|
|
945
|
+
if (!$subject) {
|
|
946
|
+
return response()->error('Unable to capture photo as proof.', 422);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
$disk = $request->input('disk', config('filesystems.default'));
|
|
950
|
+
$bucket = $request->input("filesystems.disks.{$disk}.bucket", config('filesystems.disks.s3.bucket'));
|
|
951
|
+
$remarks = $request->input('remarks', 'Verified by Photo');
|
|
952
|
+
$data = $metadata;
|
|
953
|
+
|
|
954
|
+
if (empty($incoming)) {
|
|
955
|
+
return response()->error('No photo data to capture.', 422);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
foreach ($incoming as $item) {
|
|
959
|
+
$proof = Proof::create([
|
|
960
|
+
'company_uuid' => session('company'),
|
|
961
|
+
'order_uuid' => $order->uuid,
|
|
962
|
+
'subject_uuid' => $subject->uuid,
|
|
963
|
+
'subject_type' => Utils::getModelClassName($subject),
|
|
964
|
+
'remarks' => $remarks,
|
|
965
|
+
'raw_data' => $item instanceof UploadedFile ? null : $item,
|
|
966
|
+
'data' => $data,
|
|
967
|
+
]);
|
|
968
|
+
|
|
969
|
+
$file = $this->storeProofPhoto($proof, $item, $disk, $bucket);
|
|
970
|
+
$proof->update(['file_uuid' => $file->uuid]);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
return new ProofResource($proof);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* Normalize console proof upload payloads into one photo list before validation.
|
|
978
|
+
*/
|
|
979
|
+
protected function collectProofPhotoInputs(Request $request): array
|
|
980
|
+
{
|
|
981
|
+
$incoming = [];
|
|
982
|
+
foreach (['photos', 'photo', 'files', 'file'] as $key) {
|
|
983
|
+
$this->appendProofPhotoInputs($incoming, $request->file($key));
|
|
984
|
+
$this->appendProofPhotoInputs($incoming, $request->input($key));
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
$incoming = array_values(array_filter($incoming, function ($value) {
|
|
988
|
+
return $value instanceof UploadedFile || is_string($value);
|
|
989
|
+
}));
|
|
990
|
+
|
|
991
|
+
return $this->dedupeProofPhotoInputs($incoming);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Append a nested file/input value to the proof input list.
|
|
996
|
+
*/
|
|
997
|
+
protected function appendProofPhotoInputs(array &$incoming, mixed $value): void
|
|
998
|
+
{
|
|
999
|
+
if ($value === null) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (is_array($value)) {
|
|
1004
|
+
foreach ($value as $nested) {
|
|
1005
|
+
$this->appendProofPhotoInputs($incoming, $nested);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
$incoming[] = $value;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* Remove duplicate photo inputs exposed under multiple multipart aliases.
|
|
1016
|
+
*/
|
|
1017
|
+
protected function dedupeProofPhotoInputs(array $incoming): array
|
|
1018
|
+
{
|
|
1019
|
+
$seen = [];
|
|
1020
|
+
|
|
1021
|
+
return array_values(array_filter($incoming, function ($value) use (&$seen) {
|
|
1022
|
+
$fingerprint = $this->proofPhotoInputFingerprint($value);
|
|
1023
|
+
if ($fingerprint === null || isset($seen[$fingerprint])) {
|
|
1024
|
+
return false;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
$seen[$fingerprint] = true;
|
|
1028
|
+
|
|
1029
|
+
return true;
|
|
1030
|
+
}));
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* Build a stable fingerprint for an uploaded file or Base64 proof payload.
|
|
1035
|
+
*/
|
|
1036
|
+
protected function proofPhotoInputFingerprint(mixed $value): ?string
|
|
1037
|
+
{
|
|
1038
|
+
if ($value instanceof UploadedFile) {
|
|
1039
|
+
return hash('sha256', implode('|', [
|
|
1040
|
+
$value->getRealPath(),
|
|
1041
|
+
$value->getSize(),
|
|
1042
|
+
$value->getClientOriginalName(),
|
|
1043
|
+
$value->getClientMimeType(),
|
|
1044
|
+
]));
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (is_string($value)) {
|
|
1048
|
+
$payload = Str::contains($value, 'base64,') ? Str::after($value, 'base64,') : $value;
|
|
1049
|
+
|
|
1050
|
+
return hash('sha256', $payload);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* Determine whether a proof input is a usable Base64 image payload.
|
|
1058
|
+
*/
|
|
1059
|
+
protected function isValidBase64ProofPhoto(mixed $value): bool
|
|
1060
|
+
{
|
|
1061
|
+
if (!is_string($value)) {
|
|
1062
|
+
return false;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
$payload = Str::contains($value, 'base64,') ? Str::after($value, 'base64,') : $value;
|
|
1066
|
+
|
|
1067
|
+
return base64_decode($payload, true) !== false;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Store a proof photo and create its file record.
|
|
1072
|
+
*/
|
|
1073
|
+
protected function storeProofPhoto(Proof $proof, UploadedFile|string $photo, string $disk, string $bucket): File
|
|
1074
|
+
{
|
|
1075
|
+
$isFile = $photo instanceof UploadedFile;
|
|
1076
|
+
$contents = $isFile ? file_get_contents($photo->getRealPath()) : base64_decode(Str::contains($photo, 'base64,') ? Str::after($photo, 'base64,') : $photo);
|
|
1077
|
+
$extension = $isFile ? $photo->getClientOriginalExtension() : 'png';
|
|
1078
|
+
$contentType = $isFile ? $photo->getClientMimeType() : 'image/png';
|
|
1079
|
+
$company = session('company');
|
|
1080
|
+
$path = "uploads/{$company}/photos/{$proof->public_id}.{$extension}";
|
|
1081
|
+
|
|
1082
|
+
Storage::disk($disk)->put($path, $contents);
|
|
1083
|
+
|
|
1084
|
+
return File::create([
|
|
1085
|
+
'company_uuid' => $company,
|
|
1086
|
+
'uploader_uuid' => session('user'),
|
|
1087
|
+
'name' => basename($path),
|
|
1088
|
+
'original_filename' => basename($path),
|
|
1089
|
+
'extension' => $extension,
|
|
1090
|
+
'content_type' => $contentType,
|
|
1091
|
+
'path' => $path,
|
|
1092
|
+
'bucket' => $bucket,
|
|
1093
|
+
'type' => 'photo',
|
|
1094
|
+
'size' => strlen($contents),
|
|
1095
|
+
])->setKey($proof);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Resolve the subject that proof belongs to.
|
|
1100
|
+
*/
|
|
1101
|
+
protected function resolveProofSubject(Order $order, ?string $subjectId = null): Order|Waypoint|Place|null
|
|
1102
|
+
{
|
|
1103
|
+
if (!$subjectId) {
|
|
1104
|
+
return $order;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
$stop = $this->resolveServiceStopFromKey($order->payload, $subjectId);
|
|
1108
|
+
if ($stop) {
|
|
1109
|
+
return ($stop['waypoint'] ?? null) instanceof Waypoint ? $stop['waypoint'] : ($stop['place'] ?? $order);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
return Waypoint::withoutGlobalScopes()
|
|
1113
|
+
->where('payload_uuid', $order->payload_uuid)
|
|
1114
|
+
->where(function ($query) use ($subjectId) {
|
|
1115
|
+
$query
|
|
1116
|
+
->where('public_id', $subjectId)
|
|
1117
|
+
->orWhere('uuid', $subjectId)
|
|
1118
|
+
->orWhereHas('place', function ($query) use ($subjectId) {
|
|
1119
|
+
$query->where('public_id', $subjectId)->orWhere('uuid', $subjectId);
|
|
1120
|
+
});
|
|
1121
|
+
})
|
|
1122
|
+
->first();
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Resolve proof to the model expected by order completion.
|
|
1127
|
+
*/
|
|
1128
|
+
protected function resolveProof($proof): ?Proof
|
|
1129
|
+
{
|
|
1130
|
+
if ($proof instanceof Proof) {
|
|
1131
|
+
return $proof;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (is_string($proof)) {
|
|
1135
|
+
return Proof::where('public_id', $proof)->orWhere('uuid', $proof)->first();
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Determine whether the payload should use waypoint-scoped activity.
|
|
1143
|
+
*/
|
|
1144
|
+
protected function payloadHasWaypoints(?Payload $payload): bool
|
|
1145
|
+
{
|
|
1146
|
+
if (!$payload) {
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
$payload->loadMissing('waypoints');
|
|
1151
|
+
|
|
1152
|
+
return $payload->waypoints->isNotEmpty();
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Insert an activity on the payload's current waypoint and its destination entities.
|
|
1157
|
+
*/
|
|
1158
|
+
protected function updateCurrentWaypointActivity(?Payload $payload, Activity $activity, $location = null, $proof = null): ?Waypoint
|
|
1159
|
+
{
|
|
1160
|
+
if (!$payload || !Utils::isActivity($activity) || !$location) {
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
$payload->loadMissing(['order', 'waypointMarkers', 'entities']);
|
|
1165
|
+
$currentWaypoint = $payload->waypointMarkers->firstWhere('place_uuid', $payload->current_waypoint_uuid);
|
|
1166
|
+
if (!$currentWaypoint) {
|
|
1167
|
+
return null;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
$currentWaypoint->insertActivity($activity, $location, $proof);
|
|
1171
|
+
$activity->fireEvents($payload->order, $currentWaypoint);
|
|
1172
|
+
|
|
1173
|
+
$entities = $payload->entities->where('destination_uuid', $payload->current_waypoint_uuid);
|
|
1174
|
+
foreach ($entities as $entity) {
|
|
1175
|
+
$entity->insertActivity($activity, $location, $proof);
|
|
1176
|
+
if ($activity->complete()) {
|
|
1177
|
+
event(new EntityCompleted($entity, $activity));
|
|
1178
|
+
} else {
|
|
1179
|
+
event(new EntityActivityChanged($entity, $activity));
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
if ($activity->complete()) {
|
|
1184
|
+
event(new WaypointCompleted($currentWaypoint, $activity));
|
|
1185
|
+
} else {
|
|
1186
|
+
event(new WaypointActivityChanged($currentWaypoint, $activity));
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
return $currentWaypoint;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* Reload waypoint marker status and decide whether every stop is complete.
|
|
1194
|
+
*/
|
|
1195
|
+
protected function allWaypointMarkersComplete(?Payload $payload): bool
|
|
1196
|
+
{
|
|
1197
|
+
if (!$payload) {
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
$waypointMarkers = $this->freshWaypointMarkers($payload);
|
|
1202
|
+
|
|
1203
|
+
return $waypointMarkers->isNotEmpty() && $waypointMarkers->every(fn (Waypoint $waypoint) => $this->waypointMarkerIsComplete($waypoint));
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
/**
|
|
1207
|
+
* Move the current destination to the next incomplete waypoint.
|
|
1208
|
+
*/
|
|
1209
|
+
protected function advanceCurrentWaypointDestination(?Payload $payload): ?Waypoint
|
|
1210
|
+
{
|
|
1211
|
+
if (!$payload) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
$nextWaypoint = $this->freshWaypointMarkers($payload)
|
|
1216
|
+
->sortBy('order')
|
|
1217
|
+
->first(function (Waypoint $waypoint) use ($payload) {
|
|
1218
|
+
return !$this->waypointMarkerIsComplete($waypoint) && $waypoint->place_uuid !== $payload->current_waypoint_uuid;
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
if (!$nextWaypoint) {
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
$payload->setCurrentWaypoint($nextWaypoint);
|
|
1226
|
+
$payload->setRelation('currentWaypoint', $nextWaypoint->getPlace());
|
|
1227
|
+
$payload->setRelation('currentWaypointMarker', $nextWaypoint);
|
|
1228
|
+
|
|
1229
|
+
return $nextWaypoint;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* Reload waypoint markers with current tracking status.
|
|
1234
|
+
*/
|
|
1235
|
+
protected function freshWaypointMarkers(Payload $payload)
|
|
1236
|
+
{
|
|
1237
|
+
$payload->unsetRelation('waypointMarkers');
|
|
1238
|
+
$payload->load(['waypointMarkers.trackingNumber.status']);
|
|
1239
|
+
|
|
1240
|
+
return $payload->waypointMarkers;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* Determine whether a waypoint marker's latest status is complete.
|
|
1245
|
+
*/
|
|
1246
|
+
protected function waypointMarkerIsComplete(Waypoint $waypoint): bool
|
|
1247
|
+
{
|
|
1248
|
+
if (!$waypoint->tracking_number_uuid) {
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
return (bool) data_get($this->trackingNumberStatus($waypoint->tracking_number_uuid), 'complete', false);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Determine whether the payload's current waypoint already has an activity row.
|
|
1257
|
+
*/
|
|
1258
|
+
protected function payloadHasCurrentWaypointActivity(?Payload $payload, Activity $activity): bool
|
|
1259
|
+
{
|
|
1260
|
+
if (!$payload || !$payload->current_waypoint_uuid) {
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
$payload->loadMissing('waypointMarkers');
|
|
1265
|
+
$currentWaypoint = $payload->waypointMarkers->firstWhere('place_uuid', $payload->current_waypoint_uuid);
|
|
1266
|
+
if (!$currentWaypoint || !$currentWaypoint->tracking_number_uuid) {
|
|
1267
|
+
return false;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
return TrackingStatus::where('tracking_number_uuid', $currentWaypoint->tracking_number_uuid)
|
|
1271
|
+
->where('code', TrackingStatus::prepareCode($activity->code))
|
|
1272
|
+
->exists();
|
|
1273
|
+
}
|
|
1274
|
+
|
|
797
1275
|
/**
|
|
798
1276
|
* Finds and responds with the orders next activity update based on the orders configuration.
|
|
799
1277
|
*
|