# ReopenTimecard

## Permission Scope

`timecard`

## Overview

ReopenTimecard returns a SUBMITTED, APPROVED, or LOCKED Timecard back to OPEN for rework. It is now the *only* path to correct reported data while a Timecard is SUBMITTED or APPROVED, since supersede corrections (CorrectReportedBlock/DeclareReportedBlock/ImportReportedBlocks/FormReportedBlocks) are guarded to OPEN-only. Reopening a LOCKED Timecard additionally flags the period as a historical correction, preserving the prior sign-off trail.

## Business Rules

- Reopening is allowed from SUBMITTED, APPROVED, or LOCKED, and always transitions to OPEN
- Reopening from SUBMITTED or APPROVED is a normal send-back for rework; reported corrections resume flowing via supersede once back in OPEN — this is the only route back to OPEN for correction while SUBMITTED/APPROVED
- Reopening from LOCKED preserves the prior sign-off trail (`approvedAt`/`approvedBy` are not erased) and sets `historicalCorrection = true`, since the period was previously closed
- Reopening from LOCKED clears the lock markers (`lockedAt`/`lockedBy`): an OPEN Timecard must never carry them (that combination is impossible in the lifecycle); `lockTimecard` re-stamps them on the next lock
- `historicalCorrection` is true if and only if at least one change (including the reopen itself, when reopening from LOCKED) was applied after the Timecard first reached LOCKED
- A reopen from LOCKED should be accompanied by a reason, recorded consistently with the historical-correction discipline (TimeCorrectionLog)
- The Timecard's category totals are subject to re-aggregation once reported/calculated blocks change after reopening
- Reopening from SUBMITTED resolves the in-flight bundled approval request in the same transaction: `withdrawApprovalRequest` if the actor is the requester, `sendBackApprovalStep` if the actor is an approval-step assignee, or `cancelApprovalRequest` for other admin-initiated reopen operations
- Reopening from APPROVED or LOCKED makes no approval-module call: the request tied to that Timecard is already resolved (approved) by the time it reaches those statuses

## Process Flow

```mermaid
flowchart TD
    A[Request to reopen a Timecard] --> B[Look up Timecard]
    B --> C{Timecard exists and status in SUBMITTED/APPROVED/LOCKED?}
    C -- No, not found --> D[Reject: TIMECARD_NOT_FOUND]
    C -- No, wrong status --> E[Reject: INVALID_STATUS_TRANSITION]
    C -- Yes --> F{Current status = LOCKED?}
    F -- Yes --> G[Set historicalCorrection = true; record reason in TimeCorrectionLog]
    F -- No --> H{Current status = SUBMITTED?}
    H -- Yes --> P{Actor role}
    P -- Requester --> Q[withdrawApprovalRequest]
    P -- Approval-step assignee --> R[sendBackApprovalStep]
    P -- Other admin operation --> S[cancelApprovalRequest]
    H -- No, APPROVED --> T[No approval-module call; request already resolved]
    Q --> I[Set status = OPEN]
    R --> I
    S --> I
    T --> I
    G --> I
    I --> J[ReportedTimeBlock corrections resume via supersede]
```

## External Dependencies

- [time-tracking::TimeCorrectionLog](../model/TimeCorrectionLog.md) - records the reason and trail when reopening from LOCKED
- approval module (cross-module, bundled) - resolves the in-flight approval request when reopening from SUBMITTED, via `withdrawApprovalRequest`/`sendBackApprovalStep`/`cancelApprovalRequest` depending on actor role (ADR-003)

## Error Scenarios

- **TIMECARD_NOT_FOUND**: no Timecard exists for the given id
- **INVALID_STATUS_TRANSITION**: the Timecard's current status does not permit this transition
- **REOPEN_REASON_REQUIRED**: Reopening from LOCKED without a supplied reason

## Test Cases

- rejects with TIMECARD_NOT_FOUND when no Timecard exists for the given id
- rejects with INVALID_STATUS_TRANSITION when the Timecard is OPEN
- reopens a SUBMITTED Timecard to OPEN, clearing the in-flight submittedAt
- withdraws the in-flight request when the actor is the requester (SUBMITTED reopen)
- sends back the in-flight request when the actor is an assignee (not the requester)
- cancels the in-flight request when the actor is neither requester nor assignee
- reopens an APPROVED Timecard to OPEN, clearing the in-flight sign-off trail
- rejects with REOPEN_REASON_REQUIRED when reopening a LOCKED Timecard without a reason
- reopens a LOCKED Timecard to OPEN, preserving the prior sign-off trail as a historical correction
- records the reopen reason in TimeCorrectionLog when reopening from LOCKED
