Skip to content

Commit 811aa71

Browse files
authored
Merge pull request #415 from fleetbase/release/v0.6.75
release: v0.6.75
2 parents 33032e5 + c1ee6e3 commit 811aa71

98 files changed

Lines changed: 5127 additions & 492 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎RELEASE.md‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,34 @@
1-
> v0.6.74 ~ "Settings-backed geocoding everywhere"
1+
> v0.6.75 ~ "Road distance and ETAs from one routing engine, tenant-isolated profile accounts and App Review access for Navigator"
22
33
---
44
## Highlights
55

6-
- **Fix: the console geocoder endpoints return results.** `int/v1/geocoder/query` and `int/v1/geocoder/reverse` returned `[]` for valid addresses whenever the Google Maps API key was set in the console instead of in the environment. This broke address lookup in the coordinates and autocomplete inputs, for example when editing a place. Both endpoints now use the same geocoder as `places/lookup`. (#408, fleetbase/fleetbase#723)
7-
- **Fix: every other geocoding path uses the settings-backed key.** Place creation from an address or coordinates, place search, address-only place creation on the public API, and driver tracking locality all go through the same geocoder. Failed lookups are now logged instead of hidden. (#409)
8-
- **Fix: `config/geocoder.php` passes the region rather than the locale to Google Maps, and uses the default cache store.** (#409)
6+
- **Feature: one routing engine for order distance, ETAs and the map.** An order's stored distance and time, its live ETAs and remaining distance, and the route drawn on the map now all come from the organization's routing engine. Before this, order distance defaulted to a straight-line estimate at a flat ~50 km/h; a reported ~30 km run was stored as 25.0 km / 30 min and is now 32.1 km / 32 min. The engine is chosen in order: the organization's routing engine, then the admin default, then the platform's Valhalla server, then the straight-line estimate. An engine only counts if it is allowed and configured. A failed road lookup falls back to the straight-line estimate and is logged, instead of storing 0. Road results are cached for 24 hours. (#406)
7+
- **Feature: routing provider controls in Admin → Fleet-Ops → Routing.** Administrators choose which providers organizations may use (Google, Valhalla, OSRM, VROOM), with per-organization exceptions. They also set the default routing and optimization engines, and whether server-side routing uses roads or only the straight-line estimate. Saved admin settings take precedence over environment variables. **Google is off until an administrator enables it**, for map routing, optimization, order distance and ETAs. The public OSRM demo server is never called by the server. The admin menu entry "Fleet-Ops Config" is now "Fleet-Ops". (#406)
8+
- **Feature: the OSRM server is a setting, for the platform and per organization.** Administrators set it in Admin → Fleet-Ops → Routing → OSRM Server; `OSRM_HOST` is now only the starting value. Organizations that route or optimize with OSRM can use their own server under Fleet-Ops Settings → Routing → OSRM Server, like the Valhalla and VROOM overrides. An organization host must be a public address (`OSRM_ALLOW_PRIVATE_HOSTS=true` lifts that on self-hosted installs). Order distance, ETAs, tracking and the console's OSRM routes and route control all use the resolved server. The public demo server still only draws map routes.
9+
- **Security: an organization's routing host can't point at a private address.** Order distance, ETAs and VROOM allocation ignore an organization's Valhalla or VROOM override whose host is loopback, a private or link-local network, cloud metadata or another non-public range, and use the system server instead. This also covers values saved before the Valhalla and VROOM extensions started rejecting them. (#417)
10+
- **Security: a profile never links to another organization's login account.** Creating a driver, customer or contact with an email or phone that belongs to another organization's account linked that account and exposed its name, email and phone. The new organization could then reset its password or deactivate it. Only accounts that already belong to the organization, or belong to no organization, are reused now. The refusal does not say whose account it is. `POST /v1/drivers` also ignores a `company` that is not the API credential's own. Logins already shared by the old behaviour can only be managed by their holder. (#414)
11+
- **Fix: orders can be edited with a partial payload.** `PUT /v1/orders/{id}` accepts a `payload` with only the parts that change, such as only entities or only a new dropoff. (#411)
12+
- **Fix: addresses sent with coordinates keep their street.** A place given as a street address plus `latitude`/`longitude` was reverse-geocoded and stored as a different nearby address. The street and the coordinates are now kept as sent, without a geocoding call. (#411)
13+
- **Feature: idempotent order creation.** `POST /v1/orders` accepts an `Idempotency-Key` header. A retry with the same key within 24 hours returns the order already created, with `Idempotent-Replayed: true`, and a concurrent duplicate gets `409`. (#412)
14+
- **Fix: `scheduled_at` keeps its UTC offset.** `2026-10-21T11:00:00-07:00` is now stored as 18:00 UTC instead of 11:00, and `PUT /v1/orders/{id}/schedule` stores the UTC instant of a date and time in the company timezone. Rows written before this keep their old values. (#412)
15+
- **Fix: service quotes.** The `currency` filter works again, stored quotes expire after 48 hours by default (`fleetops.service_quotes.ttl_minutes`), and `preview=1` prices a request without storing quote rows. (#412)
16+
- **Fix: tracking numbers with a space can be looked up again.** Since v0.6.72, a number such as `AL 7796486490AE`, generated for a company whose name has a space in its first three letters, got "not found". Single inner spaces are accepted now. New numbers only use letters and digits: the company prefix skips spaces and punctuation ("Al Noor" gives `ALN`), and a region such as "New York" becomes `NEWYORK`. (#413, #416)
17+
- **Feature: App Review access for Navigator.** Apple and Google reviewers can't receive the texted sign-in code. An organization can turn on **Settings → Navigator App → App Review**: reviewers sign in to its Navigator with the test number +1 202 555 0144 (a range kept for fiction, never texted) and a six-digit code the owner creates and enters in App Store Connect and Google Play Console. The code is stored hashed, shown once, and only works for that organization. The first sign-in adds an **App Reviewer** driver, which ad-hoc offers, nearest-driver assignment and the orchestrator always leave out. Built on core-api's App Review access; the `NAVIGATOR_*` review env settings still work on self-hosted installs. See `docs/NAVIGATOR_APP_REVIEW.md`. (#418)
918

1019
---
1120
## Upgrading
12-
- No migrations or commands.
21+
- **Requires fleetbase/core-api v1.6.74** (`composer.json` now requires `^1.6.74`), which adds App Review access and its `review_accesses` table: run `php artisan migrate`.
22+
- **Routing defaults changed (#406).** Review **Admin → Fleet-Ops → Routing** after upgrading:
23+
- The default routing engine is now **Valhalla** (it was OSRM). Without a Valhalla server (`VALHALLA_HOST`, or the Valhalla extension's settings), order distance and ETAs use the straight-line estimate. Self-hosters running their own OSRM can make it the default in the panel or with `ROUTING_DISPLAY_ENGINE=osrm`.
24+
- **Google is off.** Turn it on in the panel or set `ROUTING_GOOGLE_ENABLED=true`. `DISTANCE_MATRIX_PROVIDER=google` also enables it until the panel is saved.
25+
- Tracking follows the routing engine. `TRACKING_PROVIDER` and `TRACKING_PROVIDER_FALLBACKS` are no longer read, and saved tracking provider and fallback choices are ignored.
26+
- `DISTANCE_MATRIX_PROVIDER` only seeds the admin defaults now; it no longer overrides order distance.
27+
- New starting values, read until the panel is first saved: `ROUTING_GOOGLE_ENABLED`, `ROUTING_DISPLAY_ENGINE`, `ROUTING_OPTIMIZATION_ENGINE`, `ROUTING_SERVER_ROUTING`, plus `VALHALLA_HOST`, `VALHALLA_API_KEY`, `VALHALLA_COSTING` and `VALHALLA_TIMEOUT`.
28+
- Existing orders: started, unfinished orders from the last 2 days are re-measured by `fleetops:update-estimations`; unstarted orders pick up road distance at their next ready or edit; completed and cancelled orders keep their stored values. No migration.
29+
- **OSRM server.** Nothing to do: `OSRM_HOST` keeps working until a host is saved in Admin → Fleet-Ops → Routing. Organization OSRM servers are new and empty.
30+
- **Private routing hosts (#417).** On self-hosted installs, an organization override pointing at a private host (for example `http://valhalla:8002`) now falls back to the system server. Set `VALHALLA_ALLOW_PRIVATE_HOSTS=true` or `VROOM_ALLOW_PRIVATE_HOSTS=true` to allow it. System-level private hosts are unaffected.
31+
- **Fleetbase Cloud.** After deploying, set the Valhalla host to `https://valhalla.routing.fleetbase.io` with Cloud's gateway key, Google and OSRM off, and Valhalla as the default engine with road routing. Enter the gateway key only after fleetbase/valhalla v0.0.5 and fleetbase/vroom v0.0.5 ship (valhalla#9, vroom#8), because the extensions' settings endpoints return the key to any signed-in user until then.
1332

1433
---
1534
## Need help?

‎addon/components/admin/routing-settings.hbs‎

Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,123 @@
11
<div class="space-y-6">
22
<InfoBlock @type="info" @icon="circle-info" class="mb-4">
3-
Configure system-wide routing engine defaults here. Organizations can optionally override these values from their own routing settings.
3+
Configure system-wide routing engine defaults here. Organizations can optionally override these values from their own routing settings, but only with providers that are switched on
4+
below.
45
</InfoBlock>
56

6-
<ContentPanel @title="Tracking Defaults" @open={{true}} @wrapperClass="bordered-classic">
7-
<InputGroup @name="Default Tracking Provider" @helpText="Default provider for organization-level tracking intelligence unless an organization overrides it.">
7+
<ContentPanel @title="Provider Access" @open={{true}} @wrapperClass="bordered-classic">
8+
<p class="mb-4 text-sm text-gray-600 dark:text-gray-400">
9+
Choose which providers organizations may use for map routing, route optimization, order distance and tracking. A provider switched off here is never called, even if an
10+
organization selected it; they fall back to the defaults below. Changes apply on every server within 30 seconds.
11+
</p>
12+
<div class="space-y-3">
13+
{{#each this.providerAccessRows as |provider|}}
14+
<div class="flex items-center justify-between rounded-md border border-gray-200 px-3 py-2 dark:border-gray-700" data-test-provider-access={{provider.key}}>
15+
<div>
16+
<div class="text-sm font-semibold">{{provider.label}}</div>
17+
{{#if provider.billed}}
18+
<div class="text-xs text-red-600 dark:text-red-400">Billed per request to the platform's Google Maps key.</div>
19+
{{/if}}
20+
</div>
21+
<Toggle @isToggled={{provider.enabled}} @onToggle={{fn this.toggleProvider provider.key}} />
22+
</div>
23+
{{/each}}
24+
</div>
25+
</ContentPanel>
26+
27+
<ContentPanel @title="Routing Defaults" @open={{true}} @wrapperClass="bordered-classic">
28+
<InputGroup
29+
@name="Default Routing Engine"
30+
@helpText="Used for everything route, distance and time for organizations that have not chosen their own: map routes, each order's distance and time, and live ETAs while it is in progress."
31+
>
32+
<Select
33+
class="w-full"
34+
@value={{this.defaultDisplayEngine}}
35+
@options={{this.displayEngineOptions}}
36+
@optionLabel="name"
37+
@optionValue="key"
38+
@placeholder="Select default routing engine"
39+
@onSelect={{fn (mut this.defaultDisplayEngine)}}
40+
/>
41+
</InputGroup>
42+
<InputGroup @name="Default Route Optimization Engine" @helpText="Decides the order stops are visited in, for organizations that have not chosen their own.">
43+
<Select
44+
class="w-full"
45+
@value={{this.defaultOptimizationEngine}}
46+
@options={{this.optimizationEngineOptions}}
47+
@optionLabel="name"
48+
@optionValue="key"
49+
@placeholder="Select default route optimization engine"
50+
@onSelect={{fn (mut this.defaultOptimizationEngine)}}
51+
/>
52+
</InputGroup>
53+
<InputGroup
54+
@name="Server-side Routing"
55+
@helpText="Straight-line stops every routing call for order distance, time and live ETAs at once, for every organization. Map routes are not affected."
56+
>
857
<Select
958
class="w-full"
10-
@value={{this.trackingProvider}}
11-
@options={{this.trackingProviderOptions}}
59+
@value={{this.serverRouting}}
60+
@options={{this.serverRoutingOptions}}
1261
@optionLabel="label"
1362
@optionValue="value"
14-
@placeholder="Select default tracking provider"
15-
@onSelect={{fn (mut this.trackingProvider)}}
63+
@onSelect={{fn (mut this.serverRouting)}}
64+
data-test-server-routing
1665
/>
1766
</InputGroup>
18-
<InputGroup @name="Default Fallback Providers" @helpText="Providers used when the selected tracking provider is unavailable.">
19-
<div class="fleetbase-model-select fleetbase-power-select ember-model-select">
20-
<PowerSelectMultiple
21-
@searchEnabled={{true}}
22-
@searchField="label"
23-
@options={{this.trackingProviderOptions}}
24-
@selected={{this.selectedTrackingFallbackOptions}}
25-
@onChange={{this.setTrackingFallbacks}}
26-
@placeholder="Select default fallback providers"
27-
@triggerClass="form-select form-input form-input-sm flex-1"
28-
as |provider|
29-
>
30-
{{provider.label}}
31-
</PowerSelectMultiple>
32-
</div>
67+
</ContentPanel>
68+
69+
<ContentPanel @title="OSRM Server" @open={{true}} @wrapperClass="bordered-classic">
70+
<InputGroup @name="OSRM Host" @helpText={{this.osrmHelpText}}>
71+
<Input @value={{this.osrmHost}} placeholder="https://osrm.example.com" class="w-full form-input" data-test-osrm-host />
3372
</InputGroup>
73+
</ContentPanel>
74+
75+
<ContentPanel @title="Organization Exceptions" @open={{true}} @wrapperClass="bordered-classic">
76+
<p class="mb-4 text-sm text-gray-600 dark:text-gray-400">
77+
Switch a provider on for specific organizations only, for example Google for a customer who pays for it.
78+
</p>
79+
<div class="space-y-3">
80+
{{#each this.providerExceptionRows as |row|}}
81+
<div class="flex items-center gap-3 rounded-md border border-gray-200 px-3 py-2 dark:border-gray-700" data-test-provider-exception={{row.exception.company_uuid}}>
82+
<div class="w-1/3 truncate text-sm font-semibold">{{row.exception.company_name}}</div>
83+
<div class="fleetbase-model-select fleetbase-power-select ember-model-select flex-1">
84+
<PowerSelectMultiple
85+
@options={{this.exceptionProviderOptions}}
86+
@selected={{row.selected}}
87+
@onChange={{fn this.setExceptionProviders row.exception}}
88+
@placeholder="Select providers"
89+
@triggerClass="form-select form-input form-input-sm flex-1"
90+
as |provider|
91+
>
92+
{{provider.label}}
93+
</PowerSelectMultiple>
94+
</div>
95+
<Button @type="danger" @size="xs" @icon="trash" @onClick={{fn this.removeProviderException row.exception}} />
96+
</div>
97+
{{else}}
98+
<div class="text-sm text-gray-500">No exceptions. Every organization follows Provider Access.</div>
99+
{{/each}}
100+
</div>
101+
<InputGroup @name="Add Organization" class="mt-4">
102+
<ModelSelect
103+
@modelName="company"
104+
@query={{hash view="admin"}}
105+
@placeholder="Search organizations"
106+
@triggerClass="form-select form-input"
107+
@infiniteScroll={{false}}
108+
@renderInPlace={{true}}
109+
@onChange={{this.addProviderException}}
110+
as |company|
111+
>
112+
{{company.name}}
113+
</ModelSelect>
114+
</InputGroup>
115+
</ContentPanel>
116+
117+
<ContentPanel @title="Tracking Behaviour" @open={{true}} @wrapperClass="bordered-classic">
118+
<p class="mb-2 text-sm text-gray-600 dark:text-gray-400">
119+
Live ETAs, remaining distance and route lines come from each organization's routing engine. These settings only tune their behaviour.
120+
</p>
34121
<div class="flex justify-end">
35122
<Button
36123
@type="default"
@@ -68,6 +155,7 @@
68155
<RegistryComponent />
69156
</div>
70157
</RegistryYield>
158+
<Spacer @height="200px" />
71159
</div>
72160

73161
<EmberWormhole @to="next-view-section-subheader-actions">
@@ -80,4 +168,4 @@
80168
@isLoading={{or this.saveSettings.isRunning this.loadSettings.isRunning}}
81169
@disabled={{this.saveSettings.isRunning}}
82170
/>
83-
</EmberWormhole>
171+
</EmberWormhole>

0 commit comments

Comments
 (0)