feat: one-click company setup from BankID directorships (#309)
* feat: one-click company setup from BankID directorships After BankID auth, surface Bolagsverket companies where the user is a director and provision a fully-configured gnubok company with one click instead of walking the 4-step wizard. Also exposed via CompanySwitcher's "Lägg till företag" for returning users. - New /select-company route merges gnubok memberships with TIC CompanyRoles; cards flag already-registered org numbers. - createCompanyFromTicRole server action derives entity_type, f-skatt, VAT, moms_period, and SPAR address defaults, then delegates to createCompanyFromOnboarding for consistent provisioning. - TIC /bankid/complete now requests enrichment on login too, so returning users see fresh CompanyRoles in the picker. - Middleware routes zero-membership users to /select-company when enrichment is available, /onboarding otherwise. - Inline enrichment picker removed from WelcomeOnboarding (wizard is now the manual fallback); SPAR address pre-fill preserved. - Unit tests for mapEntityType helper and createCompanyFromTicRole defaults (VAT-AB, non-VAT EF, unmappable, unauth). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: address PR review feedback on BankID company picker Greptile P1 + swedish-compliance bot findings: - Move enrichment row cleanup out of createCompanyFromOnboarding and into createCompanyFromTicRole. The manual wizard also goes through createCompanyFromOnboarding, and was wiping the enrichment row before the returning-user "Lägg till företag" flow could use it. - Refuse to provision when TIC /lookup is missing. Silently defaulting vat_registered to false for a momsregistrerat bolag would create a company that issues invoices without moms (ML 17 kap violation). The picker now routes to the manual wizard with org_number pre-filled when the lookup fails, so the user confirms VAT/F-skatt manually. - Default accounting_method by entity type: enskild firma → cash (K1/kontantmetoden per BFNAR 2013:2), aktiebolag → accrual (K2/K3). - Document that moms_period='quarterly' is a provisional middle-tier default; Skatteverket's assigned period depends on turnover and the user can correct it in /settings/tax. - Fix the misleading "re-fetch from BankID" comment — /select-company only reads the cached enrichment row; it's refreshed only on the next BankID auth. - Extend test coverage: lookup-missing refusal, EF kontantmetoden default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: tighten entity-type mapping and clarify K1 threshold Second round of PR review fixes (swedish-compliance bot): - mapEntityType now uses explicit allow-lists instead of substring matches. "Enskild stiftelse" / "Enskild näringsverksamhet utan firma" no longer false-match as enskild_firma (would have provisioned with K1/kontantmetoden — ML/BFL risk). Regression guard test added. - Publikt aktiebolag explicitly included (same K2/K3 regime as private AB); Bankaktiebolag / Försäkringsaktiebolag excluded (FFFS regime). - Remove misleading claim that onboarding UI flags moms_period as provisional — no such UI exists by design (approved one-click UX). - Expand accounting_method comment to cite the 3 MSEK K1→K3 threshold (BFNAR 2013:2 vs 2017:3) so the EF→cash default is honest about its scope. 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:
co-authored by
Claude Opus 4.7
parent
adf58a51c0
commit
f3a3d07ed3
@@ -0,0 +1,46 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { mapEntityType } from '../entity-type-map'
|
||||
|
||||
describe('mapEntityType', () => {
|
||||
it('maps the exact AB codes and labels to aktiebolag', () => {
|
||||
expect(mapEntityType('AB')).toBe('aktiebolag')
|
||||
expect(mapEntityType('ab')).toBe('aktiebolag')
|
||||
expect(mapEntityType('Aktiebolag')).toBe('aktiebolag')
|
||||
expect(mapEntityType('Publikt aktiebolag')).toBe('aktiebolag') // same K2/K3 regime
|
||||
expect(mapEntityType(' Aktiebolag ')).toBe('aktiebolag') // whitespace tolerant
|
||||
})
|
||||
|
||||
it('maps the exact EF codes and labels to enskild_firma', () => {
|
||||
expect(mapEntityType('EF')).toBe('enskild_firma')
|
||||
expect(mapEntityType('ef')).toBe('enskild_firma')
|
||||
expect(mapEntityType('Enskild firma')).toBe('enskild_firma')
|
||||
expect(mapEntityType('Enskild näringsidkare')).toBe('enskild_firma')
|
||||
})
|
||||
|
||||
it('returns null for unsupported entity types', () => {
|
||||
expect(mapEntityType('HB')).toBeNull()
|
||||
expect(mapEntityType('Handelsbolag')).toBeNull()
|
||||
expect(mapEntityType('KB')).toBeNull()
|
||||
expect(mapEntityType('Kommanditbolag')).toBeNull()
|
||||
expect(mapEntityType('Stiftelse')).toBeNull()
|
||||
expect(mapEntityType('Ekonomisk förening')).toBeNull()
|
||||
expect(mapEntityType('Bostadsrättsförening')).toBeNull()
|
||||
})
|
||||
|
||||
it('does not false-match strings that merely contain "enskild" or "aktiebolag"', () => {
|
||||
// Regression guard: a loose substring match would misclassify these and
|
||||
// provision them with K1/kontantmetoden defaults (ML/BFL risk).
|
||||
expect(mapEntityType('Enskild stiftelse')).toBeNull()
|
||||
expect(mapEntityType('Enskild näringsverksamhet utan firma')).toBeNull()
|
||||
// Bank- and försäkringsaktiebolag follow FFFS, not K2/K3 — not a safe
|
||||
// one-click provision.
|
||||
expect(mapEntityType('Försäkringsaktiebolag')).toBeNull()
|
||||
expect(mapEntityType('Bankaktiebolag')).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null for empty or nullish input', () => {
|
||||
expect(mapEntityType('')).toBeNull()
|
||||
expect(mapEntityType(null)).toBeNull()
|
||||
expect(mapEntityType(undefined)).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { EntityType } from '@/types'
|
||||
|
||||
/**
|
||||
* Explicit allow-lists for TIC/Bolagsverket `legalEntityType` → gnubok
|
||||
* EntityType. Strict (not substring) matching avoids misclassifications like
|
||||
* "Enskild stiftelse" → enskild_firma, which would provision with K1/
|
||||
* kontantmetoden defaults — an ML/BFL correctness risk.
|
||||
*
|
||||
* Publikt aktiebolag is included because the bookkeeping regime (K2/K3) and
|
||||
* VAT treatment are identical to a privat AB. Specialized AB forms
|
||||
* (Bankaktiebolag, Försäkringsaktiebolag) are deliberately excluded — they
|
||||
* follow FFFS and need manual setup.
|
||||
*
|
||||
* Extend only with values whose bookkeeping regime is known to match.
|
||||
*/
|
||||
const AKTIEBOLAG_VALUES = new Set<string>([
|
||||
'ab',
|
||||
'aktiebolag',
|
||||
'publikt aktiebolag',
|
||||
])
|
||||
|
||||
const ENSKILD_FIRMA_VALUES = new Set<string>([
|
||||
'ef',
|
||||
'enskild firma',
|
||||
'enskild näringsidkare',
|
||||
])
|
||||
|
||||
export function mapEntityType(ticType: string | null | undefined): EntityType | null {
|
||||
if (!ticType) return null
|
||||
const normalized = ticType.trim().toLowerCase()
|
||||
if (AKTIEBOLAG_VALUES.has(normalized)) return 'aktiebolag'
|
||||
if (ENSKILD_FIRMA_VALUES.has(normalized)) return 'enskild_firma'
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user