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:
co-authored by
Claude Opus 4.7
parent
fc7a46c3f2
commit
ea1bf01f1e
@@ -12,6 +12,7 @@ import {
|
||||
JournalEntryNotFoundError,
|
||||
} from '@/lib/bookkeeping/errors'
|
||||
import { resolveDefaultSeriesForSource } from '@/lib/bookkeeping/voucher-series-resolver'
|
||||
import { syncInvoiceStatusFromPaymentEntry, isPaymentSourceType } from '@/lib/bookkeeping/payment-sync'
|
||||
import type {
|
||||
CreateJournalEntryInput,
|
||||
CreateJournalEntryLineInput,
|
||||
@@ -589,92 +590,12 @@ export async function reverseEntry(
|
||||
throw new EntryAlreadyReversedError()
|
||||
}
|
||||
|
||||
// If this was a payment entry, sync the linked invoice/supplier-invoice status
|
||||
const paymentSourceTypes = [
|
||||
'invoice_paid', 'invoice_cash_payment',
|
||||
'supplier_invoice_paid', 'supplier_invoice_cash_payment',
|
||||
]
|
||||
|
||||
if (paymentSourceTypes.includes(original.source_type) && original.source_id) {
|
||||
// The GL reversal is already handled above (line-by-line mirror of the original
|
||||
// verifikation per BFL 5 kap 5§). Here we sync the business-level invoice state.
|
||||
// Payment amounts come from the payments table, not from GL line inspection —
|
||||
// this works identically for kontantmetod and faktureringsmetod.
|
||||
const entryId = original.id
|
||||
|
||||
if (original.source_type.startsWith('supplier_invoice')) {
|
||||
const { data: payment } = await supabase
|
||||
.from('supplier_invoice_payments')
|
||||
.select('amount')
|
||||
.eq('journal_entry_id', entryId)
|
||||
.single()
|
||||
|
||||
const { data: supplierInvoice } = await supabase
|
||||
.from('supplier_invoices')
|
||||
.select('paid_amount, total_amount, due_date')
|
||||
.eq('id', original.source_id)
|
||||
.eq('company_id', companyId)
|
||||
.single()
|
||||
|
||||
if (supplierInvoice && payment) {
|
||||
const newPaidAmount = Math.round((supplierInvoice.paid_amount - payment.amount) * 100) / 100
|
||||
const newRemaining = Math.round((supplierInvoice.total_amount - Math.max(0, newPaidAmount)) * 100) / 100
|
||||
let newStatus: string
|
||||
if (newPaidAmount > 0) {
|
||||
newStatus = 'partially_paid'
|
||||
} else if (supplierInvoice.due_date && new Date(supplierInvoice.due_date) < new Date()) {
|
||||
newStatus = 'overdue'
|
||||
} else {
|
||||
newStatus = 'approved'
|
||||
}
|
||||
|
||||
await supabase
|
||||
.from('supplier_invoices')
|
||||
.update({
|
||||
status: newStatus,
|
||||
paid_amount: Math.max(0, newPaidAmount),
|
||||
remaining_amount: newRemaining,
|
||||
paid_at: null,
|
||||
payment_journal_entry_id: null,
|
||||
})
|
||||
.eq('id', original.source_id)
|
||||
.eq('company_id', companyId)
|
||||
}
|
||||
} else {
|
||||
const { data: payment } = await supabase
|
||||
.from('invoice_payments')
|
||||
.select('amount')
|
||||
.eq('journal_entry_id', entryId)
|
||||
.single()
|
||||
|
||||
const { data: customerInvoice } = await supabase
|
||||
.from('invoices')
|
||||
.select('paid_amount, due_date')
|
||||
.eq('id', original.source_id)
|
||||
.eq('company_id', companyId)
|
||||
.single()
|
||||
|
||||
if (customerInvoice) {
|
||||
const paymentAmount = payment?.amount ?? customerInvoice.paid_amount
|
||||
const newPaidAmount = Math.round((customerInvoice.paid_amount - paymentAmount) * 100) / 100
|
||||
const revertStatus = newPaidAmount > 0
|
||||
? 'partially_paid'
|
||||
: customerInvoice.due_date && new Date(customerInvoice.due_date) < new Date()
|
||||
? 'overdue'
|
||||
: 'sent'
|
||||
|
||||
await supabase
|
||||
.from('invoices')
|
||||
.update({
|
||||
status: revertStatus,
|
||||
paid_at: null,
|
||||
paid_amount: Math.max(0, newPaidAmount),
|
||||
})
|
||||
.eq('id', original.source_id)
|
||||
.eq('company_id', companyId)
|
||||
.in('status', ['paid', 'partially_paid'])
|
||||
}
|
||||
}
|
||||
// If this was a payment entry, sync the linked invoice/supplier-invoice status.
|
||||
// Helper is shared with the DELETE journal entry route so both code paths leave
|
||||
// the invoice in a consistent state (BFL 5 kap 5§ requires GL reversal; this
|
||||
// covers the business-level state that lives outside the GL).
|
||||
if (isPaymentSourceType(original.source_type)) {
|
||||
await syncInvoiceStatusFromPaymentEntry(supabase, companyId, original as JournalEntry)
|
||||
}
|
||||
|
||||
// Fetch complete reversal entry with lines
|
||||
|
||||
Reference in New Issue
Block a user