Files
accounted/app/api/transactions/[id]/uncategorize/route.ts
T
Jakob WennbergandClaude Opus 4.6 f3ec634a46 feat: open-source under AGPL-3.0, redesign UI to grayscale palette, add uncategorize API, fix VAT account names
Add LICENSE (AGPL-3.0-or-later), CONTRIBUTING.md, SECURITY.md, DCO, and NOTICE files.
Rewrite README for open-source audience with self-hosting instructions.
Redesign color palette to grayscale chrome theme across all components.
Add transaction uncategorize API route with tests.
Fix VAT account name mismatches in migration 052.
Improve import page with SIE file support and loading skeleton.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 18:18:11 +01:00

77 lines
2.2 KiB
TypeScript

import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
import { reverseEntry } from '@/lib/bookkeeping/engine'
import { ensureInitialized } from '@/lib/init'
ensureInitialized()
export async function POST(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { id } = await params
// Fetch transaction
const { data: transaction, error: txError } = await supabase
.from('transactions')
.select('id, journal_entry_id')
.eq('id', id)
.eq('user_id', user.id)
.single()
if (txError || !transaction) {
return NextResponse.json({ error: 'Transaction not found' }, { status: 404 })
}
if (!transaction.journal_entry_id) {
return NextResponse.json({ error: 'Transaction has no journal entry' }, { status: 400 })
}
// Verify journal entry is posted
const { data: entry, error: entryError } = await supabase
.from('journal_entries')
.select('id, status')
.eq('id', transaction.journal_entry_id)
.eq('user_id', user.id)
.single()
if (entryError || !entry) {
return NextResponse.json({ error: 'Journal entry not found' }, { status: 400 })
}
if (entry.status !== 'posted') {
return NextResponse.json({ error: 'Journal entry is not posted' }, { status: 400 })
}
// Storno reversal (legally compliant — never deletes)
try {
await reverseEntry(supabase, user.id, transaction.journal_entry_id)
} catch (err) {
const message = err instanceof Error ? err.message : 'Reversal failed'
return NextResponse.json({ error: message }, { status: 500 })
}
// Reset transaction categorization
const { error: updateError } = await supabase
.from('transactions')
.update({
is_business: null,
category: null,
journal_entry_id: null,
})
.eq('id', id)
.eq('user_id', user.id)
if (updateError) {
return NextResponse.json({ error: 'Failed to reset transaction' }, { status: 500 })
}
return NextResponse.json({ success: true })
}