Skip to main content

headless_lms_models/library/credit_registration/
submission_context.rs

1//! Everything a submission needs that does not live on the ledger row yet. One query rather than a
2//! lookup per row: the phases work in batches of up to a hundred.
3
4use std::collections::HashMap;
5
6use crate::prelude::*;
7
8use super::payload::CompletionFacts;
9
10/// The module configuration, the linked student number and the completion, for one ledger row.
11#[derive(Debug, Clone, PartialEq)]
12pub struct SubmissionContext {
13    pub registration_id: Uuid,
14    /// `None` once the account's number has been unlinked.
15    pub student_number: Option<String>,
16    pub sisu_person_id: Option<String>,
17    pub uh_course_code: Option<String>,
18    pub ects_credits: Option<f32>,
19    pub configured_grade_scale_id: Option<String>,
20    /// The active realisations a teacher configured, which the enrolment choice prefers.
21    pub configured_realisation_ids: Vec<String>,
22    pub completion: CompletionFacts,
23}
24
25/// The per-row facts each phase needs to act on a batch of ledger rows without a lookup per row.
26pub async fn get_submission_contexts(
27    conn: &mut PgConnection,
28    registration_ids: &[Uuid],
29) -> ModelResult<HashMap<Uuid, SubmissionContext>> {
30    let rows = sqlx::query!(
31        r#"
32-- Nullability is stated for the outer-joined columns rather than inferred: sqlx-cli 0.9.0 derives
33-- them the wrong way round, and an offline build of this query then fails to compile.
34SELECT cr.id,
35  vsn.student_number AS "student_number?",
36  vsn.sisu_person_id AS "sisu_person_id?",
37  cm.uh_course_code,
38  cm.ects_credits,
39  conf.grade_scale_id AS "configured_grade_scale_id?",
40  COALESCE(
41    ARRAY_AGG(realisation.course_unit_realisation_id) FILTER (
42      WHERE realisation.id IS NOT NULL
43    ),
44    '{}'
45  ) AS "configured_realisation_ids!: Vec<String>",
46  cmc.passed,
47  cmc.grade,
48  cmc.completion_date,
49  cmc.completion_language
50FROM credit_registrations cr
51  JOIN course_module_completions cmc ON cmc.id = cr.course_module_completion_id
52  AND cmc.deleted_at IS NULL
53  JOIN course_modules cm ON cm.id = cr.course_module_id
54  AND cm.deleted_at IS NULL
55  LEFT JOIN verified_student_numbers vsn ON vsn.user_id = cr.user_id
56  AND vsn.deleted_at IS NULL
57  LEFT JOIN course_module_suotar_configurations conf ON conf.course_module_id = cr.course_module_id
58  AND conf.deleted_at IS NULL
59  LEFT JOIN course_module_suotar_realisations realisation ON realisation.course_module_id = cr.course_module_id
60  AND realisation.active
61  AND realisation.deleted_at IS NULL
62WHERE cr.id = ANY($1::uuid [])
63  AND cr.deleted_at IS NULL
64GROUP BY cr.id,
65  vsn.student_number,
66  vsn.sisu_person_id,
67  cm.uh_course_code,
68  cm.ects_credits,
69  conf.grade_scale_id,
70  cmc.passed,
71  cmc.grade,
72  cmc.completion_date,
73  cmc.completion_language
74        "#,
75        registration_ids,
76    )
77    .fetch_all(conn)
78    .await?;
79    Ok(rows
80        .into_iter()
81        .map(|row| {
82            (
83                row.id,
84                SubmissionContext {
85                    registration_id: row.id,
86                    student_number: row.student_number,
87                    sisu_person_id: row.sisu_person_id,
88                    uh_course_code: row.uh_course_code,
89                    ects_credits: row.ects_credits,
90                    configured_grade_scale_id: row.configured_grade_scale_id,
91                    configured_realisation_ids: row.configured_realisation_ids,
92                    completion: CompletionFacts {
93                        passed: row.passed,
94                        grade: row.grade,
95                        completion_date: row.completion_date,
96                        completion_language: row.completion_language,
97                    },
98                },
99            )
100        })
101        .collect())
102}