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
This commit is contained in:
Joshua Oosthuizen
2026-09-03 13:27:19 +02:00
co-authored by Claude Sonnet 5
commit 4acbc03b35
279 changed files with 38537 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { createManualEntry, fetchJournalEntries, fetchJournalEntry, reverseJournalEntry } from '../api/journalEntries.api';
import type { BranchSelection } from '../store/branchStore';
export function useJournalEntries(branchScope: BranchSelection) {
return useQuery({ queryKey: ['journalEntries', branchScope], queryFn: () => fetchJournalEntries(branchScope) });
}
export function useJournalEntry(id: number | undefined) {
return useQuery({
queryKey: ['journalEntry', id],
queryFn: () => fetchJournalEntry(id as number),
enabled: id !== undefined,
});
}
export function useCreateManualEntry() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createManualEntry,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['journalEntries'] });
queryClient.invalidateQueries({ queryKey: ['glAccounts'] });
},
});
}
export function useReverseJournalEntry() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, reason }: { id: number; reason?: string }) => reverseJournalEntry(id, reason),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['journalEntries'] });
queryClient.invalidateQueries({ queryKey: ['journalEntry'] });
},
});
}