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:
@@ -3974,7 +3974,8 @@
|
||||
"income": "Einnahmen",
|
||||
"costs": "Ausgaben",
|
||||
"result": "Ergebnis",
|
||||
"vatPayable": "MWST-Zahllast (Umsatz- − Vorsteuer)"
|
||||
"vatPayable": "MWST-Zahllast (Umsatz- − Vorsteuer)",
|
||||
"vatUnconfigured": "Die MWST-Registrierung ist nicht konfiguriert, daher kann die MWST-Zahllast nicht berechnet werden. Lege sie unter Einstellungen → Buchhaltung fest."
|
||||
},
|
||||
"cost": {
|
||||
"source": "Art",
|
||||
|
||||
@@ -3974,7 +3974,8 @@
|
||||
"income": "Income",
|
||||
"costs": "Costs",
|
||||
"result": "Result",
|
||||
"vatPayable": "VAT payable (output − input)"
|
||||
"vatPayable": "VAT payable (output − input)",
|
||||
"vatUnconfigured": "VAT registration isn’t configured, so VAT payable can’t be computed. Set it under Settings → Accounting."
|
||||
},
|
||||
"cost": {
|
||||
"source": "Type",
|
||||
|
||||
@@ -442,9 +442,17 @@ export const TaxReportPage: React.FC = () => {
|
||||
<div className="flex justify-between gap-3 text-xs text-neutral-500 dark:text-neutral-400">
|
||||
<span>{t('taxReport.summary.vatPayable', 'VAT payable (output − input)')}</span>
|
||||
<span className="tabular-nums">
|
||||
{formatMinor(report.summary.vatPayableMinor, report.currency, intlLocale)}
|
||||
{report.summary.vatRegistrationConfigured === false || report.summary.vatPayableMinor == null
|
||||
? '—'
|
||||
: formatMinor(report.summary.vatPayableMinor, report.currency, intlLocale)}
|
||||
</span>
|
||||
</div>
|
||||
{report.summary.vatRegistrationConfigured === false && (
|
||||
<p className="flex items-start gap-1.5 text-xs text-amber-600 dark:text-amber-400 pt-1">
|
||||
<AlertCircle className="w-3.5 h-3.5 flex-shrink-0 mt-0.5" />
|
||||
<span>{t('taxReport.summary.vatUnconfigured', 'VAT registration isn’t configured, so VAT payable can’t be computed. Set it under Settings → Accounting.')}</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -84,7 +84,12 @@ export interface TaxReportSummary {
|
||||
costGrossMinor: number;
|
||||
resultNetMinor: number;
|
||||
resultGrossMinor: number;
|
||||
vatPayableMinor: number;
|
||||
/** Whether `accounting_vat_registered` is configured. When false, the
|
||||
* report refuses to guess and `vatPayableMinor` is null. */
|
||||
vatRegistrationConfigured?: boolean;
|
||||
/** output VAT − reclaimable input VAT; `null` when VAT registration is
|
||||
* unconfigured (the UI shows "—" + a warning instead of a guess). */
|
||||
vatPayableMinor: number | null;
|
||||
}
|
||||
|
||||
/** A single row of the unified ledger (#5). Outgoing invoices carry
|
||||
|
||||
Reference in New Issue
Block a user