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:
@@ -17,6 +17,7 @@ import {
|
||||
Wrench,
|
||||
Calculator,
|
||||
Landmark,
|
||||
ScanLine,
|
||||
} from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Card } from '../../../components/common';
|
||||
@@ -260,39 +261,6 @@ export const FeaturesTab: React.FC = () => {
|
||||
onToggle={(next) => setFlag('bills', next)}
|
||||
/>
|
||||
|
||||
<FeatureCard
|
||||
icon={Calculator}
|
||||
title={t('settings.features.taxReport.title', 'Tax report')}
|
||||
description={t(
|
||||
'settings.features.taxReport.description',
|
||||
'Period-scoped revenue list with net + VAT breakdown grouped by VAT rate. Export as PDF (landscape, company letterhead) or CSV for your accountant. Cancelled invoices stay visible for a gap-free audit trail but are excluded from totals.',
|
||||
)}
|
||||
status="new"
|
||||
statusLabel={statusLabel('new')}
|
||||
sidebarLabel={t('settings.features.taxReport.sidebar', 'Tax')}
|
||||
enabled={staged.taxReport}
|
||||
onToggle={(next) => setFlag('taxReport', next)}
|
||||
disabled={!staged.bills}
|
||||
lockedReason={!staged.bills ? t(
|
||||
'settings.features.taxReport.requiresBills',
|
||||
'Enable Bills first — the tax report reads from your invoices.',
|
||||
) : undefined}
|
||||
/>
|
||||
|
||||
<FeatureCard
|
||||
icon={Landmark}
|
||||
title={t('settings.features.accounting.title', 'Accounting')}
|
||||
description={t(
|
||||
'settings.features.accounting.description',
|
||||
'Capture incoming supplier invoices (upload or phone/tablet camera), categorize expenses and re-bill costs to clients on the relevant event. When enabled, the Tax report moves out of CRM into a dedicated Accounting section. VAT / tax treatment is provided as guidance only — verify with your Treuhänder before relying on it.',
|
||||
)}
|
||||
status="new"
|
||||
statusLabel={statusLabel('new')}
|
||||
sidebarLabel={t('settings.features.accounting.sidebar', 'Accounting')}
|
||||
enabled={staged.accounting}
|
||||
onToggle={(next) => setFlag('accounting', next)}
|
||||
/>
|
||||
|
||||
<FeatureCard
|
||||
icon={Briefcase}
|
||||
title={t('settings.features.hoursLogging.title', 'Hours logging')}
|
||||
@@ -308,6 +276,63 @@ export const FeaturesTab: React.FC = () => {
|
||||
/>
|
||||
</Section>
|
||||
|
||||
{/* Accounting — top-level master + sub-toggles. The Tax export
|
||||
relocated here permanently out of CRM. Sub-toggles are disabled
|
||||
until the Accounting master is on. */}
|
||||
<Section title={t('settings.features.sections.accounting', 'Accounting')}>
|
||||
<FeatureCard
|
||||
icon={Landmark}
|
||||
title={t('settings.features.accounting.title', 'Accounting')}
|
||||
description={t(
|
||||
'settings.features.accounting.description',
|
||||
'A dedicated Accounting area, separate from CRM. Turn this on, then enable the sub-features below (Tax export, Incoming invoices). VAT / tax treatment is guidance only — verify with your Treuhänder before relying on it.',
|
||||
)}
|
||||
status="new"
|
||||
statusLabel={statusLabel('new')}
|
||||
sidebarLabel={t('settings.features.accounting.sidebar', 'Accounting')}
|
||||
enabled={staged.accounting}
|
||||
onToggle={(next) => setFlag('accounting', next)}
|
||||
/>
|
||||
|
||||
<FeatureCard
|
||||
icon={Calculator}
|
||||
title={t('settings.features.taxReport.title', 'Tax export')}
|
||||
description={t(
|
||||
'settings.features.taxReport.description',
|
||||
'Period-scoped revenue list with net + VAT breakdown grouped by VAT rate. Export as PDF (landscape, company letterhead) or CSV for your accountant. Cancelled invoices stay visible for a gap-free audit trail but are excluded from totals.',
|
||||
)}
|
||||
status="new"
|
||||
statusLabel={statusLabel('new')}
|
||||
sidebarLabel={t('settings.features.taxReport.sidebar', 'Tax')}
|
||||
enabled={staged.taxReport}
|
||||
onToggle={(next) => setFlag('taxReport', next)}
|
||||
disabled={!staged.accounting}
|
||||
lockedReason={!staged.accounting ? t(
|
||||
'settings.features.taxReport.requiresAccounting',
|
||||
'Enable Accounting first — Tax export lives in the Accounting section.',
|
||||
) : undefined}
|
||||
/>
|
||||
|
||||
<FeatureCard
|
||||
icon={ScanLine}
|
||||
title={t('settings.features.incomingInvoices.title', 'Incoming invoices')}
|
||||
description={t(
|
||||
'settings.features.incomingInvoices.description',
|
||||
'Capture received supplier invoices (upload or phone/tablet camera), categorize expenses, and re-bill costs to clients on the relevant event with a contract-driven markup.',
|
||||
)}
|
||||
status="new"
|
||||
statusLabel={statusLabel('new')}
|
||||
sidebarLabel={t('settings.features.incomingInvoices.sidebar', 'Incoming')}
|
||||
enabled={staged.incomingInvoices}
|
||||
onToggle={(next) => setFlag('incomingInvoices', next)}
|
||||
disabled={!staged.accounting}
|
||||
lockedReason={!staged.accounting ? t(
|
||||
'settings.features.incomingInvoices.requiresAccounting',
|
||||
'Enable Accounting first — Incoming invoices live in the Accounting section.',
|
||||
) : undefined}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
{/* Insights & Access */}
|
||||
<Section title={t('settings.features.sections.insights', 'Insights & Access')}>
|
||||
<FeatureCard
|
||||
|
||||
Reference in New Issue
Block a user