fix(providers): map Fortnox VAT-inclusive rows net of VAT, and refuse migrated rows that contradict their header (#2302)
The first production run of the row-completion pass (#2291) wrote 345 Profilio invoices whose rows summed to the invoice GROSS with 25 % VAT computed on top, beside a header (Net / TotalVAT) that was right. Fortnox prices an invoice either excluding or including VAT and says which with the invoice-level VATIncluded flag; the mapper had always read the row Total and Price as net. Every such row set is 1.25 x its header net, to the öre, across all 345. - lib/providers/fortnox/mapper.ts: netOfVat() divides row Total and Price by (1 + rate) when VATIncluded is true; TotalExcludingVAT and PriceExcludingVAT are preferred when the payload carries them. A row without a rate cannot be split and keeps its amount. - complete-invoice-lines.ts: rows whose net or VAT disagree with the header the same payload established by more than 1 kr are reported as rowsMismatch and left untouched. Öresavrundning stays inside the tolerance; VAT-inside rows, header-level freight and discounts do not. Rows that contradict their own header are worse than no rows. - Cron summary carries rowsMismatch. None of the 345 is open or booked; a separate repair removes today's rows for them so the fixed pass refills them. Claude-Session: https://claude.ai/code/session_01DG5aYcshzKJ1EA7PPhGtVf Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5.1
parent
7c36d471b5
commit
e80ea74e76
+1
-1
@@ -44,7 +44,7 @@ const mockComplete = vi.mocked(completeMigratedInvoiceLines)
|
||||
|
||||
const EMPTY = {
|
||||
candidates: 0, providerInvoices: 0, matched: 0, unmatched: 0, completed: 0, headersUpdated: 0,
|
||||
totalMismatch: 0, noLinesAtProvider: 0, notHydrated: 0, vatUnresolved: 0, failed: 0, remaining: 0,
|
||||
totalMismatch: 0, noLinesAtProvider: 0, rowsMismatch: 0, notHydrated: 0, vatUnresolved: 0, failed: 0, remaining: 0,
|
||||
hydration: { needed: 0, hydrated: 0, failed: 0, skippedForBudget: 0 }, dryRun: false,
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ export const GET = withCronContext('cron.arcim_migration_complete_invoice_lines'
|
||||
remaining: 0,
|
||||
notHydrated: 0,
|
||||
totalMismatch: 0,
|
||||
rowsMismatch: 0,
|
||||
failed: 0,
|
||||
}
|
||||
|
||||
@@ -141,6 +142,7 @@ export const GET = withCronContext('cron.arcim_migration_complete_invoice_lines'
|
||||
totals.remaining += result.remaining
|
||||
totals.notHydrated += result.notHydrated
|
||||
totals.totalMismatch += result.totalMismatch
|
||||
totals.rowsMismatch += result.rowsMismatch
|
||||
totals.failed += result.failed
|
||||
itemCtx.log.info('migrated invoice rows completed for company', {
|
||||
companyId: consent.company_id,
|
||||
@@ -151,6 +153,7 @@ export const GET = withCronContext('cron.arcim_migration_complete_invoice_lines'
|
||||
remaining: result.remaining,
|
||||
notHydrated: result.notHydrated,
|
||||
totalMismatch: result.totalMismatch,
|
||||
rowsMismatch: result.rowsMismatch,
|
||||
hydration: result.hydration,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user