headless_lms_models/course_module_suotar_configurations/
configuration.rs1use crate::credit_registrations::CreditRegistrationErrorCode;
4use crate::prelude::*;
5use utoipa::ToSchema;
6
7#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
8pub struct CourseModuleSuotarConfiguration {
9 pub id: Uuid,
10 pub created_at: DateTime<Utc>,
11 pub updated_at: DateTime<Utc>,
12 pub deleted_at: Option<DateTime<Utc>>,
13 pub course_module_id: Uuid,
14 pub paused_at: Option<DateTime<Utc>>,
15 pub paused_by_user_id: Option<Uuid>,
16 pub pause_reason: Option<String>,
17 pub config_checked_at: Option<DateTime<Utc>>,
18 pub course_code_allowed: Option<bool>,
20 pub config_check_message: Option<String>,
21 pub checked_course_code: Option<String>,
23 pub course_code_rejection: Option<String>,
25 pub last_listing_attempted_at: Option<DateTime<Utc>>,
26 pub last_listed_at: Option<DateTime<Utc>>,
27 pub last_listing_error: Option<CreditRegistrationErrorCode>,
28 pub consecutive_listing_failures: i32,
29 pub last_listed_person_count: Option<i32>,
30 pub last_already_linked_count: Option<i32>,
31 pub last_mailed_count: Option<i32>,
32 pub last_suppressed_by_dedup_count: Option<i32>,
33 pub last_suppressed_by_rate_cap_count: Option<i32>,
34 pub last_no_address_count: Option<i32>,
35}
36
37pub async fn exists(conn: &mut PgConnection, course_module_id: Uuid) -> ModelResult<bool> {
40 let res = sqlx::query_scalar!(
41 r#"
42SELECT EXISTS (
43 SELECT 1
44 FROM course_module_suotar_configurations
45 WHERE course_module_id = $1
46 AND deleted_at IS NULL
47 ) AS "exists!"
48 "#,
49 course_module_id,
50 )
51 .fetch_one(conn)
52 .await?;
53 Ok(res)
54}
55
56pub async fn ensure_exists(conn: &mut PgConnection, course_module_id: Uuid) -> ModelResult<()> {
61 sqlx::query!(
62 r#"
63INSERT INTO course_module_suotar_configurations (course_module_id)
64VALUES ($1) ON CONFLICT (course_module_id) DO
65UPDATE
66SET deleted_at = NULL
67 "#,
68 course_module_id,
69 )
70 .execute(conn)
71 .await?;
72 Ok(())
73}
74
75#[derive(Debug, Clone, PartialEq)]
81pub struct SuotarModuleOverview {
82 pub course_module_id: Uuid,
83 pub course_id: Uuid,
84 pub course_name: String,
85 pub course_module_name: Option<String>,
86 pub uh_course_code: Option<String>,
87 pub ects_credits: Option<f32>,
88 pub enrolment_link: Option<String>,
90 pub paused_at: Option<DateTime<Utc>>,
91 pub pause_reason: Option<String>,
92 pub config_checked_at: Option<DateTime<Utc>>,
93 pub course_code_allowed: Option<bool>,
94 pub config_check_message: Option<String>,
95 pub last_listed_at: Option<DateTime<Utc>>,
96 pub eligible_completion_count: i64,
99}
100
101pub async fn get_module_overviews(
103 conn: &mut PgConnection,
104 limit: i64,
105) -> ModelResult<Vec<SuotarModuleOverview>> {
106 let res = sqlx::query_as!(
107 SuotarModuleOverview,
108 r#"
109SELECT cm.id AS "course_module_id!",
110 cm.course_id AS "course_id!",
111 c.name AS "course_name!",
112 cm.name AS course_module_name,
113 cm.uh_course_code,
114 cm.ects_credits,
115 NULLIF(TRIM(cm.completion_registration_link_override), '') AS "enrolment_link?",
116 conf.paused_at AS "paused_at?",
117 conf.pause_reason AS "pause_reason?",
118 conf.config_checked_at AS "config_checked_at?",
119 conf.course_code_allowed AS "course_code_allowed?",
120 conf.config_check_message AS "config_check_message?",
121 conf.last_listed_at AS "last_listed_at?",
122 (
123 SELECT COUNT(*)
124 FROM course_module_completions cmc
125 WHERE cmc.course_module_id = cm.id
126 AND cmc.deleted_at IS NULL
127 AND cmc.passed
128 AND cmc.eligible_for_ects
129 ) AS "eligible_completion_count!"
130FROM course_modules cm
131 JOIN courses c ON c.id = cm.course_id AND c.deleted_at IS NULL
132 LEFT JOIN course_module_suotar_configurations conf ON conf.course_module_id = cm.id
133 AND conf.deleted_at IS NULL
134WHERE cm.enable_credit_registration_via_suotar
135 AND cm.deleted_at IS NULL
136ORDER BY c.name,
137 cm.order_number
138LIMIT $1
139 "#,
140 limit,
141 )
142 .fetch_all(conn)
143 .await?;
144 Ok(res)
145}