# Indirect Goods Upgrade — Phase 2 Domain Services Completion

Completed: 11 July 2026

## Outcome

Phase 2 added the inactive domain layer behind the Phase 1 schema. No controller, route, UI or existing issue path was switched to these services.

## New domain models

- `IndirectGoodsFulfillment`
- `IndirectGoodsFulfillmentAllocation`
- `IndirectGoodsDirectDelivery`
- `IndirectGoodsDirectDeliveryItem`
- `IndirectGoodsFocInventory`
- `IndirectGoodsFocAdjustment`
- `IndirectGoodsReturn`
- `IndirectGoodsReturnItem`
- `IndirectGoodsDivisionDelivery`

Existing indirect request, request-item and stock-movement models were extended with the Phase 1 relationships and audit fields.

## New services

### `IndirectGoodsAvailabilityService`

- Reads warehouse availability by item and division.
- Reads issuable FOC availability by item and division.

### `IndirectGoodsIssueService`

- Transaction-safe warehouse issue.
- Transaction-safe FOC issue.
- Request and inventory row locking.
- Idempotency-key handling.
- FEFO then FIFO source selection.
- Source allocation and destination provenance.
- Real before/moved/after movements.
- Request status and quantity updates.

### `IndirectGoodsDirectDeliveryService`

- Completes accepted supplier-to-project deliveries.
- Does not update normal warehouse inventory.
- Creates fulfillment/allocation provenance and project movement.
- Protects request balances and rejected quantity.

### `IndirectGoodsFocService`

- Performs locked FOC adjustments.
- Prevents negative FOC stock.
- Creates approved adjustment audit rows.

### `IndirectGoodsReturnService`

- Completes only submitted/inspected returns.
- Locks source fulfillment, allocation and project stock.
- Prevents over-return.
- Reduces project inventory.
- Adds returned stock only to indirect FOC inventory.
- Creates FOC adjustment and movement history.
- Never increases normal indirect warehouse inventory.

### `IndirectGoodsConsumptionService`

- Allocates consumption against outstanding fulfillment provenance.
- Prevents returned and consumed quantities claiming the same issue.
- Reduces project inventory and records movement history.

### `IndirectGoodsDivisionLedgerService`

- Rebuilds division summary rows from authoritative ledgers.
- Supports legacy request quantities until Phase 3 backfill is complete.

### `IndirectGoodsReconciliationService`

- Reports warehouse, project, consumed, released, returned and transferred totals.
- Reports negative rows and over-accounted fulfillment/allocation rows.
- Calculates the project reconciliation variance.

## Domain invariants

- Returned + consumed + transferred cannot exceed fulfilled quantity.
- Allocation accounting cannot exceed issued allocation quantity.
- Accepted + rejected direct-delivery quantity cannot exceed delivered quantity.
- Warehouse, project and FOC quantities cannot be reduced below zero.
- Issue quantity cannot exceed request balance.
- Return quantity cannot exceed both fulfillment allocation and project availability.
- FOC returns always use the indirect FOC ledger.

## Verification results

### Syntax

All indirect model and service PHP files pass `php -l`.

### Unit tests

`IndirectGoodsDomainInvariantTest`: 5 passed, 5 assertions.

Coverage includes:

- fulfillment conservation;
- fulfillment over-accounting rejection;
- allocation double-claim rejection;
- direct-delivery acceptance validation;
- valid partial delivery acceptance.

### Container resolution

Issue, return and direct-delivery services resolve successfully through the Laravel service container.

### Read-only database reconciliation

| Metric | Result |
|---|---:|
| Warehouse available | 178,264.48 |
| Project available | 23,829.02 |
| Project consumed | 2.00 |
| Released | 23,831.02 |
| Returned to FOC | 0.00 |
| Transferred out | 0.00 |
| Request balance | 782.00 |
| Reconciliation variance | 0.00 |
| Negative warehouse rows | 0 |
| Negative project rows | 0 |
| Negative FOC rows | 0 |
| Over-accounted fulfillments | 0 |
| Over-accounted allocations | 0 |

### Existing test-suite note

The repository's default `tests/Feature/ExampleTest.php` fails because it expects `/` to return HTTP 200, while this application has no root route and returns HTTP 404. This is an existing placeholder-test mismatch and is unrelated to Phase 2. All new Phase 2 tests pass.

## Phase gate

Phase 2 is complete. Phase 3 may backfill historic fulfillment and allocation records using dry-run and committed modes. Live issue controllers must remain on the legacy path until Phase 4.
