Bug/skv konto numbers (#498)
* feat(skattekonto): add overdue transactions handling and split logic * feat: enhance transaction handling and loading states - Update BalanceHero component to display last synced date and additional information about Skatteverket updates. - Refactor BookDirectlyDialog to simplify transaction linking logic and improve UI for transaction selection. - Revamp InvoiceInboxWorkspace layout for better responsiveness and user experience, including improved skeleton loading states. - Introduce new loading states for ExtensionWorkspace to match the live layout and improve user feedback during data fetching. - Implement exchange rate fetching in QuickReviewDialog, ensuring transactions are always processed in SEK with error handling for rate fetching. - Add structured error handling for unavailable exchange rates in the transaction API. * feat(skattekonto): add 'Skattekonto – saldo & transaktioner' scope and update authorization checks * feat: implement reverse charge handling in supplier invoice calculations and UI
This commit is contained in:
@@ -132,7 +132,11 @@ export const POST = withRouteContext(
|
||||
|
||||
const subtotal = items.reduce((sum, i) => sum + i.line_total, 0)
|
||||
const vatAmount = items.reduce((sum, i) => sum + i.vat_amount, 0)
|
||||
const total = Math.round((subtotal + vatAmount) * 100) / 100
|
||||
// Reverse charge: supplier never invoices VAT, so the payable total equals
|
||||
// the net. VAT is still tracked separately (vat_amount) for declarations
|
||||
// and books fiktiv 2614/2645 in the engine, but neither side moves cash.
|
||||
const payableVat = body.reverse_charge ? 0 : vatAmount
|
||||
const total = Math.round((subtotal + payableVat) * 100) / 100
|
||||
|
||||
// Representation (BAS 6070–6079): ingående moms is only deductible up to
|
||||
// 300 SEK base/person per ML 8 kap. 1 §, and the income-tax deduction was
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { withRouteContext } from '@/lib/api/with-route-context'
|
||||
import { errorResponse, errorResponseFromCode } from '@/lib/errors/get-structured-error'
|
||||
import { fetchExchangeRate } from '@/lib/currency/riksbanken'
|
||||
import type { Currency, Transaction } from '@/types'
|
||||
|
||||
export const POST = withRouteContext(
|
||||
'transaction.refreshExchangeRate',
|
||||
async (_request, ctx, { params }: { params: Promise<{ id: string }> }) => {
|
||||
const { id } = await params
|
||||
const { supabase, companyId, log, requestId } = ctx
|
||||
|
||||
const { data: transaction, error: fetchError } = await supabase
|
||||
.from('transactions')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.eq('company_id', companyId)
|
||||
.single<Transaction>()
|
||||
|
||||
if (fetchError || !transaction) {
|
||||
return errorResponseFromCode('TX_CATEGORIZE_TX_NOT_FOUND', log, { requestId })
|
||||
}
|
||||
|
||||
// No-op for SEK transactions, or when the rate is already cached.
|
||||
if (
|
||||
transaction.currency === 'SEK' ||
|
||||
(transaction.amount_sek != null && transaction.exchange_rate != null)
|
||||
) {
|
||||
return NextResponse.json({ data: transaction })
|
||||
}
|
||||
|
||||
const rate = await fetchExchangeRate(transaction.currency as Currency, new Date(transaction.date))
|
||||
if (!rate) {
|
||||
return errorResponseFromCode('TX_EXCHANGE_RATE_UNAVAILABLE', log, {
|
||||
requestId,
|
||||
details: { currency: transaction.currency, date: transaction.date },
|
||||
})
|
||||
}
|
||||
|
||||
const amountSek = Math.round(transaction.amount * rate.rate * 100) / 100
|
||||
|
||||
const { data: updated, error: updateError } = await supabase
|
||||
.from('transactions')
|
||||
.update({
|
||||
amount_sek: amountSek,
|
||||
exchange_rate: rate.rate,
|
||||
exchange_rate_date: rate.date,
|
||||
})
|
||||
.eq('id', id)
|
||||
.eq('company_id', companyId)
|
||||
.is('journal_entry_id', null)
|
||||
.select('*')
|
||||
.single<Transaction>()
|
||||
|
||||
if (updateError || !updated) {
|
||||
return errorResponse(updateError ?? new Error('Failed to persist exchange rate'), log, {
|
||||
requestId,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({ data: updated })
|
||||
},
|
||||
{ requireWrite: true },
|
||||
)
|
||||
Reference in New Issue
Block a user