The quotations module serves sales quotes with their feasibility, design, BOM, costing, management approval, send, and outcome steps, and quote line items. All 34 endpoints sit under /api/v1/quotations. For business meaning see the Quotations module page.
Group facts¶
| Item | Value | Source |
|---|---|---|
| Base path | /api/v1/quotations |
src/cluster_erp/core/module_base.py:37-38, src/cluster_erp/modules/quotations/module.py:46 |
| Routers | 2 router objects in 2 files; listed in the section Endpoint summary | src/cluster_erp/modules/quotations/module.py:35 |
| Endpoints | 34, counted from the route decorators and add_api_route calls of the routers that register_routes mounts |
src/cluster_erp/modules/quotations/line_items/routes.py, src/cluster_erp/modules/quotations/quotes/routes.py |
| Authentication | Bearer header or session cookie, resolved by get_current_user; see Authentication and headers |
src/cluster_erp/modules/auth/guards.py:85-167 |
| Permission guard | 34 of 34 routes declare require_permission |
src/cluster_erp/modules/auth/guards.py:205-240 |
| App entitlement gate | require_app("quotations") on every mounted router |
src/cluster_erp/modules/quotations/module.py:52 |
| Error format | JSON body {"error": {...}}; see Errors |
src/cluster_erp/server/error_handler.py:33-46 |
Endpoint summary¶
One row per sub-module folder. The router prefix is added to the base path.
| Sub-module | Routers | Endpoints | Router source |
|---|---|---|---|
line_items |
router |
7 | src/cluster_erp/modules/quotations/line_items/routes.py:21 |
quotes |
router |
27 | src/cluster_erp/modules/quotations/quotes/routes.py:37 |
Quote line items endpoints¶
Rejection reasons, all line items, the line items of a quote, and line item records.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/quotations/quotes/rejection-reasons |
list_rejection_reasons |
quote_line_item.read |
none | none | dict |
200 (default) | src/cluster_erp/modules/quotations/line_items/routes.py:24 |
GET |
/api/v1/quotations/quotes/line-items |
list_all_line_items |
quote_line_item.read |
none | quote_id, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/quotations/line_items/routes.py:49 |
GET |
/api/v1/quotations/quotes/{quote_id}/line-items |
list_line_items |
quote_line_item.read |
none | none | list[QuoteLineItemRead] |
200 (default) | src/cluster_erp/modules/quotations/line_items/routes.py:80 |
POST |
/api/v1/quotations/quotes/{quote_id}/line-items |
create_line_item |
quote_line_item.create |
QuoteLineItemCreate |
none | QuoteLineItemRead |
201 |
src/cluster_erp/modules/quotations/line_items/routes.py:95 |
PUT |
/api/v1/quotations/quotes/line-items/{item_id} |
update_line_item |
quote_line_item.update |
QuoteLineItemUpdate |
none | QuoteLineItemRead |
200 (default) | src/cluster_erp/modules/quotations/line_items/routes.py:112 |
DELETE |
/api/v1/quotations/quotes/line-items/{item_id} |
delete_line_item |
quote_line_item.delete |
none | none | none | 204 |
src/cluster_erp/modules/quotations/line_items/routes.py:130 |
POST |
/api/v1/quotations/quotes/line-items/bulk-delete |
bulk_delete_line_items |
quote_line_item.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/quotations/line_items/routes.py:142 |
Quotes endpoints¶
Quote records and the workflow actions, completeness, document flow, change history, versions, and similar projects.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/quotations/quotes/ |
list_quotes |
quote.read |
none | search, status, rfq_id, customer_id, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:52 |
POST |
/api/v1/quotations/quotes/ |
create_quote |
quote.create |
QuoteCreate |
none | QuoteRead |
201 |
src/cluster_erp/modules/quotations/quotes/routes.py:88 |
GET |
/api/v1/quotations/quotes/{quote_id} |
get_quote |
quote.read |
none | none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:100 |
PUT |
/api/v1/quotations/quotes/{quote_id} |
update_quote |
quote.update |
QuoteUpdate |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:114 |
DELETE |
/api/v1/quotations/quotes/{quote_id} |
delete_quote |
quote.delete |
none | none | none | 204 |
src/cluster_erp/modules/quotations/quotes/routes.py:129 |
POST |
/api/v1/quotations/quotes/bulk-delete |
bulk_delete_quotes |
quote.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:141 |
POST |
/api/v1/quotations/quotes/{quote_id}/start-feasibility |
start_feasibility |
quote.start_feasibility |
QuoteActionBody \| None |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:156 |
POST |
/api/v1/quotations/quotes/{quote_id}/mark-design-required |
mark_design_required |
quote.mark_design_required |
QuoteActionBody \| None |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:169 |
POST |
/api/v1/quotations/quotes/{quote_id}/complete-feasibility |
complete_feasibility |
quote.complete_feasibility |
QuoteCompleteFeasibility |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:184 |
POST |
/api/v1/quotations/quotes/{quote_id}/complete-design |
complete_design |
quote.complete_design |
QuoteActionBody \| None |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:197 |
POST |
/api/v1/quotations/quotes/{quote_id}/attach-bom |
attach_bom |
quote.attach_bom |
QuoteAttachBom |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:210 |
POST |
/api/v1/quotations/quotes/{quote_id}/attach-costing |
attach_costing |
quote.attach_costing |
QuoteAttachCosting |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:223 |
GET |
/api/v1/quotations/quotes/{quote_id}/mgmt-approvals |
list_mgmt_approvals |
mgmt_approval.read |
none | none | list[MgmtApprovalRead] |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:236 |
POST |
/api/v1/quotations/quotes/{quote_id}/schedule-mgmt-approval |
schedule_mgmt_approval |
quote.schedule_mgmt_approval |
QuoteScheduleMgmtApproval |
none | MgmtApprovalRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:251 |
POST |
/api/v1/quotations/quotes/{quote_id}/record-mgmt-decision |
record_mgmt_decision |
mgmt_approval.record_decision |
QuoteRecordMgmtDecision |
none | MgmtApprovalRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:267 |
POST |
/api/v1/quotations/quotes/{quote_id}/send |
send_quote |
quote.send |
QuoteSend |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:283 |
POST |
/api/v1/quotations/quotes/{quote_id}/mark-won |
mark_won |
quote.mark_won |
QuoteActionBody \| None |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:301 |
POST |
/api/v1/quotations/quotes/{quote_id}/mark-lost |
mark_lost |
quote.mark_lost |
QuoteMarkLost |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:314 |
POST |
/api/v1/quotations/quotes/{quote_id}/revise |
revise_quote |
quote.revise |
QuoteActionBody \| None |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:327 |
POST |
/api/v1/quotations/quotes/{quote_id}/feasibility-failed |
feasibility_failed |
quote.feasibility_failed |
QuoteFeasibilityFailed |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:340 |
POST |
/api/v1/quotations/quotes/{quote_id}/return-to-bom |
return_to_bom |
quote.return_to_bom |
QuoteActionBody \| None |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:353 |
POST |
/api/v1/quotations/quotes/{quote_id}/mark-expired |
mark_expired |
quote.mark_expired |
QuoteActionBody \| None |
none | QuoteRead |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:366 |
GET |
/api/v1/quotations/quotes/{quote_id}/completeness |
get_completeness |
quote.read |
none | none | dict |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:384 |
GET |
/api/v1/quotations/quotes/{quote_id}/document-flow |
get_document_flow |
quote.read |
none | none | dict |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:399 |
GET |
/api/v1/quotations/quotes/{quote_id}/history |
get_change_history |
quote.read |
none | none | dict |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:412 |
GET |
/api/v1/quotations/quotes/{quote_id}/versions |
list_versions |
quote.read |
none | none | list[QuoteRead] |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:424 |
GET |
/api/v1/quotations/quotes/{quote_id}/similar-projects |
similar_projects |
quote.read |
none | none | list[SimilarProjectRead] |
200 (default) | src/cluster_erp/modules/quotations/quotes/routes.py:439 |
Error codes¶
The table lists the error codes that code under src/cluster_erp/modules/quotations/ raises, found by a syntax-tree scan for AppError(...), the factories in src/cluster_erp/core/errors.py (not_found, conflict, protected, permission_denied, privilege_escalation, check_version), and HTTPException(...). A status of 400 is the AppError default (src/cluster_erp/core/errors.py:23). Guard errors (401, 403), the entitlement codes, and validation errors (422) apply to every route and are listed on Errors.
| Code | HTTP status | Raised in | First site |
|---|---|---|---|
CONCURRENCY_VERSION_CONFLICT |
409 |
quotes |
src/cluster_erp/modules/quotations/quotes/services.py:255 |
QUOTE_ACTIVE_EXISTS |
409 |
quotes |
src/cluster_erp/modules/quotations/quotes/services.py:1312 |
QUOTE_IMMUTABLE_AFTER_SENT |
400 |
line_items, quotes |
src/cluster_erp/modules/quotations/line_items/services.py:266 |
QUOTE_INCOMPLETE |
409 |
quotes |
src/cluster_erp/modules/quotations/quotes/services.py:583 |
QUOTE_INVALID_STATUS |
400, 409 |
quotes |
src/cluster_erp/modules/quotations/quotes/services.py:474 |
QUOTE_MGMT_APPROVAL_REQUIRED |
400 |
quotes |
src/cluster_erp/modules/quotations/quotes/services.py:505 |
QUOTE_NOT_APPROVED |
400 |
quotes |
src/cluster_erp/modules/quotations/quotes/services.py:564 |
RESOURCE_NOT_FOUND |
404 |
line_items, quotes |
src/cluster_erp/modules/quotations/line_items/routes.py:126 |
VALIDATION_ERROR |
422 |
line_items, quotes |
src/cluster_erp/modules/quotations/line_items/services.py:171 |
Events emitted¶
The table lists the contract classes that code under src/cluster_erp/modules/quotations/ passes directly to EventBus.emit, found by a syntax-tree scan. Events built in a variable first are not counted. The contracts are described in Contracts reference.
| Contract | Sites | First site |
|---|---|---|
QuoteBomAttached |
1 | src/cluster_erp/modules/quotations/quotes/services.py:389 |
QuoteCostingCompleted |
1 | src/cluster_erp/modules/quotations/quotes/services.py:457 |
QuoteCreated |
2 | src/cluster_erp/modules/quotations/quotes/services.py:229 |
QuoteExpired |
1 | src/cluster_erp/modules/quotations/quotes/services.py:678 |
QuoteFeasibilityCompleted |
2 | src/cluster_erp/modules/quotations/quotes/services.py:339 |
QuoteLost |
1 | src/cluster_erp/modules/quotations/quotes/services.py:656 |
QuoteMgmtApprovalRecorded |
1 | src/cluster_erp/modules/quotations/quotes/services.py:540 |
QuoteSent |
1 | src/cluster_erp/modules/quotations/quotes/services.py:607 |
QuoteWon |
2 | src/cluster_erp/modules/quotations/quotes/services.py:630 |
RfqDeclined |
1 | src/cluster_erp/modules/quotations/quotes/services.py:765 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.