Skip to content

feat(settings): admin-editable currency via settings table - #30

Merged
bitsandbots merged 2 commits into
mainfrom
feature/settings-table-currency
May 15, 2026
Merged

feat(settings): admin-editable currency via settings table#30
bitsandbots merged 2 commits into
mainfrom
feature/settings-table-currency

Conversation

@bitsandbots

Copy link
Copy Markdown
Owner

Replaces the hardcoded $CURRENCY_CODE = 'USD' in includes/load.php with a DB-backed value an admin can change from a new Settings page. Single-tenant — one currency per deployment.

  • New settings(setting_key, setting_value, updated_at) table (migration 004 + schema.sql).
  • Settings::get/set with per-request cache (includes/settings.php); gracefully degrades to defaults if the table is missing (e.g. brief window between deploy and migration apply).
  • Admin-only /users/settings.php page: CSRF-protected dropdown of the ~91 ISO 4217 codes formatcurrency() already supports, with a live sample render of 1234.56 at the chosen code.
  • formatcurrency.php refactor: the currency table is now exposed through a memoized currency_table() helper + supported_currency_codes() so the form validates against the exact list the renderer knows. Unknown codes fall back to USD instead of throwing on undefined-index.
  • New SettingsTest.php integration suite; skips cleanly when the local DB doesn't have the table yet.
    After merge, on each existing deployment:
sudo mysqldump --single-transaction inventory > inventory-pre-004.sql
sudo mysql inventory < migrations/004_settings_table.up.sql

CI doesn't need this — schema.sql is the canonical fresh state and already contains the table.

  • bash tests/run.sh — 5/5 suites pass locally (SettingsTest skipped because migration not applied here)
  • php -l clean across all modified PHP (caught by the new pre-commit hook from PR chore(hooks): add pre-commit php -l hook (opt-in) #29)
  • formatcurrency() regression: unknown code returns $100.00 USD instead of warning
  • CSP smoke: live deploy returns style-src 'self', login page has 0 inline styles (pre-existing behaviour unchanged)
  • Reviewer: apply migration 004 on a staging DB, change currency to EUR via the new page, confirm an existing invoice + a daily-sales report render as €
  • Reviewer: confirm CI's "Test Suite" job runs SettingsTest against the schema.sql-imported settings table (i.e. it does NOT skip in CI)
  • Reviewer: non-admin (group_level 2 or 3) hitting /users/settings.php is redirected by page_require_level(1)
    Per-organization / per-user currency requires a tenancy model the codebase doesn't have. Captured in docs/gap-analysis.md as a follow-up.
    🤖 Generated with Claude Code

Replaces the hardcoded `$CURRENCY_CODE = 'USD'` in includes/load.php
with a DB-backed value an admin can change from a Settings page. One
currency per deployment (single-tenant); per-org/per-user currency
remains future work and requires a tenancy model the codebase
doesn't have today.
Pieces
------
- migrations/004_settings_table.{up,down}.sql — new `settings`
  table with `setting_key` PK + `setting_value` + `updated_at`,
  seeded with `currency_code = 'USD'`. schema.sql also gains the
  table so a fresh install (and CI) has it from minute one.
- includes/settings.php — `Settings::get($key, $default)` /
  `Settings::set($key, $value)` with per-request cache.
  Settings::load() swallows a missing-table error and uses defaults,
  so the brief window between a deploy and applying migration 004
  doesn't 500 the whole site.
- includes/database.php — new `connection()` getter so Settings
  can run a query that handles its own errors instead of dying
  through `query()`/`prepare_query()`.
- includes/formatcurrency.php — extracted the ISO 4217 table into
  a memoized `currency_table()` helper and added
  `supported_currency_codes()` (sorted list of keys), so the
  Settings page can validate POSTed codes against the exact same
  list `formatcurrency()` knows how to render. `formatcurrency()`
  also gains a safety fallback to USD when handed an unknown code,
  instead of throwing on undefined index.
- includes/load.php — `$CURRENCY_CODE = Settings::get('currency_code', 'USD')`.
- users/settings.php — admin-only (`page_require_level(1)`), CSRF-
  protected, dropdown of supported codes + live "1234.56" sample
  rendered through formatcurrency() at the chosen code so the
  admin sees exactly what the system will produce.
- layouts/admin_menu.php — link under User Management → Settings.
Tests
-----
- tests/SettingsTest.php (integration): supported list shape, get()
  default fallback, set() upsert + cache-invalidation round-trip,
  currency_code → formatcurrency() round-trip, unknown-code
  fallback. Skips with a clear hint when the settings table is
  absent locally (migration not yet applied) so a dev clone without
  the migration still runs the rest of the suite cleanly.
- tests/run.sh wired in the new suite. tests/bootstrap.php now
  requires settings.php + formatcurrency.php.
Deploy
------
After merge, on each existing deployment:
    sudo mysqldump --single-transaction inventory > inventory-pre-004.sql
    sudo mysql inventory < migrations/004_settings_table.up.sql
The fallback in Settings::load() means traffic between the deploy
and the migration apply continues to render as USD instead of
erroring; the migration window is non-fatal.
gap-analysis.md item 2 from the post-PR-27 list is now resolved
(single-tenant scope). migrations/README.md index updated to cover
002/003/004 (rows for the new migration and to backfill the prior
two that were never indexed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
formatcurrency() emits the euro sign as `&euro;` (HTML entity) so that
the value is safe to drop directly into an HTML page. CI's SettingsTest
checked for the literal `€` character and failed against `&euro;1.234,56`.
Decode through html_entity_decode() before substring-matching. No
production behaviour changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bitsandbots
bitsandbots merged commit a1d6b2a into main May 15, 2026
2 checks passed
@bitsandbots
bitsandbots deleted the feature/settings-table-currency branch May 15, 2026 15:06
bitsandbots added a commit to jleog/inventory that referenced this pull request May 22, 2026
First of three deferred sub-projects from next_steps_inventory.md
("Deferred work" item 1). Scope:
- 5 in-scope tables: users, customers, sales, orders, stock
- New helpers in includes/sql.php: soft_delete_by_id, restore_by_id,
  purge_by_id, find_with_deleted, find_by_id_with_deleted,
  table_has_soft_delete (cached schema introspection).
- find_all/find_by_id default-filter deleted_at IS NULL; raw-SQL
  helpers hand-edited per the in-scope table list.
- Migrations 005-009 add deleted_at + deleted_by columns + FK to
  users(id) ON DELETE SET NULL (mirrors PR bitsandbots#27's fk_log_user).
- Admin-only users/trash.php + restore.php + purge.php; row-local
  cascade (a deleted customer's existing sales still show).
- New tests/SoftDeleteTest.php (10 cases) wired into tests/run.sh.
Deploy follows PR bitsandbots#30's defensive pattern: table_has_soft_delete()
returns false until the column lands, so traffic between code-deploy
and migration-apply still serves correct unfiltered reads.
Awaiting user review before writing-plans is invoked.
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