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}
30
31pub async fn exists(conn: &mut PgConnection, course_module_id: Uuid) -> ModelResult<bool> {
34 let res = sqlx::query_scalar!(
35 r#"
36SELECT EXISTS (
37 SELECT 1
38 FROM course_module_suotar_configurations
39 WHERE course_module_id = $1
40 AND deleted_at IS NULL
41 ) AS "exists!"
42 "#,
43 course_module_id,
44 )
45 .fetch_one(conn)
46 .await?;
47 Ok(res)
48}
49
50pub async fn ensure_exists(conn: &mut PgConnection, course_module_id: Uuid) -> ModelResult<()> {
55 sqlx::query!(
56 r#"
57INSERT INTO course_module_suotar_configurations (course_module_id)
58VALUES ($1) ON CONFLICT (course_module_id) DO
59UPDATE
60SET deleted_at = NULL
61 "#,
62 course_module_id,
63 )
64 .execute(conn)
65 .await?;
66 Ok(())
67}
68
69#[derive(Debug, Clone, PartialEq)]
75pub struct SuotarModuleOverview {
76 pub course_module_id: Uuid,
77 pub course_id: Uuid,
78 pub course_name: String,
79 pub course_module_name: Option<String>,
80 pub uh_course_code: Option<String>,
81 pub ects_credits: Option<f32>,
82 pub enrolment_link: Option<String>,
84 pub paused_at: Option<DateTime<Utc>>,
85 pub pause_reason: Option<String>,
86 pub config_checked_at: Option<DateTime<Utc>>,
87 pub course_code_allowed: Option<bool>,
88 pub config_check_message: Option<String>,
89 pub last_listed_at: Option<DateTime<Utc>>,
90 pub eligible_completion_count: i64,
93}
94
95pub async fn get_module_overviews(
97 conn: &mut PgConnection,
98 limit: i64,
99) -> ModelResult<Vec<SuotarModuleOverview>> {
100 let res = sqlx::query_as!(
101 SuotarModuleOverview,
102 r#"
103SELECT cm.id AS "course_module_id!",
104 cm.course_id AS "course_id!",
105 c.name AS "course_name!",
106 cm.name AS course_module_name,
107 cm.uh_course_code,
108 cm.ects_credits,
109 NULLIF(TRIM(cm.completion_registration_link_override), '') AS "enrolment_link?",
110 conf.paused_at AS "paused_at?",
111 conf.pause_reason AS "pause_reason?",
112 conf.config_checked_at AS "config_checked_at?",
113 conf.course_code_allowed AS "course_code_allowed?",
114 conf.config_check_message AS "config_check_message?",
115 conf.last_listed_at AS "last_listed_at?",
116 (
117 SELECT COUNT(*)
118 FROM course_module_completions cmc
119 WHERE cmc.course_module_id = cm.id
120 AND cmc.deleted_at IS NULL
121 AND cmc.passed
122 AND cmc.eligible_for_ects
123 ) AS "eligible_completion_count!"
124FROM course_modules cm
125 JOIN courses c ON c.id = cm.course_id AND c.deleted_at IS NULL
126 LEFT JOIN course_module_suotar_configurations conf ON conf.course_module_id = cm.id
127 AND conf.deleted_at IS NULL
128WHERE cm.deleted_at IS NULL
129 AND TRIM(cm.uh_course_code) <> ''
130 AND cm.completions_can_be_registered_to_sisu
131ORDER BY c.name,
132 cm.order_number
133LIMIT $1
134 "#,
135 limit,
136 )
137 .fetch_all(conn)
138 .await?;
139 Ok(res)
140}