Merge pull request #245 from fleetbase/dev-v0.6.49 #2
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "steve.vandeheuvel/Fleetops Upgrade"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
v0.6.49
Introduces a full abstraction layer that decouples FleetOps from Leaflet-specific APIs, enabling multiple map providers to coexist and be selected at runtime. Google Maps is implemented as the first alternative provider with 1-to-1 feature parity. ## New files ### Core abstraction - addon/services/map-adapter-interface.js Abstract base class defining the ~30-method contract all adapters must implement (viewport, markers, overlays, drawing, popups, context menus, events, utilities). - addon/services/map-manager.js Provider-agnostic central service that replaces leaflet-map-manager as the single point of contact for all map operations. Resolves the correct adapter from config/environment or runtime settings, then delegates every call. Exposes setActiveProvider() for runtime switching and waitForMap() for async initialisation. ### Adapters - addon/services/map-adapter/leaflet.js Wraps the existing Leaflet/ember-leaflet implementation. Preserves all existing behaviour: L.Marker with leaflet-marker-rotate, L.Polyline routing, leaflet-draw for geofences, leaflet-contextmenu, slideTo() smooth animation, and layer visibility panes. - addon/services/map-adapter/google.js Full Google Maps JavaScript API adapter. Implements every interface method using the Maps JS API v3: - Smooth marker animation via requestAnimationFrame interpolation (equivalent to Leaflet's slideTo) - Marker rotation via CSS transform on the icon element - google.maps.drawing.DrawingManager for geofence creation/editing - google.maps.InfoWindow-based context menus - google.maps.Polyline for route overlays - google.maps.Polygon / Circle for service area overlays - Traffic and Transit layer support - Map type switching (roadmap/satellite/hybrid/terrain) ### Components - addon/components/map/google-live-map.{js,hbs} Google Maps surface component. Initialised imperatively via did-insert. Mirrors all features of leaflet-live-map: driver/vehicle markers, service area polygons, route polylines, position playback, context menus, and drawing tools. - addon/components/map-settings.{js,hbs} Admin settings panel for selecting the map provider and configuring Google Maps options (API key, map type, traffic/transit layers). Settings are persisted via the new fleet-ops/settings/map API. ## Modified files ### Components - addon/components/map/leaflet-live-map.{js,hbs} Injects mapManager service alongside existing services. Template conditionally renders Map::GoogleLiveMap or the existing Leaflet branch based on mapManager.isGoogleMaps. All marker/polygon registration calls now go through mapManager so both adapters receive the same data. ### Services - addon/services/movement-tracker.js Replaces direct leafletLayer access with mapManager.updateMarkerPosition() and mapManager.setMarkerRotation(). Falls back gracefully when no marker is registered for a given model. - addon/services/position-playback.js Replaces marker.slideTo() / marker.setRotationAngle() with mapManager.updateMarkerPosition() / mapManager.setMarkerRotation(). The playback task is now provider-agnostic. - addon/services/geofence.js Replaces leafletMapManager.showDrawControl() / hideDrawControl() / editPolygon() with mapManager equivalents. ### Configuration - config/environment.js Adds mapProvider (default: 'leaflet'), googleMapsApiKey, and googleMapsLibraries config keys. All values are read from env vars (MAP_PROVIDER, GOOGLE_MAPS_API_KEY, GOOGLE_MAPS_LIBRARIES). - index.js Adds contentFor('head') hook that injects the Google Maps JS API script tag when mapProvider === 'google' and a key is configured. ### Server - server/src/Http/Controllers/Internal/v1/SettingController.php Adds getMapSettings() and saveMapSettings() methods. The Google Maps API key is stored in a separate protected setting key and is never returned in GET responses. - server/src/routes.php Registers GET/POST fleet-ops/settings/map routes. ### i18n - translations/en-us.yaml Adds map-settings.* translation keys for the new settings panel. ## App re-exports - app/services/map-manager.js - app/services/map-adapter-interface.js - app/services/map-adapter/leaflet.js - app/services/map-adapter/google.js - app/components/map-settings.js - app/components/map/google-live-map.js ## How to enable Google Maps 1. Set MAP_PROVIDER=google in your .env file 2. Set GOOGLE_MAPS_API_KEY=<your-key> in your .env file 3. Ensure the key has these APIs enabled in Google Cloud Console: - Maps JavaScript API - Drawing Library - Geometry Library - Geocoding API 4. Rebuild the frontend (pnpm build) Or configure at runtime via Settings > Map Settings in the FleetOps admin panel (no rebuild required for provider toggle). ## Adding a new map provider 1. Create addon/services/map-adapter/<name>.js extending MapAdapterInterface 2. Create app/services/map-adapter/<name>.js re-export 3. Set mapProvider: '<name>' in config/environment.js 4. No changes to any component or service are requiredWires the previously-created MapSettings component into the FleetOps settings section as a first-class route, following the exact same patterns used by the existing Routing and Notifications settings pages. ## New files ### Route layer - addon/routes/settings/map.js Minimal Ember route class (matches settings/routing.js pattern). - addon/controllers/settings/map.js Full controller with @tracked state for mapProvider, googleMapsApiKey, googleMapsMapType, googleMapsTrafficLayer, googleMapsTransitLayer. Implements getSettings and saveSettings ember-concurrency tasks that call the fleet-ops/settings/map API endpoints added in the previous commit. Applies the new provider to the live mapManager on save so the map switches without a page reload. - addon/templates/settings/map.hbs Full settings page template following the Layout::Section::Header + Layout::Section::Body + ContentPanel pattern used by all other settings routes. Includes: - Save button in the header (disabled while tasks run) - Map provider Select (Leaflet / Google Maps) - Conditional Google Maps options panel: - API key password input - Map type Select (roadmap / satellite / hybrid / terrain) - Traffic layer Toggle - Transit layer Toggle - Required Google Cloud APIs info box - Loading spinner while getSettings is running - RegistryYield extension point for third-party panels ### App re-exports (3 files) - app/routes/settings/map.js - app/controllers/settings/map.js - app/templates/settings/map.js ## Modified files ### Router - addon/routes.js Adds this.route('map') inside the settings route group, between routing and payments (alphabetical / logical order). ### Sidebar - addon/components/layout/fleet-ops-sidebar.js Adds a Map Settings item to the settingsItems array: - intl key: menu.map - icon: map - route: settings.map - permission: fleet-ops view map-settings Inserted between Routing and Custom Fields to maintain logical order. ### Translations - translations/en-us.yaml Adds two new translation blocks: 1. menu.map: "Map" — sidebar label 2. settings.map.* — 20 keys covering all labels, help texts, placeholders, and status messages used by the new route template. Keys mirror the settings.routing.* structure for consistency.The Google Maps API key is already managed at the system admin level through the core-api Settings → Services panel, which stores it at `config('services.google_maps.api_key')` via `Setting::configureSystem`. FleetOps should not duplicate this responsibility. ## Backend (server/src/Http/Controllers/Internal/v1/SettingController.php) `getMapSettings` - Removed the separate `fleet-ops.map-settings.google-api-key` lookup. - Now reads `config('services.google_maps.api_key', env('GOOGLE_MAPS_API_KEY', ''))` — the same key managed by core-api — and includes it in the response so the frontend Google Maps adapter can initialise correctly. - Single source of truth: the key lives only in the system-level services config; FleetOps reads but never stores it. `saveMapSettings` - Removed all API key acceptance and storage logic. - Any `googleMapsApiKey` field sent by a client is silently stripped before the settings blob is persisted, preventing accidental storage. - Comment updated to make the delegation to core-api explicit. ## Frontend (addon/controllers/settings/map.js) - Removed `@tracked googleMapsApiKey` property. - Removed `onApiKeyChange` action. - Removed the conditional API key inclusion from the `saveSettings` task payload — the key is no longer sent to the server from this page. - Removed the stale comment about the key not being returned by the server (it now is returned, sourced from the system config). - Fixed the `notifications.success` call to use the correct `settings.map.*` translation key namespace (was incorrectly referencing `map-settings.*`). ## Frontend (addon/templates/settings/map.hbs) - Removed the API key `<InputGroup>` block (password input + help text). - Removed the "Required Google Cloud APIs" info box — this information belongs in the core-api admin panel where the key is configured. - The Google Maps conditional section now shows only the map type selector and the traffic/transit layer toggles. ## Translations (translations/en-us.yaml) Removed 8 translation keys from the `settings.map` block that were exclusively used by the now-deleted API key input and info box: - `google-maps-api-key` - `google-maps-api-key-help-text` - `google-maps-api-key-placeholder` - `google-api-key-requirements-title` - `google-api-key-requirement-maps-js` - `google-api-key-requirement-drawing` - `google-api-key-requirement-geometry` - `google-api-key-requirement-geocoding`setupfromsetupControllerhook d1fd13e9c9order/details/purchase-ratecomponent f3da6f5912dropdownButtonRenderInPlacearg from fleet-listing-panel component 50b231531eIntroduces a B2C customer surface directly in FleetOps so portals can authenticate end-customers with the existing flb_live_… API credential — no Storefront publishable-key + Store/Network coupling required. Endpoints (all under /v1/customers/...): Public (API credential only): POST request-creation-code send email/SMS verification code POST / create Contact+User after verifying code POST login email/phone + password → Sanctum token POST login-with-sms send login code (SMS, falls back to email) POST verify-code verify code → Sanctum token POST forgot-password send reset code POST reset-password verify reset code + set new password Authenticated (require Customer-Token): GET me / PUT me profile read/update (mirrors to linked User) POST logout / logout-all revoke current / all tokens for this user GET orders scoped Order::where('customer_uuid', …) POST orders create freight order with customer_uuid set GET orders/{id} owner-checked order detail GET places customer's saved Places POST register-device push-token registration for linked User Implementation: - Tokens are Sanctum PersonalAccessToken with `name` = Contact UUID (matches Storefront convention so SDKs and headers are interchangeable). - AuthenticateCustomerToken middleware verifies the Customer-Token header and cross-checks the resolved Contact's company_uuid against the API credential's session('company'); 401/403 on mismatch. - CustomerAuth helper resolves the token with a company-preferred fallback for the multi-company edge case. - Customer model is a thin Contact specialization with type=customer. - Verification slugs are fleetops_* (create_customer, customer_login, customer_password_reset) — no Storefront slugs reused. - No new tables, no migrations: contacts.email/phone/user_uuid, orders.customer_uuid, and personal_access_tokens already cover it. - OAuth providers (Apple/Google/Facebook) deferred to a follow-up. Static-shape tests live in server/tests/CustomerEndpointTest.php and follow the package's existing pest convention. End-to-end HTTP tests belong in the parent api/ harness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Two related bugs in the freshly-added v1/customers endpoints surfaced on first live test (HAR shows: 400 from /v1/customers/request-creation-code with "Attempt to read property 'name' on null (View: .../mail/verification.blade.php)"). Bug 1: requestCreationCode passed an unsaved Contact as the verification subject, so the morphTo subject_uuid was null. The verification mail template references {{ $user->name }} via the morphTo relation, which resolves null → fatal in blade. Fix: look up an existing User by identity, or stub-create one with `name = "Pending Customer"`. The stub gives the mail renderer a real record to greet, and create() backfills name + password on verification. Bug 2: `password` and `type` are guarded on the User model, so `User::create([... 'password' => ..., 'type' => 'customer'])` silently dropped both fields. Customers created via signup would therefore have no password and no type — login would fail. Fix: assign password via `$user->password = $plaintext` (the model's setPasswordAttribute mutator hashes) and set type via `setUserType()`. Also stop double-hashing in resetPassword (was Hash::make then mutator). Also makes create() idempotent on the Contact: reuse the existing customer-Contact for (user, company) instead of crashing on the second signup attempt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>The signup form already collected the customer's name (and phone) by the time it asked for a verification code. Plumb those through so: - the verification email greets the customer by their real name - the pre-created User row holds real values instead of the "Pending Customer" placeholder - create() doesn't need to overwrite stub values on confirmation VerifyCreateCustomerRequest now accepts optional `name` and `phone`; requestCreationCode uses them when stub-creating the User, and refreshes the row's name when an existing pending stub is re-prompted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>When `POST /v1/customers` receives a home-address payload (either as a top-level `address` object or nested under `meta.address`), create a Place record: - company_uuid = the API credential's company - owner_uuid / owner_type = the new customer Contact (polymorphic) - type = "residential" - Both Storefront-style (street1/province/postal_code) and portal-form-style (line1/state/zip) keys are accepted. Then link the Place to the customer via Contact.place_uuid so it appears as the default address and `GET /v1/customers/places` returns it. Idempotent: only creates a Place when the Contact has no place_uuid set and the payload has at least one usable address field. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Two additive, canonical extensions to the public Fleet-Ops API surface, both convention-aligned with existing resources: 1. OrderConfigs as a first-class read-only public resource (mirrors how Places, Vendors, Contacts, Orders are already exposed): GET /v1/order-configs — list configs for the company GET /v1/order-configs/{id} — find by uuid|public_id|namespace|key `find()` defers to `OrderConfig::resolveFromIdentifier()` so callers can use `/transport`, the namespace, the public_id, or the uuid. A new `v1/OrderConfig` resource projects only the public-safe shape: id, key, name, namespace, description, tags, status, version, and the activity `flow[]` carrying `{code, status, details, color, complete, pod_method, require_pod}`. Internal-only fields (raw entities JSON, flow logic blocks) are filtered out so the public response is small, safe, and useful for drivers/portals/integrations that need to render status chips and activity labels from the canonical config. 2. `company` sub-object on the `Customer` resource (returned by /v1/customers/me, /login, /signup, /verify-code, etc.). Resolves currency through the existing canonical helper `Utils::getCompanyTransactionCurrency()` which already does the `companies.currency` → ledger `base_currency` → "USD" chain. The sub-object exposes id, name, currency, country, phone — same fields any caller could already discover via other channels, just bundled conveniently so authenticated customer apps don't need a separate request to render currency labels or contact info. No new write surface, no new auth requirements, no client-portal aliases. Both endpoints require only the public Fleet-Ops API key, like the existing /v1/tracking-numbers/{n} public read endpoint. Static-shape tests assert: - order-configs routes register both methods - OrderConfigController exposes only read-only methods (no create/ update/delete on the public surface) - OrderConfig resource emits `flow[]` with the canonical keys and never exposes the raw entities JSON - Customer resource exposes `company` and uses `Utils::getCompanyTransactionCurrency` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Adds an optional `service_quote` field to the customer order create endpoint, matching how `OrderController::create` resolves quotes: - CreateCustomerOrderRequest accepts a `service_quote` string (uuid or `sqte_…` public_id). - `createOrder()` resolves it via `ServiceQuote::resolveFromRequest` and calls `$order->purchaseServiceQuote()` after creation so the PurchaseRate is locked onto the order with the quoted pricing. This lets customer portals pull live quotes from `GET /v1/service-quotes`, present them to the customer, and submit the chosen one when creating the order — same flow operators use. No write-surface changes beyond accepting one more optional field; all existing tests + canonical-shape assertions still hold. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>vehicle/pillcomponent 10589ed55e