{{ liveMessage() }}

@if (isLoading()) {
{{ loadingProgress() }}%

{{ isCancelling() ? tr().planning.cancelling : tr().loading }}

}

{{ tr().planning.title }}

@if (planningOutcome()?.kind === 'InvalidInput') { } @else if (planningOutcome()?.kind === 'PlanningConflict') { } @else if (planningOutcome()?.kind === 'PlanningIncomplete') {

{{ outcomeLabel(planningOutcome()!) }}

} @else if (planningOutcome()?.kind === 'Plan') {

{{ tr().planning.complete }}

}
@if (displayedPlan()) { }
@if (planningOutcome(); as outcome) { @if (outcome.kind === 'InvalidInput') { } } @if (planningExplanation(); as outcomeExplanation) { @if (outcomeExplanation.kind === 'ConflictExplanation') {

{{ tr().planning.conflictDetails }}

{{ tr().planning.conflictingRules }}:

{{ tr().planning.bottleneck }}: {{ outcomeExplanation.bottleneck.message }}

} @else if (outcomeExplanation.kind === 'IncompleteExplanation') {

{{ tr().planning.incompleteDetails }}

{{ outcomeExplanation.reason.message }}

{{ tr().planning.completedStages }}
{{ outcomeExplanation.completedStages.length ? outcomeExplanation.completedStages.join(', ') : tr().planning.none }}
{{ tr().planning.uncompletedStages }}
{{ outcomeExplanation.uncompletedStages.length ? outcomeExplanation.uncompletedStages.join(', ') : tr().planning.none }}
} } @if (displayedPlan(); as plan) {
{{ tr().planning.daysPlaced }}
{{ plan.placements.length }}
{{ tr().planning.daysGained }}
{{ plan.metrics.nonWorkingDayGain }}
{{ tr().planning.longestWorkSequence }}
{{ plan.metrics.longestWorkSequence }}
@for (contribution of plan.timeWindowContributions ?? []; track contribution.ruleId) {

{{ contribution.label }}

{{ contribution.declaredDays.length }} {{ tr().bookedDays.booked }} + {{ contribution.placementDates.length }} {{ tr().planning.explanation.leaveDays }} = {{ contribution.declaredDays.length + contribution.placementDates.length }} / {{ contribution.requested }}

@for (day of contribution.declaredDays; track day.date) { {{ displayDate(day.date) }} · {{ tr().dayTypes[day.type] }} }

{{ tr().bookedDays.fixed }}

} @if (plan.calculationDate) {

{{ tr().planning.calculationDate }} : {{ displayDate(plan.calculationDate) }}

} @for ( contribution of plan.ifThenContributions ?? []; track contribution.ruleId + contribution.occurrence ) {

{{ contribution.label }} · {{ displayDate(contribution.occurrence) }}

{{ contribution.declaredDays.length }} {{ tr().bookedDays.booked }} + {{ contribution.placementDates.length }} {{ tr().planning.explanation.leaveDays }} = {{ contribution.requested }} / {{ contribution.requested }}

@for (day of contribution.declaredDays; track day.date) { {{ displayDate(day.date) }} · {{ tr().dayTypes[day.type] }} } @if (contribution.declaredDays.length) {

{{ tr().bookedDays.fixed }}

} @if (contribution.earnedEntitlement; as earned) {

{{ earned.added ? tr().planning.futureEntitlement : tr().planning.includedEntitlement }} : {{ earned.quantity }} {{ tr().dayTypes[earned.type] }}

{{ tr().planning.details.available }} {{ displayDate(earned.availableFrom) }} · {{ tr().planning.details.expires }} {{ displayDate(earned.expiryDate) }}

}
} @for (period of plan.timeOffPeriods; track period.start) { @if (period.declaredDates?.length) {

{{ tr().planning.explanation.breakTitle }} · {{ displayDate(period.start) }} – {{ displayDate(period.end) }}

{{ period.declaredDates!.length }} {{ tr().bookedDays.booked }} + {{ period.placementDates.length }} {{ tr().planning.explanation.leaveDays }} + {{ period.nonWorkingDates.length }} {{ tr().planning.explanation.freeDays }} = {{ period.duration }} {{ tr().planning.explanation.daysOff }}

{{ tr().bookedDays.fixed }}

} } }
@if (selectedExplanation(); as selected) {
{{ tr().planning.whyPlacement }}{{ tr().planning.explanationHelp }}

{{ displayDate(selected.placement.date) }} · {{ balanceLabel(selected) }}

@if (planExplanation()!.incomplete; as interrupted) {
{{ tr().planning.incumbentExplanation }}

{{ interrupted.reason.message }}

{{ tr().planning.completedStages }}: {{ interrupted.completedStages.length ? interrupted.completedStages.join(', ') : tr().planning.none }}

{{ tr().planning.uncompletedStages }}: {{ interrupted.uncompletedStages.length ? interrupted.uncompletedStages.join(', ') : tr().planning.none }}

} @if (selected.timeOffPeriod; as period) {

{{ tr().planning.explanation.breakTitle }} · {{ displayDate(period.start) }} – {{ displayDate(period.end) }}

@if (period.declaredDates?.length) { {{ period.declaredDates!.length }}{{ tr().bookedDays.booked }} + } {{ period.placementDates.length }}{{ tr().planning.explanation.leaveDays }} + {{ period.nonWorkingDates.length }}{{ tr().planning.explanation.freeDays }} = {{ period.duration }}{{ tr().planning.explanation.daysOff }}

{{ tr().planning.explanation.wholeBreak }}

}

{{ leaveUsed(selected) }} @if (selected.balance) { {{ tr().planning.explanation.expires }} {{ displayDate(planningDate(selected.balance.expiryDate)) }}. }

@if (selected.ruleIds.length) {

{{ tr().planning.explanation.linkedRules }} @for (ruleId of selected.ruleIds; track ruleId; let last = $last) { {{ ruleLabel(ruleId) }}{{ last ? '.' : ', ' }} }

} @if (hasAtRiskContribution(selected)) {

{{ tr().planning.explanation.atRiskContribution }}

} @if (selected.supporting) {

{{ tr().planning.details.supporting }}

}

{{ tr().planning.remaining }}: {{ selected.remainingQuantity }} {{ tr().userInput.daysSuffix }} @if (selected.remainingBalance; as remaining) { · {{ translationService.getRemainingBalanceReason( remaining, displayedPlan(), planningOutcome()?.kind === 'PlanningIncomplete' ) }} }

@let decisiveObjective = selected.firstDecisiveObjective ?? selected.comparison.firstDecisiveObjective; @if (decisiveObjective) {

{{ tr().planning.details.firstDifference }}: {{ objectiveCopy(decisiveObjective).label }}

}

{{ tr().planning.explanation.impact }}

{{ tr().planning.explanation.impactHelp }}

@if (placementChanges().length) { @for (change of placementChanges(); track change.id) { }
{{ tr().planning.explanation.criterion }} {{ tr().planning.explanation.withoutDay }} {{ tr().planning.explanation.withDay }}
{{ change.label }}{{ change.help }} @if (change.id === decisiveObjective) { {{ tr().planning.details.firstDifference }} } {{ change.before }} {{ change.after }}
} @else {

{{ tr().planning.explanation.noChange }}

}
{{ tr().planning.technicalDetails }}

{{ tr().planning.details.intro }}

{{ tr().planning.details.balance }} · {{ balanceLabel(selected) }}

@if (selected.balance?.availableFrom; as available) {
{{ tr().planning.details.available }}
{{ displayDate(planningDate(available)) }}
} @if (selected.balance; as balance) {
{{ tr().planning.details.expires }}
{{ displayDate(planningDate(balance.expiryDate)) }}
}
{{ tr().planning.remaining }}
{{ selected.remainingQuantity }} {{ tr().userInput.daysSuffix }}
@if (selected.remainingBalance; as remaining) {

{{ translationService.getRemainingBalanceReason( remaining, displayedPlan(), planningOutcome()?.kind === 'PlanningIncomplete' ) }}

}
} @else if (explanationError()) { } @if (planningOutcome() && planningOutcome()?.kind !== 'InvalidInput') {
{{ tr().planning.traceTitle }}{{ tr().planning.traceHelp }} @if (traceLoading()) {

{{ tr().planning.traceLoading }}

} @else if (planningTrace(); as trace) { @if (trace.error) { }
@if (selectedTracePhase(); as phase) {

{{ phaseLabel(phase.name) }}

@if (phase.decisions.length) { @if (selectedTraceDecision(); as decision) {
@if (historicalEligibilityLoading()) {

{{ tr().planning.historicalEligibilityLoading }}

} @if (historicalEligibilityError(); as error) {

{{ tr().planning.historicalEligibilityError }} {{ error }}

}

{{ tr().planning.dates }}: {{ decision.dates.length ? decision.dates.join(', ') : tr().planning.none }}

@if (decision.reason) {

{{ tr().planning.reason }}: {{ decision.reason.message }}

}

{{ tr().planning.candidates }}

@if (decision.candidates.length > 50) { }
@for ( candidate of visibleTraceCandidates(); track $index ) {
{{ traceStatusLabel(candidate.status) }} · @if ( candidate.sourceDate && candidate.sourceDate !== candidate.date ) { {{ candidate.sourceDate }} → } {{ candidate.dates?.join(', ') || candidate.date || tr().planning.none }} @if (candidate.reason) {

{{ candidate.reason.message }}

} @if (candidate.technicalComparison?.length) {
{{ tr().planning.details .traceStageTitle }}

{{ tr().planning.details .traceStageHelp }}

@for ( comparator of candidate.technicalComparison; track $index ) { }
{{ tr().planning .explanation .criterion }} {{ tr().planning.before }} {{ tr().planning.after }}
{{ comparator.name }} {{ comparator.direction === 'maximize' ? tr() .planning .details .higher : tr() .planning .details .lower }} @if ( comparator.name === candidate.firstDecisiveComparator ) { {{ tr() .planning .details .firstDifference }} } {{ comparator.before }} {{ comparator.after }}
} @if (candidate.comparison; as comparison) {
} @if (candidate.placement) { }
}

{{ tr().planning.deltas }}

    @for (delta of decision.deltas; track $index) {
  • {{ formatIdentifier(delta.kind) }} @if (delta.fromDate || delta.date) { · {{ delta.fromDate || delta.date }} } @if (delta.toDate) { → {{ delta.toDate }} } @if (delta.balanceId) { · {{ delta.balanceId }} } @if (delta.remainingAfter !== undefined) { · {{ delta.remainingBefore }} → {{ delta.remainingAfter }} }
  • }
@if (traceState(); as state) {

{{ traceMoment() === 'before' ? tr().planning.before : tr().planning.after }}: {{ state.placements.length }} {{ tr().planning.placements }}, {{ state.earnedActivations.length }} {{ tr().planning.activations }} @for (balance of state.balances; track balance.id) { {{ balance.id }}: {{ balance.remaining }} {{ tr().planning.balanceRemaining }} {{ tr().planning.details.available }} {{ displayDate(balance.availableFrom) }} · {{ tr().planning.details.expires }} {{ displayDate(balance.expiryDate) }} }

} } } @else {

{{ tr().planning.noDecisions }}

}
}
} @else if (traceResolved()) {

{{ tr().planning.traceUnavailable }}

}
}
@if (traceReplay()) {
{{ tr().planning.replayNotice }}
}

{{ declarationEntry() ? tr().bookedDays.help : tr().planning.calendarHelp }}

@if (declarationEntry() && !calendarHorizon()) {

{{ tr().settings.invalidHorizon }}

}
{{ tr().planning.legend }}
@for (month of months(); track month.year + '-' + month.monthIndex) { }

{{ traceEffect ? tr().planning.details.traceImpactTitle : tr().planning.details.criteria }}

{{ traceEffect ? tr().planning.details.traceImpactHelp : tr().planning.details.criteriaHelp }}

@for ( objective of objectiveRows(comparison.before, comparison.after); track objective.id; let index = $index ) { @if (objective.numeric) { } }
{{ tr().planning.explanation.criterion }} {{ beforeLabel }} {{ afterLabel }}
{{ index + 1 }}. {{ objective.label }}

{{ objective.help }}

{{ objective.direction === 'maximize' ? tr().planning.details.higher : tr().planning.details.lower }} @if ( !traceEffect && objective.id === comparison.firstDecisiveObjective ) { {{ tr().planning.details.firstDifference }} }
{{ objective.before ?? tr().planning.none }} {{ objective.value }}
@if (hasTieBreaks(comparison.after)) {
{{ tr().planning.details.tieBreaks }}
    @for ( objective of objectiveRows(comparison.before, comparison.after); track objective.id ) { @if (!objective.numeric) {
  1. {{ objective.label }} @if ( !traceEffect && objective.id === comparison.firstDecisiveObjective ) { {{ tr().planning.details.firstDifference }} }
  2. } }
}
{{ tr().planning.details.audit }}

{{ tr().planning.details.auditHelp }}

{{ data | json }}

{{ tr().planning.statistics.title }}

@if (displayedPlan(); as plan) { @if (planningOutcome()?.kind === 'PlanningIncomplete') {

{{ tr().planning.statistics.partialPlan }}

}

{{ tr().planning.statistics.help }}

{{ tr().planning.statistics.leaveDays }}
{{ plan.placements.length }}
{{ tr().planning.daysGained }} ({{ tr().planning.statistics.calendarDays }})
{{ formatStatistic(plan.metrics.nonWorkingDayGain) }}
{{ tr().planning.statistics.averageWorkSequence }} ({{ tr().planning.statistics.workingDays }})
{{ formatStatistic(plan.metrics.averageWorkSequence) }}
{{ tr().planning.longestWorkSequence }} ({{ tr().planning.statistics.workingDays }})
{{ formatStatistic(plan.metrics.longestWorkSequence) }}
{{ tr().planning.statistics.averageTimeOffPeriod }} ({{ tr().planning.statistics.calendarDays }})
{{ formatStatistic(plan.metrics.averageTimeOffPeriod) }}
{{ tr().planning.statistics.longestTimeOffPeriod }} ({{ tr().planning.statistics.calendarDays }})
{{ formatStatistic(plan.metrics.longestTimeOffPeriod) }}
{{ tr().planning.statistics.weekendConnectedHolidays }} ({{ tr().planning.statistics.calendarDays }})
{{ formatStatistic(weekendConnectedHolidays()) }}
{{ tr().planning.statistics.earnedLeave }} ({{ tr().planning.statistics.leaveDaysUnit }})
{{ formatStatistic(earnedLeave().total) }}
@if (earnedLeave().contributions.length) {

{{ tr().planning.statistics.earnedLeaveByRule }} ({{ tr().planning.statistics.leaveDaysUnit }})

@for ( contribution of earnedLeave().contributions; track contribution.ruleId ) {
{{ contribution.label }}
{{ formatStatistic(contribution.quantity) }}
}
} }

{{ tr().planning.comparison.title }}

{{ traceReplay() ? tr().planning.explanation.traceHelp : tr().planning.explanation.moveHelp }}

@if (counterfactualLoading()) {

{{ tr().planning.counterfactualLoading }}

} @else if (counterfactualError()) { } @else if (counterfactual(); as analysis) {
{{ traceReplay() ? tr().planning.comparison.candidate : tr().planning.explanation.plannedDate }} {{ displayDate(analysis.placement.date) }}
{{ tr().planning.explanation.tryDate }}
@if (analysis.results[0]; as firstDate) {

{{ tr().planning.comparison.dateRange }} {{ displayDate(firstDate.date) }} – {{ displayDate(analysis.results.at(-1)!.date) }}

} @if (invalidComparisonDate()) { } @if (nearbyComparisonDates().length) {
{{ tr().planning.comparison.nearby }} @for (alternative of nearbyComparisonDates(); track alternative.date) { }
} @else {

{{ tr().planning.comparison.noAlternatives }}

} @if (selectedCounterfactualResult(); as result) {

{{ displayDate(result.date) }} · {{ result.status === 'eligible' ? tr().planning.comparison.available : tr().planning.comparison.unavailable }}

@if (result.status === 'ineligible') {

{{ tr().planning.comparison.unavailable }}

@if (result.reason) {

{{ tr().planning.explanation.unavailable[ result.reason.code ] ?? result.reason.message }}

}
} @else if (!traceReplay() && result.date === analysis.placement.date) {

{{ tr().planning.explanation.sameDate }}

} @else {

{{ tr().planning.comparison.changes }}

{{ tr().planning.comparison.available }}
@if (dateComparison()) { @if (dateChanges().length) { @for (change of dateChanges(); track change.id) { }
{{ tr().planning.explanation.criterion }} {{ traceReplay() ? tr().planning.explanation .beforeDecision : tr().planning.explanation.currentPlan }} {{ traceReplay() ? tr().planning.explanation .withCandidate : tr().planning.explanation.alternative }}
{{ change.label }} {{ change.before }} {{ change.after }}
} @else {

{{ tr().planning.explanation.noChange }}

} } @else {

{{ tr().planning.explanation.comparisonUnavailable }}

} }
{{ tr().planning.technicalDetails }}

{{ tr().planning.historical }}: {{ dateStatuses()[result.date]?.label ?? traceStatusLabel( result.historicalStatus ?? 'not-examined' ) }}

@if (dateStatuses()[result.date]?.reason; as historicalReason) {

{{ tr().planning.reason }}: {{ historicalReason }}

} @if (dateComparison(); as comparison) { } @else { }
} @else if (!invalidComparisonDate()) {

{{ tr().planning.comparison.chooseDate }}

} }