Skip to content

Order Line Management

This page covers the operations for managing lines within a Purchase Order: adding a single line, adding lines from Kanban Cards (to an existing order), adding lines from items, and removing lines.

Use case references:


Use case: PRO::PO::0003::0006 — Priority: MVP2-B

A single order line can be added to an existing order in NEW state in two ways:

  1. From a Kanban Card: The line takes the values of the current version of the item referenced by the card (title, quantity, unit cost from the order’s supplier name).
  2. As a blank line: The UI presents an item selector filtered to items that share the order’s vendor.
    • Alphabetically ordered by item name for the tenant.
    • The user can start typing to filter by name prefix (case-insensitive match at start of name).
    • The user selects from the list or presses Enter after typing a complete name.
    • If the item name matches an existing item, the line is filled with the item’s current values.
    • If the item name does not match, a modal allows the user to confirm creation of a new item. Canceling the modal reverts to the field being edited.

Bulk line addition from a CSV file is also supported: if the item name in the CSV matches an existing item (case-insensitively), that item is used; otherwise the line is marked INVALID and must be corrected before the order can be submitted.

OrderRecordService.addLine at operations/src/main/kotlin/cards/arda/operations/procurement/orders/service/OrderRecordService.kt

  • The target order (identified by eId + asOf or rId) must exist; otherwise the service replies with ArgumentValidation (OrderRecordService.kt:152-167, 173-186).
  • When the incoming line references an Item (linePayload.item.eId), the item lookup must succeed at the requested coordinates; if it no longer exists the system keeps the original payload without failing (OrderRecordService.kt:156-164).
  • Supplier compatibility is not explicitly validated by this operation; incompatible data is left to downstream validation.
  1. The service loads the parent order snapshot for the provided coordinates.
  2. If the incoming line references an Item, the latest version is fetched and OrderHelper.adjustLine enriches missing fields (title, quantity, unitCost, received) using the order’s supplierName (OrderHelper.kt:354-374).
  3. The adjusted (or original) payload is inserted through OrderLineUniverse.add, respecting the optional insertion index (OrderHelper.kt:381-394).
  4. The caller-provided postProcess callback is invoked; its failure halts the transaction (OrderHelper.kt:392-393).
  • The new OrderLine version is persisted and returned to the caller wrapped in an EntityRecord.
  • The parent OrderHeader is not automatically rebalanced (goods value, supplier); additional orchestration via OrderHelper.adjustHeader is required when needed.
  • If postProcess raises an error, the transaction aborts and the error is propagated.

Bulk CSV handling and item name matching described in the UI flow are not implemented at the service layer. Lines are persisted exactly as supplied (after optional enrichment via adjustLine).

API Route: POST /v1/order/order/{eId}/lines


Add Lines from Kanban Cards (to Existing Order)

Section titled “Add Lines from Kanban Cards (to Existing Order)”

Use cases: PRO::PO::0003::0001 and PRO::PO::0003::0004 — Priority: MVP3

Given an existing Draft Purchase Order, the user can add new lines by selecting additional Kanban Cards from the Order Queue.

  1. User selects a set of Cards from the Order Queue.
  2. User clicks Order and selects “Add to existing order.”
  3. The system presents a selector of orders in NEW status, ordered most-recently-updated first.
  4. User selects an order and clicks Add.
  5. The system validates the selected cards for compatibility with the selected order.
  6. The system adds the new lines to the order and displays the updated order.
    • Added cards are transitioned to REQUESTED status by the backend.

The logic is the same as Create from Kanban Cards with these exceptions:

  1. If any selected card does not belong to an item with a compatible supplier for the selected order, the system shows an error.
  2. Cards compatible with pre-existing lines (same item and unit of measure) are aggregated into those existing lines, increasing their quantity.
  3. Cards not compatible with any existing line are added as new lines.

PlantUML diagram

StepRouteNotes
List Order Headers with Status NEWPOST /v1/order/order/queryFilter for status: NEW, sorted by updated descending
Add Cards to OrderPUT /v1/order/order/from-kanban-cards/{eId}Body is a list of Card EIds; returns the Updated Full Order Record
List Cards in Status REQUESTINGPOST /v1/kanban/kanban-card/queryFilter for status: REQUESTING

Use cases: PRO::PO::0001::0006 and PRO::PO::0001::0007 — Priority: MVP3

The user selects a set of Items from the Item List and the system creates a Purchase Order with one line per selected item.

OrderFromItemsService.addOrderFromItems at operations/src/main/kotlin/cards/arda/operations/procurement/orders/service/OrderFromItemsService.kt

  • The items collection must contain at least one entry and cannot exceed LineUniverse.MAX_PAGE_SIZE (currently 1000); otherwise an ArgumentValidation error is returned (OrderFromItemsService.kt:29-33, 60-73).
  • Every requested Item must exist at TimeCoordinates.now(effectiveTime); missing items result in a NotFound error before any persistence (OrderFromItemsService.kt:74-84).
  • Items must share a compatible supplier according to OrderHelper.selectCompatibleSupply; if no shared supplier exists among items that have a preferred supply, the call fails with IllegalArgumentException (OrderHelper.kt:134-147).
  • Creation fails if the selected items do not have a shared vendor (blank primary and secondary suppliers is considered a match with any other item).
  1. Load all requested Items via ItemService.listEntities as of the effective timestamp (OrderFromItemsService.kt:74-84).
  2. Build draft order lines by calling OrderHelper.composeLine for each item (OrderFromItemsService.kt:85-87).
  3. Compose a provisional header with defaults (OrderStatus.NEW, allowPartial=true, deliverBy = now + 7 days) via OrderHelper.composeHeader (OrderHelper.kt:261-283).
  4. Enrich every line with supplier-driven details (title, quantity, unit cost) via OrderHelper.adjustLine, and recompute the header (supplier, goods value) through OrderHelper.adjustHeader (OrderFromItemsService.kt:88-91).
  5. Persist the header and lines in a single transaction with OrderHelper.addOrderWithLines, invoking any supplied postProcess callback after persistence (OrderFromItemsService.kt:92-93).
  • A new OrderRecord (header plus paginated lines) is stored and returned on success.
  • Goods value is derived from enriched line costs when supplier data is available; otherwise it remains null.
  • No Kanban Card associations are created by this flow.
  • Each line references the selected item through ItemReference.Value.fromItem.
  • Quantities default to the supplier’s reorder quantity; they remain null when not available. The implementation does not use Item.minQuantity, contrary to earlier plans.
  • supplierName is derived from the compatible supplier returned by selectCompatibleSupply; if none is found, the field remains null.
  • orderMethod reflects the shared effective supply method when consistent, or OrderMethod.UNKNOWN otherwise (OrderHelper.kt:165-176).
  • goodsValue is recomputed from enriched lines during adjustHeader; if any line references an item missing supply data, goods value may remain null.

Use case: PRO::PO::0003::0005 — Priority: MVP3

Given an existing Draft Purchase Order, the user can remove lines.

  1. User selects an Order from the Draft Orders list and selects the Edit Lines action.
  2. The system opens and displays the list of lines in the Order.
  3. The user selects a line and clicks Remove.
  4. The system removes the line from the Order and displays the updated list.

If multiple lines need to be removed, the frontend loops through them and calls the API for each one. A batch remove endpoint may be provided in future.

Postcondition: The selected line is removed from the PO. Cards previously associated only with the removed line are returned to REQUESTING via shelve events.

PlantUML diagram

StepRouteNotes
List Draft OrdersPOST /v1/order/order/queryFilter for status: NEW
List Lines for OrderGET /v1/order/order/full/{eId}Returns full Order Record (header + lines)
Remove Line from OrderDELETE /v1/order/order/{eId}/lines/{lineEId}Removes a line from the order

See the OpenAPI Specification for full route details.