import { Document, Page, Text, View, StyleSheet, } from '@react-pdf/renderer' import type { KassaflodesanalysReport } from './kassaflodesanalys' import type { CompanySettings } from '@/types' // Single-file PDF template — the kassaflödesanalys layout is distinct enough // from the BR/RR template (no per-account rows, three labelled sections, a // reconciliation footer) that a dedicated template keeps things readable. // Typography mirrors CLAUDE.md design system: Times-Roman as the serif // (closest stock @react-pdf font to Hedvig Letters Serif, which @react-pdf // can't ship by default), Helvetica for body, Courier for tabular numbers // so columns align without us shipping a custom font. const styles = StyleSheet.create({ page: { paddingTop: 40, paddingHorizontal: 40, paddingBottom: 110, fontSize: 10, fontFamily: 'Helvetica', }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 24, paddingBottom: 14, borderBottomWidth: 1, borderBottomColor: '#d4d4d4', }, titleBlock: { flex: 1 }, title: { fontSize: 22, fontFamily: 'Times-Roman', color: '#1a1a1a', marginBottom: 4, }, subtitle: { fontSize: 11, color: '#333', marginBottom: 2 }, period: { fontSize: 10, color: '#666' }, companyInfo: { textAlign: 'right' }, companyName: { fontSize: 11, fontWeight: 'bold', marginBottom: 2 }, companyMeta: { fontSize: 9, color: '#666' }, section: { marginBottom: 18 }, sectionHeading: { fontSize: 12, fontFamily: 'Times-Roman', color: '#1a1a1a', marginBottom: 8, paddingBottom: 4, borderBottomWidth: 1, borderBottomColor: '#1a1a1a', }, row: { flexDirection: 'row', paddingVertical: 3, }, label: { flex: 1, color: '#1a1a1a', }, amount: { width: 130, textAlign: 'right', fontFamily: 'Courier', color: '#1a1a1a', }, subtotalRow: { flexDirection: 'row', paddingVertical: 5, marginTop: 4, borderTopWidth: 1, borderTopColor: '#1a1a1a', }, subtotalLabel: { flex: 1, fontWeight: 'bold', fontSize: 11, }, subtotalAmount: { width: 130, textAlign: 'right', fontFamily: 'Courier', fontWeight: 'bold', fontSize: 11, }, totalBlock: { marginTop: 20, paddingTop: 12, borderTopWidth: 2, borderTopColor: '#1a1a1a', }, totalLabel: { flex: 1, fontWeight: 'bold', fontFamily: 'Times-Roman', fontSize: 13, }, totalAmount: { width: 130, textAlign: 'right', fontFamily: 'Courier', fontWeight: 'bold', fontSize: 13, }, reconciliationBlock: { marginTop: 24, padding: 12, borderWidth: 1, borderColor: '#d4d4d4', borderRadius: 4, backgroundColor: '#fafafa', }, reconciliationOk: { borderColor: '#16a34a', backgroundColor: '#f0fdf4', }, reconciliationBad: { // Terracotta destructive-color analogue for PDFs (since CSS vars // aren't available). Surfaces a clear "mismatch" signal without // depending on the chrome design tokens. borderColor: '#b91c1c', backgroundColor: '#fef2f2', }, reconciliationTitle: { fontSize: 11, fontWeight: 'bold', color: '#1a1a1a', marginBottom: 6, }, reconciliationRow: { flexDirection: 'row', paddingVertical: 2, }, reconciliationLabel: { flex: 1, color: '#444' }, reconciliationAmount: { width: 130, textAlign: 'right', fontFamily: 'Courier', color: '#1a1a1a', }, reconciliationMismatch: { color: '#b91c1c', fontWeight: 'bold', }, footer: { position: 'absolute', bottom: 24, left: 40, right: 40, borderTopWidth: 0.5, borderTopColor: '#d4d4d4', paddingTop: 6, flexDirection: 'row', justifyContent: 'space-between', }, footerText: { fontSize: 8, color: '#888' }, }) function formatAmount(n: number): string { return new Intl.NumberFormat('sv-SE', { minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(n) } function formatOrgNumber(orgNumber: string): string { const cleaned = orgNumber.replace(/\D/g, '') if (cleaned.length === 10) { return `${cleaned.slice(0, 6)}-${cleaned.slice(6)}` } return orgNumber } function formatDateSv(iso: string): string { if (!iso) return '' return new Date(iso).toLocaleDateString('sv-SE') } interface KassaflodePDFProps { report: KassaflodesanalysReport company: CompanySettings generatedAt: string } export function KassaflodesanalysPDF({ report, company, generatedAt, }: KassaflodePDFProps) { const companyName = company.company_name || '' const periodLabel = `${formatDateSv(report.period_start)} – ${formatDateSv(report.period_end)}` const recon = report.reconciliation const reconStyles = [ styles.reconciliationBlock, recon.is_reconciled ? styles.reconciliationOk : styles.reconciliationBad, ] return ( Kassaflödesanalys {companyName && {companyName}} Period: {periodLabel} Indirekt metod enligt BFNAR 2012:1 kap 7 {company.company_name && ( {company.company_name} )} {company.org_number && ( Org.nr: {formatOrgNumber(company.org_number)} )} {company.vat_number && ( VAT: {company.vat_number} )} {/* Section 1: Löpande verksamhet */} Den löpande verksamheten Resultat efter finansiella poster {formatAmount(report.lopande.resultat_efter_finansiella_poster)} Justeringar för avskrivningar {formatAmount(report.lopande.avskrivningar)} Övriga ej-kassaflödespåverkande poster {formatAmount(report.lopande.ovriga_ej_kassaflodesposter)} Förändring av kortfristiga fordringar {formatAmount(report.lopande.delta_kortfristiga_fordringar)} Förändring av varulager {formatAmount(report.lopande.delta_varulager)} Förändring av kortfristiga skulder {formatAmount(report.lopande.delta_kortfristiga_skulder)} Betald inkomstskatt {formatAmount(report.lopande.skatt_betald)} Kassaflöde från den löpande verksamheten {formatAmount(report.lopande.total)} {/* Section 2: Investeringsverksamhet */} Investeringsverksamheten Förvärv av anläggningstillgångar {formatAmount(report.investerings.forvarv_anlaggningar)} Avyttring av anläggningstillgångar {formatAmount(report.investerings.avyttring_anlaggningar)} Kassaflöde från investeringsverksamheten {formatAmount(report.investerings.total)} {/* Section 3: Finansieringsverksamhet */} Finansieringsverksamheten Förändring av lån (långfristiga skulder) {formatAmount(report.finansierings.delta_lan)} Utdelningar till ägare {formatAmount(report.finansierings.utdelningar)} Nyemission {formatAmount(report.finansierings.nyemission)} Erhållna aktieägartillskott {formatAmount(report.finansierings.erhallna_aktieagartillskott)} Kassaflöde från finansieringsverksamheten {formatAmount(report.finansierings.total)} {/* Total */} Årets kassaflöde {formatAmount(report.total_cash_flow)} {/* Reconciliation */} Avstämning mot likvida medel (19xx) Ingående saldo {formatAmount(recon.opening_cash_1xxx)} Utgående saldo {formatAmount(recon.closing_cash_1xxx)} Faktisk förändring {formatAmount(recon.delta_actual)} Beräknad förändring {formatAmount(recon.delta_calculated)} {!recon.is_reconciled && ( Avvikelse — kontrollera bokföringen {formatAmount(recon.mismatch_amount)} )} {companyName} {company.org_number ? ` · ${formatOrgNumber(company.org_number)}` : ''} `Genererad ${formatDateSv(generatedAt)} · Sida ${pageNumber} av ${totalPages}` } /> ) }