Fix/m sprint fixes (#613)

* fix(dashboard): exclude ignored and already-triaged transactions from stale count

The "Gamla transaktioner" widget counted transactions that had been ignored
or already marked as is_business=true but not yet booked, so users saw a
nag for a row they had already dealt with — and the /transactions inbox
correctly hid it. Align the count with the inbox criterion (is_business
IS NULL, is_ignored = false) so the widget clears when the row leaves
the inbox.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(transactions): read entity_type from settings response wrapper

The transactions page read entityRes.entity_type directly, but
/api/settings returns { data: { entity_type, ... } }. The expression
was always undefined, so setEntityType never fired and entityType
stayed at its initial 'enskild_firma'. The template picker's
entity_type filter then dropped every aktiebolag-tagged user template
for AB customers — only entity_type='all' templates made it through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* stale templates
bank sync
journal entry from transaction

* fixed pr comments

* fixed pr comment

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-05-30 01:28:41 +02:00
committed by GitHub
co-authored by Claude Opus 4.7
parent fc7a46c3f2
commit ea1bf01f1e
76 changed files with 5474 additions and 532 deletions
@@ -0,0 +1,87 @@
import type { JournalEntryLine } from '@/types'
export interface CorrectionLineInput {
account_number: string
debit_amount: string | number
credit_amount: string | number
}
export interface AccountRow {
account_number: string
original: number
storno: number
correction: number
delta: number
}
function toNumber(v: string | number | null | undefined): number {
if (v == null) return 0
const n = typeof v === 'string' ? parseFloat(v) : v
return Number.isFinite(n) ? n : 0
}
function round2(n: number): number {
return Math.round(n * 100) / 100
}
/**
* Build per-account diff rows: original net, storno (= −original), proposed
* correction net, and förändring (= storno + correction = correction − original).
*
* Net per row is debit − credit. Accounts appearing only on one side still get
* a row, so the user sees account swaps clearly (old account drains to zero,
* new account picks up the value).
*
* Corrected lines with account_number.length !== 4 are skipped — those are
* incomplete user input mid-edit, not real proposals.
*/
export function buildCorrectionRows(
original: JournalEntryLine[],
corrected: CorrectionLineInput[]
): AccountRow[] {
const map = new Map<string, AccountRow>()
const ensure = (acc: string): AccountRow => {
let row = map.get(acc)
if (!row) {
row = { account_number: acc, original: 0, storno: 0, correction: 0, delta: 0 }
map.set(acc, row)
}
return row
}
for (const line of original) {
if (!line.account_number) continue
const net = toNumber(line.debit_amount) - toNumber(line.credit_amount)
const row = ensure(line.account_number)
row.original += net
row.storno -= net
}
for (const line of corrected) {
if (!line.account_number || line.account_number.length !== 4) continue
const net = toNumber(line.debit_amount) - toNumber(line.credit_amount)
const row = ensure(line.account_number)
row.correction += net
}
for (const row of map.values()) {
row.original = round2(row.original)
row.storno = round2(row.storno)
row.correction = round2(row.correction)
row.delta = round2(row.storno + row.correction)
}
return Array.from(map.values()).sort((a, b) =>
a.account_number.localeCompare(b.account_number)
)
}
export function formatSignedAmount(n: number): string {
if (n === 0) return '–'
const abs = Math.abs(n).toLocaleString('sv-SE', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
return n > 0 ? `+${abs}` : `−${abs}`
}