Fix/bank and onboarding (#413)

* fix(onboarding): validate org number at step 2 instead of final submit

Run normalizeOrgNumber (Luhn + 10/12-digit check) inside the Step 2
Zod schema and gate the duplicate-check and TIC lookup effects on it,
so users get an inline error on the field they just typed instead of
filling out two more steps and being bounced back from the server.
Server-side check stays as defense in depth. Also fixes the old regex
incorrectly rejecting valid 12-digit org numbers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(reconciliation): pass real userId to transaction.reconciled events

runReconciliation and manualLink fell back to companyId when no userId
was provided, causing FK violations on event_log.user_id (which references
auth.users). Make userId a required arg in both functions, drop the
fallback, and forward a real user id from every caller (user.id from
authed routes, connection.user_id from the cron path).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(enable-banking): use local companyId instead of reinlining ctx fallback

Three sites in the enable-banking sync handler reinline `ctx?.companyId ?? user.id`
instead of using the local `companyId` declared at the top of the block. Collapse
all three to the local for consistency and to remove drift risk if the fallback
expression ever changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-05-07 13:32:06 +02:00
committed by GitHub
co-authored by Claude Opus 4.7
parent dbe634d0aa
commit 9e1e13d388
7 changed files with 25 additions and 22 deletions
@@ -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<typeof schema>
const ORG_NUMBER_REGEX = /^\d{6,8}[-\s]?\d{4}$/
interface Step2Props {
initialData: Partial<FormData>
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
}