c0ecf2fa3bebd46bdfd0169efd73b89653d1dfed
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f266c386f3 |
chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers (#2150)
* chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers Remove 33 dead files, ~270 unreferenced exports/types, 13 dead i18n namespaces and 4 unused dependencies; fold byte-identical helper copies into one canonical home each (lib/utils chunk/sleep/utcDateStamp, lib/dates/iso, lib/invariants/uuid, lib/xml/escape, lib/reports/sru/format, lib/pdf/number-text, lib/browser/panel-request, lib/api/v1/body + v1ValidationError rolled out to ~55 v1 routes, booking-template schemas). No behaviour change: v1 bodies and status codes, MCP tool schemas, DB writes and money math are untouched. Naive ore rounding was deliberately not swapped for roundOre; see DECISIONS.md 2026-09-02 for the full list of things left alone on purpose. tsc, lint, 19588 unit tests and check:guards green; antipattern baseline ratcheted (naive-ore-round 622 -> 620, hand-rolled-invariant 115 -> 113). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(transactions): import RawTransaction from @/types after the ingest re-export removal CI's type ratchet (check:types, full tsconfig) caught the one test file that still imported the type through lib/transactions/ingest. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
521f437072 |
feat(migration): link migrated invoices to their registration voucher (#1463) (#2024)
Visma and Fortnox migrations now carry each invoice's source voucher reference, and after the invoice steps a core linker resolves it against the SIE-imported ledger (voucher-ref resolver by date, corroborated by the 244x credit / 151x debit amount, posted only, unreferenced only) and writes registration_journal_entry_id / journal_entry_id. Anything ambiguous, mismatched or unresolved is reported and left NULL; journal entries are never written. The arcim-migration /reconcile endpoint can relink already-migrated companies. Payment vouchers are PR B. Refs #1463 |
||
|
|
dc5079a912 |
fix(providers): stop inventing 25% VAT on migrated invoices (#1745)
* fix(providers): stop inventing 25% VAT on migrated invoices An invoice migrated from Fortnox displayed "Momsbehandling: 25 % moms" next to "Moms: 0 kr", with no line items behind it. It was not a display bug: the record really did hold vat_rate 25 and vat_amount 0. Fortnox answers GET /3/invoices with the short form, which carries no Net, no TotalVAT and no InvoiceRows; those live only on the detail form. The migration mapped the list payload alone, so `Net ?? total` made the net equal the gross, VAT derived as gross minus net came out 0, and with no rows to read a rate from, inferVatTreatment/inferVatRate fell through to their `return 'standard_25'` / `return 25` defaults. The result balanced, so nothing downstream noticed. Measured on prod: 8 712 sales invoices across 43 companies assert a rate beside 0 kr of VAT (286 MSEK of subtotal), plus 1 240 supplier invoices. None are booked, but 263 are still open, and the no-items booking fallback in invoice-entries.ts credits the full gross to 30xx and emits no 2611 line at all. Not Fortnox-only. Visma reported its VAT-inclusive TotalAmount as the ex-VAT amount and read rows via `LineTotal`/`VatRatePercent`, neither of which exists in the eAccounting schema (the real names are AmountNoVat and PercentVat), so its lines all landed at 0. Bjorn Lunden reported the gross as the net with no lines at all. Briox and WINT had the same gross-as-net fallback, and Bokio defaulted a missing totalTax to 0. - lib/providers/amounts.ts: readers that return undefined for an absent field, so "the provider says zero" stays distinct from "did not say" - every mapper: populate taxTotal and per-line taxAmount from what the payload actually states; leave the net undefined when it does not - provider-data-fetcher: hydrate the detail endpoint that every config has always declared and nothing ever called, open invoices first, within a time budget, reporting whatever it could not reach - entity-mapper: derive rate and treatment from evidence; when there is none, write vat_rate null and flag vatUnresolved instead of asserting a standard rate Existing rows are untouched; repairing them needs a separate decision. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(providers): keep subtotal + VAT equal to the invoice total Providers state net, VAT and gross independently and they need not agree: Fortnox's Total is the amount to pay after öresavrundning while Net + TotalVAT is the unrounded Gross, so the two differ by up to 50 öre. Passing both through as stated put that gap into the invoice row, where subtotal + vat_amount no longer equalled total. The header booking path in invoice-entries.ts derives the 1510 debit from the sum of its credits, so the receivable would land a few öre away from what the customer owes while the verifikat still balanced: the same silent shape as the bug this branch fixes. resolveVatTriple now always returns a pair summing to the gross, keeping the VAT intact (it reaches the momsdeklaration) and absorbing the rounding into the net. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(providers): address invoice detail by the configured idField Hydration built the detail path from dto.id. Björn Lundén's sales config names invoiceNumber as its idField while its mapper builds dto.id from entityId, so BL sales invoices would have been hydrated from the wrong resource, or from none. Every other provider/resource pair happens to agree on the two, which is what made the mismatch easy to miss. The config's idField is the authority, read off the raw payload, with dto.id only as the fallback. The regression test uses BL with entityId 99001 and invoiceNumber 5 so the two cannot coincide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(providers): store vat_rate null for migrated mixed-rate invoices resolveInvoiceVat labelled the header with the first line's rate, so an invoice carrying both 25 % and 6 % lines was recorded as a 25 % invoice. buildInvoiceWriteData already stores isMixedRate ? null : theRate for natively created invoices; migrated ones now match. The money was already right and stays right: generatePerRateLines groups per item rate, so a mixed invoice books 25 % and 6 % separately off the per-line vat_rate/vat_amount this branch fixed. Only the header label was overstating what the source said. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(providers): bound hydration against auth failures and the clock Two failure modes that only appear against a real provider. A 401 or 403 fails identically for every remaining invoice, so the pass now stops on the first one instead of issuing hundreds more doomed calls. That matters more than it looks: TokenBucketRateLimiter keys on the literal string 'global', so Fortnox's 4 req/s is a platform-wide budget shared by every company and every concurrent migration, not a per-token one. A 404 is about one invoice and does not stop the pass. The budget was checked before starting a call but never during one. The clients retry 429s and 5xx with backoff (Fortnox: 6 attempts, up to 60 s apart), so a call starting one millisecond inside the budget could still be retrying minutes later, and three concurrent ones could hold the migration past its 300 s function ceiling. Each call is now raced against the deadline; the socket is not cancelled, but control returns and the remaining invoices are reported unhydrated instead of the run dying. Both outcomes are reported as HydrationReport.abortedBy so a partial pass is visible rather than looking complete. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <invoice@arcim.io> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
00ae3540db |
feat(customers): carry contact person and invoice copy recipients through migration (#1392)
* feat(customers): carry contact person and invoice copy recipients through migration Extends the arcim-migration entity mapper, Fortnox provider mapper, canonical DTOs, customer APIs (web + v1) and invoice send flows so contact person and customer-level invoice CC/BCC addresses survive provider migrations. NULL means unconfigured and empty means an explicit clear, so re-syncs enrich legacy gaps without resurrecting deliberately removed values. Fortnox fixed assets are split into a dedicated follow-up issue. Fixes #1345 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(db): bump customer metadata migration past pack-slug version Main already contains 20260803230000; keep new versions strictly newest so Supabase branching applies them in order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(customers): complete Customer type consumers and make enrichment payload resolvable The preview-pdf mock customer and the makeCustomer fixture now carry the three new metadata fields, fixing the type-check failure in Build (zero extensions) and Vercel. The enrichment update in the migration orchestrator now spells its payload as an object literal typed CustomerMetadataEnrichment (absent keys drop at serialization), so the phantom-column guard resolves the columns instead of counting another unresolvable dynamic payload past its ceiling. The cc/bcc guards also verify element types instead of casting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e42da5c32b |
Staging (#181)
* refactor: remove unnecessary secondary action from EmptyInvoices component * feat: add direct provider layer and provider_consents migration Replace Arcim Sync gateway dependency with direct provider clients for Fortnox, Visma, Briox, Bokio, and Björn Lundén. Adds OAuth config, rate limiting, retry logic, data fetching, and consent storage via new provider_consents/tokens/otc tables. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: migrate arcim extension to direct provider APIs Replace Arcim Sync gateway calls with direct provider API access. Use FortnoxClient.getText() for SIE endpoints that return plain text instead of JSON. OAuth callback now returns HTML with postMessage to communicate with the opener window instead of redirecting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use OAuth popup window instead of new tab Open provider login in a centered popup that auto-closes on completion via postMessage, keeping the user on a single tab. Falls back to redirect flow if popup is blocked. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add connection status, consent reuse, and SIE duplicate detection - Add listConsents() to query active consents by company - Add GET /status route returning consents, SIE import history, and entity counts - /connect reuses existing accepted consent instead of creating duplicates, and cleans up abandoned (status 0) consents - /status only returns accepted (status 1) consents - /sie-data checks each file's SHA-256 hash against sie_imports to report per-file import status (alreadyImported, importedAt) - /sie-data blocks on SIE validation failure (mirrors manual upload) - /import-sie validates unmapped accounts and auto-activates missing BAS accounts in chart_of_accounts (mirrors manual upload) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: show active connections, SIE file status, and smart re-sync UI - ProviderStep shows active connections with last import date, entity counts, "Synka igen" button, and disconnect option - Already-connected providers greyed out in selection grid - OptionsStep shows per-fiscal-year import status (imported vs new) - SIE toggle disabled with explanation when all files already imported - handleStartMigration skips already-imported SIE files - Auto-skip mapping step and disable SIE on re-sync when up to date - Result step hides empty "0 importerade" rows and shows "Allt är uppdaterat" when nothing new was fetched - OptionRow supports disabled state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: adjust COMING_SOON_PROVIDERS based on NODE_ENV for development and production * Removed duplicate * Removed duplicate * refactor: redesign reports page navigation from grid boxes to bordered card layout Replace the 4-column grid of uneven TabsList boxes with a CSS grid card using auto-sized columns separated by 1px border dividers. All sections now share equal height via items-stretch, with clear visual separation between groups. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: trigger Vercel deployment * Update supabase/migrations/20260402010000_provider_consents.sql Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Update lib/providers/rate-limiter.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * chore: re-trigger checks after migration sync --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> |