Keep "both parents" wishes both-off, and warn when they can't be
A lower-priority 'both' wish (e.g. a yearly autumn week) quietly fell back to one parent per day once summer/Jul had used both parents' vacation, and still counted as funded, so nothing warned. - Allocator rescue pass: frees a vacation day by switching one parent's vacation to FP on another date (interior v+v, then Mon/Fri bridge days without creating an FP weekend wrap, then solo days) and spends it on the one-parent date as co-leave. Pinned days count as present. - WindowOutcome.unmet_both_dates + new both_parents_unmet plan warning. - Frontend maps the new fields; Dashboard warning card, year-row flag and a Calendar save-toast line list the affected years (sv + en). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,57 @@ describe('PlanWarningCard', () => {
|
||||
expect(warning).not.toHaveTextContent('förslag på')
|
||||
})
|
||||
|
||||
it('renders a both_parents_unmet warning with the (re-translated) wish name and its figures', () => {
|
||||
render(
|
||||
<PlanWarningCard
|
||||
index={2}
|
||||
nameFor={nameFor}
|
||||
warning={makePlanWarning({
|
||||
kind: 'both_parents_unmet',
|
||||
messageKey: 'planWarnings.both_parents_unmet',
|
||||
data: {
|
||||
wish_id: 'lw-autumn',
|
||||
series_id: null,
|
||||
wish_name: 'Höst',
|
||||
wish_name_message_key: null,
|
||||
year: 2029,
|
||||
count: 3,
|
||||
total_days: 5,
|
||||
first_date: '2029-09-10',
|
||||
last_date: '2029-09-14',
|
||||
},
|
||||
})}
|
||||
/>,
|
||||
)
|
||||
const warning = screen.getByTestId('dashboard-plan-warning-2')
|
||||
expect(warning).toHaveTextContent("Föräldrarna kunde inte vara lediga tillsammans varje dag")
|
||||
expect(warning).toHaveTextContent('Höst 2029: föräldrarna kunde inte vara lediga tillsammans 3 av 5 dagar')
|
||||
expect(warning).not.toHaveTextContent('2029-09-10')
|
||||
})
|
||||
|
||||
it('re-translates a preset wish name in a both_parents_unmet warning via its message key', () => {
|
||||
render(
|
||||
<PlanWarningCard
|
||||
index={2}
|
||||
nameFor={nameFor}
|
||||
warning={makePlanWarning({
|
||||
kind: 'both_parents_unmet',
|
||||
messageKey: 'planWarnings.both_parents_unmet',
|
||||
data: {
|
||||
wish_name: 'Summer',
|
||||
wish_name_message_key: 'wishEditor.seasonSommar',
|
||||
year: 2029,
|
||||
count: 1,
|
||||
total_days: 5,
|
||||
},
|
||||
})}
|
||||
/>,
|
||||
)
|
||||
const warning = screen.getByTestId('dashboard-plan-warning-2')
|
||||
expect(warning).toHaveTextContent('Sommar 2029')
|
||||
expect(warning).not.toHaveTextContent('Summer 2029')
|
||||
})
|
||||
|
||||
it('falls back to the raw kind and messageKey for an unrecognised warning', () => {
|
||||
render(
|
||||
<PlanWarningCard
|
||||
|
||||
@@ -2,25 +2,29 @@ import { ReactNode } from 'react'
|
||||
import type { PlanWarning } from '@/types'
|
||||
import { useTranslation, toTranslationParams, type TranslationKey } from '@/i18n'
|
||||
import { formatDateForDisplay } from '@/utils/dates'
|
||||
import { resolveWishNameFromData } from '@/utils/wishName'
|
||||
|
||||
// The three `PlanWarning` kinds the backend can emit
|
||||
// The four `PlanWarning` kinds the backend can emit
|
||||
// (`allocation_service.py`'s `_sgi_risk_warnings`, `_age4_warnings`,
|
||||
// `_vacation_expiry_warnings`). `kind` -> title, `messageKey` -> the body
|
||||
// template. The backend's own `message_key` is already `f"planWarnings.
|
||||
// {kind}"`, i.e. a real translation key — routed through a lookup anyway
|
||||
// (like `SurplusAdviceTip`'s `SURPLUS_ADVICE_BODY_KEYS`) so an unrecognised
|
||||
// kind (a newer backend rule this catalogue hasn't caught up with) falls
|
||||
// back to the raw value instead of rendering a missing-key placeholder.
|
||||
// `_vacation_expiry_warnings`, `_both_parents_unmet_warnings`). `kind` ->
|
||||
// title, `messageKey` -> the body template. The backend's own `message_key`
|
||||
// is already `f"planWarnings.{kind}"`, i.e. a real translation key — routed
|
||||
// through a lookup anyway (like `SurplusAdviceTip`'s
|
||||
// `SURPLUS_ADVICE_BODY_KEYS`) so an unrecognised kind (a newer backend rule
|
||||
// this catalogue hasn't caught up with) falls back to the raw value instead
|
||||
// of rendering a missing-key placeholder.
|
||||
const PLAN_WARNING_TITLE_KEYS: Record<string, TranslationKey> = {
|
||||
sgi_risk: 'planWarnings.sgiRiskTitle',
|
||||
days_lost_at_age_4: 'planWarnings.daysLostAtAge4Title',
|
||||
vacation_expiring: 'planWarnings.vacationExpiringTitle',
|
||||
both_parents_unmet: 'planWarnings.bothParentsUnmetTitle',
|
||||
}
|
||||
|
||||
const PLAN_WARNING_BODY_KEYS: Record<string, TranslationKey> = {
|
||||
'planWarnings.sgi_risk': 'planWarnings.sgi_risk',
|
||||
'planWarnings.days_lost_at_age_4': 'planWarnings.days_lost_at_age_4',
|
||||
'planWarnings.vacation_expiring': 'planWarnings.vacation_expiring',
|
||||
'planWarnings.both_parents_unmet': 'planWarnings.both_parents_unmet',
|
||||
}
|
||||
|
||||
// `data` fields that carry an ISO-8601 date and must go through
|
||||
@@ -30,6 +34,7 @@ const DATE_FIELDS_BY_KIND: Record<string, readonly string[]> = {
|
||||
sgi_risk: ['week_start'],
|
||||
days_lost_at_age_4: ['fourth_birthday'],
|
||||
vacation_expiring: [],
|
||||
both_parents_unmet: [],
|
||||
}
|
||||
|
||||
export interface PlanWarningCardProps {
|
||||
@@ -61,6 +66,13 @@ export function PlanWarningCard({ warning, nameFor, index }: PlanWarningCardProp
|
||||
if (typeof uuid === 'string') {
|
||||
params.parent_name = nameFor(uuid)
|
||||
}
|
||||
if (warning.kind === 'both_parents_unmet') {
|
||||
// §both-parents-rescue — re-translate a preset wish's name into the
|
||||
// UI's current locale, same rule `resolveWishName` applies everywhere
|
||||
// else a wish name is shown (see `resolveWishNameFromData`'s doc
|
||||
// comment).
|
||||
params.wish_name = resolveWishNameFromData(warning.data, t)
|
||||
}
|
||||
for (const field of DATE_FIELDS_BY_KIND[warning.kind] ?? []) {
|
||||
const raw = warning.data[field]
|
||||
if (typeof raw === 'string') {
|
||||
|
||||
@@ -439,5 +439,29 @@ describe('YearRows', () => {
|
||||
expect(screen.getByTestId('year-row-shortfall-lw-1-2026')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('year-row-remedy-lw-1-2026-0')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
// §both-parents-rescue
|
||||
it('flags a wish row whose window still has unmet both-parents dates', () => {
|
||||
renderRows({
|
||||
outcomes: [
|
||||
makeWindowOutcome({
|
||||
wishId: 'lw-1',
|
||||
wishName: 'Höst',
|
||||
year: 2027,
|
||||
unmetBothDates: ['2027-09-10', '2027-09-11', '2027-09-12'],
|
||||
}),
|
||||
],
|
||||
})
|
||||
expect(screen.getByTestId('year-row-both-unmet-lw-1-2027')).toHaveTextContent(
|
||||
'3 dagar kunde inte täckas av båda föräldrarna',
|
||||
)
|
||||
})
|
||||
|
||||
it('shows no both-parents-unmet flag when the list is empty', () => {
|
||||
renderRows({
|
||||
outcomes: [makeWindowOutcome({ wishId: 'lw-1', year: 2026, unmetBothDates: [] })],
|
||||
})
|
||||
expect(screen.queryByTestId('year-row-both-unmet-lw-1-2026')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -475,6 +475,14 @@ export function YearRows({
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{outcome.unmetBothDates.length > 0 && (
|
||||
<span
|
||||
className="year-row__both-unmet"
|
||||
data-testid={`year-row-both-unmet-${outcome.wishId}-${year}`}
|
||||
>
|
||||
{t('yearRow.bothParentsUnmet', { count: outcome.unmetBothDates.length })}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -95,6 +95,11 @@ export const calendar = {
|
||||
'{name} ({start}–{end}): only {daysFunded} of {daysWished} days funded — {shortfallDays} {shortfallDays|day|days} short',
|
||||
'calendar.toastRedDayShift':
|
||||
'{name}: moved to {start} – {end} to catch {days} extra free {days|day|days}',
|
||||
// §both-parents-rescue — a line in the sync toast when at least one wish
|
||||
// outcome has `unmetBothDates.length > 0` for that year, despite the
|
||||
// backend's own repair pass (donating a vacation day from another wish in
|
||||
// the same year). `{years}` is a comma-separated list, e.g. "2027, 2029".
|
||||
'calendar.toastBothParentsUnmet': "Both parents couldn't be off together for {years}",
|
||||
// Reallocation pass — `reallocate_vacation_by_value` swaps vacation onto
|
||||
// better dates 1-for-1 with parental leave. `{from}`/`{to}` already carry
|
||||
// the period name plus its dates (e.g. "Summer (Jun 20, Jun 21)"), or the
|
||||
|
||||
@@ -66,4 +66,8 @@ export const dashboard = {
|
||||
'yearRow.vacationTitle': 'Vacation',
|
||||
'yearRow.weeksFunded': '{funded} of {wished} weeks',
|
||||
'yearRow.wishesTitle': 'Leave periods',
|
||||
// §both-parents-rescue — shown under a wish when
|
||||
// `WindowOutcome.unmetBothDates` isn't empty, i.e. despite the backend's
|
||||
// own repair pass, both parents couldn't be off together every day.
|
||||
'yearRow.bothParentsUnmet': "{count} {count|day|days} couldn't be covered by both parents",
|
||||
} as const
|
||||
|
||||
@@ -77,6 +77,13 @@ export const plan = {
|
||||
// `data.rescue_days_available` is > 0 (`PlanWarningCard.tsx`). Never shown
|
||||
// on its own, so no separate title.
|
||||
'planWarnings.vacationExpiringRescueHint': 'There are {count} suggested dates to rescue them — see the suggestions below.',
|
||||
// §both-parents-rescue — `_both_parents_unmet_warnings` (allocation_service.py):
|
||||
// a 'both'-mode period that still has dates only one parent could be off
|
||||
// on, despite the backend's own repair pass (donating a vacation day from
|
||||
// another wish in the same year).
|
||||
'planWarnings.bothParentsUnmetTitle': "Both parents couldn't be off together every day",
|
||||
'planWarnings.both_parents_unmet':
|
||||
"{wish_name} {year}: both parents couldn't be off together on {count} of {total_days} days — not enough vacation or parental-leave days.",
|
||||
// §vacation-rescue — `VacationRescueTip.tsx`, one card per (parent, year)
|
||||
// group of `PlanProposal.rescueDays`.
|
||||
'planResults.vacationRescueTitle': 'Rescue vacation days {year}',
|
||||
|
||||
@@ -92,6 +92,11 @@ export const calendar = {
|
||||
'{name} ({start}–{end}): endast {daysFunded} av {daysWished} dagar finansierades — {shortfallDays} {shortfallDays|dag|dagar} saknas',
|
||||
'calendar.toastRedDayShift':
|
||||
'{name}: flyttad till {start} – {end} för att fånga {days} extra {days|ledig dag|lediga dagar}',
|
||||
// §both-parents-rescue — en rad i synk-toasten när minst ett önskemåls
|
||||
// utfall har `unmetBothDates.length > 0` för det året, trots backendens
|
||||
// egen reparation (donerar en semesterdag från ett annat önskemål samma
|
||||
// år). `{years}` är en kommaseparerad lista, t.ex. "2027, 2029".
|
||||
'calendar.toastBothParentsUnmet': 'Båda föräldrarna kunde inte vara lediga samtidigt för {years}',
|
||||
// Reallocation pass — `reallocate_vacation_by_value` swaps vacation onto
|
||||
// better dates 1-for-1 with föräldrapenning. `{from}`/`{to}` already carry
|
||||
// the period name plus its dates (e.g. "Sommar (20 jun, 21 jun)"), or the
|
||||
|
||||
@@ -66,4 +66,8 @@ export const dashboard = {
|
||||
'yearRow.vacationTitle': 'Semester',
|
||||
'yearRow.weeksFunded': '{funded} av {wished} veckor',
|
||||
'yearRow.wishesTitle': 'Ledigheter',
|
||||
// §both-parents-rescue — visas under ett önskemål när
|
||||
// `WindowOutcome.unmetBothDates` inte är tom, dvs. trots backendens egen
|
||||
// reparation kunde inte båda föräldrarna vara lediga varje dag.
|
||||
'yearRow.bothParentsUnmet': '{count} {count|dag|dagar} kunde inte täckas av båda föräldrarna',
|
||||
} as const
|
||||
|
||||
@@ -77,6 +77,13 @@ export const plan = {
|
||||
// `data.rescue_days_available` is > 0 (`PlanWarningCard.tsx`). Never shown
|
||||
// on its own, so no separate title.
|
||||
'planWarnings.vacationExpiringRescueHint': 'Det finns förslag på {count} datum för att rädda dem — se förslagen nedan.',
|
||||
// §both-parents-rescue — `_both_parents_unmet_warnings` (allocation_service.py):
|
||||
// a 'both'-läge-period som fortfarande har datum där bara en förälder
|
||||
// kunde vara ledig, trots backendens egen reparation (donerar en
|
||||
// semesterdag från ett annat önskemål samma år).
|
||||
'planWarnings.bothParentsUnmetTitle': 'Föräldrarna kunde inte vara lediga tillsammans varje dag',
|
||||
'planWarnings.both_parents_unmet':
|
||||
'{wish_name} {year}: föräldrarna kunde inte vara lediga tillsammans {count} av {total_days} dagar — inte tillräckligt med semester eller föräldrapenningdagar.',
|
||||
// §vacation-rescue — `VacationRescueTip.tsx`, one card per (parent, year)
|
||||
// group of `PlanProposal.rescueDays`.
|
||||
'planResults.vacationRescueTitle': 'Rädda semesterdagar {year}',
|
||||
|
||||
@@ -2051,6 +2051,47 @@ describe('Calendar', () => {
|
||||
expect(toast).toHaveTextContent('2')
|
||||
})
|
||||
|
||||
it('shows a toast when a sibling year could not keep both parents off despite the rescue pass', async () => {
|
||||
// §both-parents-rescue — unlike the shortfall summary above, this is
|
||||
// NOT gated on `changes` membership: a rescue can ripple into a
|
||||
// sibling year this save never directly touched, so every outcome in
|
||||
// the batch is scanned (mirrors the shift-gain loop).
|
||||
const created = makeLeaveWish({ id: 'lw-preset', name: 'Höst' })
|
||||
const syncWithWish = vi.fn().mockResolvedValue(
|
||||
makePlanSyncWithWishResponse({
|
||||
wish: created,
|
||||
sync: makePlanSyncResponse({
|
||||
changes: [],
|
||||
proposal: makePlanProposal({
|
||||
outcomes: [
|
||||
makeWindowOutcome({
|
||||
wishId: 'lw-preset',
|
||||
year: 2027,
|
||||
unmetBothDates: ['2027-09-10'],
|
||||
}),
|
||||
makeWindowOutcome({
|
||||
wishId: 'lw-other',
|
||||
year: 2029,
|
||||
unmetBothDates: ['2029-09-10', '2029-09-11'],
|
||||
}),
|
||||
makeWindowOutcome({ wishId: 'lw-clean', year: 2028, unmetBothDates: [] }),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
)
|
||||
vi.mocked(useLeaveWishes).mockReturnValue(makeLeaveWishesResult({ data: [created] }))
|
||||
vi.mocked(usePlanSync).mockReturnValue(makePlanSyncResult({ syncWithWish }))
|
||||
|
||||
render(<Calendar />)
|
||||
fireEvent.click(screen.getByTestId('preset-click'))
|
||||
|
||||
const toast = await screen.findByTestId('calendar-toast')
|
||||
expect(toast).toHaveTextContent("kunde inte vara lediga samtidigt")
|
||||
expect(toast).toHaveTextContent('2027, 2029')
|
||||
expect(toast).not.toHaveTextContent('2028')
|
||||
})
|
||||
|
||||
it('does not show the shift toast when the wish with a positive shiftGainDays is not in changes', async () => {
|
||||
// A plain re-open (nothing actually touched this wish's dates this
|
||||
// sync) must never re-announce a stable, already-applied shift --
|
||||
|
||||
@@ -374,6 +374,26 @@ function buildSyncToastMessage(
|
||||
)
|
||||
}
|
||||
|
||||
// §both-parents-rescue — the backend's own repair pass
|
||||
// (`_Ledger.rescue_degraded_both_windows`) already tried to fix a 'both'
|
||||
// window that fell back to a sequential swap by donating a vacation day
|
||||
// from elsewhere in the same year; this reports what's left over — every
|
||||
// wish year that still has a date only one parent could be off on, no
|
||||
// matter which wish was actually touched by this save (mirrors the
|
||||
// shift-gain loop above, not gated on `watchWishId`/`changes` membership,
|
||||
// since a rescue can ripple into a sibling year this save never directly
|
||||
// touched).
|
||||
const bothParentsUnmetYears = Array.from(
|
||||
new Set(
|
||||
result.proposal.outcomes
|
||||
.filter((o) => o.unmetBothDates.length > 0)
|
||||
.map((o) => o.year),
|
||||
),
|
||||
).sort((a, b) => a - b)
|
||||
if (bothParentsUnmetYears.length > 0) {
|
||||
parts.push(t('calendar.toastBothParentsUnmet', { years: bothParentsUnmetYears.join(', ') }))
|
||||
}
|
||||
|
||||
for (const rejection of result.applyResult.rejections) {
|
||||
parts.push(renderRejection(rejection, t, members))
|
||||
}
|
||||
|
||||
@@ -1571,7 +1571,10 @@ describe('endpoint functions (representative coverage)', () => {
|
||||
weeks_wished: 6,
|
||||
weeks_funded: 6,
|
||||
shortfall_days: 0,
|
||||
strategy: 'parent_swap',
|
||||
strategy: 'co_leave',
|
||||
requested_strategy: 'dubbeldag',
|
||||
strategy_degraded: true,
|
||||
unmet_both_dates: ['2026-09-10', '2026-09-11'],
|
||||
message_key: 'planOutcomes.windowExplanation',
|
||||
data: {
|
||||
wish_name: 'Sommar',
|
||||
@@ -1592,6 +1595,11 @@ describe('endpoint functions (representative coverage)', () => {
|
||||
message_key: 'planWarnings.days_lost_at_age_4',
|
||||
data: { expiring_days: 10 },
|
||||
},
|
||||
{
|
||||
kind: 'both_parents_unmet',
|
||||
message_key: 'planWarnings.both_parents_unmet',
|
||||
data: { wish_name: 'Höst', year: 2026, count: 2, total_days: 5 },
|
||||
},
|
||||
],
|
||||
pool_balances: [
|
||||
{
|
||||
@@ -1665,6 +1673,11 @@ describe('endpoint functions (representative coverage)', () => {
|
||||
})
|
||||
expect(result.warnings[0]!.kind).toBe('days_lost_at_age_4')
|
||||
expect(result.warnings[0]!.messageKey).toBe('planWarnings.days_lost_at_age_4')
|
||||
expect(result.warnings[1]!.kind).toBe('both_parents_unmet')
|
||||
expect(result.warnings[1]!.data).toEqual({ wish_name: 'Höst', year: 2026, count: 2, total_days: 5 })
|
||||
expect(result.outcomes[0]!.requestedStrategy).toBe('dubbeldag')
|
||||
expect(result.outcomes[0]!.strategyDegraded).toBe(true)
|
||||
expect(result.outcomes[0]!.unmetBothDates).toEqual(['2026-09-10', '2026-09-11'])
|
||||
expect(result.poolBalances[0]!.sjukpenningLeft).toBe(100)
|
||||
expect(result.vacationBalances[0]!.alreadyUsed).toBe(19)
|
||||
expect(result.vacationBalances[0]!.isAssumed).toBe(true)
|
||||
|
||||
@@ -407,6 +407,9 @@ interface RawWindowOutcome {
|
||||
weeks_funded: number
|
||||
shortfall_days: number
|
||||
strategy: string
|
||||
requested_strategy: string
|
||||
strategy_degraded: boolean
|
||||
unmet_both_dates: string[]
|
||||
message_key: string
|
||||
data: Record<string, unknown>
|
||||
cost_breakdown: Record<string, number>
|
||||
@@ -504,6 +507,9 @@ function toPlanProposal(raw: RawPlanProposal): PlanProposal {
|
||||
weeksFunded: o.weeks_funded,
|
||||
shortfallDays: o.shortfall_days,
|
||||
strategy: o.strategy,
|
||||
requestedStrategy: o.requested_strategy,
|
||||
strategyDegraded: o.strategy_degraded,
|
||||
unmetBothDates: o.unmet_both_dates,
|
||||
messageKey: o.message_key,
|
||||
data: o.data,
|
||||
costBreakdown: toCostBreakdown(o.cost_breakdown),
|
||||
|
||||
@@ -466,10 +466,15 @@
|
||||
}
|
||||
|
||||
.year-row__wish-weeks--short,
|
||||
.year-row__shortfall {
|
||||
.year-row__shortfall,
|
||||
.year-row__both-unmet {
|
||||
color: var(--c-danger);
|
||||
}
|
||||
|
||||
.year-row__both-unmet {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.year-row__remedies {
|
||||
flex-basis: 100%;
|
||||
padding-left: var(--sp-4);
|
||||
|
||||
@@ -423,7 +423,22 @@ export interface WindowOutcome {
|
||||
weeksWished: number
|
||||
weeksFunded: number
|
||||
shortfallDays: number
|
||||
strategy: string // 'dubbeldag' | 'parent_swap' | 'solo' — kept open-ended, see allocation_service.py
|
||||
strategy: string // 'dubbeldag' | 'parent_swap' | 'solo' | 'co_leave' | 'swap' | 'unplaced' — kept open-ended, see allocation_service.py
|
||||
// What `parent_mode` (+ wants-dubbeldag) actually asked for, independent
|
||||
// of whether it was achieved — see `WindowOutcome.requested_strategy`
|
||||
// (allocation_service.py). Compare against `strategy` to tell "as
|
||||
// requested" from "fell back".
|
||||
requestedStrategy: string
|
||||
// True when `strategy !== requestedStrategy` for a 'both'-mode window —
|
||||
// see `WindowOutcome.strategy_degraded`.
|
||||
strategyDegraded: boolean
|
||||
// §both-parents-rescue — dates in this window still funded for only one
|
||||
// parent after the backend's own repair pass tried to fix them by
|
||||
// donating a vacation day from elsewhere in the same year. Empty for
|
||||
// every non-'both' window and for a window that funded cleanly or was
|
||||
// fully rescued. See `WindowOutcome.unmet_both_dates`
|
||||
// (allocation_service.py) and `PlanWarning` kind 'both_parents_unmet'.
|
||||
unmetBothDates: string[] // ISO-8601 dates
|
||||
messageKey: string
|
||||
data: Record<string, unknown>
|
||||
costBreakdown: CostBreakdown
|
||||
@@ -443,7 +458,7 @@ export interface WindowOutcome {
|
||||
}
|
||||
|
||||
export interface PlanWarning {
|
||||
kind: string // 'sgi_risk' | 'days_lost_at_age_4' | 'vacation_expiring'
|
||||
kind: string // 'sgi_risk' | 'days_lost_at_age_4' | 'vacation_expiring' | 'both_parents_unmet'
|
||||
messageKey: string
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -63,6 +63,9 @@ export function makeWindowOutcome(overrides: Partial<WindowOutcome> = {}): Windo
|
||||
weeksFunded: 6,
|
||||
shortfallDays: 0,
|
||||
strategy: 'parent_swap',
|
||||
requestedStrategy: 'parent_swap',
|
||||
strategyDegraded: false,
|
||||
unmetBothDates: [],
|
||||
messageKey: 'planOutcomes.windowExplanation',
|
||||
data: {},
|
||||
costBreakdown: makeCostBreakdown({ free: 12, vacation: 19, sjukpenning: 11 }),
|
||||
|
||||
Reference in New Issue
Block a user