# CloseInventorySupplyPlan

## Permission Scope

supplyPlan

## Overview

closeInventorySupplyPlan removes expected inbound supply from open planning without deleting projection rows. Source modules call this command when a single supply plan, selected source lines, or an entire source document is closed, cancelled, or otherwise no longer contributes open inbound supply.

## Business Rules

- Input is a discriminated union:
  - `{ target: "SUPPLY_PLAN", id }`
  - `{ target: "SOURCE_DOCUMENT", sourceType, sourceId }`
  - `{ target: "SOURCE_LINES", sourceType, sourceLineIds }`
- `SUPPLY_PLAN` closes exactly one plan by id
- `SUPPLY_PLAN` requires the supply plan to exist and be OPEN
- `SOURCE_DOCUMENT` closes only OPEN supply plans matching `sourceType` and `sourceId`
- `SOURCE_LINES` closes only OPEN supply plans matching `sourceType` and `sourceLineIds`
- `SOURCE_LINES` with an empty `sourceLineIds` array closes no rows
- Sets `status = CLOSED`
- Does not change `expectedQuantity` or `receivedQuantity`
- Does not write InventoryLedger because source closure is not a physical stock movement

## Process Flow

```mermaid
flowchart TD
    A[Receive close request] --> B{target?}
    B -->|SUPPLY_PLAN| C{Plan exists and OPEN?}
    C -->|No| D[Return error]
    C -->|Yes| E[Set one plan CLOSED]
    B -->|SOURCE_DOCUMENT| F[Close OPEN plans by source document]
    B -->|SOURCE_LINES| G{sourceLineIds empty?}
    G -->|Yes| H[Return empty supplyPlans]
    G -->|No| K[Close OPEN plans by source lines]
    E --> I[Return closed plan]
    F --> J[Return closed plans]
    K --> J
```

## External Dependencies

- None

## Error Scenarios

- **INVENTORY_SUPPLY_PLAN_NOT_FOUND**: Referenced supply plan does not exist
- **SUPPLY_PLAN_NOT_OPEN**: Supply plan is not OPEN

## Test Cases

- closes supply plan by id
- returns error when supply plan not found
- returns error when supply plan is closed
- closes open supply plans for a source document
- closes open supply plans for selected source lines
- returns no rows when sourceLineIds is empty
