PhoenixKitWarehouse.SupplierOrders (PhoenixKitWarehouse v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

Manually attaches a traceability reference (type "internal_order") to a 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.

Creates a new draft supplier order.

Generates draft supplier orders from a posted internal order.

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

Returns the supplier order or raises.

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.

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

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

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

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

Posts a supplier order in an Ecto.Multi transaction.

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.

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.

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

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

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

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

Functions

add_source_ref(order, type, uuid)

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(order, attrs)

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(attrs)

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(internal_order, actor_uuid)

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(uuid)

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

get_supplier_order!(uuid)

Returns the supplier order or raises.

import_from_internal_orders(supplier_order, io_uuids, actor_uuid)

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(opts \\ [])

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(order, performed_by_uuid)

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(supplier_order_uuids)

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(supplier_order)

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(order, type, uuid)

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

set_storage_folder(order, storage_folder_uuid)

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

soft_delete_supplier_order(order, actor_uuid)

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

update_draft(supplier_order, attrs)

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.