# Indirect Goods Upgrade — Phase 4 Warehouse Issue Completion

Completed: 11 July 2026

## Outcome

Live indirect warehouse issues now use the fulfillment and allocation service introduced in Phase 2. Existing direct-goods issue behavior is unchanged. Indirect supplier direct-delivery and indirect FOC issue remain disabled until their dedicated phases.

## Live paths changed

### Shared Material Issue

`MaterialIssueController::processMultiSource()` now:

- processes only `inventory_indirect` through `IndirectGoodsIssueService::issueWarehouse()`;
- requires a division for every indirect warehouse issue line;
- creates a stable per-form idempotency token;
- no longer combines indirect warehouse, direct-delivery and FOC inputs into one warehouse deduction;
- rejects indirect direct-delivery input while Phase 5 is disabled;
- rejects indirect FOC input while Phase 6 is disabled.

### Standalone Indirect Goods Issue

`IndirectGoodsRequestController::issue()` now:

- requires the request to have a division;
- checks warehouse availability in that division;
- issues the lower of remaining demand and available stock;
- supports partial issues;
- uses the same transactional fulfillment service as the shared screen;
- no longer calls the legacy cross-division `IndirectGoodsRequest::issue()` path.

## Feature gates

Added `config/indirect_goods.php`:

| Flag | Phase 4 state |
|---|---|
| `warehouse_issue_enabled` | enabled |
| `direct_delivery_enabled` | disabled |
| `foc_issue_enabled` | disabled |
| `returns_enabled` | disabled |
| `reports_v2_enabled` | disabled |

Indirect direct-delivery and FOC rows are hidden from the shared issue form while their gates are disabled. Server-side validation also rejects crafted input.

## Warehouse issue transaction

For each issued line the service now atomically:

1. locks request and request-item rows;
2. revalidates request status and remaining balance;
3. locks eligible warehouse batches in the selected division;
4. deducts FEFO, then FIFO;
5. adds stock to project inventory with invoice/batch provenance;
6. creates fulfillment and allocation rows;
7. creates a real before/moved/after movement;
8. updates request released and balance quantities;
9. updates request and line fulfillment statuses;
10. rebuilds the indirect division summary;
11. commits all effects together or rolls them all back.

## Verification

### Static and view checks

- Modified controllers and config pass PHP syntax checks.
- Blade templates compile successfully.
- Domain invariant tests: 5 passed.

### Rolled-back end-to-end smoke test

One unit was issued from an eligible pending request inside an outer transaction.

Verified during the transaction:

- fulfillment type: `inventory`;
- fulfillment quantity: 1.0000;
- allocation rows: 1;
- warehouse movement before: 11,231.00;
- moved: 1.00;
- warehouse movement after: 11,230.00;
- destination project inventory linked;
- request line became partially fulfilled;
- indirect FOC rows remained zero.

After rollback:

- fulfillment/allocation/movement counts returned to their prior values;
- warehouse and project totals returned to their prior values;
- reconciliation variance remained 0.0000.

### Idempotency test

Calling the service twice with the same idempotency key produced:

- the same fulfillment ID;
- one fulfillment row;
- one allocation row.

The test transaction was rolled back.

## Phase gate

Phase 4 is complete. Phase 5 may enable the existing indirect direct-delivery domain service through validated forms, routes and documents. Indirect FOC and return gates remain disabled.
