# SendBackApprovalStep

## Permission Scope

request

## Overview

SendBackApprovalStep is the non-terminal counterpart to `rejectApprovalStep`. An approver who needs the requester to correct the underlying target — rather than killing the request — sends the request back with a required reason, returning it to the requester as `REVISION_REQUESTED`. It carries the same step-level guards as reject, but nothing terminates: the requester revises the target and resubmits via `resubmitApprovalRequest`.

## 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`, both ids set, `comment = reason`
- 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

## 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[Insert SEND_BACK decision]
    F --> G[Update request: status = REVISION_REQUESTED<br/>step and assignee rows untouched]
    G --> H[Return the request awaiting revision]
```

## 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

## Test Cases

- sends a step back from a required = true assignee and transitions the request to REVISION_REQUESTED
- sends a step back from a required = false (optional) assignee and transitions the request to REVISION_REQUESTED
- writes one ApprovalDecision row with decision = SEND_BACK, both ids set, and comment = reason
- leaves ApprovalStep and ApprovalStepAssignee rows unchanged and only updates the request
- does not set resolvedAt on the request because REVISION_REQUESTED is non-terminal
- 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
