# RecalculateRange

## Permission Scope

`calculatedBlock`

## Overview

RecalculateRange re-derives CalculatedTimeBlocks over a date range in response to a retroactive correction or a rule change, discarding and regenerating only the affected period.

## Business Rules

- A retroactive correction (an effective-dated change, ADR-013) or a rule change over a period invalidates and triggers re-derivation of the CalculatedTimeBlocks in that period only — unaffected periods are left untouched
- Recalculation discards prior calculated blocks for the affected workdays and regenerates them from current ReportedTimeBlocks plus the rules effective on each workDate
- The range is bounded and processed per-workday, applying the same rule-resolution and categorization logic as CalculateTimeBlocks (workforce context → WorkRuleAssignment in force → WorkRule generation effective on each workDate)
- After regeneration, OPEN Timecards covering workdays in the range have their per-category totals (`categoryTotals`, keyed by the strategy-defined category key — not fixed JP columns) refreshed from the covered CalculatedTimeBlocks
- Recalculation binds to WorkRule/TimeEntryCode keys, not display names, so a rule rename alone never changes results
- `minutes` remains a non-negative integer for every regenerated block

## Process Flow

```mermaid
flowchart TD
    A[Trigger: retroactive correction or rule-generation change over a date range] --> B[Enumerate workdays in the affected range for the Assignment]
    B --> C[For each workday: gather current ReportedTimeBlocks]
    C --> D[Resolve rules effective on that workDate]
    D --> E[Discard prior CalculatedTimeBlocks for the workday]
    E --> F[Regenerate categorized CalculatedTimeBlocks]
    F --> G{More workdays in range?}
    G -- Yes --> C
    G -- No --> H[Recalculation complete for range]
```

## External Dependencies

- [time-tracking::ReportedTimeBlock](../model/ReportedTimeBlock.md) - the current declared blocks re-consumed for each workday in the range
- [time-tracking::WorkRuleAssignment](../model/WorkRuleAssignment.md) - the effective-dated binding re-resolved per workDate
- [time-tracking::WorkRule](../model/WorkRule.md) - re-resolved per workDate to reflect any rule-generation change (ADR-015)
- [time-tracking::TimeEntryCode](../model/TimeEntryCode.md) - resolved by category/key to bind `timeEntryCodeKey`/`payCodeKey`
- [time-tracking::CompanyHoliday](../model/CompanyHoliday.md) - consulted per workday for holiday classification
- workforce Assignment / WorkerEmployment / Position (cross-module, injected queries) - establish the worker's rule-resolution targets

## Error Scenarios

- **INVALID_RANGE**: The provided date range is empty or `end` precedes `start`
- **WORK_RULE_NOT_FOUND**: no WorkRule exists for the given id, or no WorkRule is effective for the relevant date
- **ASSIGNMENT_NOT_FOUND**: no workforce Assignment is effective for the relevant worker/date

## Test Cases

- rejects an empty/inverted range where end precedes start
- rejects when no workforce Assignment is effective for the range
- discards prior CalculatedTimeBlocks in the range and regenerates from current ReportedTimeBlocks
- re-resolves the WorkRule generation per workDate so a rule change mid-range applies from its effective date
- is idempotent: recalculating the same range twice with unchanged inputs discards and reproduces the same set without accumulating duplicates
- discards prior calculated blocks for the range even when no current ReportedTimeBlocks remain (empty regeneration)
- refreshes covering OPEN Timecard category totals for the recalculated range
