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:
@@ -1,22 +1,39 @@
|
||||
/**
|
||||
* Migration 122: seed the `accounting` feature flag (default OFF).
|
||||
* Migration 122: seed the Accounting feature flags.
|
||||
*
|
||||
* Gates the new top-level Accounting area (inbound supplier invoices,
|
||||
* billable / re-billable expenses, Erfolgsrechnung). Admins opt in under
|
||||
* Settings → Features. Separate from the CRM `bills` flag.
|
||||
* - `accounting` : top-level MASTER for the Accounting section
|
||||
* (separate from CRM). Default OFF — EXCEPT on
|
||||
* installs that already had the Tax report
|
||||
* (`taxReport`) enabled: the Tax export relocated
|
||||
* permanently into Accounting, so we auto-enable the
|
||||
* master there to preserve the existing menu (per the
|
||||
* "migrations preserve visual state" rule). Otherwise
|
||||
* admins opt in under Settings → Features.
|
||||
* - `incomingInvoices` : Accounting sub-feature (supplier-invoice capture +
|
||||
* expenses + re-bill). Always default OFF (new).
|
||||
*
|
||||
* Idempotent: inserts only when the row is missing (mirrors the
|
||||
* 095_add_customer_portal_flag pattern). 107_crm_consolidated already
|
||||
* shipped its flag set on fresh installs and won't re-run.
|
||||
* Idempotent: each row is inserted only when missing. 107_crm_consolidated
|
||||
* already shipped its flag set and won't re-run.
|
||||
*/
|
||||
exports.up = async function (knex) {
|
||||
if (!(await knex.schema.hasTable('feature_flags'))) return;
|
||||
const existing = await knex('feature_flags').where({ key: 'accounting' }).first();
|
||||
if (existing) return;
|
||||
await knex('feature_flags').insert({ key: 'accounting', value: false });
|
||||
|
||||
const existingAccounting = await knex('feature_flags').where({ key: 'accounting' }).first();
|
||||
if (!existingAccounting) {
|
||||
// Preserve visuals: if Tax was already on, light up the Accounting
|
||||
// master so the relocated Tax export doesn't vanish on upgrade.
|
||||
const taxRow = await knex('feature_flags').where({ key: 'taxReport' }).first();
|
||||
const taxOn = !!(taxRow && (taxRow.value === true || taxRow.value === 1 || taxRow.value === '1'));
|
||||
await knex('feature_flags').insert({ key: 'accounting', value: taxOn });
|
||||
}
|
||||
|
||||
const existingIncoming = await knex('feature_flags').where({ key: 'incomingInvoices' }).first();
|
||||
if (!existingIncoming) {
|
||||
await knex('feature_flags').insert({ key: 'incomingInvoices', value: false });
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function (knex) {
|
||||
if (!(await knex.schema.hasTable('feature_flags'))) return;
|
||||
await knex('feature_flags').where({ key: 'accounting' }).del();
|
||||
await knex('feature_flags').whereIn('key', ['accounting', 'incomingInvoices']).del();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user