From 06c067869169684a93dfdf0a4cd36cc405b30550 Mon Sep 17 00:00:00 2001 From: Emil Date: Fri, 20 Feb 2026 16:02:19 +0100 Subject: [PATCH] refactor: consolidate lib/invoice/ into lib/invoices/ Merge the singular lib/invoice/ directory into the plural lib/invoices/ to align with the codebase convention (transactions/, extensions/, reports/, etc.). Updates all import paths and CLAUDE.md architecture docs. Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 13 ++++++++++--- app/(dashboard)/invoices/[id]/credit/page.tsx | 2 +- app/(dashboard)/invoices/[id]/page.tsx | 2 +- app/(dashboard)/invoices/new/page.tsx | 2 +- app/api/invoices/[id]/pdf/route.ts | 2 +- app/api/invoices/[id]/send/route.ts | 2 +- app/api/invoices/route.ts | 2 +- app/api/transactions/batch-match-invoices/route.ts | 2 +- components/invoices/InvoiceReviewContent.tsx | 2 +- lib/{invoice => invoices}/invoice-matching.ts | 0 lib/{invoice => invoices}/pdf-template.tsx | 0 lib/{invoice => invoices}/vat-rules.ts | 0 lib/transactions/__tests__/ingest.test.ts | 2 +- lib/transactions/ingest.ts | 2 +- 14 files changed, 20 insertions(+), 13 deletions(-) rename lib/{invoice => invoices}/invoice-matching.ts (100%) rename lib/{invoice => invoices}/pdf-template.tsx (100%) rename lib/{invoice => invoices}/vat-rules.ts (100%) diff --git a/CLAUDE.md b/CLAUDE.md index ca8f6687..e0e8fac8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,8 +74,7 @@ lib/ events/ Event bus (bus.ts, types.ts) extensions/ Extension registry, loader, types import/ SIE and bank file parser - invoice/ VAT rules for invoicing - invoices/ Invoice business logic helpers + invoices/ Invoice helpers (VAT rules, PDF template, matching, reminders) reports/ Financial reports (trial-balance, income-statement, balance-sheet, vat-declaration, sie-export, supplier-ledger, supplier-reconciliation, @@ -266,7 +265,7 @@ All defined in `lib/events/types.ts`: ### Scope -Only test business logic in `lib/`. No component tests, no API route tests, no E2E tests. +Test business logic in `lib/` and API routes in `app/api/`. No component tests, no E2E tests. ### Framework @@ -302,6 +301,14 @@ mockResult({ data: makeTransaction(), error: null }) - Test error paths (missing fiscal period, unbalanced entries) - Verify events are emitted correctly +### API Route Tests + +- Colocated `__tests__/` directories alongside route files (e.g., `app/api/invoices/__tests__/route.test.ts`) +- Mock `@/lib/supabase/server`, `@/lib/init`, and lib functions — do NOT re-test lib business logic +- Use `createMockRequest()`, `parseJsonResponse()`, `createMockRouteParams()` from `tests/helpers.ts` +- Use `createQueuedMockSupabase()` for routes with multiple sequential Supabase calls +- Test: auth (401), validation (400), not found (404), errors (500), happy path, non-blocking journal entry failures + ### Reference Tests - `lib/bookkeeping/__tests__/engine.test.ts` — Balance validation diff --git a/app/(dashboard)/invoices/[id]/credit/page.tsx b/app/(dashboard)/invoices/[id]/credit/page.tsx index b31b1933..596af3bc 100644 --- a/app/(dashboard)/invoices/[id]/credit/page.tsx +++ b/app/(dashboard)/invoices/[id]/credit/page.tsx @@ -10,7 +10,7 @@ import { Label } from '@/components/ui/label' import { Separator } from '@/components/ui/separator' import { useToast } from '@/components/ui/use-toast' import { formatCurrency, formatDate } from '@/lib/utils' -import { getVatTreatmentLabel } from '@/lib/invoice/vat-rules' +import { getVatTreatmentLabel } from '@/lib/invoices/vat-rules' import { Loader2, ArrowLeft, AlertTriangle } from 'lucide-react' import type { Invoice, InvoiceItem, Customer } from '@/types' diff --git a/app/(dashboard)/invoices/[id]/page.tsx b/app/(dashboard)/invoices/[id]/page.tsx index e88673d9..a45681e5 100644 --- a/app/(dashboard)/invoices/[id]/page.tsx +++ b/app/(dashboard)/invoices/[id]/page.tsx @@ -10,7 +10,7 @@ import { Badge } from '@/components/ui/badge' import { Separator } from '@/components/ui/separator' import { useToast } from '@/components/ui/use-toast' import { formatCurrency, formatDate } from '@/lib/utils' -import { getVatTreatmentLabel } from '@/lib/invoice/vat-rules' +import { getVatTreatmentLabel } from '@/lib/invoices/vat-rules' import { Loader2, ArrowLeft, diff --git a/app/(dashboard)/invoices/new/page.tsx b/app/(dashboard)/invoices/new/page.tsx index 35c67569..586781a4 100644 --- a/app/(dashboard)/invoices/new/page.tsx +++ b/app/(dashboard)/invoices/new/page.tsx @@ -16,7 +16,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Separator } from '@/components/ui/separator' import { useToast } from '@/components/ui/use-toast' import { formatCurrency } from '@/lib/utils' -import { getVatRules, getVatTreatmentLabel } from '@/lib/invoice/vat-rules' +import { getVatRules, getVatTreatmentLabel } from '@/lib/invoices/vat-rules' import { Loader2, Plus, Trash2, ArrowLeft } from 'lucide-react' import { ConfirmationDialog } from '@/components/ui/confirmation-dialog' import { InvoiceReviewContent } from '@/components/invoices/InvoiceReviewContent' diff --git a/app/api/invoices/[id]/pdf/route.ts b/app/api/invoices/[id]/pdf/route.ts index 34b70c66..29757dba 100644 --- a/app/api/invoices/[id]/pdf/route.ts +++ b/app/api/invoices/[id]/pdf/route.ts @@ -1,7 +1,7 @@ import { createClient } from '@/lib/supabase/server' import { NextResponse } from 'next/server' import { renderToBuffer } from '@react-pdf/renderer' -import { InvoicePDF } from '@/lib/invoice/pdf-template' +import { InvoicePDF } from '@/lib/invoices/pdf-template' import type { Invoice, InvoiceItem, Customer, CompanySettings } from '@/types' export async function GET( diff --git a/app/api/invoices/[id]/send/route.ts b/app/api/invoices/[id]/send/route.ts index 16dd9adf..ffe095dd 100644 --- a/app/api/invoices/[id]/send/route.ts +++ b/app/api/invoices/[id]/send/route.ts @@ -3,7 +3,7 @@ import { NextResponse } from 'next/server' import { eventBus } from '@/lib/events' import { ensureInitialized } from '@/lib/init' import { renderToBuffer } from '@react-pdf/renderer' -import { InvoicePDF } from '@/lib/invoice/pdf-template' +import { InvoicePDF } from '@/lib/invoices/pdf-template' import { sendEmail, isResendConfigured } from '@/lib/email/resend' import { generateInvoiceEmailHtml, diff --git a/app/api/invoices/route.ts b/app/api/invoices/route.ts index 013ac235..c993fb5e 100644 --- a/app/api/invoices/route.ts +++ b/app/api/invoices/route.ts @@ -3,7 +3,7 @@ import { NextResponse } from 'next/server' import { eventBus } from '@/lib/events' import { ensureInitialized } from '@/lib/init' import type { CreateInvoiceInput, EntityType, Invoice, CreditNote } from '@/types' -import { getVatRules, calculateVat, calculateTotal } from '@/lib/invoice/vat-rules' +import { getVatRules, calculateVat, calculateTotal } from '@/lib/invoices/vat-rules' import { fetchExchangeRate, convertToSEK } from '@/lib/currency/riksbanken' import { createCreditNoteJournalEntry, diff --git a/app/api/transactions/batch-match-invoices/route.ts b/app/api/transactions/batch-match-invoices/route.ts index 55ec2a6b..a3d4e9eb 100644 --- a/app/api/transactions/batch-match-invoices/route.ts +++ b/app/api/transactions/batch-match-invoices/route.ts @@ -1,6 +1,6 @@ import { createClient } from '@/lib/supabase/server' import { NextResponse } from 'next/server' -import { getBestInvoiceMatch } from '@/lib/invoice/invoice-matching' +import { getBestInvoiceMatch } from '@/lib/invoices/invoice-matching' import type { Transaction } from '@/types' /** diff --git a/components/invoices/InvoiceReviewContent.tsx b/components/invoices/InvoiceReviewContent.tsx index 7da82d14..5baf2164 100644 --- a/components/invoices/InvoiceReviewContent.tsx +++ b/components/invoices/InvoiceReviewContent.tsx @@ -2,7 +2,7 @@ import { Badge } from '@/components/ui/badge' import { Separator } from '@/components/ui/separator' -import { getVatTreatmentLabel } from '@/lib/invoice/vat-rules' +import { getVatTreatmentLabel } from '@/lib/invoices/vat-rules' import { formatCurrency } from '@/lib/utils' import type { Customer, Currency, VatTreatment } from '@/types' diff --git a/lib/invoice/invoice-matching.ts b/lib/invoices/invoice-matching.ts similarity index 100% rename from lib/invoice/invoice-matching.ts rename to lib/invoices/invoice-matching.ts diff --git a/lib/invoice/pdf-template.tsx b/lib/invoices/pdf-template.tsx similarity index 100% rename from lib/invoice/pdf-template.tsx rename to lib/invoices/pdf-template.tsx diff --git a/lib/invoice/vat-rules.ts b/lib/invoices/vat-rules.ts similarity index 100% rename from lib/invoice/vat-rules.ts rename to lib/invoices/vat-rules.ts diff --git a/lib/transactions/__tests__/ingest.test.ts b/lib/transactions/__tests__/ingest.test.ts index 25b76326..efd1eae9 100644 --- a/lib/transactions/__tests__/ingest.test.ts +++ b/lib/transactions/__tests__/ingest.test.ts @@ -26,7 +26,7 @@ vi.mock('@/lib/bookkeeping/transaction-entries', () => ({ })) const mockGetBestInvoiceMatch = vi.fn() -vi.mock('@/lib/invoice/invoice-matching', () => ({ +vi.mock('@/lib/invoices/invoice-matching', () => ({ getBestInvoiceMatch: (...args: unknown[]) => mockGetBestInvoiceMatch(...args), })) diff --git a/lib/transactions/ingest.ts b/lib/transactions/ingest.ts index bda52e55..6dced623 100644 --- a/lib/transactions/ingest.ts +++ b/lib/transactions/ingest.ts @@ -1,7 +1,7 @@ import type { SupabaseClient } from '@supabase/supabase-js' import { evaluateMappingRules } from '@/lib/bookkeeping/mapping-engine' import { createTransactionJournalEntry } from '@/lib/bookkeeping/transaction-entries' -import { getBestInvoiceMatch } from '@/lib/invoice/invoice-matching' +import { getBestInvoiceMatch } from '@/lib/invoices/invoice-matching' import type { Transaction } from '@/types' /**