Settings
Two owners, one screen. The host document — one JSON row in settings —
holds the school's identity, currencies, methods and calendar. Each enabled
plugin contributes a tab rendered from its own contract value. The Save
button commits both, or neither.
The host document
settings.data is one JSON row decoded into SettingsData
(internal/domain/settings/settings.go):
| Block | Contents | Defaults |
|---|---|---|
school |
name, address, phone, email, logo | empty |
currencies |
default + enabled list | default USD, enabled from SUPPORTED_CURRENCIES (USD,SYP,TRY,EUR) |
features |
multi_currency flag |
on; the form does not edit it, and the save preserves it explicitly so a zero value cannot wipe it |
decimal_places |
display precision | 2 |
description_types |
fee-description labels {en, ar} |
tuition, registration, school books, uniform, transport, activities, exams, lab |
payment_methods |
{code, en, ar, requires_reference} per method |
cash (no reference), shamcash (requires one) |
holidays |
YYYY-MM-DD dates |
empty |
weekend_days |
lowercase weekday names | friday |
The settings service caches the decoded document in memory (the money formatter calls it per table cell) and invalidates on save; a missing row yields the defaults without persisting them, and legacy documents get description-types/payment-methods/weekend-days backfilled on read.
payment_methods doubles as the register's method catalog: each entry's
code becomes a MethodTag with its requires_reference flag
(application/money/methods.go), asked for on every write — so adding a method
is a settings edit, not a code change, and a malformed row (blank or duplicate
code) is skipped rather than allowed to take every posting down.
Host-owned vs plugin-owned
| Host document | Plugin tab (register) | |
|---|---|---|
| Storage | settings row, JSON |
re_settings key/value rows, via the plugin's Put/Update |
| Schema | the Go struct | the plugin's Fields() — labels translated by the host under plugin_<id>_<key> (fallback: the plugin's own label) |
| Tab visibility | always (settings read) | only for plugins that registered and initialised — a failed Init shows no tab, the same rule that hides its features |
| Values today | everything above | the register's own fields, incl. lock_enforced (refuse postings dated inside a locked period) and variance_category (how a drawer-count difference is booked) — see nx-register |
| Refresh | settings service cache invalidated on save | RefreshSettings after commit in this process; peers re-read within the plugin host's 5 s TTL |
The host never sees the plugin's struct or its table name; the plugin never sees the host document. Full rules: settings contract.
The save, precisely
- Validate everything up front: decimal places positive; method codes
match
^[a-z0-9]+$(lowercase alphanumeric); the default currency must be in the enabled list (ErrDefaultCurrencyNotEnabled); a currency being dropped from the enabled list is refused while any student fee or salary still references it — the message counts both. - One transaction (
money.RunPluginTx→db.RunInTx+runtime.WithTx):settings.Save→ box/currency sync → box/method sync → every plugin panel'sPut/Updatewith its audit row. Every writer resolves its handle from the transaction's context, so they all join it; a failure rolls back all of it. - After commit: each plugin refreshes its in-memory settings so the change applies without a restart (a failure here is a delay, not a loss — peers re-read on the TTL).
First-boot setup
/setup (gates settings R/W) is the fresh-install form: pick the currencies
to accept and an opening balance per (currency, method); the save creates one
box per pair, posts each opening as that box's opening receipt, records an
audit row per box, and persists the enabled/default currencies.
RequireSetup runs on every protected route: a user who may write settings is
redirected to /setup until at least one box exists (the /setup routes
themselves and settings-read-only users pass through).
Rendered from simple-lms/settings.md · OKF bundle · regenerate with npm run render