Add modal review/confirmation dialogs before submitting invoices, supplier invoices, and journal entries. Since journal entries are legally immutable once posted, users now see a full summary with an amber warning before confirming. Add AccountNumber component with rich tooltips showing account name, class, type, and plain-language Swedish explanation for ~45 key BAS accounts across all report views. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3.2 KiB
3.2 KiB
name, description
| name | description |
|---|---|
| erp-api-route | Generate Next.js 16 API routes for erp-base with correct auth guards, Supabase client usage, event emission, journal entry creation, and error handling. Use when creating new API endpoints in app/api/. Handles the Next.js 16 async params pattern, ensureInitialized() for events, non-blocking journal entry wrapping, and defense-in-depth user_id filtering. |
ERP API Route Generator
Standard Route Template
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { data, error } = await supabase
.from('table')
.select('*')
.eq('user_id', user.id) // Defense in depth alongside RLS
.order('created_at', { ascending: false })
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
return NextResponse.json({ data })
}
Route That Emits Events
Add at module level (outside the handler):
import { eventBus } from '@/lib/events/bus'
import { ensureInitialized } from '@/lib/init'
ensureInitialized() // MUST be module-level — loads extensions
Then emit after successful operations:
await eventBus.emit('invoice.created', { invoice: result, userId: user.id })
Dynamic Route Params (Next.js 16)
Params are a Promise — must await:
export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params // MUST await
// ...
}
Non-Blocking Journal Entry Creation
Journal entry failures must never block the business operation:
try {
const entry = await createXxxJournalEntry(user.id, ...)
if (entry) {
await supabase.from('table')
.update({ journal_entry_id: entry.id })
.eq('id', id)
}
} catch (err) {
console.error('Failed to create journal entry:', err)
// Continue — don't fail the request
}
Response Conventions
- Success:
NextResponse.json({ data: result }) - Success with count:
NextResponse.json({ data, count }) - Error:
NextResponse.json({ error: 'message' }, { status: N })
DB Query Pattern
Every query re-filters by user_id as defense in depth:
const { data, error } = await supabase
.from('table')
.select('*')
.eq('user_id', user.id) // Always include
.eq('id', id)
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
if (!data) {
return NextResponse.json({ error: 'Not found' }, { status: 404 })
}
Common Mistakes
- Forgetting
ensureInitialized()on routes that emit events — events silently won't fire - Using
params.idinstead of(await params).id— Next.js 16 breaking change - Missing
user_idfilter on queries — relies solely on RLS - Blocking on journal entry failure — must wrap in try/catch
- Returning
{ message }instead of{ error }on failure — inconsistent with codebase - Forgetting
awaitoncreateClient()— it's async in server context