Skip to content

Custom field persistence fixes and order reporting schema - #342

Open
roncodes wants to merge 6 commits into
mainfrom
feature/ember-ui-coverage-campaign
Open

roncodes wants to merge 6 commits into
mainfrom
feature/ember-ui-coverage-campaign

Conversation

@roncodes

@roncodes roncodes commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Summary

Custom field values were never persisted for fuel reports or service areas. The upload landed and the record saved, but the custom_field_values rows were silently dropped.

Ember Data's REST serializer sends a record under a camelCase root (fuelReport, serviceArea). The base controller's payload reader accepts both spellings, so ordinary attributes saved fine, but these two afterSave hooks read $request->array('fuel_report.custom_field_values') / 'service_area.custom_field_values' from the raw request, which was always empty. Single-word resources (driver, vehicle, order, …) were unaffected because their root spells the same either way.

Change

Both hooks now read the values from the payload the base controller already extracted, which the after hook receives as its third argument. The root key's spelling no longer matters.

Context

Found while testing the ember-ui signature pad custom field on a fuel report (ember-ui feature/code-coverage-campaign). This branch carries the fleetops side of that testing round.

Verification

  • php -l passes on both controllers inside the API container.
  • Manual: sign a fuel report custom field, press Done, save, and confirm a row appears in custom_field_values and view mode shows the signature.

Order reporting schema

This branch also reworks the fleet-ops report schema (server/src/Support/Reporting/FleetOpsReportSchema.php) so the report builder can answer basic order questions such as "which products sold the most this month?" and "what did orders total this month?". It needs fleetbase/core-api#269.

Orders

  • Identifiers: the order's ID and Internal ID are now selectable. Before, core-api hid every *_id column as a foreign key, so the ID never appeared.
  • Order columns added:
    • customer and facilitator kind;
    • dispatched and started flags;
    • time window;
    • ad hoc distance, POD method, route optimized, priority and notes.
  • Relationships added:
    • Tracking: tracking number, region and latest tracking status;
    • Order Config, Customer Vendor, Created By, Purchase Rate → Service Quote, and Payload Return.
  • Payload: now has its own columns, and exposes its items (payload.entities) with name, SKU, price, dimensions, metadata and destination. Selecting an item column gives one row per item; group by an item column to summarise per product.
  • Summary columns:
    • Total/Completed/Canceled Orders are distinct counts, so they stay correct when item rows are selected;
    • distance and duration totals/averages;
    • transaction amount sum/average, which now sum transaction.amount (they summed a column that doesn't exist on orders).
  • Labels:
    • a table's own columns drop the table name ("Order ID" → "ID", "Order Type" → "Type");
    • distance and duration are labelled in meters and seconds, which is how they're stored;
    • money columns are marked "(minor units)".
  • No meta assumptions: meta has no fixed shape, so no column assumes a key in it. meta stays selectable as raw JSON on orders and items, and reports read keys from it with computed columns, for example CAST(JSON_UNQUOTE(JSON_EXTRACT(payload.entities.meta, '$.quantity')) AS DECIMAL(15,2)).

Other tables

The old schema referenced 13 columns or join keys that don't exist in the database:

  • Drivers: declared name/email/phone and a current_vehicle_uuid join. These now come from the user and vehicle relationships.
  • Vehicles: joined drivers on a non-existent current_driver_uuid.
  • Fuel Reports: declared cost, odometer_reading and report_date. It now uses amount, odometer, metric_unit and created_at.

Every table, and the item join, now leaves soft-deleted rows out.

Compatibility

CI installs core-api 1.6.55 from composer.lock. The schema still registers there, because softDeletes() is only called when the installed core-api supports it. The schema tests pass against both 1.6.55 and the core-api branch.

Verification

  • Every one of the 338 declared columns and join keys exists in the dev MySQL schema. The old schema had 13 that didn't.
  • Against 76 real storefront orders on MySQL in strict mode, these return correct results through the API:
    • top products (grouping by item name and summing a computed quantity from item meta);
    • monthly totals;
    • an order list with ID, tracking number and items.
  • ReportSchemaContractsTest/ProviderContractsTest pass, the schema is at 100% line coverage, and lint is clean.

… values

Ember Data's REST serializer sends a record under a camelCase root
(`fuelReport`, `serviceArea`). The base controller's payload reader
accepts both spellings, so the ordinary attributes saved, but these two
afterSave hooks read `$request->array('fuel_report.custom_field_values')`
and `'service_area.custom_field_values'` from the raw request, which was
always empty. No custom field value was ever persisted for either
resource; single-word resources (driver, vehicle, ...) were unaffected.

Read the values from the payload the base controller already extracted,
which the after hook receives as its third argument, so the root key's
spelling no longer matters.

Found while testing the ember-ui signature pad custom field on a fuel
report: the upload landed, the report saved, and the value vanished.
…refront totals

The Orders report table was missing most of what an order report needs, and
several tables referenced columns that do not exist.

Orders
- Order ID / Internal ID now show (core-api hid every *_id column as a key).
- New columns: customer and facilitator kind, dispatched/started flags, time
  window, ad hoc distance, POD method, route optimized, priority, notes.
- Storefront totals from meta as expression columns: storefront, currency,
  subtotal, delivery fee, tip and total (in the currency's smallest unit).
- Summary columns: total/completed/canceled orders (distinct, so they stay right
  when items are joined), order total/average, delivery fees, tips; the
  transaction sums now read transaction.amount instead of a missing orders.amount.
- New relationships: tracking number (+ latest tracking status), order config,
  customer vendor, created by, purchase rate -> service quote, payload return.
- Payload now has its own columns and exposes its items (payload.entities, one
  row per item) with name, SKU, price, product ID, quantity and line total
  from meta, plus the item destination.
- Distance/duration are labelled in meters/seconds (what is stored); the
  km->miles transformer on a meters column is gone.

Other tables
- Drivers: name/email/phone and current_vehicle_uuid do not exist on drivers;
  use the user and vehicle relationships.
- Vehicles: the driver join used a non-existent current_driver_uuid.
- Fuel reports: cost/odometer_reading/report_date do not exist; use amount,
  odometer, metric unit and created_at.
- Every table and item join leaves soft-deleted rows out.

Requires fleetbase/core-api feature/report-framework-enhancements for expression
columns and soft-delete filtering; on older core-api releases the schema still
registers (the new columns fall back to plain computed columns).
meta has no fixed structure: users, integrations and extensions each put
their own keys there, so a column such as "Order Total" read from
meta.total is empty or wrong for most orders. Remove the columns built on
meta keys (storefront, order currency/subtotal/delivery fee/tip/total, the
order total/average/fee/tip summaries, and item product ID, quantity and
line total). meta stays selectable as raw JSON on orders and items, and a
key is read with a computed column, e.g.
CAST(JSON_UNQUOTE(JSON_EXTRACT(payload.entities.meta, '$.quantity')) AS DECIMAL(15,2)).
A table's own columns no longer repeat the table's name: Orders "Order ID"
and "Order Type" are "ID" and "Type", and every table's public ID is "ID".
Relationship labels are tuned for core-api's whole-name prefixes:
"Tracking Status" (not "Tracking Status Status"), "Service Quote Amount",
"Transaction Gateway ID", "Transaction Item Quantity", "Inspection Form Name",
"Target Vehicle ID".
@roncodes roncodes changed the title fix(custom-fields): persist fuel report and service area custom field values Custom field persistence fixes and order reporting schema Sep 25, 2026
…Fleet widget

Default dashboard
- Fleet-ops widgets declare an `order` so the shared default dashboard lays
  out cleanly beside other extensions' widgets: the KPI row (Radar, Active
  Orders, Drivers Online, with ledger's Revenue), ledger's KPI row, the Live
  Fleet Map at full width, then Revenue Trend, Top Drivers and Maintenance
  Overview a third of the width each.
- Earnings and Avg Order Value are no longer defaults (still addable).

Live Fleet widget
- Its marker popups and hover tooltips are now the live map's own cards: the
  driver and vehicle cards moved into shared Map::MarkerCard::Driver/Vehicle
  components (the live map rendered each twice, inline), and the widget uses
  them too.
- /fleet-ops/analytics/live-fleet adds each marker's `card`, the same index
  resource the live map's endpoints return (status, driver, trailers,
  devices, order, speed, heading, location labels).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant