fix(accounting): PR #622 concerns — flag-cache, customer master gate, VAT-unconfigured, helpers, page cap

1. requireFeatureFlag now caches each flag for 10s (the accounting area is 10+
   gated endpoints); PUT /admin/feature-flags invalidates the cache so toggles
   still take effect immediately.
2. Customer routes (/quotes, /invoices, /contracts + their PDFs) now gate via
   getEffectiveFeaturesForCustomer — the global MASTER flag AND the per-customer
   override — instead of the per-customer column alone, via a shared
   customerFeatureAllowed() helper. Admin disabling a feature globally is now
   honoured for customers too.
4. Tax-report VAT-payable: when accounting_vat_registered is UNSET, stop guessing
   from grandTotalVat>0 (a zero-output-VAT quarter silently flipped to "not
   registered" and hid the reclaim). Treat null as "not configured":
   vatPayableMinor=null + vatRegistrationConfigured=false; the UI renders "—" and
   a "configure VAT registration" warning. Tests updated.
5. Shared upsertAppSetting() in utils/appSettings — the two adminSettings upsert
   loops use it, so the app_settings created_at class can't be re-introduced.
6. PDF rasterise per-file bound: getRenderedPagePath refuses pages beyond
   MAX_RENDERABLE_PAGES (200); page_count is capped to match at ingest, so a
   hostile high-page PDF can't drive an unbounded pager.
7. (no code) original_filename is only rendered via auto-escaped JSX; the two
   dangerouslySetInnerHTML sites are admin-authored content — paranoia pass clean.

Concerns 3 (foreign-VAT reclaim-country) and 8 (imap_pass plaintext) are PR-reply
/ doc items, addressed in the PR response, not code.
This commit is contained in:
Luca
2026-06-16 18:33:47 +02:00
parent cd6d57839b
commit a93b6dc232
13 changed files with 134 additions and 80 deletions
+19 -1
View File
@@ -35,4 +35,22 @@ async function getAppSetting(key, defaultValue = null) {
}
}
module.exports = { getAppSetting };
/**
* Schema-correct upsert into app_settings. The table has NO `created_at`
* column (only setting_key/setting_value/setting_type + updated_at — see
* src/database/db.js), so inserting created_at throws and silently breaks the
* FIRST save of any new key. Centralised here so route authors can't
* re-introduce that bug (PR #622 concern 5). `setting_value` is expected to be
* already JSON-stringified, matching getAppSetting's JSON.parse on read.
*/
async function upsertAppSetting(setting_key, setting_value, setting_type, conn = db) {
const existing = await conn('app_settings').where({ setting_key }).first();
if (existing) {
await conn('app_settings').where({ setting_key })
.update({ setting_value, setting_type, updated_at: new Date() });
} else {
await conn('app_settings').insert({ setting_key, setting_value, setting_type, updated_at: new Date() });
}
}
module.exports = { getAppSetting, upsertAppSetting };