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
+45 -7
View File
@@ -42,7 +42,7 @@ import {
import { linkInvoiceToVoucher } from '@/lib/invoices/voucher-matching'
import { getErrorEntry } from '@/lib/errors/structured-errors'
import { parseSIEFile } from '@/lib/import/sie-parser'
import { executeSIEImport } from '@/lib/import/sie-import'
import { executeSIEImport, undoSIEImport } from '@/lib/import/sie-import'
import type { AccountMapping } from '@/lib/import/types'
import { AccountsNotInChartError, isBookkeepingError, ACCOUNTS_NOT_IN_CHART } from '@/lib/bookkeeping/errors'
import { getEmailService } from '@/lib/email/service'
@@ -661,15 +661,21 @@ async function commitMarkInvoicePaid(
const isRealInvoice = !invoice.document_type || invoice.document_type === 'invoice'
let journalEntryId: string | null = null
// Route on invoice state, not the company's current accounting_method —
// an invoice booked at send under accrual must clear 1510 here even if
// the company has since switched to kontantmetoden.
const invoiceAlreadyBooked = !!(invoice as { journal_entry_id?: string | null }).journal_entry_id
const useCashEntry = !invoiceAlreadyBooked && accountingMethod === 'cash'
if (isRealInvoice) {
if (accountingMethod === 'accrual') {
const je = await createInvoicePaymentJournalEntry(
supabase, companyId, userId, invoice as Invoice, paymentDate, undefined, invoice.customer?.name
if (useCashEntry) {
const je = await createInvoiceCashEntry(
supabase, companyId, userId, invoice as Invoice, paymentDate, entityType, invoice.customer?.name
)
journalEntryId = je?.id ?? null
} else {
const je = await createInvoiceCashEntry(
supabase, companyId, userId, invoice as Invoice, paymentDate, entityType, invoice.customer?.name
const je = await createInvoicePaymentJournalEntry(
supabase, companyId, userId, invoice as Invoice, paymentDate, undefined, invoice.customer?.name
)
journalEntryId = je?.id ?? null
}
@@ -910,9 +916,14 @@ async function commitMatchTransactionInvoice(
const accountingMethod = settings?.accounting_method || 'accrual'
const entityType = (settings?.entity_type as EntityType) || 'enskild_firma'
// Route on invoice state, not the company's current setting. Mirror of
// the match-invoice route fix — see that handler for the full rationale.
const invoiceAlreadyBooked = !!(invoice as { journal_entry_id?: string | null }).journal_entry_id
const useCashEntry = !invoiceAlreadyBooked && accountingMethod === 'cash' && isFullyPaid
let journalEntryId: string | null = null
try {
if (accountingMethod === 'cash' && isFullyPaid) {
if (useCashEntry) {
const je = await createInvoiceCashEntry(
supabase, companyId, userId, invoice as Invoice, transaction.date, entityType, invoice.customer?.name
)
@@ -2155,6 +2166,30 @@ async function commitImportSie(
}
}
async function commitUndoSieImport(
supabase: SupabaseClient,
companyId: string,
params: Record<string, unknown>,
): Promise<ExecutorResult> {
const importId = params.import_id as string
if (!importId) {
return { error: 'import_id is required', status: 400 }
}
const result = await undoSIEImport(supabase, companyId, importId)
if (!result.success) {
return { error: result.error ?? 'SIE undo failed', status: 400 }
}
return {
data: {
import_id: importId,
deleted_entries: result.deletedEntries,
},
}
}
// ── Phase 4: arbitrary-line bookkeeping primitives ───────────────
/**
@@ -2700,6 +2735,9 @@ export async function commitPendingOperation(
case 'import_sie':
result = await commitImportSie(supabase, userId, companyId, pendingOp.params)
break
case 'undo_sie_import':
result = await commitUndoSieImport(supabase, companyId, pendingOp.params)
break
case 'create_voucher':
result = await commitCreateVoucher(supabase, userId, companyId, pendingOp.params, opts)
break