# `PhoenixKitWarehouse.SupplierOrders`
[🔗](https://github.com/BeamLabEU/phoenix_kit_warehouse/blob/0.4.0/lib/phoenix_kit_warehouse/supplier_orders.ex#L1)

Context for managing supplier orders (purchase orders to a single supplier).

Supplier orders are generated (semi-automatically) from posted internal orders.
Posting has NO stock effect — goods receipt will do that.

# `add_source_ref`

Manually attaches a traceability reference (`type` "internal_order") to a
supplier order.

Pure metadata — does not touch `lines` and is not gated to draft status.
A duplicate `{type, uuid}` pair is a no-op.

# `correct_supplier_order`

Corrects the note and/or storage_folder_uuid of a supplier order without
changing status or lines. Works on documents in any status.

# `create_supplier_order`

Creates a new draft supplier order.

`location_uuid` defaults to the configured default warehouse when not given.
`created_by_uuid` is set programmatically — not via cast.

# `generate_from_internal_order`

Generates draft supplier orders from a posted internal order.

All-or-nothing inside a single `Ecto.Multi` transaction:

1. Computes net shortfall per line:
     shortfall = max(0, required − on_hand)
   Lines with shortfall == 0 are dropped (fully stocked).

2. Resolves supplier per material via the item's manufacturer:
   - Exactly 1 linked supplier → line is assigned to that supplier.
   - 0 OR >1 linked suppliers → line goes to the "unassigned" bucket
     (never auto-picked from multiple suppliers).

3. Groups assigned lines by supplier_uuid → creates ONE DRAFT per supplier.
   Line shape carries: on_hand_quantity, shortfall_quantity,
   ordered_quantity (= shortfall, keeper edits later),
   base_price (catalogue base price), required_quantity.

Returns `{:ok, %{supplier_orders: [...drafts...], unassigned_lines: [...]}}`.
Drafts are NOT posted.

# `get_supplier_order`

Returns `{:ok, order}` or `{:error, :not_found}`.

# `get_supplier_order!`

Returns the supplier order or raises.

# `import_from_internal_orders`

Imports lines from one or more posted internal orders into an existing draft
supplier order, filtering to items whose resolved supplier matches
`supplier_order.supplier_uuid`.

Steps:
1. Loads each selected IO (must be posted and non-deleted).
2. Enriches lines using the same stock + catalogue helpers as
   `generate_from_internal_order/2`.
3. Filters to items whose single resolved supplier == supplier_order.supplier_uuid.
4. Merges new lines with existing ones by item_uuid, summing
   required/shortfall/ordered quantities.
5. Appends `source_refs` entries of type `"internal_order"` for each IO.
6. Sets `internal_order_uuid` to the first selected IO (primary back-compat).
7. Saves via `update_draft/2`.

Returns:
- `{:ok, updated_order}` on success.
- `{:error, :no_supplier}` when `supplier_order.supplier_uuid` is nil.
- `{:error, :not_draft}` when the order is not a draft.
- `{:error, reason}` on DB failure.

# `list_posted_internal_orders`

Lists posted (non-deleted) internal orders for use as source-picker candidates
when importing into a supplier order.

Returns a list of `%InternalOrder{}` ordered by number descending.

# `list_posted_supplier_orders`

Lists non-deleted POSTED supplier orders ordered by number descending.

Used as candidates for importing lines into a goods receipt.

# `list_supplier_orders`

Lists non-deleted supplier orders ordered by number descending (newest first).

# `list_suppliers`

Lists all suppliers from the catalogue.
Returns a list of supplier structs with at least `:uuid` and `:name` fields.

# `post_supplier_order`

Posts a supplier order in an `Ecto.Multi` transaction.

- Locks the row FOR UPDATE and re-checks status == "draft" (prevents
  double-posting of the same draft).
- Deduplicates lines by item_uuid.
- Flips status → "posted", sets posted_at and performed_by_uuid.
- Does NOT write any stock rows.

Returns `{:error, :not_draft}` for non-draft orders.

# `received_summaries`

Returns `%{supplier_order_uuid => %{item_uuid => Decimal}}` — for each uuid in
`supplier_order_uuids`, the received_quantity already recorded against it,
summed across all POSTED, non-deleted goods receipts referencing it — either
as the primary `supplier_order_uuid` FK, or as a secondary `"supplier_order"`
source_refs entry.

# `received_summary`

Returns `%{item_uuid => Decimal}` summing `received_quantity` already recorded
against `supplier_order` across all POSTED, non-deleted goods receipts
referencing it — either as the primary `supplier_order_uuid` FK, or as a
secondary `"supplier_order"` source_refs entry.

# `remove_source_ref`

Detaches a traceability reference from a supplier order. No-op when the
`{type, uuid}` pair isn't present.

# `set_storage_folder`

Sets the `storage_folder_uuid` on a supplier order.
Works on documents in any status.

# `soft_delete_supplier_order`

Soft-deletes a draft supplier order. Returns {:error, :not_draft} for posted documents.

# `update_draft`

Updates a draft supplier order. Returns `{:error, :not_draft}` when not in
draft status.

Locks the row FOR UPDATE and re-checks status == "draft" in the DB (not
just the in-memory struct) so a stale tab cannot overwrite an order that
was posted concurrently by another tab/user.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
