What it does
An Account Preference is one row per User (account_preference), not per Organization: two Members of one household may use different languages and units. It holds the locale (pt-BR, en-US, de-DE), the main currency totals convert into, the distance unit (km/mi), the volume unit (l/gal), the reminder channels (push, email) and the data region (eu, br, us).
The first read creates the row from the device locale the client sends, idempotently: two first reads at once store one row.
| Device language | Locale | Currency | Distance | Volume | Region |
|---|---|---|---|---|---|
pt-* | pt-BR | BRL | km | l | br |
de-* | de-DE | EUR | km | l | eu |
| anything else | en-US | USD | mi | gal | us |
Distance is always stored in km and volume in millilitres; the units only change how values are shown. A Vehicle keeps its own currency; totals in the main currency show the rate and its date.
The data region is pinned when the row is created and is not part of update: moving an account between regions is a support process (a data migration), so the profile shows it read-only.
In the app
The Profile tab (M-Perfil) reads the preference once under the tabs and keeps it in one shared query. Until that read returns, the app formats with the device locale's defaults; then the server value wins, including the app language. Language, main currency, units and reminders each open a sheet that sends only the field it changes; the cache updates at once and rolls back if the server refuses. The rows stay disabled until the first read, so an update never meets NOT_FOUND.
Every screen formats amounts, dates, distances and volumes through useFormat() (apps/mobile/src/features/account-preference/account-preference.hooks.ts), which binds the @autofolio/domain formatters to the account locale and units.
Procedures
| Procedure | Who | Failures |
|---|---|---|
accountPreference.get { deviceLocale? } | Any signed-in User, for their own row | UNAUTHORIZED |
accountPreference.update (partial: locale, currency, distanceUnit, volumeUnit, remindByPush, remindByEmail) | Any signed-in User, for their own row | UNAUTHORIZED, INPUT_VALIDATION (unknown key such as dataRegion, currency outside BRL/EUR/USD/GBP/CHF), NOT_FOUND (no row yet: call get first) |
Both return the whole preference. No Active Organization is needed.
Data and isolation
The account_preference table has row-level security keyed by the session's User (ownedByCurrentUser). app_tenant may insert the row but update only the choice columns, never data_region or user_id. Deleting the User deletes the row.
Key files
packages/api/src/features/account-preference/account-preference.contract.tspackages/api/src/features/account-preference/account-preference.service.tsapps/mobile/src/features/account-preference/account-preference.hooks.tsapps/mobile/src/features/settings/settings.profile.screen.tsxpackages/db/src/schema/preferences.ts