Skip to main content

headless_lms_models/
course_module_suotar_realisations.rs

1use utoipa::ToSchema;
2
3use crate::prelude::*;
4
5#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
6pub struct CourseModuleSuotarRealisation {
7    pub id: Uuid,
8    pub created_at: DateTime<Utc>,
9    pub updated_at: DateTime<Utc>,
10    pub deleted_at: Option<DateTime<Utc>>,
11    pub course_module_id: Uuid,
12    pub course_unit_realisation_id: String,
13    pub label: Option<String>,
14    pub active: bool,
15    pub last_listed_at: Option<DateTime<Utc>>,
16    pub last_listed_person_count: Option<i32>,
17    pub last_already_linked_count: Option<i32>,
18    pub last_mailed_count: Option<i32>,
19    pub last_suppressed_by_dedup_count: Option<i32>,
20    pub last_suppressed_by_rate_cap_count: Option<i32>,
21    pub last_no_address_count: Option<i32>,
22}
23
24/// Outcome counters for one enrolment-discovery run over one realisation. Written whole, so the
25/// dashboard never mixes two runs.
26#[derive(Debug, Clone, PartialEq, Default)]
27pub struct RealisationListingOutcome {
28    pub listed_person_count: i32,
29    pub already_linked_count: i32,
30    pub mailed_count: i32,
31    pub suppressed_by_dedup_count: i32,
32    pub suppressed_by_rate_cap_count: i32,
33    pub no_address_count: i32,
34}
35
36pub async fn upsert(
37    conn: &mut PgConnection,
38    course_module_id: Uuid,
39    course_unit_realisation_id: &str,
40    label: Option<&str>,
41    active: bool,
42) -> ModelResult<Uuid> {
43    let res = sqlx::query!(
44        r#"
45INSERT INTO course_module_suotar_realisations (
46    course_module_id,
47    course_unit_realisation_id,
48    label,
49    active
50  )
51VALUES ($1, $2, $3, $4) ON CONFLICT (
52    course_module_id,
53    course_unit_realisation_id,
54    deleted_at
55  ) DO
56UPDATE
57SET label = $3,
58  active = $4
59RETURNING id
60        "#,
61        course_module_id,
62        course_unit_realisation_id,
63        label,
64        active,
65    )
66    .fetch_one(conn)
67    .await?;
68    Ok(res.id)
69}
70
71pub async fn get_by_course_module_id(
72    conn: &mut PgConnection,
73    course_module_id: Uuid,
74) -> ModelResult<Vec<CourseModuleSuotarRealisation>> {
75    let res = sqlx::query_as!(
76        CourseModuleSuotarRealisation,
77        r#"
78SELECT *
79FROM course_module_suotar_realisations
80WHERE course_module_id = $1
81  AND deleted_at IS NULL
82ORDER BY created_at
83        "#,
84        course_module_id
85    )
86    .fetch_all(conn)
87    .await?;
88    Ok(res)
89}
90
91/// Realisations enrolment discovery should poll: active, on an enabled module that is not paused.
92pub async fn get_all_active_for_enabled_modules(
93    conn: &mut PgConnection,
94) -> ModelResult<Vec<CourseModuleSuotarRealisation>> {
95    let res = sqlx::query_as!(
96        CourseModuleSuotarRealisation,
97        r#"
98SELECT cmsr.*
99FROM course_module_suotar_realisations cmsr
100  JOIN course_modules cm ON cm.id = cmsr.course_module_id
101  LEFT JOIN course_module_suotar_configurations c ON c.course_module_id = cm.id
102  AND c.deleted_at IS NULL
103WHERE cmsr.active
104  AND cmsr.deleted_at IS NULL
105  AND cm.enable_credit_registration_via_suotar
106  AND c.paused_at IS NULL
107  AND cm.deleted_at IS NULL
108ORDER BY cmsr.last_listed_at ASC NULLS FIRST
109        "#,
110    )
111    .fetch_all(conn)
112    .await?;
113    Ok(res)
114}
115
116pub async fn record_listing_outcome(
117    conn: &mut PgConnection,
118    id: Uuid,
119    outcome: &RealisationListingOutcome,
120) -> ModelResult<()> {
121    sqlx::query!(
122        r#"
123UPDATE course_module_suotar_realisations
124SET last_listed_at = now(),
125  last_listed_person_count = $2,
126  last_already_linked_count = $3,
127  last_mailed_count = $4,
128  last_suppressed_by_dedup_count = $5,
129  last_suppressed_by_rate_cap_count = $6,
130  last_no_address_count = $7
131WHERE id = $1
132  AND deleted_at IS NULL
133        "#,
134        id,
135        outcome.listed_person_count,
136        outcome.already_linked_count,
137        outcome.mailed_count,
138        outcome.suppressed_by_dedup_count,
139        outcome.suppressed_by_rate_cap_count,
140        outcome.no_address_count,
141    )
142    .execute(conn)
143    .await?;
144    Ok(())
145}
146
147pub async fn soft_delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
148    sqlx::query!(
149        r#"
150UPDATE course_module_suotar_realisations
151SET deleted_at = now()
152WHERE id = $1
153  AND deleted_at IS NULL
154        "#,
155        id
156    )
157    .execute(conn)
158    .await?;
159    Ok(())
160}