This page describes the purchase_orders sub-module in src/cluster_erp/modules/procurement/purchase_orders/ and the shared helper src/cluster_erp/modules/procurement/delivery_address.py. A purchase order (PurchaseOrder) is an order placed with a vendor. Receiving an order line posts a goods receipt in the inventory module through the EventBus.
Scope and source¶
- Defined in:
models.py(PurchaseOrder,PurchaseOrderItem),services.py(PurchaseOrderService),routes.py,metadata.py, anditem_metadata.pyof the sub-module. - Count: 2 tables, 18 routes (
@router.decorators inroutes.py), 6ActionMetaentries (5 on the order, 1 on the item).
Order fields¶
| Name | Meaning | Source |
|---|---|---|
po_no |
Business key from the yearly sequence purchase_order |
src/cluster_erp/modules/procurement/purchase_orders/models.py:31, src/cluster_erp/modules/procurement/purchase_orders/services.py:245 |
vendor_id, vendor_name_cache |
Vendor party and its cached name | src/cluster_erp/modules/procurement/purchase_orders/models.py:37-42 |
purchase_request_id, project_id |
Source request and project | src/cluster_erp/modules/procurement/purchase_orders/models.py:46-52 |
status |
Default draft |
src/cluster_erp/modules/procurement/purchase_orders/models.py:56-62 |
order_date, eta_date, actual_arrival_date |
Dates | src/cluster_erp/modules/procurement/purchase_orders/models.py:63-71 |
currency, total_amount |
Currency and total Numeric(19, 4) |
src/cluster_erp/modules/procurement/purchase_orders/models.py:75-82 |
incoterm, payment_terms |
Commercial terms | src/cluster_erp/modules/procurement/purchase_orders/models.py:85-89 |
delivery_party_id, delivery_address_id, delivery_address |
Structured delivery address and its text snapshot | src/cluster_erp/modules/procurement/purchase_orders/models.py:97-107 |
sent_at, sent_by, acknowledged_at |
Lifecycle record | src/cluster_erp/modules/procurement/purchase_orders/models.py:111-119 |
has_technical_attachment, cancel_reason, notes |
Send gate flag, reason, notes | src/cluster_erp/modules/procurement/purchase_orders/models.py:123-133 |
Item fields¶
| Name | Meaning | Source |
|---|---|---|
product_id, product_model_id, product_version_id, description |
Catalogue item with breakdown, or a text item with a description only | src/cluster_erp/modules/procurement/purchase_orders/models.py:159-174, src/cluster_erp/modules/procurement/purchase_orders/schemas.py:20-37 |
vendor_part_number |
Vendor's part number | src/cluster_erp/modules/procurement/purchase_orders/models.py:175 |
qty_ordered, qty_received, qty_rejected |
Quantities Numeric(14, 4) |
src/cluster_erp/modules/procurement/purchase_orders/models.py:179-190 |
unit_price, line_total, unit_id |
Price Numeric(18, 4), total Numeric(19, 4), unit |
src/cluster_erp/modules/procurement/purchase_orders/models.py:195-203 |
expected_date, actual_received_date, batch_id, heat_lot_id |
Receipt data | src/cluster_erp/modules/procurement/purchase_orders/models.py:207-219 |
status |
Default open; then partial or received |
src/cluster_erp/modules/procurement/purchase_orders/models.py:223-228, src/cluster_erp/modules/procurement/purchase_orders/services.py:759-762 |
Statuses and actions¶
The service allows these transitions (src/cluster_erp/modules/procurement/purchase_orders/services.py:67-75):
stateDiagram-v2 [*] --> draft draft --> sent: send sent --> acknowledged: acknowledge acknowledged --> in_transit: in_transit acknowledged --> received: last line received in_transit --> received: last line received received --> in_transit: receipt reversed received --> closed: close draft --> cancelled: cancel sent --> cancelled: cancel acknowledged --> cancelled: cancel in_transit --> cancelled: cancel
In prose: an order starts as draft. send needs an order date and the flag has_technical_attachment (src/cluster_erp/modules/procurement/purchase_orders/services.py:620-641). Receiving is allowed in acknowledged and in_transit; when every live line is received, the order becomes received (src/cluster_erp/modules/procurement/purchase_orders/services.py:705-774). A receipt reversal can move a received order back to in_transit (src/cluster_erp/modules/procurement/purchase_orders/services.py:854-891). close follows received; cancel is open until receipt (src/cluster_erp/modules/procurement/purchase_orders/services.py:892-929).
| Action | Label | Condition | Endpoint | Permission | Source |
|---|---|---|---|---|---|
send |
Send | status is draft |
POST /purchase-orders/{id}/send |
purchase_order.send |
src/cluster_erp/modules/procurement/purchase_orders/metadata.py:92-108, src/cluster_erp/modules/procurement/purchase_orders/routes.py:201 |
acknowledge |
Acknowledge | status is sent |
POST /purchase-orders/{id}/acknowledge |
purchase_order.update |
src/cluster_erp/modules/procurement/purchase_orders/metadata.py:109-125, src/cluster_erp/modules/procurement/purchase_orders/routes.py:213 |
in_transit |
Mark In Transit | status is acknowledged |
POST /purchase-orders/{id}/in-transit |
purchase_order.update |
src/cluster_erp/modules/procurement/purchase_orders/metadata.py:126-143, src/cluster_erp/modules/procurement/purchase_orders/routes.py:225 |
close |
Close | status is received |
POST /purchase-orders/{id}/close |
purchase_order.update |
src/cluster_erp/modules/procurement/purchase_orders/metadata.py:144-158, src/cluster_erp/modules/procurement/purchase_orders/routes.py:269 |
cancel |
Cancel | status is draft, sent, acknowledged, or in_transit |
POST /purchase-orders/{id}/cancel |
purchase_order.update |
src/cluster_erp/modules/procurement/purchase_orders/metadata.py:159-192, src/cluster_erp/modules/procurement/purchase_orders/routes.py:281 |
receive (item) |
Receive Goods | item status is open or partial |
POST /purchase-orders/items/{id}/receive |
purchase_order.receive |
src/cluster_erp/modules/procurement/purchase_orders/item_metadata.py:57-125, src/cluster_erp/modules/procurement/purchase_orders/routes.py:254 |
Order routes¶
| Method and path | Permission | Source |
|---|---|---|
GET /purchase-orders/, GET /purchase-orders/{po_id} |
purchase_order.read |
src/cluster_erp/modules/procurement/purchase_orders/routes.py:58, src/cluster_erp/modules/procurement/purchase_orders/routes.py:147 |
POST /purchase-orders/, POST /purchase-orders/from-pr/{pr_id} |
purchase_order.create |
src/cluster_erp/modules/procurement/purchase_orders/routes.py:98-110 |
GET /purchase-orders/receivable-lines |
purchase_order.read |
src/cluster_erp/modules/procurement/purchase_orders/routes.py:129 |
PUT /purchase-orders/{po_id} |
purchase_order.update |
src/cluster_erp/modules/procurement/purchase_orders/routes.py:161 |
DELETE /purchase-orders/{po_id}, POST /purchase-orders/bulk-delete |
purchase_order.delete, purchase_order.bulk_delete |
src/cluster_erp/modules/procurement/purchase_orders/routes.py:174-186 |
POST /purchase-orders/{po_id}/items/{item_id}/receive |
purchase_order.receive |
src/cluster_erp/modules/procurement/purchase_orders/routes.py:237 |
POST /purchase-orders/{po_id}/items, PUT and DELETE /purchase-orders/{po_id}/items/{item_id} |
purchase_order.update |
src/cluster_erp/modules/procurement/purchase_orders/routes.py:297-331 |
Goods receipt¶
The diagram shows a receipt of one line.
sequenceDiagram participant U as receive route participant S as PurchaseOrderService participant B as EventBus participant I as inventory U->>S: receive_item S->>S: check status, breakdown, quantity S->>B: emit PoReceivedFull or PoReceivedPartial S->>B: query PostGoodsReceipt B->>I: _handle_post_goods_receipt I-->>S: movement id
In prose: the service refuses a text item and an item without model and version, because stock is posted per breakdown (src/cluster_erp/modules/procurement/purchase_orders/services.py:718-731). The received quantity may not exceed the ordered quantity (src/cluster_erp/modules/procurement/purchase_orders/services.py:738-749). The service updates the line and order status, emits PoReceivedFull or PoReceivedPartial, and then queries PostGoodsReceipt with the order as reference, the line id, and the order's project as stock owner (src/cluster_erp/modules/procurement/purchase_orders/services.py:750-822). An error from inventory fails the receipt with PO_GOODS_RECEIPT_FAILED; a missing inventory handler is logged and the receipt stays a purchasing record only (src/cluster_erp/modules/procurement/purchase_orders/services.py:823-837). A reversed goods movement gives the open quantity back through the query ReconcilePoReceiptReversal (src/cluster_erp/modules/procurement/module.py:192-202, src/cluster_erp/modules/procurement/module.py:885-916).
Constraints and validation¶
- On create, the vendor is read with
GetPartyById; a blacklisted vendor is refused (src/cluster_erp/modules/procurement/purchase_orders/services.py:215-243). - Each catalogue item's breakdown is checked with
ensure_product_breakdown(src/cluster_erp/modules/procurement/purchase_orders/services.py:279,src/cluster_erp/modules/procurement/purchase_orders/services.py:507). - Unit price is rounded to four places and the line total to the column scale, both with
ROUND_HALF_EVEN; the header total is the sum (src/cluster_erp/modules/procurement/purchase_orders/services.py:112-123,src/cluster_erp/modules/procurement/purchase_orders/services.py:307-312). - The header and items change only in
draft; only a draft can be deleted (src/cluster_erp/modules/procurement/purchase_orders/services.py:415-468). - The delivery address must be a shipping address of the delivery party; its text is stored as a snapshot (
src/cluster_erp/modules/procurement/delivery_address.py:24-28,src/cluster_erp/modules/procurement/delivery_address.py:78,src/cluster_erp/modules/procurement/purchase_orders/services.py:251). - Events:
PoCreated,PoSent,PoAcknowledged,PoReceivedFull,PoReceivedPartial,PoCancelled(src/cluster_erp/modules/procurement/purchase_orders/services.py:338,src/cluster_erp/modules/procurement/purchase_orders/services.py:648,src/cluster_erp/modules/procurement/purchase_orders/services.py:672,src/cluster_erp/modules/procurement/purchase_orders/services.py:779-797,src/cluster_erp/modules/procurement/purchase_orders/services.py:921).
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| purchase order | PurchaseOrder |
An order placed with a vendor. | Satın alma siparişi | src/cluster_erp/modules/procurement/purchase_orders/models.py:24 |
| text item | PurchaseOrderItem without product_id |
An order line with a description and no product; it is never goods-received. | Metin kalemi | src/cluster_erp/modules/procurement/purchase_orders/services.py:718-724 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.