Skip to main content

teamcraft/services/
requests.rs

1//! Engineering change requests (ECR): the step *before* a change order.
2//!
3//! ```text
4//!   anybody signed in            CAD editors of the affected groups / admins
5//!   ─────────────────            ───────────────────────────────────────────
6//!   raise ──▶ new ──▶ in_triage ──▶ accepted ──▶ create / link change order (ECO)
7//!              │          │    ──▶ rejected               │
8//!              │          │    ──▶ deferred ──▶ in_triage │ change order released
9//!              └──────────┴──▶ withdrawn (requester)      ▼
10//!                                                    implemented
11//! ```
12//!
13//! Anyone who finds a problem – shop floor, simulation, test, purchasing,
14//! viewers included – raises a request and discusses it. CAD editors of the
15//! project groups of the affected items (or admins) triage and decide. An
16//! accepted request is implemented by a change order: either one created
17//! from the request (with the in-work revisions of the affected items) or an
18//! existing open one that is linked. When a linked change order is released,
19//! the request becomes *implemented* in the same transaction
20//! (`on_change_released`, called from `changes::review`).
21
22use chrono::NaiveDate;
23use serde::Serialize;
24use serde_json::json;
25use sqlx::{PgConnection, PgPool};
26use uuid::Uuid;
27
28use crate::{
29    auth::Actor,
30    domain::{
31        self, NotificationKind, Priority, RequestDecision, RequestReason, RequestStatus, Role,
32        WebhookEvent,
33    },
34    error::{AppError, AppResult},
35    models::{Change, ChangeRequest, RequestAttachment, RequestComment, RequestItem},
36    services::{
37        access, audit, changes, next_counter,
38        notifications::{self, Notice},
39        users, webhooks, Page,
40    },
41    vault::Vault,
42};
43
44mod attachment_files;
45mod implementation;
46mod notices;
47mod permissions;
48mod queries;
49mod raising;
50#[cfg(test)]
51mod tests;
52mod triage;
53
54pub use attachment_files::*;
55pub use implementation::*;
56use notices::*;
57pub use permissions::*;
58pub use queries::*;
59pub use raising::*;
60pub use triage::*;
61
62/// Largest attachment (screenshot, mark-up, PDF report).
63pub const MAX_ATTACHMENT_BYTES: usize = 10 * 1024 * 1024;
64/// At most this many attachments per request.
65pub const MAX_ATTACHMENTS: i64 = 20;
66/// At most this many affected items per request.
67pub const MAX_ITEMS: i64 = 100;
68
69const REQUEST_SELECT: &str = "
70    SELECT cr.id, cr.number, cr.title, cr.description, cr.reason, cr.priority, cr.status,
71           cr.requested_by, rq.display_name AS requested_by_name,
72           cr.assignee, asg.display_name AS assignee_name, asg.username AS assignee_username,
73           cr.decision_comment, cr.decided_by, dby.display_name AS decided_by_name, cr.decided_at,
74           cr.due_date, cr.created_at, cr.updated_at, cr.closed_at,
75           (SELECT COUNT(*) FROM change_request_items x WHERE x.request_id = cr.id) AS item_count
76    FROM change_requests cr
77    JOIN users rq ON rq.id = cr.requested_by
78    LEFT JOIN users asg ON asg.id = cr.assignee
79    LEFT JOIN users dby ON dby.id = cr.decided_by";
80
81const OPEN_STATES: &str = "('new', 'in_triage', 'accepted', 'deferred')";
82
83pub fn status_of(request: &ChangeRequest) -> RequestStatus {
84    RequestStatus::parse(&request.status).unwrap_or(RequestStatus::Withdrawn)
85}