Files
cross-financial-management/client/src/features/admin/tax/TaxCodeDialog.tsx
T
Joshua OosthuizenandClaude Sonnet 5 4acbc03b35 Initial commit: multi-company financial management system
Root company app (src/, client/) — GL foundation, AR, and AP:
- Segmented Chart of Accounts (Sage 300-style), double-entry PostingService with
  three-layer balance enforcement (app validation, CHECK constraints, deferred
  DB trigger), gap-free document numbering, RBAC with branch restrictions.
- Accounts Receivable: Customers, Invoices, Credit Notes, Receipts with
  allocation, all posting through PostingService.
- Accounts Payable: Suppliers, Supplier Invoices, Debit Notes, Payments with
  allocation, mirroring AR's posting pattern.
- Tax Codes shared between AR and AP.
- Company-app login is a token handoff from the Account Manager (RS256,
  single-use, company-scoped) rather than a local password.
- Frontend reskinned to the approved mockup: navy sidebar, dashboard, tree-view
  Chart of Accounts, Customer/Supplier 360 pages.

Account Manager (manager/, manager/client/) — central login gateway:
- Password + TOTP 2FA + Google/Microsoft OAuth, company picker, and the RS256
  handoff token issuance that the company app verifies.

Covered by unit + integration tests against a real Postgres database (GL
balance invariants, AR/AP posting correctness and allocation limits, the full
handoff exchange including replay/cross-company/expiry/forgery rejection).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tq2DbEvzHzs13jDFhLaX44
2026-09-03 13:27:19 +02:00

51 lines
1.9 KiB
TypeScript

import { useState } from 'react';
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField } from '@mui/material';
import { useCreateTaxCode } from '../../../hooks/useTaxCodes';
export function TaxCodeDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
const createTaxCode = useCreateTaxCode();
const [code, setCode] = useState('');
const [name, setName] = useState('');
const [ratePercent, setRatePercent] = useState('');
const [error, setError] = useState<string | null>(null);
const handleSubmit = async () => {
setError(null);
try {
await createTaxCode.mutateAsync({ code, name, ratePercent: Number(ratePercent) });
onClose();
setCode('');
setName('');
setRatePercent('');
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to create tax code');
}
};
return (
<Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
<DialogTitle>New Tax Code</DialogTitle>
<DialogContent>
<Stack spacing={2} sx={{ mt: 1 }}>
<TextField label="Code" value={code} onChange={(e) => setCode(e.target.value.toUpperCase())} helperText="e.g. STD, ZERO" />
<TextField label="Name" value={name} onChange={(e) => setName(e.target.value)} />
<TextField
label="Rate %"
type="number"
value={ratePercent}
onChange={(e) => setRatePercent(e.target.value)}
slotProps={{ htmlInput: { min: 0, max: 100, step: '0.001' } }}
/>
{error && <span style={{ color: 'red' }}>{error}</span>}
</Stack>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button variant="contained" onClick={handleSubmit} disabled={createTaxCode.isPending}>
Create
</Button>
</DialogActions>
</Dialog>
);
}