Files
accounted/app/api/documents/reanchor/cron/route.ts
T
MattssonandClaude Fable 5 e113e9c099 fix(vat,documents): EU reverse-charge packs feed ruta 20/21; daily reanchor cron for floating supplier-invoice underlag (#2095)
Two user reports (Anders, 2026-08-25 + 2026-08-29):

1. The seeded standardmallar "Inkop EU-varor/-tjanster, omvand moms 25%"
   booked the cost on 4010/6540, which no momsdeklaration ruta reads, so the
   fiktiv moms filled ruta 30/48 while ruta 20/21 (inkopsvarde) stayed 0;
   Skatteverket rejects that (FK004, ML 13 kap). The packs now book directly
   on the basis accounts 4515/4535 (ACCOUNT_RUTA -> ruta 20/21); the
   transaction-picker path already skips its own basis emission for basis
   debit accounts, so no double counting. Regression test pins every
   reverse-charge pack to a 44xx/45xx business debit. Prod rows update via
   the existing pack sync cron (upsert on pack_slug).

2. A kontantmetod payment verifikat stayed "Underlag saknas" although the
   invoice PDF was attached and eligible on every static condition: the
   inline anchorSupplierInvoiceDocument silently did nothing (prod case
   2026-08-28, verified in audit_log: no document_attachments update between
   the payment booking and the user's manual re-upload). The helper now
   verifies the guarded update actually matched a row instead of claiming
   success on zero rows, logs its silent bail branches, and a new daily cron
   (/api/documents/reanchor/cron) re-runs the anchor for any floating
   retained document with a posted verifikat, replacing the pattern of
   one-off repair migrations (20260727180000, 20260824150000). The sweep
   names the FK in its embed and is idempotent; locked/closed periods are
   skipped as before.


Claude-Session: https://claude.ai/code/session_01Jj6Rg1ViyFRej55gbxLVgj

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 10:16:07 +02:00

41 lines
1.8 KiB
TypeScript

import { NextResponse } from 'next/server'
import { withCronContext } from '@/lib/api/with-cron-context'
import { createServiceRoleClient } from '@/lib/supabase/service-client'
import { sweepFloatingSupplierInvoiceDocuments } from '@/lib/core/documents/supplier-invoice-underlag'
import { errorResponseFromCode } from '@/lib/errors/get-structured-error'
/**
* GET /api/documents/reanchor/cron: daily 03:30 UTC (schedule in vercel.json).
*
* Re-anchors supplier-invoice retained documents that are floating although
* the invoice has a posted verifikat. The inline anchoring in the payment
* routes is best-effort (the booking is already committed when it runs), so a
* transient failure there leaves the verifikat flagged "Underlag saknas" with
* the PDF plainly attached to the invoice, and until now only a hand-written
* repair migration ever retried. Prod case 2026-08-28: kontantmetod payment
* verifikat posted, document eligible, anchor silently did nothing.
*
* Idempotent: an anchored document is never moved, locked/closed periods are
* skipped, and a clean run touches nothing.
*/
export const maxDuration = 120
export const GET = withCronContext('cron.documents_reanchor', async (_request, ctx) => {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
if (!supabaseUrl || !supabaseServiceKey) {
return errorResponseFromCode('INTERNAL_ERROR', ctx.log, {
requestId: ctx.requestId,
details: { reason: 'Missing Supabase configuration' },
})
}
const supabase = createServiceRoleClient(supabaseUrl, supabaseServiceKey)
const result = await sweepFloatingSupplierInvoiceDocuments(supabase)
ctx.log.info('reanchor sweep finished', { ...result })
return NextResponse.json({ data: result })
})