Bug/momsdek overflow (#465)
* feat(settings): add option for company name position in invoice PDF * feat(migrations): add backfill for VAT account labels to correct bad seed data * feat(migrations): add backfill for VAT account labels to correct bad seed data * fix(ui): improve accessibility for company name position toggle in PDF settings * fix(bookkeeping): align BAS 2026 reference data with official PDF Reconciled lib/bookkeeping/bas-data/ against the BAS 2026 v1.1 official chart (1286 accounts). All real discrepancies fixed: - 2089 Fond för utvecklingsutgifter: k2_excluded → true - 8417 Räntekostnader för dold räntekompensation: k2_excluded → true - 1250, 1260 renamed to "(Fritt konto för Inventarier, verktyg och installationer)" — BAS 2026 freed these slots - Periodiseringsfond 2120-2139: added year suffixes (2120 = "...2020" etc.) and added 8 missing accounts (2121-2127, 2129) for years 2019, 2021-2027. Dropped phantom 2022/2024 prior-parser garbage. - 4075-4078: EUland → EU-land - 8411: förlagsoch → förlags- och Verified: 1282 of 1286 PDF accounts match exactly after edits (remaining 4 are PDF-parser artifacts, not real data). Build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(migrations): backfill BAS 2026 account labels in chart_of_accounts Companion to the TS reference fix. Updates existing companies' rows where they still carry seed-data typos or generic names that don't match BAS 2026: - 4075-4078: EUland → EU-land (hyphenation) - 8411: förlagsoch → förlags- och (hyphenation) - 2120, 2130-2137, 2139: rename "Periodiseringsfond" (generic, no year) to the BAS 2026 canonical name with year suffix Defensive: every WHERE clause matches an EXACT current value. Rows that have been manually renamed by users — including those with a wrong year that may reference legacy fonds from an earlier BAS numbering cycle — are left untouched. No row is deleted. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(migrations): fix wrong-year labels on Periodiseringsfond accounts Follow-up to 20260513140000. The first backfill only renamed accounts whose name was the generic "Periodiseringsfond" (no year). Many customers were seeded from an older BAS numbering cycle where 2126 = "2016", 2127 = "2017", etc. — BAS 2026 reuses those account numbers for years 2026/2027. This migration aligns the year tag with the BAS 2026 meaning of each account number across 2120-2127, 2129, 2130-2137, 2139. Only rows whose name still starts with "Periodiseringsfond" are touched — customers who renamed the account to something custom keep their name. Verified on staging: all 18 accounts now carry BAS 2026 canonical names. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(data): update account names and descriptions for clarity and consistency * fix(migrations): backfill account names for BAS 2026 freed accounts and refine Periodiseringsfond name matching * feat: enhance VAT handling with reverse charge logic and supplier type support * feat: implement VAT declaration validation rules and enhance moms box mapping --------- 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
9e0b89ee7e
commit
da39eb2d43
@@ -24,6 +24,8 @@ interface JournalEntryPreviewProps {
|
||||
templateDebitAccount?: string
|
||||
templateCreditAccount?: string
|
||||
templateVatRate?: number
|
||||
templateVatTreatment?: VatTreatment | null
|
||||
templateSupplierType?: 'eu_business' | 'non_eu_business' | 'swedish_business'
|
||||
/** For multi-line counterparty template bookings */
|
||||
linePattern?: LinePatternEntry[]
|
||||
settlementAccount?: string
|
||||
@@ -39,6 +41,8 @@ export default function JournalEntryPreview({
|
||||
templateDebitAccount,
|
||||
templateCreditAccount,
|
||||
templateVatRate,
|
||||
templateVatTreatment,
|
||||
templateSupplierType,
|
||||
linePattern,
|
||||
settlementAccount = '1930',
|
||||
}: JournalEntryPreviewProps) {
|
||||
@@ -91,6 +95,7 @@ export default function JournalEntryPreview({
|
||||
const vatAmt = extractVatAmount(absAmount, vatRate)
|
||||
const netAmt = extractNetAmount(absAmount, vatRate)
|
||||
const isIncome = amount > 0
|
||||
const isReverseCharge = templateVatTreatment === 'reverse_charge' && !isIncome
|
||||
|
||||
if (isIncome) {
|
||||
// Income: debit bank gross, credit revenue net, credit output VAT
|
||||
@@ -101,6 +106,32 @@ export default function JournalEntryPreview({
|
||||
const outputVatAccount = vatRate === 0.06 ? '2631' : vatRate === 0.12 ? '2621' : '2611'
|
||||
result.push({ side: 'kredit', account: outputVatAccount, amount: vatAmt })
|
||||
}
|
||||
} else if (isReverseCharge) {
|
||||
// Expense with reverse charge: full reverse-charge verifikation
|
||||
// (must match engine output in buildMappingResultFromTemplate).
|
||||
const rcRate = 0.25
|
||||
const rcVatAmt = Math.round(absAmount * rcRate * 100) / 100
|
||||
const supplierType = templateSupplierType ?? 'eu_business'
|
||||
const isDomestic = supplierType === 'swedish_business'
|
||||
|
||||
// Expense gross + bank
|
||||
result.push({ side: 'debet', account: templateDebitAccount, amount: absAmount })
|
||||
result.push({ side: 'kredit', account: templateCreditAccount, amount: absAmount })
|
||||
|
||||
// Fiktiv moms pair: 2645 (or 2647 domestic) / 2614
|
||||
result.push({ side: 'debet', account: isDomestic ? '2647' : '2645', amount: rcVatAmt })
|
||||
result.push({ side: 'kredit', account: '2614', amount: rcVatAmt })
|
||||
|
||||
// Basbelopp pair: 44xx|45xx / 4598 — populates rutor 20–24.
|
||||
// Skip if the debit account is already a basis account.
|
||||
if (!/^4[45]\d{2}$/.test(templateDebitAccount)) {
|
||||
const basisAccount =
|
||||
supplierType === 'eu_business' ? '4535'
|
||||
: supplierType === 'non_eu_business' ? '4531'
|
||||
: '4425'
|
||||
result.push({ side: 'debet', account: basisAccount, amount: absAmount })
|
||||
result.push({ side: 'kredit', account: '4598', amount: absAmount })
|
||||
}
|
||||
} else {
|
||||
// Expense: debit expense net + input VAT, credit bank gross
|
||||
result.push({ side: 'debet', account: templateDebitAccount, amount: netAmt })
|
||||
@@ -150,7 +181,7 @@ export default function JournalEntryPreview({
|
||||
}
|
||||
|
||||
return result
|
||||
}, [amount, category, vatTreatment, accountOverride, entityType, templateDebitAccount, templateCreditAccount, templateVatRate, linePattern, settlementAccount])
|
||||
}, [amount, category, vatTreatment, accountOverride, entityType, templateDebitAccount, templateCreditAccount, templateVatRate, templateVatTreatment, templateSupplierType, linePattern, settlementAccount])
|
||||
|
||||
if (lines.length === 0) return null
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ export default function QuickReviewDialog({
|
||||
}
|
||||
onOpenChange(o)
|
||||
}}>
|
||||
<DialogContent className="max-w-md max-h-[85vh] overflow-y-auto">
|
||||
<DialogContent className="max-w-md sm:max-w-lg max-h-[85vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Granska bokföring</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -264,7 +264,13 @@ export default function QuickReviewDialog({
|
||||
{...(isCounterpartyTemplate
|
||||
? { linePattern: counterpartyLinePattern ?? undefined }
|
||||
: templateId && template
|
||||
? { templateDebitAccount: template.debit_account, templateCreditAccount: template.credit_account, templateVatRate: template.vat_rate }
|
||||
? {
|
||||
templateDebitAccount: template.debit_account,
|
||||
templateCreditAccount: template.credit_account,
|
||||
templateVatRate: template.vat_rate,
|
||||
templateVatTreatment: template.vat_treatment,
|
||||
templateSupplierType: template.reverse_charge_supplier_type,
|
||||
}
|
||||
: { category, vatTreatment: isLiabilityAccount ? 'none' : vatTreatment, accountOverride, entityType }
|
||||
)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user