From fa7d4075cfab00b7eb6d5eea604b50a090ba661a Mon Sep 17 00:00:00 2001 From: Mattsson <111893710+mattssonn@users.noreply.github.com> Date: Tue, 5 May 2026 09:53:37 +0200 Subject: [PATCH] Supp/invoice bfl errors (#390) * feat(accounting): update accounting method validation and messaging for aktiebolag and enskild firma * Remove AI subsystem and related code - Deleted AI proposals and requests persistence logic from `lib/ai/proposals/persist.ts`. - Removed re-validation logic for proposals in `lib/ai/proposals/re-validate.ts`. - Cleaned up schemas related to AI flows in `lib/api/schemas.ts`. - Removed AI-related fields from bookkeeping engine in `lib/bookkeeping/engine.ts`. - Eliminated AI event types from `lib/events/types.ts`. - Updated tests to reflect the removal of AI-related functionality in `lib/extensions/__tests__/sectors.test.ts`. - Adjusted initialization logic in `lib/init.ts` to exclude AI proposal handler registration. - Cleaned up transaction ingestion logic in `lib/transactions/ingest.ts` to remove AI flow checks. - Updated helper functions in `tests/helpers.ts` to remove AI-related settings. - Removed AI-related types and interfaces from `types/index.ts`. - Added migration script to drop AI-related tables and settings from the database. * fix(migrations): ensure foreign key constraint is dropped before removing AI tables * feat(invoice-inbox): implement deterministic invoice field extraction and inbox provisioning - Added `extract-invoice-fields.ts` for extracting fields from PDF invoices using regex and pdfjs-dist, replacing the previous AI classifier. - Introduced `inbox-provisioning.ts` to manage company inbox addresses and rotation of inboxes using Supabase RPCs. - Created `resend-inbound.ts` for handling inbound email events and attachments via the Resend API. - Defined the extension manifest for the invoice inbox, specifying required environment variables and descriptions. - Migrated database schema to remove AI-related columns and tighten the status enum in `invoice_inbox_items`. * feat(invoice-inbox): remove AI-specific columns and tighten status enum * fix(skattekonto): remove manual entry creation reference from transaction input * fix(schemas): remove accounting method validation for aktiebolag in UpdateSettingsSchema --- app/(dashboard)/agent-inbox/page.tsx | 171 -- app/(dashboard)/expenses/[id]/page.tsx | 18 +- app/(dashboard)/layout.tsx | 13 +- app/(dashboard)/pending/page.tsx | 6 +- app/(dashboard)/receipts/page.tsx | 59 - app/(dashboard)/settings/bookkeeping/page.tsx | 44 +- .../supplier-invoices/[id]/page.tsx | 18 +- app/(dashboard)/transactions/page.tsx | 65 +- app/api/ai/backfill/cancel/route.ts | 38 - app/api/ai/backfill/receipts/route.ts | 222 -- .../ai/inbox-items/[id]/attach-file/route.ts | 154 -- .../inbox-items/[id]/request-receipt/route.ts | 218 -- app/api/ai/learning/remember/route.ts | 127 -- app/api/ai/proposals/[id]/accept/route.ts | 248 --- .../ai/proposals/[id]/change-match/route.ts | 177 -- app/api/ai/proposals/[id]/reject/route.ts | 117 - app/api/ai/proposals/[id]/route.ts | 79 - app/api/ai/proposals/batch-accept/route.ts | 142 -- app/api/ai/proposals/route.ts | 53 - app/api/ai/requests/[id]/resolve/route.ts | 106 - app/api/documents/match-sweep/route.ts | 38 - app/api/settings/route.ts | 8 - app/api/transactions/[id]/categorize/route.ts | 10 +- .../transactions/suggest-categories/route.ts | 44 - app/api/transactions/uncategorized/route.ts | 93 - components/RecaptIdentify.tsx | 2 +- components/agent-inbox/AgentInbox.tsx | 562 ----- .../agent-inbox/ChangeTransactionDialog.tsx | 378 ---- components/agent-inbox/EditBookingDialog.tsx | 155 -- .../agent-inbox/LearningPromptDialog.tsx | 43 - components/agent-inbox/ProposalCard.tsx | 346 --- .../agent-inbox/ReceiptDetailDialog.tsx | 408 ---- components/agent-inbox/RequestCard.tsx | 93 - .../agent-inbox/TransactionDetailDialog.tsx | 122 -- components/agent-inbox/receipt-quality.ts | 55 - components/dashboard/DashboardContent.tsx | 23 - components/dashboard/DashboardNav.tsx | 4 - .../extensions/ExtensionWorkspaceLoader.tsx | 10 +- .../general/AiCategorizationWorkspace.tsx | 4 +- .../general/InvoiceInboxWorkspace.tsx | 1952 +++++------------ components/onboarding/Step4VatAccounting.tsx | 9 +- components/receipts/ManualExtractDialog.tsx | 177 -- components/receipts/ReceiptsList.tsx | 540 ----- components/settings/ApiKeysPanel.tsx | 2 +- .../transactions/SwipeCategorizationView.tsx | 83 +- .../transactions/TransactionInboxCard.tsx | 14 +- components/transactions/transaction-types.ts | 3 +- extensions.config.json | 2 +- extensions/general/ai-agent/index.ts | 15 - .../general/ai-agent/lib/bedrock-client.ts | 30 - .../general/ai-agent/lib/bedrock-service.ts | 44 - .../general/ai-agent/lib/generate-booking.ts | 328 --- .../general/ai-agent/lib/generate-match.ts | 194 -- .../ai-agent/lib/prompts/booking-prompt.ts | 166 -- .../ai-agent/lib/prompts/match-prompt.ts | 80 - extensions/general/ai-agent/manifest.json | 23 - .../components/BankingSettingsPanel.tsx | 29 + extensions/general/enable-banking/index.ts | 2 +- .../__tests__/fetch-candidates.test.ts | 139 -- .../__tests__/match-receipt.test.ts | 134 -- extensions/general/inbox-smart-match/index.ts | 117 - .../inbox-smart-match/lib/fetch-candidates.ts | 170 -- .../inbox-smart-match/lib/match-receipt.ts | 200 -- .../inbox-smart-match/lib/process-match.ts | 214 -- .../general/inbox-smart-match/manifest.json | 23 - .../__tests__/convert-route.test.ts | 107 +- .../__tests__/extract-invoice-fields.test.ts | 150 ++ extensions/general/invoice-inbox/index.ts | 771 +------ .../invoice-inbox/lib/classify-document.ts | 637 ------ .../invoice-inbox/lib/error-messages.ts | 45 - .../lib/extract-invoice-fields.ts | 307 +++ .../invoice-inbox/lib/textract-expense.ts | 191 -- .../general/invoice-inbox/manifest.json | 11 +- .../mcp-server/resources/capabilities.ts | 2 +- .../mcp-server/resources/company-current.ts | 2 +- extensions/general/mcp-server/server.ts | 95 +- .../push-notifications/payload-builders.ts | 4 +- .../skatteverket/lib/skattekonto-booking.ts | 1 - lib/ai/feature-flag.ts | 38 - lib/ai/orchestrator.ts | 385 ---- lib/ai/proposal-service.ts | 123 -- lib/ai/proposals/__tests__/persist.test.ts | 296 --- .../proposals/__tests__/re-validate.test.ts | 422 ---- lib/ai/proposals/apply.ts | 188 -- lib/ai/proposals/persist.ts | 264 --- lib/ai/proposals/re-validate.ts | 346 --- lib/api/__tests__/schemas.test.ts | 12 +- lib/api/schemas.ts | 12 - lib/bookkeeping/engine.ts | 2 - lib/company/__tests__/actions.test.ts | 2 +- lib/company/actions.ts | 8 +- .../__tests__/document-matcher.test.ts | 352 --- lib/documents/batch-match.ts | 127 -- lib/documents/document-matcher.ts | 322 --- lib/events/types.ts | 9 - lib/extensions/__tests__/sectors.test.ts | 6 +- .../_generated/enabled-extensions.ts | 1 + lib/extensions/_generated/extension-list.ts | 2 + .../_generated/sector-definitions.ts | 15 + lib/extensions/_generated/workspace-map.tsx | 1 + lib/init.ts | 3 - lib/transactions/__tests__/ingest.test.ts | 2 - lib/transactions/ingest.ts | 30 +- package.json | 2 - .../20260504120000_remove_ai_subsystem.sql | 38 + ...180000_invoice_inbox_remove_ai_columns.sql | 50 + tests/helpers.ts | 15 +- types/index.ts | 166 +- 108 files changed, 1491 insertions(+), 13234 deletions(-) delete mode 100644 app/(dashboard)/agent-inbox/page.tsx delete mode 100644 app/(dashboard)/receipts/page.tsx delete mode 100644 app/api/ai/backfill/cancel/route.ts delete mode 100644 app/api/ai/backfill/receipts/route.ts delete mode 100644 app/api/ai/inbox-items/[id]/attach-file/route.ts delete mode 100644 app/api/ai/inbox-items/[id]/request-receipt/route.ts delete mode 100644 app/api/ai/learning/remember/route.ts delete mode 100644 app/api/ai/proposals/[id]/accept/route.ts delete mode 100644 app/api/ai/proposals/[id]/change-match/route.ts delete mode 100644 app/api/ai/proposals/[id]/reject/route.ts delete mode 100644 app/api/ai/proposals/[id]/route.ts delete mode 100644 app/api/ai/proposals/batch-accept/route.ts delete mode 100644 app/api/ai/proposals/route.ts delete mode 100644 app/api/ai/requests/[id]/resolve/route.ts delete mode 100644 app/api/documents/match-sweep/route.ts delete mode 100644 app/api/transactions/uncategorized/route.ts delete mode 100644 components/agent-inbox/AgentInbox.tsx delete mode 100644 components/agent-inbox/ChangeTransactionDialog.tsx delete mode 100644 components/agent-inbox/EditBookingDialog.tsx delete mode 100644 components/agent-inbox/LearningPromptDialog.tsx delete mode 100644 components/agent-inbox/ProposalCard.tsx delete mode 100644 components/agent-inbox/ReceiptDetailDialog.tsx delete mode 100644 components/agent-inbox/RequestCard.tsx delete mode 100644 components/agent-inbox/TransactionDetailDialog.tsx delete mode 100644 components/agent-inbox/receipt-quality.ts delete mode 100644 components/receipts/ManualExtractDialog.tsx delete mode 100644 components/receipts/ReceiptsList.tsx delete mode 100644 extensions/general/ai-agent/index.ts delete mode 100644 extensions/general/ai-agent/lib/bedrock-client.ts delete mode 100644 extensions/general/ai-agent/lib/bedrock-service.ts delete mode 100644 extensions/general/ai-agent/lib/generate-booking.ts delete mode 100644 extensions/general/ai-agent/lib/generate-match.ts delete mode 100644 extensions/general/ai-agent/lib/prompts/booking-prompt.ts delete mode 100644 extensions/general/ai-agent/lib/prompts/match-prompt.ts delete mode 100644 extensions/general/ai-agent/manifest.json delete mode 100644 extensions/general/inbox-smart-match/__tests__/fetch-candidates.test.ts delete mode 100644 extensions/general/inbox-smart-match/__tests__/match-receipt.test.ts delete mode 100644 extensions/general/inbox-smart-match/index.ts delete mode 100644 extensions/general/inbox-smart-match/lib/fetch-candidates.ts delete mode 100644 extensions/general/inbox-smart-match/lib/match-receipt.ts delete mode 100644 extensions/general/inbox-smart-match/lib/process-match.ts delete mode 100644 extensions/general/inbox-smart-match/manifest.json create mode 100644 extensions/general/invoice-inbox/__tests__/extract-invoice-fields.test.ts delete mode 100644 extensions/general/invoice-inbox/lib/classify-document.ts delete mode 100644 extensions/general/invoice-inbox/lib/error-messages.ts create mode 100644 extensions/general/invoice-inbox/lib/extract-invoice-fields.ts delete mode 100644 extensions/general/invoice-inbox/lib/textract-expense.ts delete mode 100644 lib/ai/feature-flag.ts delete mode 100644 lib/ai/orchestrator.ts delete mode 100644 lib/ai/proposal-service.ts delete mode 100644 lib/ai/proposals/__tests__/persist.test.ts delete mode 100644 lib/ai/proposals/__tests__/re-validate.test.ts delete mode 100644 lib/ai/proposals/apply.ts delete mode 100644 lib/ai/proposals/persist.ts delete mode 100644 lib/ai/proposals/re-validate.ts delete mode 100644 lib/documents/__tests__/document-matcher.test.ts delete mode 100644 lib/documents/batch-match.ts delete mode 100644 lib/documents/document-matcher.ts create mode 100644 supabase/migrations/20260504120000_remove_ai_subsystem.sql create mode 100644 supabase/migrations/20260504180000_invoice_inbox_remove_ai_columns.sql diff --git a/app/(dashboard)/agent-inbox/page.tsx b/app/(dashboard)/agent-inbox/page.tsx deleted file mode 100644 index 8b45144f..00000000 --- a/app/(dashboard)/agent-inbox/page.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { notFound, redirect } from 'next/navigation' -import Link from 'next/link' -import { createClient } from '@/lib/supabase/server' -import { ensureInitialized } from '@/lib/init' -import { ENABLED_EXTENSION_IDS } from '@/lib/extensions/_generated/enabled-extensions' -import { isAgentInboxEnabled } from '@/lib/ai/feature-flag' -import { requireCompanyId } from '@/lib/company/context' -import { PageHeader } from '@/components/ui/page-header' -import { Button } from '@/components/ui/button' -import { Card, CardContent } from '@/components/ui/card' -import { Sparkles } from 'lucide-react' -import AgentInbox from '@/components/agent-inbox/AgentInbox' -import type { AIProposal, AIRequest, InvoiceInboxItem, Transaction, DocumentAttachment, MatchProposalPayload } from '@/types' - -ensureInitialized() - -// Expanded card data the client component needs to render each proposal's -// context (receipt thumbnail + matched transaction summary). -export interface AgentInboxItemView { - proposal: AIProposal | null - request: AIRequest | null - inbox_item: InvoiceInboxItem & { document: DocumentAttachment | null } - transaction: Transaction | null -} - -export default async function AgentInboxPage() { - // Hard gate: extension not enabled at build time, OR the feature flag is - // off in this environment (prod by default) → 404. - if (!ENABLED_EXTENSION_IDS.has('ai-agent') || !isAgentInboxEnabled()) { - notFound() - } - - const supabase = await createClient() - const { data: { user } } = await supabase.auth.getUser() - if (!user) redirect('/login') - - const companyId = await requireCompanyId(supabase, user.id) - - // Soft gate: per-company toggle. If not enabled, show an empty-state - // pointing to settings rather than 404 — the extension exists, the user - // just hasn't opted in yet. - const { data: settings } = await supabase - .from('company_settings') - .select('ai_flow_enabled') - .eq('company_id', companyId) - .maybeSingle() - - if (!settings?.ai_flow_enabled) { - return ( -
- Aktivera AI-agenten under bokföringsinställningar. Varje transaktion blir då ett - granskningsförslag istället för automatisk bokföring. -
- -{activeTab === 'pending' - ? 'När din AI-agent skapar bokföring visas den här för granskning.' - : 'Historik för AI-agentens operationer visas här.'} + ? 'När en operation kräver godkännande visas den här för granskning.' + : 'Operationer du har godkänt eller avvisat visas här.'}
diff --git a/app/(dashboard)/receipts/page.tsx b/app/(dashboard)/receipts/page.tsx deleted file mode 100644 index 8fb39805..00000000 --- a/app/(dashboard)/receipts/page.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { notFound, redirect } from 'next/navigation' -import { createClient } from '@/lib/supabase/server' -import { ensureInitialized } from '@/lib/init' -import { ENABLED_EXTENSION_IDS } from '@/lib/extensions/_generated/enabled-extensions' -import { requireCompanyId } from '@/lib/company/context' -import ReceiptsList from '@/components/receipts/ReceiptsList' -import type { InvoiceInboxItem, DocumentAttachment } from '@/types' - -ensureInitialized() - -export type ReceiptRow = InvoiceInboxItem & { document: DocumentAttachment | null } -export type ReceiptRowWithPreview = ReceiptRow & { preview_url: string | null } - -export default async function ReceiptsPage() { - // Hard gate: if the invoice-inbox extension isn't loaded, there's no - // upload pipeline and nothing would work here. - if (!ENABLED_EXTENSION_IDS.has('invoice-inbox')) notFound() - - const supabase = await createClient() - const { data: { user } } = await supabase.auth.getUser() - if (!user) redirect('/login') - - const companyId = await requireCompanyId(supabase, user.id) - - const { data } = await supabase - .from('invoice_inbox_items') - .select('*, document:document_attachments!document_id(*)') - .eq('company_id', companyId) - .eq('document_type', 'receipt') - .order('created_at', { ascending: false }) - .limit(200) - - const rows = (data ?? []) as ReceiptRow[] - - // Batch-sign all document paths so each row renders with a thumbnail. - // Supabase exposes createSignedUrls (plural) for exactly this use case. - const paths = rows - .map((r) => r.document?.storage_path) - .filter((p): p is string => Boolean(p)) - - const urlByPath = new Map- {settings.entity_type === 'aktiebolag' - ? 'Aktiebolag med omsättning över 3 MSEK måste använda faktureringsmetoden.' - : 'Kontantmetoden är tillgänglig för enskild firma med omsättning under 3 MSEK.'} + Kontantmetoden får användas om årlig nettoomsättning normalt är högst + 3 MSEK (BFL 5 kap. 2 §). Obetalda fordringar och skulder ska bokföras + vid räkenskapsårets utgång.
- När aktiv: varje ny banktransaktion blir ett AI-förslag du granskar i - agent-inkorgen. - Den automatiska bokföringen (≥80% regelmatchning) stängs av — inget bokförs - utan din bekräftelse. -
-- ${senderName ? `${senderName} ` : ''}behöver ett kvittounderlag för ${companyName} innan en transaktion kan bokföras. -
-- ${summaryLine || 'Kvitto utan extraherade uppgifter'} -
-Öppna agent-inkorgen och ladda upp en tydlig bild eller PDF av kvittot:
- -- Send in receipts. Utan källunderlag kan bokföringen inte slutföras enligt BFL 5 kap 7§. -
-` -} - -function buildText({ companyName, summaryLine, deepLink, senderName }: TemplateArgs): string { - return [ - 'Source documents required for receipt and transaction mapping.', - '', - `${senderName ? `${senderName} ` : ''}behöver ett kvittounderlag för ${companyName} innan en transaktion kan bokföras.`, - '', - summaryLine || 'Kvitto utan extraherade uppgifter', - '', - 'Öppna agent-inkorgen och ladda upp en tydlig bild eller PDF av kvittot:', - deepLink, - '', - 'Send in receipts. Utan källunderlag kan bokföringen inte slutföras enligt BFL 5 kap 7§.', - ].join('\n') -} diff --git a/app/api/ai/learning/remember/route.ts b/app/api/ai/learning/remember/route.ts deleted file mode 100644 index b6051611..00000000 --- a/app/api/ai/learning/remember/route.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { NextResponse } from 'next/server' -import { createClient } from '@/lib/supabase/server' -import { ensureInitialized } from '@/lib/init' -import { validateBody } from '@/lib/api/validate' -import { RememberLearningSchema } from '@/lib/api/schemas' -import { requireCompanyId } from '@/lib/company/context' -import { requireWritePermission } from '@/lib/auth/require-write' -import { calculateConfidence } from '@/lib/bookkeeping/counterparty-templates' -import { gateAgentInbox } from '@/lib/ai/feature-flag' -import type { AIProposal } from '@/types' - -ensureInitialized() - -/** - * POST /api/ai/learning/remember - * - * Called from the UI's learning-prompt dialog after a user edited and - * accepted an AI booking proposal. Upserts a categorization_templates row - * with source='ai_corrected' so next time's proposal for the same - * counterparty starts from the user's preference. - * - * This is the ONLY path that creates an ai_corrected template — the - * "silent learning" rule means every template with this source represents - * an explicit user choice. - */ -export async function POST(request: Request) { - const gate = gateAgentInbox() - if (gate) return gate - - const supabase = await createClient() - const { data: { user } } = await supabase.auth.getUser() - if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - - const writeCheck = await requireWritePermission(supabase, user.id) - if (!writeCheck.ok) return writeCheck.response - - const companyId = await requireCompanyId(supabase, user.id) - - const validation = await validateBody(request, RememberLearningSchema) - if (!validation.success) return validation.response - const { - proposal_id, - counterparty_name, - debit_account, - credit_account, - vat_treatment, - category, - } = validation.data - - // Verify the proposal is accepted + belongs to this company. - const { data: proposal } = await supabase - .from('ai_proposals') - .select('*') - .eq('id', proposal_id) - .eq('company_id', companyId) - .maybeSingle() - - if (!proposal) return NextResponse.json({ error: 'Not found' }, { status: 404 }) - const typed = proposal as AIProposal - if (typed.status !== 'accepted') { - return NextResponse.json( - { error: 'Endast accepterade förslag kan lagras som mall.' }, - { status: 400 } - ) - } - if (typed.step_type !== 'booking') { - return NextResponse.json( - { error: 'Endast bokföringssteget kan lagras som mall.' }, - { status: 400 } - ) - } - - // Upsert the template. Existing row for the same (user_id, counterparty_name) - // gets its source bumped up and occurrence incremented. - const { data: existing } = await supabase - .from('categorization_templates') - .select('*') - .eq('user_id', user.id) - .eq('counterparty_name', counterparty_name) - .maybeSingle() - - const today = new Date().toISOString().slice(0, 10) - - if (existing) { - const newOccurrence = existing.occurrence_count + 1 - await supabase - .from('categorization_templates') - .update({ - debit_account, - credit_account, - vat_treatment, - category, - source: 'ai_corrected', - occurrence_count: newOccurrence, - confidence: calculateConfidence(newOccurrence), - last_seen_date: today, - is_active: true, - }) - .eq('id', existing.id) - - return NextResponse.json({ data: { template_id: existing.id, updated: true } }) - } - - const { data: created, error: insertError } = await supabase - .from('categorization_templates') - .insert({ - user_id: user.id, - company_id: companyId, - counterparty_name, - counterparty_aliases: [counterparty_name], - debit_account, - credit_account, - vat_treatment, - category, - source: 'ai_corrected', - occurrence_count: 1, - confidence: calculateConfidence(1), - last_seen_date: today, - is_active: true, - }) - .select() - .single() - - if (insertError) return NextResponse.json({ error: insertError.message }, { status: 500 }) - - return NextResponse.json({ data: { template_id: created.id, updated: false } }) -} diff --git a/app/api/ai/proposals/[id]/accept/route.ts b/app/api/ai/proposals/[id]/accept/route.ts deleted file mode 100644 index 01d0c20e..00000000 --- a/app/api/ai/proposals/[id]/accept/route.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { NextResponse } from 'next/server' -import { createClient } from '@/lib/supabase/server' -import { eventBus } from '@/lib/events/bus' -import { ensureInitialized } from '@/lib/init' -import { validateBody } from '@/lib/api/validate' -import { AcceptProposalSchema } from '@/lib/api/schemas' -import { requireCompanyId } from '@/lib/company/context' -import { requireWritePermission } from '@/lib/auth/require-write' -import { reValidateProposal } from '@/lib/ai/proposals/re-validate' -import { applyProposal } from '@/lib/ai/proposals/apply' -import { bookkeepingErrorResponse } from '@/lib/bookkeeping/errors' -import { appendProcessingHistory } from '@/lib/processing-history/append' -import { gateAgentInbox } from '@/lib/ai/feature-flag' -import type { - AIProposal, - BookingProposalPayload, - InvoiceInboxItem, - MatchProposalPayload, -} from '@/types' - -ensureInitialized() - -/** - * POST /api/ai/proposals/[id]/accept - * - * Accept a pending proposal: - * 1. Optimistic lock on version to catch concurrent clicks. - * 2. Re-validate (period open, transaction still unbooked, accounts active). - * 3. Apply via lib/ai/proposals/apply.ts (engine call happens there). - * 4. Mark status='accepted', set applied_entry_id, bump version. - * 5. If `edits` provided and differ from proposal_json, record edit_diff - * and return a `learning_prompt` hint so the UI can ask - * "remember this booking for- När nya kvitton klassas i inkorgen kommer AI-förslagen att visas här. -
-- AI-agenten skapar förslag ett kvitto i taget. Nya kort dyker upp här automatiskt. -
-- - Kvittobild saknas — ladda upp i kvittodialogen innan du kan godkänna. -
- )} - -“{reasoning}”
- )} - {hasAlternatives && ( -| Konto | -Debet | -Kredit | -
|---|---|---|
| - {line.account_number}{' '} - {line.description} - | -- {line.debit_amount > 0 ? line.debit_amount.toFixed(2) : ''} - | -- {line.credit_amount > 0 ? line.credit_amount.toFixed(2) : ''} - | -
| - {payload.vat_treatment && Moms: {payload.vat_treatment}} - {payload.default_private && Privat uttag} - | -{totalDebit.toFixed(2)} | -{totalDebit.toFixed(2)} | -
{req.message}
-Kvitton
-- {summary.receiptQueue.pending_review_count > 0 - ? `${summary.receiptQueue.pending_review_count} att granska` - : `${summary.receiptQueue.unmatched_receipts_count} omatchade`} -
-- {item.match_reasoning} -
- )} -Din fakturainkorg
-{inboxAddress.address}
-
+ {inboxAddress.address}
+
+ + {onActivateInbox ? 'Aktivera din inkorgsadress' : 'Välj ett dokument från listan'} +
++ {onActivateInbox + ? 'Ditt bolag får en unik e-postadress som leverantörer kan skicka fakturor till.' + : 'Eller dra och släpp en fil var som helst på sidan för att ladda upp.'} +
+Fel vid bearbetning
+{item.error_message}
++ Kunde inte läsa text — manuell registrering krävs. +
+ )} +- Aktiebolag med omsättning över 3 MSEK per år måste använda faktureringsmetoden. -
- )} ++ Kontantmetoden får användas om årlig nettoomsättning normalt är högst + 3 MSEK (BFL 5 kap. 2 §). +
- AI läste {v.claude_total != null ? formatCurrency(v.claude_total, currency) : '—'}, OCR läste{' '} - {v.ocr_total != null ? formatCurrency(v.ocr_total, currency) : '—'}. Granska bilden innan du godkänner. -
- ) -} - -// Optimistic placeholder shown in the list while a manual upload is in -// flight. The upload handler is synchronous (classify + store + insert -// happen before the response returns), so a 5-10 s gap otherwise leaves the -// user staring at nothing. We insert a fake row here so the UI shows a real -// card immediately and router.refresh() replaces it with the persisted row. -interface PendingUpload { - key: string - file_name: string - size_bytes: number - mime_type: string -} - -export default function ReceiptsList({ initialItems }: { initialItems: ReceiptRowWithPreview[] }) { - const [items, setItems] = useState(initialItems) - const [uploading, setUploading] = useState(false) - const [pendingUploads, setPendingUploads] = useState- Ladda upp ett kvitto (PDF, JPG, PNG, HEIC eller WebP) så tar AI hand om resten. -
-{row.error_message}
- )} -- Dokumentet från inkorgen länkas automatiskt till verifikationen. -
-{supplierName || merchantName}
- )} - {totals?.totals?.total != null && ( -- {formatCurrency(totals.totals.total)} -
- )} - > - ) - })()} - {currentTransaction.matched_inbox_item.suggested_template_id && ( -- Mall: {currentTransaction.matched_inbox_item.suggested_template_id} -
- )} -{transaction.description}
-{formatDate(transaction.date)}
- {hasDocumentMatch && ( -{formatDate(transaction.date)}