diff --git a/app/api/extensions/enable-banking/sync/cron/route.ts b/app/api/extensions/enable-banking/sync/cron/route.ts index 9c988c01..114475f5 100644 --- a/app/api/extensions/enable-banking/sync/cron/route.ts +++ b/app/api/extensions/enable-banking/sync/cron/route.ts @@ -181,7 +181,7 @@ export const GET = withCronContext('cron.bank_sync', async (_request, ctx) => { // Batch reconciliation sweep when SIE overlap detected if (sieOverlap && totalImported > 0) { try { - await runReconciliation(supabase, connection.company_id, { + await runReconciliation(supabase, connection.company_id, connection.user_id, { dateFrom: fromDate, dateTo: toDate, }) diff --git a/app/api/reconciliation/bank/link/route.ts b/app/api/reconciliation/bank/link/route.ts index b620f508..807cfae2 100644 --- a/app/api/reconciliation/bank/link/route.ts +++ b/app/api/reconciliation/bank/link/route.ts @@ -26,7 +26,7 @@ export async function POST(request: Request) { if (!validation.success) return validation.response const { transaction_id, journal_entry_id } = validation.data - const result = await manualLink(supabase, companyId, transaction_id, journal_entry_id) + const result = await manualLink(supabase, companyId, transaction_id, journal_entry_id, user.id) if (!result.success) { return NextResponse.json({ error: result.error }, { status: 400 }) diff --git a/app/api/reconciliation/bank/run/route.ts b/app/api/reconciliation/bank/run/route.ts index aac0be02..586b0631 100644 --- a/app/api/reconciliation/bank/run/route.ts +++ b/app/api/reconciliation/bank/run/route.ts @@ -26,7 +26,7 @@ export async function POST(request: Request) { if (!validation.success) return validation.response const { date_from, date_to, dry_run } = validation.data - const result = await runReconciliation(supabase, companyId, { + const result = await runReconciliation(supabase, companyId, user.id, { dateFrom: date_from, dateTo: date_to, dryRun: dry_run ?? false, diff --git a/components/onboarding/Step2CompanyDetails.tsx b/components/onboarding/Step2CompanyDetails.tsx index 86b5166b..d03a5c9c 100644 --- a/components/onboarding/Step2CompanyDetails.tsx +++ b/components/onboarding/Step2CompanyDetails.tsx @@ -12,6 +12,7 @@ import { Loader2, ArrowRight, ArrowLeft, CheckCircle2, AlertTriangle } from 'luc import type { EntityType } from '@/types' import type { CompanyLookupResult } from '@/lib/company-lookup/types' import { getBranding } from '@/lib/branding/service' +import { normalizeOrgNumber } from '@/lib/company-lookup/normalize-org-number' const branding = getBranding() @@ -19,7 +20,10 @@ const schema = z.object({ company_name: z.string().min(1, 'Företagsnamn krävs'), org_number: z.string() .min(1, 'Organisationsnummer krävs') - .regex(/^\d{6,8}[-\s]?\d{4}$/, 'Ogiltigt format. Ange XXXXXX-XXXX'), + .refine( + (val) => normalizeOrgNumber(val) !== null, + 'Ogiltigt organisationsnummer. Kontrollera att du angett ett giltigt 10- eller 12-siffrigt organisationsnummer.', + ), address_line1: z.string().optional(), postal_code: z.string().optional(), city: z.string().optional(), @@ -27,8 +31,6 @@ const schema = z.object({ type FormData = z.infer -const ORG_NUMBER_REGEX = /^\d{6,8}[-\s]?\d{4}$/ - interface Step2Props { initialData: Partial entityType?: EntityType @@ -82,7 +84,7 @@ export default function Step2CompanyDetails({ // button is disabled; the server action would also reject ('org_number_exists') // but blocking client-side avoids a wasted roundtrip. useEffect(() => { - if (!orgNumber || !ORG_NUMBER_REGEX.test(orgNumber)) { + if (!orgNumber || normalizeOrgNumber(orgNumber) === null) { setOrgNumberExists(false) return } @@ -109,7 +111,7 @@ export default function Step2CompanyDetails({ }, [orgNumber]) useEffect(() => { - if (!ticEnabled || !orgNumber || !ORG_NUMBER_REGEX.test(orgNumber)) { + if (!ticEnabled || !orgNumber || normalizeOrgNumber(orgNumber) === null) { return } diff --git a/extensions/general/enable-banking/index.ts b/extensions/general/enable-banking/index.ts index 2138ba57..349c766b 100644 --- a/extensions/general/enable-banking/index.ts +++ b/extensions/general/enable-banking/index.ts @@ -318,7 +318,7 @@ export const enableBankingExtension: Extension = { // Skip for viewers — reconciliation updates transactions which viewers cannot do. if (sieOverlap && totalImported > 0 && !isViewer) { try { - const reconResult = await runReconciliation(supabase, ctx?.companyId ?? user.id, { + const reconResult = await runReconciliation(supabase, companyId, user.id, { dateFrom: fromDate, dateTo: toDate, }) @@ -346,7 +346,7 @@ export const enableBankingExtension: Extension = { const { data: syncedTransactions } = await supabase .from('transactions') .select('*') - .eq('company_id', ctx?.companyId ?? user.id) + .eq('company_id', companyId) .eq('bank_connection_id', connection.id) .gte('created_at', syncStartedAt) .order('created_at', { ascending: false }) @@ -356,7 +356,7 @@ export const enableBankingExtension: Extension = { const emit = ctx?.emit ?? (await import('@/lib/events/bus')).eventBus.emit.bind((await import('@/lib/events/bus')).eventBus) await emit({ type: 'transaction.synced', - payload: { transactions: syncedTransactions as Transaction[], userId: user.id, companyId: ctx?.companyId ?? user.id }, + payload: { transactions: syncedTransactions as Transaction[], userId: user.id, companyId }, }) } } diff --git a/lib/reconciliation/__tests__/bank-reconciliation.test.ts b/lib/reconciliation/__tests__/bank-reconciliation.test.ts index c4ad8606..d458cca7 100644 --- a/lib/reconciliation/__tests__/bank-reconciliation.test.ts +++ b/lib/reconciliation/__tests__/bank-reconciliation.test.ts @@ -319,7 +319,7 @@ describe('runReconciliation', () => { // from('transactions').select — unmatched enqueue({ data: [] }) - const result = await runReconciliation(supabase as never, 'company-1') + const result = await runReconciliation(supabase as never, 'company-1', 'user-1') expect(result.matches).toEqual([]) expect(result.applied).toBe(0) @@ -341,7 +341,7 @@ describe('runReconciliation', () => { // from('transactions') returns unmatched transactions enqueue({ data: [tx] }) - const result = await runReconciliation(supabase as never, 'company-1', { dryRun: true }) + const result = await runReconciliation(supabase as never, 'company-1', 'user-1', { dryRun: true }) expect(result.matches).toHaveLength(1) expect(result.matches[0].method).toBe('auto_exact') @@ -366,7 +366,7 @@ describe('runReconciliation', () => { // Update transaction with link enqueue({ data: null, error: null }) - const result = await runReconciliation(supabase as never, 'company-1', { dryRun: false }) + const result = await runReconciliation(supabase as never, 'company-1', 'user-1', { dryRun: false }) expect(result.matches).toHaveLength(1) expect(result.applied).toBe(1) @@ -420,7 +420,7 @@ describe('manualLink', () => { // Transaction query returns null enqueue({ data: null, error: { message: 'Not found' } }) - const result = await manualLink(supabase as never, 'company-1', 'tx-1', 'je-1') + const result = await manualLink(supabase as never, 'company-1', 'tx-1', 'je-1', 'user-1') expect(result.success).toBe(false) expect(result.error).toBe('Transaction not found') @@ -433,7 +433,7 @@ describe('manualLink', () => { // Transaction found but already linked enqueue({ data: tx }) - const result = await manualLink(supabase as never, 'company-1', 'tx-1', 'je-1') + const result = await manualLink(supabase as never, 'company-1', 'tx-1', 'je-1', 'user-1') expect(result.success).toBe(false) expect(result.error).toBe('Transaction is already linked to a journal entry') @@ -450,7 +450,7 @@ describe('manualLink', () => { // No 1930 lines enqueue({ data: [] }) - const result = await manualLink(supabase as never, 'company-1', 'tx-1', 'je-1') + const result = await manualLink(supabase as never, 'company-1', 'tx-1', 'je-1', 'user-1') expect(result.success).toBe(false) expect(result.error).toBe('Verifikationen saknar rad på bankkonto (19xx)') @@ -471,7 +471,7 @@ describe('manualLink', () => { // Update succeeds enqueue({ data: null, error: null }) - const result = await manualLink(supabase as never, 'company-1', 'tx-1', 'je-1') + const result = await manualLink(supabase as never, 'company-1', 'tx-1', 'je-1', 'user-1') expect(result.success).toBe(true) }) diff --git a/lib/reconciliation/bank-reconciliation.ts b/lib/reconciliation/bank-reconciliation.ts index 11eb23fb..673ddf1e 100644 --- a/lib/reconciliation/bank-reconciliation.ts +++ b/lib/reconciliation/bank-reconciliation.ts @@ -131,7 +131,8 @@ export function tryReconcileTransaction( export async function runReconciliation( supabase: SupabaseClient, companyId: string, - options: ReconciliationOptions & { userId?: string } = {} + userId: string, + options: ReconciliationOptions = {} ): Promise { const { dateFrom, dateTo, dryRun = false } = options @@ -189,7 +190,7 @@ export async function runReconciliation( transaction: match.transaction, journalEntryId: match.glLine.journal_entry_id, method: match.method, - userId: options.userId ?? companyId, + userId, companyId, }, }) @@ -292,7 +293,7 @@ export async function manualLink( companyId: string, transactionId: string, journalEntryId: string, - userId?: string + userId: string ): Promise<{ success: boolean; error?: string }> { // Fetch transaction const { data: tx, error: txError } = await supabase @@ -372,7 +373,7 @@ export async function manualLink( transaction: tx as Transaction, journalEntryId, method: 'manual' as ReconciliationMethod, - userId: userId ?? companyId, + userId, companyId, }, })