From cd7f478d6f9e39b677a35cf973efaf8cac1f743e Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Sat, 24 May 2025 21:08:39 +0800 Subject: [PATCH 1/3] added ability to resolve payload values from shortcut when resolving dynamic values for logic --- composer.json | 2 +- extension.json | 2 +- package.json | 2 +- server/src/Models/Order.php | 35 +++++++++++++++++++++++++++++------ server/src/Models/Payload.php | 8 ++++++++ 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index fc7164946..bc9df5431 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "fleetbase/fleetops-api", - "version": "0.6.11", + "version": "0.6.12", "description": "Fleet & Transport Management Extension for Fleetbase", "keywords": [ "fleetbase-extension", diff --git a/extension.json b/extension.json index 9db279ca0..2f2e01a7e 100644 --- a/extension.json +++ b/extension.json @@ -1,6 +1,6 @@ { "name": "Fleet-Ops", - "version": "0.6.11", + "version": "0.6.12", "description": "Fleet & Transport Management Extension for Fleetbase", "repository": "https://github.com/fleetbase/fleetops", "license": "AGPL-3.0-or-later", diff --git a/package.json b/package.json index fc4f631e4..6ef576e2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fleetbase/fleetops-engine", - "version": "0.6.11", + "version": "0.6.12", "description": "Fleet & Transport Management Extension for Fleetbase", "fleetbase": { "route": "fleet-ops" diff --git a/server/src/Models/Order.php b/server/src/Models/Order.php index 75a07b5dc..6ebcfb9aa 100644 --- a/server/src/Models/Order.php +++ b/server/src/Models/Order.php @@ -1760,23 +1760,46 @@ public function getConfigFlow(): array */ public function resolveDynamicProperty(string $property) { - $snakedProperty = Str::snake($property); + // Special payload shortcuts + $root = Str::before($property, '.'); // e.g. "pickup" in "pickup.address" + $payloadMap = [ + 'pickup' => 'payload.pickup', + 'dropoff' => 'payload.dropoff', + 'currentWaypoint' => 'payload.currentWaypoint', + 'currentWaypointMarker' => 'payload.currentWaypointMarker', + ]; + + if (isset($payloadMap[$root])) { + $this->loadMissing($payloadMap[$root]); + $target = data_get($this, $payloadMap[$root]); + + // “pickup”, “dropoff”, or “currentWaypoint” on their own + if ($property === $root) { + return $target; + } + + // e.g. "address.city" part of "pickup.address.city" + $subKey = Str::after($property, $root . '.'); + + return data_get($target, $subKey); + } - // check if existing property - if ($this->{$snakedProperty}) { - return $this->{$snakedProperty}; + // Direct attribute on the model + $snake = Str::snake($property); + if (array_key_exists($snake, $this->getAttributes())) { + return $this->getAttribute($snake); } - // Check if custom field property + // Custom-field / meta look-ups if ($this->isCustomField($property)) { return $this->getCustomFieldValueByKey($property); } - // Check if meta attribute if ($this->hasMeta($property)) { return $this->getMeta($property); } + // Fallback deep-path access return data_get($this, $property); } diff --git a/server/src/Models/Payload.php b/server/src/Models/Payload.php index b685cc417..a3e0d4ba5 100644 --- a/server/src/Models/Payload.php +++ b/server/src/Models/Payload.php @@ -185,6 +185,14 @@ public function currentWaypoint() return $this->belongsTo(Place::class, 'current_waypoint_uuid')->withoutGlobalScopes(); } + /** + * The current waypoint of the payload in progress. + */ + public function currentWaypointMarker() + { + return $this->belongsTo(Waypoint::class, 'current_waypoint_uuid', 'place_uuid')->withoutGlobalScopes(); + } + /** * Waypoints between start and end. * From 7f97b99f47b2df4e207dee531b532714d2c4b1a9 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 26 May 2025 10:54:51 +0800 Subject: [PATCH 2/3] patch internal order creation properly load tracking number and payload after order creation, move processes to background queue for faster order creation --- .../Console/Commands/DebugOrderTracker.php | 10 +++---- .../Api/v1/ServiceAreaController.php | 4 ++- .../Internal/v1/OrderController.php | 27 ++++++++++--------- server/src/Http/Resources/v1/Waypoint.php | 2 +- server/src/Models/Order.php | 13 ++++++--- server/src/Models/Payload.php | 4 +-- server/src/Support/Geocoding.php | 2 -- 7 files changed, 36 insertions(+), 26 deletions(-) diff --git a/server/src/Console/Commands/DebugOrderTracker.php b/server/src/Console/Commands/DebugOrderTracker.php index adf3411ce..71aff64a6 100644 --- a/server/src/Console/Commands/DebugOrderTracker.php +++ b/server/src/Console/Commands/DebugOrderTracker.php @@ -29,11 +29,11 @@ class DebugOrderTracker extends Command */ public function handle() { - $order = Order::where('public_id', 'order_n227274')->first(); - if ($order) { - $tracker = new OrderTracker($order); - dd($tracker->getOrderProgressPercentage()); - } + // $order = Order::where('public_id', 'order_n227274')->first(); + // if ($order) { + // $tracker = new OrderTracker($order); + // dd($tracker->getOrderProgressPercentage()); + // } return Command::SUCCESS; } diff --git a/server/src/Http/Controllers/Api/v1/ServiceAreaController.php b/server/src/Http/Controllers/Api/v1/ServiceAreaController.php index 2553272d8..efd1115fa 100644 --- a/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +++ b/server/src/Http/Controllers/Api/v1/ServiceAreaController.php @@ -66,7 +66,9 @@ public function create(CreateServiceAreaRequest $request) try { $serviceArea = ServiceArea::create($input); } catch (\Throwable $e) { - dd($e->getMessage()); + logger()->error('Unable to create service area.', ['error' => $e->getMessage()]); + + return response()->apiError('Failed to create service area.'); } // response the driver resource diff --git a/server/src/Http/Controllers/Internal/v1/OrderController.php b/server/src/Http/Controllers/Internal/v1/OrderController.php index c25040173..d7ccb59ec 100644 --- a/server/src/Http/Controllers/Internal/v1/OrderController.php +++ b/server/src/Http/Controllers/Internal/v1/OrderController.php @@ -167,25 +167,28 @@ function (&$request, Order &$order, &$requestInput) { ); } - // notify driver if assigned - $order->notifyDriverAssigned(); + // Run background processes on queue + dispatch(function () use ($order, $serviceQuote): void { + // notify driver if assigned + $order->notifyDriverAssigned(); - // set driving distance and time - $order->setPreliminaryDistanceAndTime(); + // set driving distance and time + $order->setPreliminaryDistanceAndTime(); - // if service quote attached purchase - $order->purchaseServiceQuote($serviceQuote); + // if service quote attached purchase + $order->purchaseServiceQuote($serviceQuote); - // dispatch if flagged true - $order->firstDispatchWithActivity(); + // dispatch if flagged true + $order->firstDispatchWithActivity(); - // load tracking number - $order->load(['trackingNumber']); + // Trigger order created event + event(new OrderReady($order)); + })->afterCommit(); } ); - // Trigger order created event - event(new OrderReady($record)); + // Reload payload and tracking number + $record->load(['payload', 'trackingNumber']); return ['order' => new $this->resource($record)]; } catch (\Exception $e) { diff --git a/server/src/Http/Resources/v1/Waypoint.php b/server/src/Http/Resources/v1/Waypoint.php index 79df35100..76eed3e9c 100644 --- a/server/src/Http/Resources/v1/Waypoint.php +++ b/server/src/Http/Resources/v1/Waypoint.php @@ -58,7 +58,7 @@ public function toArray($request) 'owner' => $this->when(!Http::isInternalRequest(), Resolve::resourceForMorph($this->owner_type, $this->owner_uuid)), 'tracking_number' => $this->whenLoaded('trackingNumber', $waypoint->trackingNumber), 'customer' => $this->setCustomerType(Resolve::resourceForMorph($waypoint->customer_type, $waypoint->customer_uuid)), - 'type' => $this->type, + 'type' => $waypoint->type ?? $this->type, 'meta' => data_get($this, 'meta', Utils::createObject()), 'eta' => $this->eta, 'updated_at' => $this->updated_at, diff --git a/server/src/Models/Order.php b/server/src/Models/Order.php index 6ebcfb9aa..68dfb2955 100644 --- a/server/src/Models/Order.php +++ b/server/src/Models/Order.php @@ -1395,8 +1395,6 @@ public function updateStatus($code = null) $activity = $flow->firstWhere('code', $code); } - // dd($activity); - if (!Utils::isActivity($activity)) { return false; } @@ -1765,7 +1763,8 @@ public function resolveDynamicProperty(string $property) $payloadMap = [ 'pickup' => 'payload.pickup', 'dropoff' => 'payload.dropoff', - 'currentWaypoint' => 'payload.currentWaypoint', + 'waypoint' => 'payload.currentWaypointMarker', + 'currentWaypoint' => 'payload.currentWaypointMarker', 'currentWaypointMarker' => 'payload.currentWaypointMarker', ]; @@ -1781,6 +1780,14 @@ public function resolveDynamicProperty(string $property) // e.g. "address.city" part of "pickup.address.city" $subKey = Str::after($property, $root . '.'); + // if waypoint we can do "waypoint.place.address" or "waypoint.address" for resolution + $isWaypointMarker = Str::startsWith($root, 'currentWaypoint') || $root === 'waypoint'; + if ($isWaypointMarker && $target instanceof Waypoint) { + $target->loadMissing('place'); + + return data_get($target, $subKey) ?? data_get($target->place, $subKey); + } + return data_get($target, $subKey); } diff --git a/server/src/Models/Payload.php b/server/src/Models/Payload.php index a3e0d4ba5..7af2d7a27 100644 --- a/server/src/Models/Payload.php +++ b/server/src/Models/Payload.php @@ -313,7 +313,7 @@ public function setWaypoints($waypoints = []) } foreach ($waypoints as $index => $attributes) { - $waypoint = ['payload_uuid' => $this->payload_uuid]; + $waypoint = ['payload_uuid' => $this->payload_uuid, 'type' => data_get($attributes, 'type', 'dropoff')]; if (Utils::isset($attributes, 'place') && is_array(Utils::get($attributes, 'place'))) { $attributes = Utils::get($attributes, 'place'); @@ -363,7 +363,7 @@ public function insertWaypoints($waypoints = []) } foreach ($waypoints as $index => $attributes) { - $waypoint = ['payload_uuid' => $this->uuid, 'order' => $index]; + $waypoint = ['payload_uuid' => $this->uuid, 'order' => $index, 'type' => data_get($attributes, 'type', 'dropoff')]; if (Utils::isset($attributes, 'place') && is_array(Utils::get($attributes, 'place'))) { $placeAttributes = Utils::get($attributes, 'place'); diff --git a/server/src/Support/Geocoding.php b/server/src/Support/Geocoding.php index bff2783af..0c63106e4 100644 --- a/server/src/Support/Geocoding.php +++ b/server/src/Support/Geocoding.php @@ -209,8 +209,6 @@ public static function query(string $searchQuery, $latitude, $longitude): Collec throw $e; } - // dd($reverseQueryResults, $geodingQueryResults); - return $reverseQueryResults->merge($geodingQueryResults)->unique('street1'); } From ec295535173cc63c989a567caa52e3ce8f060cce Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Mon, 26 May 2025 13:12:25 +0800 Subject: [PATCH 3/3] patch notifications settings saving, hotfix dragging on marker component --- addon/components/leaflet-tracking-marker.js | 19 ++++++++ addon/components/live-map.hbs | 5 ++ .../operations/orders/index/new.js | 47 ++++++++++--------- .../operations/orders/index/view.js | 42 +++++++++-------- .../Internal/v1/SettingController.php | 2 +- 5 files changed, 73 insertions(+), 42 deletions(-) diff --git a/addon/components/leaflet-tracking-marker.js b/addon/components/leaflet-tracking-marker.js index d26ec8359..1a1f9c514 100644 --- a/addon/components/leaflet-tracking-marker.js +++ b/addon/components/leaflet-tracking-marker.js @@ -1,6 +1,16 @@ import MarkerLayer from 'ember-leaflet/components/marker-layer'; import { isArray } from '@ember/array'; +const __draggingHotfix = (layer) => { + if (!layer.dragging) { + layer.dragging = { + enabled: () => false, + enable: () => (layer.options.draggable = true), + disable: () => (layer.options.draggable = false), + }; + } +}; + const arrayFromLatLng = (latlng) => { if (isArray(latlng)) { return latlng; @@ -52,6 +62,7 @@ L.TrackingMarker = L.Marker.extend({ this._slideKeepAtCenter = false; this._slideDraggingWasAllowed = false; this._slideFrame = 0; + __draggingHotfix(this); }, slideTo: function (latlng, options = {}) { @@ -69,6 +80,7 @@ L.TrackingMarker = L.Marker.extend({ this._nextPosition = arrayFromLatLng(latlng); this._slideKeepAtCenter = !!options.keepAtCenter; this._slideDraggingWasAllowed = this._slideDraggingWasAllowed !== undefined ? this._slideDraggingWasAllowed : this._map.dragging.enabled(); + __draggingHotfix(this); if (this._slideKeepAtCenter) { this._map.dragging.disable(); @@ -245,6 +257,13 @@ export default class LeafletTrackingMarkerComponent extends MarkerLayer { */ rotationAngle = 0; + /** + * Default value for the draggable prop. + * + * @memberof LeafletTrackingMarkerComponent + */ + draggable = false; + getOption(key, defaultValue = null) { const value = this.options[key] ?? this[key]; if (value === undefined) { diff --git a/addon/components/live-map.hbs b/addon/components/live-map.hbs index 19d0ce9b3..2baf8592e 100644 --- a/addon/components/live-map.hbs +++ b/addon/components/live-map.hbs @@ -38,6 +38,7 @@ @icon={{icon iconUrl=driver.vehicle_avatar iconSize=(array 24 24)}} @onAdd={{fn this.triggerAction "onDriverAdded" driver}} @onClick={{fn this.triggerAction "onDriverClicked" driver}} + @draggable={{false}} as |marker| > @@ -77,6 +78,7 @@ @icon={{icon iconUrl=driver.vehicle_avatar iconSize=(array 24 24)}} @onAdd={{fn this.triggerAction "onDriverAdded" driver}} @onClick={{fn this.triggerAction "onDriverClicked" driver}} + @draggable={{false}} as |marker| > @@ -114,6 +116,7 @@ @icon={{icon iconUrl=vehicle.avatar_url iconSize=(array 24 24)}} @onAdd={{fn this.triggerAction "onVehicleAdded" vehicle}} @onClick={{fn this.triggerAction "onVehicleClicked" vehicle}} + @draggable={{false}} as |marker| > @@ -150,6 +153,7 @@ @icon={{icon iconUrl=vehicle.avatar_url iconSize=(array 24 24)}} @onAdd={{fn this.triggerAction "onVehicleAdded" vehicle}} @onClick={{fn this.triggerAction "onVehicleClicked" vehicle}} + @draggable={{false}} as |marker| > @@ -189,6 +193,7 @@ @alt={{place.address}} @onAdd={{fn this.triggerAction "onPlaceAdded" place}} @onClick={{fn this.triggerAction "onPlaceClicked" place}} + @draggable={{false}} as |marker| > diff --git a/addon/controllers/operations/orders/index/new.js b/addon/controllers/operations/orders/index/new.js index 1b682705d..ef3309e99 100644 --- a/addon/controllers/operations/orders/index/new.js +++ b/addon/controllers/operations/orders/index/new.js @@ -677,7 +677,7 @@ export default class OperationsOrdersIndexNewController extends BaseController { @action resetInterface() { if (this.leafletMap && this.leafletMap.liveMap) { - this.leafletMap.liveMap.show(['drivers', 'vehicles', 'routes']); + this.leafletMap.liveMap.showAll(); } } @@ -709,31 +709,35 @@ export default class OperationsOrdersIndexNewController extends BaseController { } @action removeRoutingControlPreview() { - const leafletMap = this.leafletMap; - const previewRouteControl = this.previewRouteControl; + return new Promise((resolve) => { + const leafletMap = this.leafletMap; + const previewRouteControl = this.previewRouteControl; - let removed = false; + let removed = false; - if (leafletMap && previewRouteControl instanceof RoutingControl) { - try { - previewRouteControl.remove(); - removed = true; - } catch (e) { - // silent - } - - if (!removed) { + if (leafletMap && previewRouteControl instanceof RoutingControl) { try { - leafletMap.removeControl(previewRouteControl); + previewRouteControl.remove(); + removed = true; } catch (e) { // silent } + + if (!removed) { + try { + leafletMap.removeControl(previewRouteControl); + } catch (e) { + // silent + } + } } - } - if (!removed) { - this.forceRemoveRoutePreview(); - } + if (!removed) { + this.forceRemoveRoutePreview(); + } + + resolve(true); + }); } @action forceRemoveRoutePreview() { @@ -1060,7 +1064,7 @@ export default class OperationsOrdersIndexNewController extends BaseController { } } - @action resetForm() { + @action async resetForm() { const order = this.store.createRecord('order', { meta: [] }); const payload = this.store.createRecord('payload'); const driversQuery = {}; @@ -1080,8 +1084,6 @@ export default class OperationsOrdersIndexNewController extends BaseController { const customFields = []; const customFieldValues = {}; - this.removeRoutingControlPreview(); - this.removeOptimizedRoute(); this.setProperties({ order, payload, @@ -1102,6 +1104,9 @@ export default class OperationsOrdersIndexNewController extends BaseController { customFields, customFieldValues, }); + + await this.removeRoutingControlPreview(); + this.removeOptimizedRoute(); this.resetInterface(); } diff --git a/addon/controllers/operations/orders/index/view.js b/addon/controllers/operations/orders/index/view.js index 38f34c90d..1d64ee58a 100644 --- a/addon/controllers/operations/orders/index/view.js +++ b/addon/controllers/operations/orders/index/view.js @@ -243,39 +243,41 @@ export default class OperationsOrdersIndexViewController extends BaseController this.customFieldGroups = customFieldGroups; } - @action resetView() { - this.removeRoutingControlPreview(); + @action async resetView() { + await this.removeRoutingControlPreview(); this.resetInterface(); } @action resetInterface() { - const liveMap = this.leafletMap ? this.leafletMap.liveMap : null; - if (liveMap) { - liveMap.reload(); - liveMap.showAll(); + if (this.leafletMap && this.leafletMap.liveMap) { + this.leafletMap.liveMap.showAll(); + this.leafletMap.liveMap.reload(); } } @action removeRoutingControlPreview() { - const { leafletMap, routeControl } = this; + return new Promise((resolve) => { + const { leafletMap, routeControl } = this; - if (routeControl instanceof RoutingControl) { - try { - routeControl.remove(); - } catch (e) { - // silent + if (routeControl instanceof RoutingControl) { + try { + routeControl.remove(); + } catch (e) { + // silent + } } - } - if (leafletMap instanceof L.Map) { - try { - leafletMap.removeControl(routeControl); - } catch (e) { - // silent + if (leafletMap instanceof L.Map) { + try { + leafletMap.removeControl(routeControl); + } catch (e) { + // silent + } } - } - this.forceRemoveRoutePreview(); + this.forceRemoveRoutePreview(); + resolve(true); + }); } @action forceRemoveRoutePreview() { diff --git a/server/src/Http/Controllers/Internal/v1/SettingController.php b/server/src/Http/Controllers/Internal/v1/SettingController.php index 945b99b12..6eabd9155 100644 --- a/server/src/Http/Controllers/Internal/v1/SettingController.php +++ b/server/src/Http/Controllers/Internal/v1/SettingController.php @@ -150,7 +150,7 @@ public function saveNotificationSettings(Request $request) if (!is_array($notificationSettings)) { throw new \Exception('Invalid notification settings data.'); } - $currentNotificationSettings = Setting::lookupCompany('notification_settings'); + $currentNotificationSettings = Setting::lookupCompany('notification_settings', []); Setting::configureCompany('notification_settings', array_merge($currentNotificationSettings, $notificationSettings)); return response()->json([