- SIE-hämtning är inte tillgänglig för denna leverantör ännu. Du kan importera SIE-filen manuellt via SIE-importen.
+ Bokföringsdata har redan importerats via SIE-fil. Du kan fortsätta med att importera kunder, leverantörer och fakturor.
+ Bokföringsdata (kontoplan, verifikationer och balanser) måste importeras via SIE-fil innan kunder, leverantörer och fakturor kan hämtas. Exportera en SIE-fil från {ARCIM_PROVIDERS.find(p => p.id === preview.consent.provider)?.name ?? 'ditt bokföringssystem'} och ladda upp den i gnubok.
+
+
+
+
+ )}
@@ -603,7 +632,7 @@ function PreviewStep({
Tillbaka
-
@@ -1387,6 +1416,19 @@ function ResultStep({
)
}
+function formatSkipReasons(reasons?: SkipReasons, entityType?: 'customer' | 'supplier' | 'invoice'): string | undefined {
+ if (!reasons) return undefined
+ const parts: string[] = []
+ if (reasons.duplicate) parts.push(`${reasons.duplicate} fanns redan`)
+ if (reasons.inactive) parts.push(`${reasons.inactive} inaktiv${reasons.inactive > 1 ? 'a' : ''}`)
+ if (reasons.noMatch) {
+ const matchLabel = entityType === 'invoice' ? 'utan matchning' : 'utan matchning'
+ parts.push(`${reasons.noMatch} ${matchLabel}`)
+ }
+ if (reasons.failed) parts.push(`${reasons.failed} misslyckades`)
+ return parts.length > 0 ? parts.join(', ') : undefined
+}
+
/** Simple row for non-SIE entity results (customers, invoices, etc.) */
function EntityResultRow({
icon,
diff --git a/extensions/general/arcim-migration/index.ts b/extensions/general/arcim-migration/index.ts
index 0cb43c0a..bbb92337 100644
--- a/extensions/general/arcim-migration/index.ts
+++ b/extensions/general/arcim-migration/index.ts
@@ -452,6 +452,13 @@ export const arcimMigrationExtension: Extension = {
}
}
+ // Check if the company already has completed SIE imports (from manual upload)
+ const { count: sieImportCount } = await supabase
+ .from('sie_imports')
+ .select('*', { count: 'exact', head: true })
+ .eq('company_id', companyId)
+ .eq('status', 'completed')
+
return NextResponse.json({
consent: {
id: consent.id,
@@ -462,6 +469,7 @@ export const arcimMigrationExtension: Extension = {
companyInfo: mapped,
sieAvailable,
sieStats,
+ hasSieData: (sieImportCount ?? 0) > 0,
})
} catch (error) {
log.error('Preview error:', error)
diff --git a/extensions/general/arcim-migration/lib/migration-orchestrator.ts b/extensions/general/arcim-migration/lib/migration-orchestrator.ts
index 2817b547..81537414 100644
--- a/extensions/general/arcim-migration/lib/migration-orchestrator.ts
+++ b/extensions/general/arcim-migration/lib/migration-orchestrator.ts
@@ -8,12 +8,12 @@
* 1. Company info → pre-fill company_settings
* 2. Customers → needed before sales invoices
* 3. Suppliers → needed before supplier invoices
- * 4. Sales invoices (open only)
- * 5. Supplier invoices (open only)
+ * 4. Sales invoices (all statuses, duplicates skipped)
+ * 5. Supplier invoices (all statuses, duplicates skipped)
*/
import type { SupabaseClient } from '@supabase/supabase-js'
-import type { MigrationProgress, MigrationResults } from '../types'
+import type { MigrationProgress, MigrationResults, SkipReasons } from '../types'
import type { ProviderName } from '@/lib/providers/types'
import { resolveConsent } from '@/lib/providers/resolve-consent'
import {
@@ -111,10 +111,12 @@ export async function executeMigration(options: MigrationOptions): Promise 0) {
console.log(`[migration] Customer skipped (duplicate org_number ${orgNumber}): ${customer.party.name}`)
customerIdMap.set(customer.id, existing[0].id)
+ skipReasons.duplicate = (skipReasons.duplicate ?? 0) + 1
skipped++
continue
}
@@ -146,6 +149,7 @@ export async function executeMigration(options: MigrationOptions): Promise 0) {
console.log(`[migration] Supplier skipped (duplicate org_number ${orgNumber}): ${supplier.party.name}`)
supplierIdMap.set(supplier.id, existing[0].id)
+ skipReasons.duplicate = (skipReasons.duplicate ?? 0) + 1
skipped++
continue
}
@@ -203,6 +210,7 @@ export async function executeMigration(options: MigrationOptions): Promise
- i.status === 'sent' || i.status === 'overdue' || i.status === 'booked'
- )
- console.log(`[migration] Sales invoices: ${invoices.length} total, ${openInvoices.length} open (filtered by status: sent/overdue/booked)`)
+ console.log(`[migration] Sales invoices: ${invoices.length} total`)
let imported = 0
let skipped = 0
+ const skipReasons: SkipReasons = {}
- for (const inv of openInvoices) {
+ for (const inv of invoices) {
const customerOrgNumber = inv.customer.legalEntity?.companyId ||
inv.customer.identifications?.find(i => i.schemeId === 'SE:ORGNR')?.id
@@ -280,6 +286,7 @@ export async function executeMigration(options: MigrationOptions): Promise 0) {
console.log(`[migration] Sales invoice ${inv.invoiceNumber} skipped — already exists`)
+ skipReasons.duplicate = (skipReasons.duplicate ?? 0) + 1
skipped++
continue
}
@@ -307,6 +315,7 @@ export async function executeMigration(options: MigrationOptions): Promise
- i.status === 'sent' || i.status === 'overdue' || i.status === 'booked' || i.status === 'draft'
- )
- console.log(`[migration] Supplier invoices: ${invoices.length} total, ${openInvoices.length} open (filtered by status: sent/overdue/booked/draft)`)
+ console.log(`[migration] Supplier invoices: ${invoices.length} total`)
let imported = 0
let skipped = 0
+ const skipReasons: SkipReasons = {}
- for (const inv of openInvoices) {
+ for (const inv of invoices) {
const supplierOrgNumber = inv.supplier.legalEntity?.companyId ||
inv.supplier.identifications?.find(i => i.schemeId === 'SE:ORGNR')?.id
@@ -392,6 +399,7 @@ export async function executeMigration(options: MigrationOptions): Promise 0) {
console.log(`[migration] Supplier invoice ${inv.invoiceNumber} skipped — already exists for supplier "${inv.supplier.name}"`)
+ skipReasons.duplicate = (skipReasons.duplicate ?? 0) + 1
skipped++
continue
}
@@ -418,6 +427,7 @@ export async function executeMigration(options: MigrationOptions): Promise): Accou
};
}
+/**
+ * Map Bokio Supplier to SupplierDto.
+ *
+ * Bokio Supplier fields:
+ * - id, name, orgNumber, vatNumber, paymentTerms
+ * - address: { line1, line2, city, postalCode, country }
+ * - contactsDetails: [{ email, phone, name }]
+ * - bankAccount, bankgiro, plusgiro
+ */
+export function mapBokioToSupplier(raw: Record): SupplierDto {
+ const name = (raw['name'] as string) ?? '';
+ const orgNumber = raw['orgNumber'] as string | undefined;
+ const address = raw['address'] as Record | undefined;
+ const contacts = (raw['contactsDetails'] as Record[] | undefined) ?? [];
+ const firstContact = contacts[0];
+
+ const party = buildParty(name, orgNumber, address);
+ if (firstContact) {
+ party.contact = {
+ email: firstContact['email'] as string | undefined,
+ telephone: firstContact['phone'] as string | undefined,
+ name: firstContact['name'] as string | undefined,
+ };
+ }
+
+ return {
+ id: String(raw['id'] ?? ''),
+ supplierNumber: String(raw['id'] ?? ''),
+ party,
+ active: true,
+ vatNumber: raw['vatNumber'] as string | undefined,
+ bankAccount: raw['bankAccount'] as string | undefined,
+ bankGiro: raw['bankgiro'] as string | undefined,
+ plusGiro: raw['plusgiro'] as string | undefined,
+ defaultPaymentTermsDays: raw['paymentTerms'] != null ? Number(raw['paymentTerms']) : undefined,
+ _raw: raw,
+ };
+}
+
+/**
+ * Map Bokio Supplier Invoice to SupplierInvoiceDto.
+ *
+ * Bokio Supplier Invoice fields:
+ * - id, invoiceNumber, status (draft|published|paid|overdue|cancelled)
+ * - invoiceDate, dueDate, currency, totalAmount, totalTax, paidAmount
+ * - supplierRef: { id, name }, lineItems: [{ id, description, quantity, unitPrice, taxRate, unitType }]
+ * - ocrNumber
+ */
+export function mapBokioToSupplierInvoice(raw: Record): SupplierInvoiceDto {
+ const currency = (raw['currency'] as string) ?? 'SEK';
+ const totalAmount = (raw['totalAmount'] as number) ?? 0;
+ const totalTax = (raw['totalTax'] as number) ?? 0;
+ const paidAmount = (raw['paidAmount'] as number) ?? 0;
+ const balance = totalAmount - paidAmount;
+
+ const supplierRef = raw['supplierRef'] as Record | undefined;
+ const rawLines = (raw['lineItems'] as Record[] | undefined) ?? [];
+
+ const lines: SupplierInvoiceLineDto[] = rawLines.map((line, idx) => {
+ const unitPrice = line['unitPrice'] as number | undefined;
+ const quantity = line['quantity'] as number | undefined;
+ const lineTotal = unitPrice != null && quantity != null ? unitPrice * quantity : 0;
+
+ return {
+ id: String(line['id'] ?? idx + 1),
+ description: line['description'] as string | undefined,
+ quantity,
+ unitCode: line['unitType'] as string | undefined,
+ unitPrice: unitPrice != null ? amount(unitPrice, currency) : undefined,
+ lineExtensionAmount: amount(lineTotal, currency),
+ taxPercent: line['taxRate'] as number | undefined,
+ };
+ });
+
+ const legalMonetaryTotal: LegalMonetaryTotalDto = {
+ lineExtensionAmount: amount(totalAmount - totalTax, currency),
+ taxInclusiveAmount: amount(totalAmount, currency),
+ payableAmount: amount(totalAmount, currency),
+ };
+
+ const paymentStatus: PaymentStatusDto = {
+ paid: paidAmount >= totalAmount && totalAmount > 0,
+ balance: amount(balance, currency),
+ };
+
+ return {
+ id: String(raw['id'] ?? ''),
+ invoiceNumber: String(raw['invoiceNumber'] ?? raw['id'] ?? ''),
+ issueDate: (raw['invoiceDate'] as string) ?? '',
+ dueDate: raw['dueDate'] as string | undefined,
+ currencyCode: currency,
+ status: deriveInvoiceStatus(raw),
+ supplier: buildParty(
+ (supplierRef?.['name'] as string) ?? '',
+ ),
+ buyer: buildParty(''),
+ lines,
+ legalMonetaryTotal,
+ paymentStatus,
+ ocrNumber: raw['ocrNumber'] as string | undefined,
+ _raw: raw,
+ };
+}
+
/**
* Map Bokio Company to CompanyInformationDto.
*
diff --git a/supabase/migrations/20260408120000_add_missing_delete_policies.sql b/supabase/migrations/20260408120000_add_missing_delete_policies.sql
new file mode 100644
index 00000000..804861d9
--- /dev/null
+++ b/supabase/migrations/20260408120000_add_missing_delete_policies.sql
@@ -0,0 +1,135 @@
+-- =============================================================================
+-- Add missing DELETE RLS policies
+-- =============================================================================
+-- The multi-tenant migration (20260330130000) dropped all existing policies
+-- but only recreated DELETE policies for company_members and api_keys.
+-- This migration adds the missing DELETE policies for all tables that need them.
+-- =============================================================================
+
+-- Direct company_id tables
+CREATE POLICY "customers_delete" ON public.customers
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "suppliers_delete" ON public.suppliers
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "invoices_delete" ON public.invoices
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "invoice_reminders_delete" ON public.invoice_reminders
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "invoice_payments_delete" ON public.invoice_payments
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "supplier_invoices_delete" ON public.supplier_invoices
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "supplier_invoice_payments_delete" ON public.supplier_invoice_payments
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "transactions_delete" ON public.transactions
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "bank_connections_delete" ON public.bank_connections
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "bank_file_imports_delete" ON public.bank_file_imports
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "receipts_delete" ON public.receipts
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "company_settings_delete" ON public.company_settings
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "chart_of_accounts_delete" ON public.chart_of_accounts
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "fiscal_periods_delete" ON public.fiscal_periods
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "journal_entries_delete" ON public.journal_entries
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "mapping_rules_delete" ON public.mapping_rules
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "categorization_templates_delete" ON public.categorization_templates
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "deadlines_delete" ON public.deadlines
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "cost_centers_delete" ON public.cost_centers
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "projects_delete" ON public.projects
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "calendar_feeds_delete" ON public.calendar_feeds
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "extension_data_delete" ON public.extension_data
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "skatteverket_tokens_delete" ON public.skatteverket_tokens
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "document_attachments_delete" ON public.document_attachments
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "invoice_inbox_items_delete" ON public.invoice_inbox_items
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "sie_imports_delete" ON public.sie_imports
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "sie_account_mappings_delete" ON public.sie_account_mappings
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "chat_sessions_delete" ON public.chat_sessions
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+CREATE POLICY "chat_messages_delete" ON public.chat_messages
+ FOR DELETE USING (company_id IN (SELECT public.user_company_ids()));
+
+-- Conditional tables (may not exist in all environments)
+DO $$ BEGIN
+ IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'salary_payments') THEN
+ EXECUTE 'CREATE POLICY "salary_payments_delete" ON public.salary_payments FOR DELETE USING (company_id IN (SELECT public.user_company_ids()))';
+ END IF;
+END $$;
+
+DO $$ BEGIN
+ IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'mileage_entries') THEN
+ EXECUTE 'CREATE POLICY "mileage_entries_delete" ON public.mileage_entries FOR DELETE USING (company_id IN (SELECT public.user_company_ids()))';
+ END IF;
+END $$;
+
+DO $$ BEGIN
+ IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'account_balances') THEN
+ EXECUTE 'CREATE POLICY "account_balances_delete" ON public.account_balances FOR DELETE USING (company_id IN (SELECT public.user_company_ids()))';
+ END IF;
+END $$;
+
+-- Sub-tables using parent join (same pattern as their SELECT/INSERT/UPDATE policies)
+CREATE POLICY "invoice_items_delete" ON public.invoice_items
+ FOR DELETE USING (
+ invoice_id IN (SELECT id FROM public.invoices WHERE company_id IN (SELECT public.user_company_ids()))
+ );
+
+CREATE POLICY "journal_entry_lines_delete" ON public.journal_entry_lines
+ FOR DELETE USING (
+ journal_entry_id IN (SELECT id FROM public.journal_entries WHERE company_id IN (SELECT public.user_company_ids()))
+ );
+
+CREATE POLICY "receipt_line_items_delete" ON public.receipt_line_items
+ FOR DELETE USING (
+ receipt_id IN (SELECT id FROM public.receipts WHERE company_id IN (SELECT public.user_company_ids()))
+ );
+
+CREATE POLICY "supplier_invoice_items_delete" ON public.supplier_invoice_items
+ FOR DELETE USING (
+ supplier_invoice_id IN (SELECT id FROM public.supplier_invoices WHERE company_id IN (SELECT public.user_company_ids()))
+ );