This page describes the purchase_rfqs sub-module in src/cluster_erp/modules/procurement/purchase_rfqs/. A purchasing RFQ (PurchaseRfq) asks several vendors to quote for the same lines. The buyer records or receives their quotations, compares and scores them, and awards lines; the award creates purchase orders. The customer-side RFQ of the crm module is a different record; see CRM RFQs.
Scope and source¶
- Defined in:
models.py(seven classes),services.py(PurchaseRfqService),routes.py,portal_routes.py, andmetadata.pyof the sub-module. - Count: 7 tables, 34 routes (32 in
routes.pyand 2 inportal_routes.py, counted withgrepfor@router.), 6ActionMetaentries.
RFQ fields¶
| Group | Fields | Source |
|---|---|---|
| Identity | code, title, rfq_type (rfi, rfq, or rfp, default rfq), status (default draft) |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:36-48, src/cluster_erp/modules/procurement/purchase_rfqs/schemas.py:10 |
| Origin | purchase_request_id, project_id, buyer_id |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:49-53 |
| Terms | quotation_deadline, validity_date, currency, target_incoterm, target_payment_terms |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:55-61 |
| Delivery | delivery_party_id, delivery_address_id, delivery_address (text snapshot) |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:66-70 |
| Evaluation | award_policy (lowest_price, weighted_score, or manual), sealed_bid, round_no |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:72-83, src/cluster_erp/modules/procurement/purchase_rfqs/schemas.py:11 |
| Lifecycle record | sent_at, sent_by, evaluation_started_at, awarded_at, awarded_by, closed_at, cancelled_at, cancelled_by, cancel_reason |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:87-97 |
Child records¶
| Entity | Table | Key fields | Source |
|---|---|---|---|
PurchaseRfqLine |
procurement_purchase_rfq_lines |
line_no, product breakdown, description, qty, unit_id, required_by_date, target_unit_price, material_spec, source_pr_item_id |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:110-134 |
PurchaseRfqVendor |
procurement_purchase_rfq_vendors |
vendor_party_id, status (default invited), contact_email, invited_at, responded_at, decline_reason, rejection_reason, portal access fields |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:137-188 |
PurchaseRfqQuotation |
procurement_purchase_rfq_quotations |
quotation_no, round_no, valid_until, currency, lead_time_days, total_amount, compliance_status, submitted_via |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:193-236 |
PurchaseRfqQuotationLine |
procurement_purchase_rfq_quotation_lines |
purchase_rfq_line_id, unit_price, qty, lead_time_days, delivery_date, moq, line_total, is_awarded |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:241-272 |
PurchaseRfqCriterion |
procurement_purchase_rfq_criteria |
key, label, weight, sort_order |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:316-335 |
PurchaseRfqQuotationScore |
procurement_purchase_rfq_scores |
quotation_id, criterion_id, score |
src/cluster_erp/modules/procurement/purchase_rfqs/models.py:279-313 |
Vendor statuses are invited, viewed, responded, declined, no_bid, awarded, and rejected (src/cluster_erp/modules/procurement/purchase_rfqs/models.py:163-167). Quotation compliance is compliant, non_compliant, or pending (src/cluster_erp/modules/procurement/purchase_rfqs/models.py:221-224).
Statuses and actions¶
The service maps each action to a target status and the allowed source statuses (src/cluster_erp/modules/procurement/purchase_rfqs/services.py:61-69).
stateDiagram-v2 [*] --> draft draft --> sent: send sent --> under_evaluation: start_evaluation under_evaluation --> sent: reopen_bidding under_evaluation --> awarded: award awarded --> closed: close draft --> cancelled: cancel sent --> cancelled: cancel under_evaluation --> cancelled: cancel cancelled --> draft: reopen
In prose: send needs a complete draft; the completeness check asks for at least one line, at least two vendors, a quotation deadline, and, for weighted_score, criteria weights that total 100 (src/cluster_erp/modules/procurement/purchase_rfqs/services.py:287-313, src/cluster_erp/modules/procurement/purchase_rfqs/services.py:386-450). reopen_bidding increases round_no and sets vendors that had responded or viewed back to invited (src/cluster_erp/modules/procurement/purchase_rfqs/services.py:326-344). award has a route but no ActionMeta; the web app awards from the custom widget purchase_rfq_comparison (src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:221, gated_apps/procurement/app.js:1655).
| Action | Label | Condition | Endpoint | Permission | Source |
|---|---|---|---|---|---|
send |
Send to Vendors | status is draft |
POST /purchase-rfqs/{id}/send |
purchase_rfq.send |
src/cluster_erp/modules/procurement/purchase_rfqs/metadata.py:524-537, src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:181 |
start-evaluation |
Start Evaluation | status is sent |
POST /purchase-rfqs/{id}/start-evaluation |
purchase_rfq.evaluate |
src/cluster_erp/modules/procurement/purchase_rfqs/metadata.py:538-551, src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:193 |
reopen-bidding |
Reopen Bidding | status is under_evaluation |
POST /purchase-rfqs/{id}/reopen-bidding |
purchase_rfq.evaluate |
src/cluster_erp/modules/procurement/purchase_rfqs/metadata.py:552-565, src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:207 |
close |
Close | status is awarded |
POST /purchase-rfqs/{id}/close |
purchase_rfq.evaluate |
src/cluster_erp/modules/procurement/purchase_rfqs/metadata.py:566-577, src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:240 |
cancel |
Cancel | status is draft, sent, or under_evaluation |
POST /purchase-rfqs/{id}/cancel |
purchase_rfq.cancel |
src/cluster_erp/modules/procurement/purchase_rfqs/metadata.py:578-604, src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:252 |
reopen |
Reopen | status is cancelled |
POST /purchase-rfqs/{id}/reopen |
purchase_rfq.update |
src/cluster_erp/modules/procurement/purchase_rfqs/metadata.py:605-616, src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:266 |
RFQ routes¶
| Method and path | Permission | Source |
|---|---|---|
GET, POST /purchase-rfqs/; GET, PUT, DELETE /purchase-rfqs/{rfq_id}; POST /purchase-rfqs/bulk-delete |
purchase_rfq.read, .create, .update, .delete |
src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:66-151 |
GET /purchase-rfqs/{rfq_id}/completeness |
purchase_rfq.read |
src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:166 |
POST /purchase-rfqs/{rfq_id}/award |
purchase_rfq.award |
src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:221 |
Lines: GET, POST /purchase-rfqs/{rfq_id}/lines; PUT, DELETE /purchase-rfq-lines/{line_id} |
purchase_rfq.read, purchase_rfq.update |
src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:279-317 |
Vendors: GET, POST /purchase-rfqs/{rfq_id}/vendors; DELETE /purchase-rfq-vendors/{vendor_id}; GET /purchase-rfq-vendors/{vendor_id}/portal-token |
purchase_rfq.read, purchase_rfq.update |
src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:329-368 |
Quotations: GET, POST /purchase-rfqs/{rfq_id}/quotations; DELETE /purchase-rfq-quotations/{quotation_id} |
purchase_rfq.read, purchase_rfq.update |
src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:398-428 |
Criteria: GET, POST /purchase-rfqs/{rfq_id}/criteria; PUT, DELETE /purchase-rfq-criteria/{criterion_id} |
purchase_rfq.read, purchase_rfq.update |
src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:440-485 |
PUT /purchase-rfqs/{rfq_id}/quotations/{quotation_id}/score, GET .../scoring, GET .../comparison |
purchase_rfq.evaluate, purchase_rfq.read |
src/cluster_erp/modules/procurement/purchase_rfqs/routes.py:496-519 |
GET /portal/rfqs/{token}, POST /portal/rfqs/{token}/quotation |
vendor portal access token | src/cluster_erp/modules/procurement/purchase_rfqs/portal_routes.py:27-53 |
Scoring and award¶
- Criteria can change only while the RFQ is
draft(src/cluster_erp/modules/procurement/purchase_rfqs/services.py:1010-1021). - The criteria
priceanddeliveryare scored automatically; others take the manual score (src/cluster_erp/modules/procurement/purchase_rfqs/models.py:276,src/cluster_erp/modules/procurement/purchase_rfqs/services.py:1152-1157). - The automatic price score is the lowest compliant total divided by the quotation total, times 100. The delivery score uses lead times the same way. A non-compliant quotation scores 0 (
src/cluster_erp/modules/procurement/purchase_rfqs/services.py:1125-1149). - The weighted score sums weight divided by 100 times score. Compliant quotations always rank above non-compliant ones (
src/cluster_erp/modules/procurement/purchase_rfqs/services.py:1150-1214). - For a sealed bid, comparison returns only a summary while the RFQ is
sent(src/cluster_erp/modules/procurement/purchase_rfqs/services.py:255-258,src/cluster_erp/modules/procurement/purchase_rfqs/services.py:840-848). awardtakes pairs of RFQ line and quotation line, marks themis_awarded, sets winning vendors toawardedand the others that took part torejected, and creates one purchase order per winning vendor throughPurchaseOrderService(src/cluster_erp/modules/procurement/purchase_rfqs/services.py:1215-1292,src/cluster_erp/modules/procurement/purchase_rfqs/services.py:1293-1338).
Vendor portal¶
An invited vendor can view the RFQ and submit a quotation through /portal/rfqs/{token} without a user session. The token is minted per vendor when it is invited, is stored as a digest, and expires after PORTAL_TOKEN_TTL_DAYS (30) days (src/cluster_erp/modules/procurement/purchase_rfqs/portal_routes.py:1-10, src/cluster_erp/modules/procurement/purchase_rfqs/services.py:605-618). A submission is accepted only while the RFQ is sent and before the deadline (src/cluster_erp/modules/procurement/purchase_rfqs/services.py:1505-1530).
Constraints and validation¶
- Lines and vendors can change only while the RFQ is
draftorsent(src/cluster_erp/modules/procurement/purchase_rfqs/services.py:452-458). - A buyer-entered quotation is accepted only while the RFQ is
sentorunder_evaluation; it records the current round and sets the vendor toresponded(src/cluster_erp/modules/procurement/purchase_rfqs/services.py:705-748). - Money and scores are
Numeric: unit priceNumeric(18, 4), weight and scoreNumeric(6, 2)(src/cluster_erp/modules/procurement/purchase_rfqs/models.py:260,src/cluster_erp/modules/procurement/purchase_rfqs/models.py:313,src/cluster_erp/modules/procurement/purchase_rfqs/models.py:332).
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| purchasing RFQ | PurchaseRfq |
A request to several vendors to quote for the same lines. | Satın alma teklif talebi | src/cluster_erp/modules/procurement/purchase_rfqs/models.py:26 |
| sealed bid | sealed_bid |
An RFQ whose quotations stay hidden from the buyer while it is out for bid. | Kapalı teklif | src/cluster_erp/modules/procurement/purchase_rfqs/models.py:78-80 |
| bidding round | round_no |
The negotiation round of an RFQ; reopening bidding increases it. | Teklif turu | src/cluster_erp/modules/procurement/purchase_rfqs/models.py:83 |
| award policy | award_policy |
How the RFQ winner is chosen: lowest price, weighted score, or manual. | İhale politikası | src/cluster_erp/modules/procurement/purchase_rfqs/models.py:72-77 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.