# SendBackApprovalStep

## Permission Scope

request

## Overview

SendBackApprovalStep is the non-terminal counterpart to `rejectApprovalStep`. An approver who needs correction — rather than killing the request — sends the request back with a required reason. It supports two destinations, selected by the optional `targetApprovalStepId`:

- **Return to requester** (`targetApprovalStepId` omitted): the request is handed back to the requester as `REVISION_REQUESTED`. The requester revises the target and resubmits via `resubmitApprovalRequest`, which restarts approval from the first step. Step and assignee rows are left untouched until resubmission.
- **Return to an earlier step** (`targetApprovalStepId` supplied): the request stays `PENDING` and rewinds to a chosen earlier step so a prior approver can reconsider, without involving the requester. The target step through the current step are reset and the target step is re-activated; approval then flows forward again from there.

Both modes carry the same step-level guards as reject, and nothing terminates.

## Business Rules

- The actor (`ctx.actorId`) must own the target `ApprovalStepAssignee` row, the row must be `PENDING`, and its step must be `IN_PROGRESS`
- The parent request must be `PENDING` (liveness guard)
- Self-send-back is blocked: `ctx.actorId` cannot equal `ApprovalRequest.requesterId`
- A non-empty `reason` is required
- Writes one `ApprovalDecision` row: `decision = SEND_BACK`, `approvalStepId` = the actor's current step, `comment = reason`; `sentBackToStepId` = the target step in step mode and null in requester mode
- **Requester mode** (`targetApprovalStepId` omitted): the request transitions to `REVISION_REQUESTED`; `resolvedAt` stays null (non-terminal). Step and assignee rows are not modified; they are reset only when the requester resubmits
- **Step mode** (`targetApprovalStepId` supplied):
  - The target step must belong to the same request and have a `stepOrder` strictly less than the actor's current step; otherwise the send-back is rejected
  - The request status is unchanged (stays `PENDING`; `resolvedAt` stays null)
  - Every step from the target step through the current step is reset to `PENDING` with `resolvedAt` cleared, and every `APPROVED` assignee on those steps is reset to `PENDING` with `resolvedAt` cleared; `DELEGATED` assignee rows are left as-is
  - The target step is re-activated to `IN_PROGRESS`; the frozen assignee set is reused (roles are not re-expanded and the source policy is not re-read)

## Process Flow

```mermaid
flowchart TD
    A[Receive send-back request] --> B{Actor owns a PENDING assignee<br/>on an IN_PROGRESS step?}
    B -->|No| BX[Return error: NOT_ACTIVE_ASSIGNEE]
    B -->|Yes| C{Request.status = PENDING?}
    C -->|No| CX[Return error: PARENT_REQUEST_NOT_PENDING]
    C -->|Yes| D{ctx.actorId === Request.requesterId?}
    D -->|Yes| DX[Return error: SELF_DECISION_NOT_ALLOWED]
    D -->|No| E{reason non-empty?}
    E -->|No| EX[Return error: MISSING_REQUIRED_FIELD]
    E -->|Yes| F{targetApprovalStepId supplied?}
    F -->|No| G1[Insert SEND_BACK decision<br/>sentBackToStepId = null]
    G1 --> H1[Update request: status = REVISION_REQUESTED<br/>step and assignee rows untouched]
    H1 --> Z1[Return the request awaiting revision]
    F -->|Yes| I{Target step in same request<br/>and stepOrder &lt; current step?}
    I -->|No| IX[Return error: INVALID_SEND_BACK_TARGET]
    I -->|Yes| G2[Insert SEND_BACK decision<br/>sentBackToStepId = target step]
    G2 --> J[Reset target..current steps to PENDING<br/>reset APPROVED assignees to PENDING<br/>keep DELEGATED rows]
    J --> K[Re-activate target step to IN_PROGRESS<br/>request stays PENDING]
    K --> Z2[Return the request rewound to the target step]
```

## External Dependencies

- None

## Error Scenarios

- **NOT_ACTIVE_ASSIGNEE**: The actor does not own a `PENDING` `ApprovalStepAssignee` row on an `IN_PROGRESS` `ApprovalStep`
- **PARENT_REQUEST_NOT_PENDING**: The parent `ApprovalRequest.status` is not `PENDING`
- **SELF_DECISION_NOT_ALLOWED**: The actor is the requester of the parent request; segregation of duties prohibits self-decision on APPROVE / REJECT / SEND_BACK / DELEGATE
- **MISSING_REQUIRED_FIELD**: A required input field is missing or empty
- **INVALID_SEND_BACK_TARGET**: The supplied targetApprovalStepId does not belong to the parent request or is not an earlier step than the actor's current step

## Test Cases

- sends a step back to the requester (no target) from a required = true assignee and transitions the request to REVISION_REQUESTED
- sends a step back to the requester from a required = false (optional) assignee and transitions the request to REVISION_REQUESTED
- writes one ApprovalDecision row with decision = SEND_BACK, approvalStepId = current step, sentBackToStepId = null, and comment = reason in requester mode
- leaves ApprovalStep and ApprovalStepAssignee rows unchanged in requester mode and only updates the request
- does not set resolvedAt on the request because REVISION_REQUESTED is non-terminal
- sends back to an earlier step (target supplied) leaving the request in PENDING and re-activating the target step to IN_PROGRESS
- writes one ApprovalDecision row with decision = SEND_BACK, approvalStepId = current step, and sentBackToStepId = the target step in step mode
- resets every step from the target step through the current step to PENDING with resolvedAt cleared in step mode
- resets APPROVED assignees on the reset steps to PENDING and leaves DELEGATED assignee rows unchanged in step mode
- does not re-expand roles on the target step; the frozen per-user assignee set is reused
- throws MISSING_REQUIRED_FIELD when reason is empty
- throws NOT_ACTIVE_ASSIGNEE when the assignee row is missing
- throws NOT_ACTIVE_ASSIGNEE when the actor does not own the assignee
- throws NOT_ACTIVE_ASSIGNEE when the assignee status is not PENDING
- throws NOT_ACTIVE_ASSIGNEE when the parent step is not IN_PROGRESS
- throws PARENT_REQUEST_NOT_PENDING when the parent request is in any non-PENDING status
- throws SELF_DECISION_NOT_ALLOWED when the actor is the requester
- throws INVALID_SEND_BACK_TARGET when the target step does not belong to the parent request
- throws INVALID_SEND_BACK_TARGET when the target step is the current step or a later step
