feat(vat): show already-booked banner when opening momsdeklaration (#1703)
The settlement check only ran on step 3, so Granska recalculated boxes with no signal that a vat_settlement (or momsomforing) already existed. Load the proposal with the report and reuse that detection for a top banner plus the stepper. Signed-off-by: Daniel Stenborg <daniel@stenborg.se> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
eb0df1722b
commit
06e554f3cc
@@ -1,5 +1,12 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||
import { buildVatSettlementProposal } from '../vat-settlement'
|
||||
import {
|
||||
buildVatSettlementProposal,
|
||||
findDraftVatSettlement,
|
||||
findPostedVatSettlement,
|
||||
vatDeadlineTaxPeriod,
|
||||
vatSettlementBookingStatus,
|
||||
type VatSettlementExistingEntry,
|
||||
} from '../vat-settlement'
|
||||
|
||||
// ============================================================
|
||||
// Mock: fetchVatAccountTotals now goes through the
|
||||
@@ -304,3 +311,46 @@ describe('buildVatSettlementProposal', () => {
|
||||
).rejects.toThrow('existing vat_settlement lookup failed: boom')
|
||||
})
|
||||
})
|
||||
|
||||
describe('vat settlement UI gate helpers', () => {
|
||||
const posted: VatSettlementExistingEntry = {
|
||||
id: 'je-posted',
|
||||
status: 'posted',
|
||||
entry_date: '2026-06-30',
|
||||
source_type: 'vat_settlement',
|
||||
voucher_series: 'A',
|
||||
voucher_number: 83,
|
||||
}
|
||||
const draft: VatSettlementExistingEntry = {
|
||||
id: 'je-draft',
|
||||
status: 'draft',
|
||||
entry_date: '2026-06-30',
|
||||
source_type: 'vat_settlement',
|
||||
voucher_series: 'A',
|
||||
voucher_number: 84,
|
||||
}
|
||||
|
||||
it('picks the posted settlement over a draft', () => {
|
||||
expect(findPostedVatSettlement([draft, posted])).toEqual(posted)
|
||||
expect(findDraftVatSettlement([draft, posted])).toBeUndefined()
|
||||
expect(vatSettlementBookingStatus([draft, posted])).toBe('booked')
|
||||
})
|
||||
|
||||
it('surfaces a draft only when nothing is posted', () => {
|
||||
expect(findPostedVatSettlement([draft])).toBeUndefined()
|
||||
expect(findDraftVatSettlement([draft])).toEqual(draft)
|
||||
expect(vatSettlementBookingStatus([draft])).toBe('draft')
|
||||
})
|
||||
|
||||
it('is none when the period has no settlement', () => {
|
||||
expect(findPostedVatSettlement([])).toBeUndefined()
|
||||
expect(vatSettlementBookingStatus([])).toBe('none')
|
||||
expect(vatSettlementBookingStatus(undefined)).toBe('none')
|
||||
})
|
||||
|
||||
it('formats the calendar tax_period for monthly and quarterly VAT', () => {
|
||||
expect(vatDeadlineTaxPeriod('monthly', 2026, 6)).toBe('2026-06')
|
||||
expect(vatDeadlineTaxPeriod('quarterly', 2026, 2)).toBe('2026-Q2')
|
||||
expect(vatDeadlineTaxPeriod('yearly', 2026, 1)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -88,6 +88,48 @@ export interface VatSettlementProposal {
|
||||
existing_entries: VatSettlementExistingEntry[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Posted settlement that gates re-booking. Same rule as VatBookingCard:
|
||||
* tagged `vat_settlement` and shape-detected momsomföring both land in
|
||||
* `existing_entries`; the first posted row is the one the UI links to.
|
||||
*/
|
||||
export function findPostedVatSettlement(
|
||||
entries: VatSettlementExistingEntry[] | undefined,
|
||||
): VatSettlementExistingEntry | undefined {
|
||||
return entries?.find((e) => e.status === 'posted')
|
||||
}
|
||||
|
||||
/** Draft settlement, ignored when a posted one already exists. */
|
||||
export function findDraftVatSettlement(
|
||||
entries: VatSettlementExistingEntry[] | undefined,
|
||||
): VatSettlementExistingEntry | undefined {
|
||||
if (findPostedVatSettlement(entries)) return undefined
|
||||
return entries?.find((e) => e.status === 'draft')
|
||||
}
|
||||
|
||||
export function vatSettlementBookingStatus(
|
||||
entries: VatSettlementExistingEntry[] | undefined,
|
||||
): 'booked' | 'draft' | 'none' {
|
||||
if (findPostedVatSettlement(entries)) return 'booked'
|
||||
if (findDraftVatSettlement(entries)) return 'draft'
|
||||
return 'none'
|
||||
}
|
||||
|
||||
/**
|
||||
* `deadlines.tax_period` key for a VAT picker. Yearly (helårsmoms) is omitted:
|
||||
* that row uses a fiscal-year label that needs company settings, and guessing
|
||||
* calendar `YYYY` would mis-label broken räkenskapsår.
|
||||
*/
|
||||
export function vatDeadlineTaxPeriod(
|
||||
periodType: VatPeriodType,
|
||||
year: number,
|
||||
period: number,
|
||||
): string | null {
|
||||
if (periodType === 'monthly') return `${year}-${String(period).padStart(2, '0')}`
|
||||
if (periodType === 'quarterly') return `${year}-Q${period}`
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the settlement verifikat proposal for a VAT period. Reads the same
|
||||
* aggregated ledger totals as the momsrapport (fetchVatAccountTotals), so the
|
||||
|
||||
Reference in New Issue
Block a user