refactor(accounting): make Accounting a master flag with sub-toggles
Replaces the earlier peer-`accounting` flag (which only *conditionally*
relocated Tax) with a cleaner top-level master + sub-toggle model, per design
discussion:
- `accounting` = explicit top-level MASTER (Settings -> Features). Off hides
the whole Accounting section.
- Sub-toggles, gated under the master:
- `taxReport` ("Tax export") moves PERMANENTLY out of CRM. Removed from the
Clients sub-nav and from the derived `clients` flag. Now INDEPENDENT of
Bills (per decision). Old /admin/clients/tax-report -> redirect to
/admin/accounting/tax-report.
- `incomingInvoices` (new) gates the supplier-invoice capture / expenses /
re-bill feature; the /api/admin/expenses router now checks it.
- Dependency rules (backend + frontend): accounting off forces taxReport +
incomingInvoices off; taxReport dropped from the clients derivation; the
bills->taxReport rule removed.
- Preserve visuals: migration 122 rewritten to auto-enable `accounting` on
installs that already had Tax on (so the tab doesn't vanish), and to seed
`incomingInvoices` off. Verified with a SQLite harness (taxReport on ->
accounting on; off -> off).
- Settings -> Features: new "Accounting" section with the master card + Tax
export + Incoming invoices sub-cards (disabled until the master is on).
- i18n: navigation.accounting, accounting.*, settings.features.{accounting,
incomingInvoices,taxReport.requiresAccounting}, sections.accounting (EN + DE,
DE authored natively); Tax report relabelled "Tax export"/"Steuerexport".
Verified: node -c, migration-122 harness, en/de JSON valid, npm run build green.
This commit is contained in:
@@ -43,17 +43,20 @@ const inboundUpload = multer({
|
||||
},
|
||||
});
|
||||
|
||||
async function requireAccountingFlag(req, res, next) {
|
||||
// Gated on the `incomingInvoices` sub-feature, which the feature-flag
|
||||
// dependency rules force OFF whenever the `accounting` master is off — so
|
||||
// this single check covers both.
|
||||
async function requireIncomingInvoicesFlag(req, res, next) {
|
||||
try {
|
||||
const row = await db('feature_flags').where({ key: 'accounting' }).first();
|
||||
const row = await db('feature_flags').where({ key: 'incomingInvoices' }).first();
|
||||
const enabled = row && (row.value === true || row.value === 1 || row.value === '1');
|
||||
if (!enabled) return res.status(403).json({ error: 'Accounting feature is disabled', code: 'ACCOUNTING_DISABLED' });
|
||||
if (!enabled) return res.status(403).json({ error: 'Incoming invoices feature is disabled', code: 'INCOMING_INVOICES_DISABLED' });
|
||||
return next();
|
||||
} catch (err) { return next(err); }
|
||||
}
|
||||
|
||||
router.use(adminAuth);
|
||||
router.use(requireAccountingFlag);
|
||||
router.use(requireIncomingInvoicesFlag);
|
||||
|
||||
// ── Expense categories (literal path — register BEFORE '/:id') ──────────────
|
||||
router.get('/categories', requirePermission('accounting.view'), handleAsync(async (_req, res) => {
|
||||
|
||||
@@ -66,6 +66,10 @@ const KNOWN_FLAGS = [
|
||||
// supplier invoices, expenses + re-bill, and the tax report (which
|
||||
// relocates here from CRM when this is on). Strictly opt-in.
|
||||
'accounting',
|
||||
// Incoming invoices (migration 124) — supplier-invoice capture +
|
||||
// expenses + re-bill. Accounting sub-feature; forced off when the
|
||||
// `accounting` master is off.
|
||||
'incomingInvoices',
|
||||
];
|
||||
|
||||
// Spec defaults for any flag missing from the DB (e.g. a row added by a
|
||||
@@ -89,6 +93,7 @@ const DEFAULT_FLAGS = {
|
||||
hoursLogging: false,
|
||||
contracts: false,
|
||||
accounting: false,
|
||||
incomingInvoices: false,
|
||||
};
|
||||
|
||||
async function readAllFlags() {
|
||||
@@ -109,10 +114,13 @@ function applyDependencyRules(flags) {
|
||||
// Sub-features can't outlive their parents.
|
||||
if (out.quotes === false) out.bills = false;
|
||||
if (out.calendar === false) out.calendarBooking = false;
|
||||
// Tax report only makes sense when bills are on — turning bills off
|
||||
// implicitly turns the tax report off too. Admins enabling tax
|
||||
// report must first enable bills.
|
||||
if (out.bills === false) out.taxReport = false;
|
||||
// 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).
|
||||
if (out.accounting === false) {
|
||||
out.taxReport = false;
|
||||
out.incomingInvoices = false;
|
||||
}
|
||||
// Clients parent flag is DERIVED from its children. Admins don't
|
||||
// toggle it directly in the Features tab — they enable a specific
|
||||
// sub-feature (Accounts today; Calendar/Quotes/Bills/Messaging
|
||||
@@ -125,9 +133,10 @@ function applyDependencyRules(flags) {
|
||||
|| out.crmDevelopment
|
||||
|| out.quotes
|
||||
|| out.bills
|
||||
|| out.taxReport
|
||||
|| out.hoursLogging
|
||||
|| out.contracts
|
||||
// NOTE: taxReport intentionally removed — Tax export moved to the
|
||||
// Accounting section (its own master), no longer a CRM sub-feature.
|
||||
// Migration 137 — admin calendar lights up the Clients section.
|
||||
// (calendarBooking is gated behind `calendar` so adding the parent
|
||||
// is sufficient.)
|
||||
|
||||
Reference in New Issue
Block a user