From 51837c3a88f711b164fafe2c7677e1a91c7542f9 Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:11:11 +0200 Subject: [PATCH] feat(accounting): invoices force-enable the Accounting master MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Invoice VAT config (codes + label) and the hourly rate now live under Settings → Accounting, so an install with Invoices must have Accounting available. - applyDependencyRules (backend adminFeatureFlags.js + frontend FeatureFlagsContext.tsx): bills on → accounting on, before the accounting→children rule so the sub-features keep their own state. - Migration 133 corrects existing installs: set the STORED accounting=true where bills is on. requireFeatureFlag('accounting') reads the raw row, so without this an upgraded install (invoices on, accounting off) would show the tab but 403 its endpoints. Idempotent; only flips on; no down. - Features tab: the Accounting card shows locked-on (disabled + hint) while Invoices is enabled. Also includes the i18n keys (en/de) for the VAT/financial settings move. --- .../core/133_accounting_on_when_bills.js | 34 +++++++++++++++++++ backend/src/routes/adminFeatureFlags.js | 5 +++ frontend/src/contexts/FeatureFlagsContext.tsx | 4 +++ .../features/settings/tabs/FeaturesTab.tsx | 7 ++++ frontend/src/i18n/locales/de.json | 18 ++++++++-- frontend/src/i18n/locales/en.json | 18 ++++++++-- 6 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 backend/migrations/core/133_accounting_on_when_bills.js diff --git a/backend/migrations/core/133_accounting_on_when_bills.js b/backend/migrations/core/133_accounting_on_when_bills.js new file mode 100644 index 00000000..06373c88 --- /dev/null +++ b/backend/migrations/core/133_accounting_on_when_bills.js @@ -0,0 +1,34 @@ +/** + * Migration 133: invoices (Bills) force-enable the Accounting master. + * + * Invoice VAT config (codes + label) and the default hourly rate now live under + * Settings → Accounting, so an install with Bills enabled must have Accounting + * available. `applyDependencyRules` enforces this on every flag READ/WRITE, but + * the `requireFeatureFlag('accounting')` middleware reads the STORED row + * directly — so existing installs that already have `bills=true, accounting=false` + * would show the Accounting tab yet 403 its endpoints. This one-time correction + * brings the stored value in line (forward fix, not a compensation: it encodes a + * new dependency rule, it doesn't patch a buggy earlier migration). + * + * Idempotent: only flips accounting ON where Bills is on; never turns it off. + */ +function isOn(row) { + return !!(row && (row.value === true || row.value === 1 || row.value === '1')); +} + +exports.up = async function (knex) { + if (!(await knex.schema.hasTable('feature_flags'))) return; + const bills = await knex('feature_flags').where({ key: 'bills' }).first(); + if (!isOn(bills)) return; + + const accounting = await knex('feature_flags').where({ key: 'accounting' }).first(); + if (!accounting) { + await knex('feature_flags').insert({ key: 'accounting', value: true }); + } else if (!isOn(accounting)) { + await knex('feature_flags').where({ key: 'accounting' }).update({ value: true }); + } +}; + +// No down — we can't know whether Accounting was independently wanted, and +// turning it back off could hide a section the admin now relies on. +exports.down = async function () {}; diff --git a/backend/src/routes/adminFeatureFlags.js b/backend/src/routes/adminFeatureFlags.js index 8c447f24..68b3f775 100644 --- a/backend/src/routes/adminFeatureFlags.js +++ b/backend/src/routes/adminFeatureFlags.js @@ -127,6 +127,11 @@ function applyDependencyRules(flags) { // Sub-features can't outlive their parents. if (out.quotes === false) out.bills = false; if (out.calendar === false) out.calendarBooking = false; + // Invoices (Bills) force-enable the Accounting master: invoice VAT config + // (codes + label) and the hourly rate live under Settings → Accounting, so + // an install with invoices must have Accounting available. Runs BEFORE the + // accounting→children rule so the sub-features keep their own stored state. + if (out.bills === true) out.accounting = true; // Accounting is a top-level MASTER; its sub-features can't outlive it. // Tax export is now independent of Bills — it relocated permanently // into the Accounting section (its own master gate). diff --git a/frontend/src/contexts/FeatureFlagsContext.tsx b/frontend/src/contexts/FeatureFlagsContext.tsx index 34d2528f..b9bc94ed 100644 --- a/frontend/src/contexts/FeatureFlagsContext.tsx +++ b/frontend/src/contexts/FeatureFlagsContext.tsx @@ -91,6 +91,10 @@ function applyDependencyRules(flags: FeatureFlags): FeatureFlags { out.galleries = true; // foundation — always on if (out.quotes === false) out.bills = false; // bills depend on quotes if (out.calendar === false) out.calendarBooking = false; // booking depends on calendar + // Invoices (Bills) force-enable the Accounting master — invoice VAT config + + // hourly rate live under Settings → Accounting. Before the accounting→children + // rule so sub-features keep their own state. + if (out.bills === true) out.accounting = true; // Accounting sub-features require the Accounting master. Tax export is // independent of Bills now — it relocated permanently into Accounting. if (out.accounting === false) { diff --git a/frontend/src/features/settings/tabs/FeaturesTab.tsx b/frontend/src/features/settings/tabs/FeaturesTab.tsx index 194aaa33..0fda93fb 100644 --- a/frontend/src/features/settings/tabs/FeaturesTab.tsx +++ b/frontend/src/features/settings/tabs/FeaturesTab.tsx @@ -324,6 +324,13 @@ export const FeaturesTab: React.FC = () => { sidebarLabel={t('settings.features.accounting.sidebar', 'Accounting')} enabled={staged.accounting} onToggle={(next) => setFlag('accounting', next)} + // Invoices force-enable Accounting (invoice VAT settings live here), + // so the master can't be turned off while Bills is on. + disabled={staged.bills} + lockedReason={staged.bills ? t( + 'settings.features.accounting.requiredByBills', + 'On automatically because Invoices is enabled — invoice VAT settings live in the Accounting section.', + ) : undefined} />