headless_lms_models/credit_registrations/
competing_credits.rs1use super::state::CreditRegistrationState;
4use crate::library::credit_registration::grade_mapping::{GradeSource, MappedGrade, map_grade};
5use crate::prelude::*;
6
7#[derive(Debug, Clone)]
10pub struct LiveSuccessForModule {
11 pub id: Uuid,
12 pub credit: RecordedCredit,
13}
14
15#[derive(Debug, Clone)]
17pub struct RecordedCredit {
18 pub grade_scale_id: Option<String>,
20 pub grade_id: Option<String>,
21 pub completion_passed: bool,
22 pub completion_grade: Option<i32>,
23}
24
25impl RecordedCredit {
26 pub fn held_grade(&self) -> Option<MappedGrade> {
30 MappedGrade::from_columns(self.grade_scale_id.as_deref(), self.grade_id.as_deref()).or_else(
31 || {
32 map_grade(GradeSource {
33 passed: self.completion_passed,
34 grade: self.completion_grade,
35 enrolment_grade_scale_id: None,
36 })
37 .ok()
38 },
39 )
40 }
41}
42
43pub async fn get_recorded_credits_for_same_module(
49 conn: &mut PgConnection,
50 id: Uuid,
51) -> ModelResult<Vec<RecordedCredit>> {
52 let res = sqlx::query_as!(
53 RecordedCredit,
54 r#"
55SELECT other.grade_scale_id::text AS "grade_scale_id?",
56 other.grade_id::text AS "grade_id?",
57 cmc.passed AS "completion_passed!",
58 cmc.grade AS "completion_grade?"
59FROM credit_registrations cr
60 JOIN credit_registrations other ON other.user_id = cr.user_id
61 AND other.course_module_id = cr.course_module_id
62 AND other.id <> cr.id
63 JOIN course_module_completions cmc ON cmc.id = other.course_module_completion_id
64WHERE cr.id = $1
65 AND other.deleted_at IS NULL
66 AND other.superseded_by_id IS NULL
67 AND other.state = ANY($2::credit_registration_state [])
68UNION ALL
69SELECT NULL::text,
70 NULL::text,
71 cmc.passed,
72 cmc.grade
73FROM credit_registrations cr
74 JOIN course_module_completion_registered_to_study_registries r ON r.user_id = cr.user_id
75 AND r.course_module_id = cr.course_module_id
76 JOIN course_module_completions cmc ON cmc.id = r.course_module_completion_id
77WHERE cr.id = $1
78 -- A null registrar is our own mirror of a row the first half already reads.
79 AND r.study_registry_registrar_id IS NOT NULL
80 AND r.deleted_at IS NULL
81 "#,
82 id,
83 &CreditRegistrationState::SUCCESS_STATES as &[CreditRegistrationState],
84 )
85 .fetch_all(conn)
86 .await?;
87 Ok(res)
88}
89
90pub async fn prepare_unsent_duplicate(
98 conn: &mut PgConnection,
99 id: Uuid,
100 weighed_grade: Option<&MappedGrade>,
101) -> ModelResult<()> {
102 sqlx::query!(
103 r#"
104UPDATE credit_registrations
105SET sisu_person_id = NULL,
106 grade_scale_id = $2,
107 grade_id = $3,
108 credits = NULL
109WHERE id = $1
110 AND deleted_at IS NULL
111 "#,
112 id,
113 weighed_grade.map(|grade| grade.grade_scale_id.as_str()),
114 weighed_grade.map(|grade| grade.grade_id.as_str()),
115 )
116 .execute(conn)
117 .await?;
118 Ok(())
119}
120
121pub async fn lock_live_successes_for_same_module(
128 conn: &mut PgConnection,
129 id: Uuid,
130) -> ModelResult<Vec<LiveSuccessForModule>> {
131 let rows = sqlx::query!(
132 r#"
133SELECT other.id,
134 other.grade_scale_id,
135 other.grade_id,
136 cmc.passed AS completion_passed,
137 cmc.grade AS completion_grade
138FROM credit_registrations cr
139 JOIN credit_registrations other ON other.user_id = cr.user_id
140 AND other.course_module_id = cr.course_module_id
141 AND other.id <> cr.id
142 JOIN course_module_completions cmc ON cmc.id = other.course_module_completion_id
143WHERE cr.id = $1
144 AND other.deleted_at IS NULL
145 AND other.superseded_by_id IS NULL
146 AND other.state = ANY($2::credit_registration_state [])
147ORDER BY other.id FOR
148UPDATE OF other
149 "#,
150 id,
151 &CreditRegistrationState::SUCCESS_STATES as &[CreditRegistrationState],
152 )
153 .fetch_all(conn)
154 .await?;
155 Ok(rows
156 .into_iter()
157 .map(|row| LiveSuccessForModule {
158 id: row.id,
159 credit: RecordedCredit {
160 grade_scale_id: row.grade_scale_id,
161 grade_id: row.grade_id,
162 completion_passed: row.completion_passed,
163 completion_grade: row.completion_grade,
164 },
165 })
166 .collect())
167}