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 <noreply@anthropic.com>
This commit is contained in:
Emil
2026-02-20 16:02:19 +01:00
co-authored by Claude Opus 4.6
parent ba94f60d06
commit 06c0678691
14 changed files with 20 additions and 13 deletions
+10 -3
View File
@@ -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
@@ -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'
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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(
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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,
@@ -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'
/**
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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),
}))
+1 -1
View File
@@ -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'
/**