Files
accounted/components/parties/RegistryFacts.tsx
T
04898c3178 feat(parties): company details on the supplier and customer pages, the registry name becomes the displayed name (#2306)
Founder test on a real company (2026-09-05): a supplier created from
"Webhallen Oktober · Dataskärmar till kontoret" kept that text as its
name, and the SCB facts fetched for the party were nowhere on the
supplier page.

- Företagsuppgifter on /suppliers/[id] and /customers/[id]: legal name,
  org number, VAT number, country, then the SCB facts under one source
  line, with "Hämta uppgifter" or "Hitta i företagsregistret" as the one
  action. The registry helpers move out of the dossier into
  RegistryFacts so the three surfaces share them.
- The enrich route makes the registry's legal name the displayed name
  of the party and of supplier and customer rows that still carry the
  party's old name; all-capitals names are set in title case
  (lib/parties/registry-name.ts). Names a person set stay.
- legacyLedgerKey: a party confirmed under the pre-2026-09-04 key keeps
  its vouchers, so a rebuild attaches the new key instead of offering
  the same company again.
- GET /api/parties/[id] reports whether SCB is configured.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 13:43:34 +02:00

60 lines
2.2 KiB
TypeScript

/**
* Registry facts as the dossier and the supplier and customer pages show
* them: which SCB fields, in what order, with what label, and how a coded
* or composite value prints. One module so the three surfaces cannot drift.
*/
import type { ReactNode } from 'react'
import type { Dossier } from '@/lib/parties/register'
export const REGISTRY_FIELDS = [
'f_tax',
'vat_registration',
'employer_registration',
'company_status',
'legal_form',
'bolagsverket_status',
'employees_band',
'turnover_band',
'industry',
'postal_address',
'seat',
'registered_at',
'active_since',
'active_until',
'phone',
'email',
'workplaces',
'trade_name',
] as const
/** Live registry facts, in the order the dossier shows them; the VAT number sits in its own row above. */
export function registryFacts(facts: Dossier['facts']): Dossier['facts'] {
const scb = facts.filter((f) => f.source === 'registry_scb')
return REGISTRY_FIELDS.flatMap((field) => scb.filter((f) => f.field === field))
}
export function registryLabel(t: (k: string) => string, field: string): string {
return REGISTRY_FIELDS.includes(field as (typeof REGISTRY_FIELDS)[number]) ? t(`fact_${field}`) : field
}
/** Coded facts show their label; address and seat compose; the rest print. */
export function registryValue(value: unknown): ReactNode {
if (value === null || value === undefined) return '·'
if (typeof value === 'string' || typeof value === 'number') return String(value)
const v = value as Record<string, unknown>
if (typeof v.label === 'string') {
const label = typeof v.year === 'string' && v.year ? `${v.label} (${v.year})` : v.label
return v.warning ? <span className="text-warning">{label}</span> : label
}
if ('street' in v || 'city' in v) {
return [v.co, v.street, [v.postal_code, v.city].filter(Boolean).join(' ')].filter(Boolean).join(', ')
}
if ('municipality' in v || 'municipality_code' in v) {
const parts = [v.municipality ?? v.municipality_code, v.county ?? v.county_code].filter(Boolean) as string[]
// "Stockholm, Stockholm": the county adds nothing when it repeats the municipality.
return parts.filter((x, i) => i === 0 || x !== parts[0]).join(', ')
}
if ('code' in v) return String(v.code)
return JSON.stringify(v)
}