Spaces:
Sleeping
Sleeping
| /** | |
| * Pure helpers for EligibilitySpineCard (display + NIP normalize). | |
| * No React / network — unit-testable with node --test. | |
| */ | |
| export const DE_MINIMIS_LIMIT_EUR = 300_000; | |
| export const MSP_LABELS: Record<string, string> = { | |
| mikro: 'Mikroprzedsiębiorstwo', | |
| mala: 'Małe przedsiębiorstwo', | |
| srednia: 'Średnie przedsiębiorstwo', | |
| duza: 'Duże przedsiębiorstwo', | |
| unknown: 'Nieustalony', | |
| }; | |
| export const SOURCE_LABELS: Record<string, string> = { | |
| sudop: 'SUDOP', | |
| manual: 'Ręczne zaświadczenie', | |
| manual_override: 'Override użytkownika', | |
| profile_field: 'Profil firmy', | |
| unknown: 'Brak źródła', | |
| sudop_error: 'Błąd SUDOP', | |
| sudop_unconfigured: 'SUDOP nie skonfigurowany', | |
| declared: 'Deklaracja', | |
| neo4j: 'Analiza powiązań', | |
| krs: 'KRS', | |
| analyzer: 'Analizator', | |
| }; | |
| /** Strip non-digits; Polish NIP is 10 digits. */ | |
| export function normalizeNip(raw: string): string { | |
| return String(raw || '').replace(/\D/g, ''); | |
| } | |
| /** True when NIP has exactly 10 digits (PL). */ | |
| export function isValidNip(raw: string): boolean { | |
| return normalizeNip(raw).length === 10; | |
| } | |
| export function formatEur(value: number | null | undefined): string { | |
| if (value === null || value === undefined || Number.isNaN(Number(value))) { | |
| return '—'; | |
| } | |
| return new Intl.NumberFormat('pl-PL', { | |
| style: 'currency', | |
| currency: 'EUR', | |
| maximumFractionDigits: 0, | |
| }).format(Number(value)); | |
| } | |
| export function mspLabel(status: string | null | undefined): string { | |
| const key = String(status || 'unknown').toLowerCase(); | |
| return MSP_LABELS[key] || status || MSP_LABELS.unknown; | |
| } | |
| export function sourceLabel(source: string | null | undefined): string { | |
| if (!source) return '—'; | |
| return SOURCE_LABELS[source] || source; | |
| } | |
| /** | |
| * De minimis usage slice for the progress bar and totals. | |
| * Falls back to 300k limit and derives remaining when API omits it. | |
| */ | |
| export function deMinimisUsage(dm: { | |
| used_eur?: number | null; | |
| limit_eur?: number | null; | |
| remaining_eur?: number | null; | |
| exhausted?: boolean; | |
| } | null | undefined): { | |
| used: number | null; | |
| limit: number; | |
| remaining: number | null; | |
| exhausted: boolean; | |
| usagePct: number; | |
| } { | |
| const limit = | |
| dm?.limit_eur != null && !Number.isNaN(Number(dm.limit_eur)) | |
| ? Number(dm.limit_eur) | |
| : DE_MINIMIS_LIMIT_EUR; | |
| const used = | |
| dm?.used_eur === null || dm?.used_eur === undefined || Number.isNaN(Number(dm.used_eur)) | |
| ? null | |
| : Number(dm.used_eur); | |
| let remaining: number | null = | |
| dm?.remaining_eur === null || | |
| dm?.remaining_eur === undefined || | |
| Number.isNaN(Number(dm.remaining_eur)) | |
| ? null | |
| : Number(dm.remaining_eur); | |
| if (remaining === null && used !== null) { | |
| remaining = Math.max(0, limit - used); | |
| } | |
| const exhausted = | |
| Boolean(dm?.exhausted) || (remaining !== null && remaining <= 0) || (used !== null && used >= limit); | |
| const usagePct = | |
| used !== null && limit > 0 ? Math.min(100, Math.max(0, (used / limit) * 100)) : 0; | |
| return { used, limit, remaining, exhausted, usagePct }; | |
| } | |