feat(accounting): incoming-invoices inbox with camera capture + triage/re-bill
Adds the Accounting → Incoming invoices frontend on top of the existing /api/admin/expenses backend: - accounting.service.ts: typed client (inbound upload/list/get/update/ categorize, expense list, re-bill, supplier-payment, categories). - AccountingInboxPage: capture a supplier invoice via the device CAMERA (<input accept="image/*" capture="environment">) or a PDF/image upload; inbox list with status badges + parsed summary; a triage modal to confirm fields and pick a disposition (re-bill / pass-through / company expense / duplicate / declined). Re-bill uses the customer picker and mints an editable scheduled invoice (chains categorize -> rebill). - AccountingLayout: "Incoming invoices" sub-nav item + AccountingIndex that redirects /admin/accounting to the first enabled sub-feature. - App.tsx: /admin/accounting/inbox route (gated by incomingInvoices). - i18n: accounting.inbox/disposition/markup + subnav.incomingInvoices + common.saving (EN + DE, DE authored natively). Camera capture needs no native app — the mobile web input drives the device camera straight into the upload endpoint. OCR/QR auto-extraction is still a backend follow-up (extractionService is a no-op), so fields are confirmed manually in the triage modal for now. Verified: npm run build green; en/de JSON valid.
This commit is contained in:
@@ -7,9 +7,9 @@
|
||||
* expenses pages slot in as additional sub-nav entries when their UIs land.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { NavLink, Outlet, Navigate, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Landmark, Calculator } from 'lucide-react';
|
||||
import { Landmark, Calculator, Inbox } from 'lucide-react';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { useFeatureFlags, type FeatureKey } from '../../contexts/FeatureFlagsContext';
|
||||
|
||||
@@ -29,6 +29,13 @@ export const AccountingLayout: React.FC = () => {
|
||||
const { flags } = useFeatureFlags();
|
||||
|
||||
const navItems: NavItem[] = [
|
||||
{
|
||||
key: 'inbox',
|
||||
to: '/admin/accounting/inbox',
|
||||
label: t('accounting.subnav.incomingInvoices', 'Incoming invoices'),
|
||||
icon: Inbox,
|
||||
featureFlag: 'incomingInvoices',
|
||||
},
|
||||
{
|
||||
key: 'tax-report',
|
||||
to: '/admin/accounting/tax-report',
|
||||
@@ -36,10 +43,7 @@ export const AccountingLayout: React.FC = () => {
|
||||
icon: Calculator,
|
||||
featureFlag: 'taxReport',
|
||||
},
|
||||
// Future Accounting sub-features (inbound inbox, expenses) slot in here
|
||||
// once their pages land, e.g.:
|
||||
// { key: 'inbox', to: '/admin/accounting/inbox', featureFlag: 'accounting' }
|
||||
// { key: 'expenses', to: '/admin/accounting/expenses', featureFlag: 'accounting' }
|
||||
// Future: expenses ledger, Erfolgsrechnung.
|
||||
];
|
||||
|
||||
const enabledItems = navItems.filter((item) => flags[item.featureFlag]);
|
||||
@@ -139,3 +143,15 @@ export const AccountingLayout: React.FC = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Index redirect for /admin/accounting — send to the first enabled
|
||||
* sub-feature (Incoming invoices preferred, then Tax export). When none
|
||||
* are on, render nothing; AccountingLayout shows its empty state.
|
||||
*/
|
||||
export const AccountingIndex: React.FC = () => {
|
||||
const { flags } = useFeatureFlags();
|
||||
if (flags.incomingInvoices) return <Navigate to="/admin/accounting/inbox" replace />;
|
||||
if (flags.taxReport) return <Navigate to="/admin/accounting/tax-report" replace />;
|
||||
return null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user