Skip to main content

headless_lms_server/domain/credit_registration/
enrolment_recheck.rs

1//! Asking for an enrolment check of a row waiting for one, from the student's or a teacher's view.
2
3use headless_lms_models::credit_registration_enrolment_check_signals;
4use headless_lms_models::credit_registration_events::{
5    CreditRegistrationEventKind, NewCreditRegistrationEvent,
6};
7use headless_lms_models::credit_registrations::CreditRegistrationState;
8use headless_lms_models::library::credit_registration::enrolment_check_schedule::EnrolmentCheckSource;
9use headless_lms_models::library::credit_registration::enrolment_checks::{
10    self, CheckRequestOutcome,
11};
12
13use crate::prelude::*;
14
15/// Whether a row's student or a teacher may ask for an enrolment check right now, which is when
16/// their buttons show.
17pub fn can_request_enrolment_recheck(
18    state: CreditRegistrationState,
19    enrolment_check_requested_at: Option<DateTime<Utc>>,
20    enrolment_checked_at: Option<DateTime<Utc>>,
21) -> bool {
22    state == CreditRegistrationState::NoUsableEnrolment
23        && !enrolment_checks::is_check_request_limited(
24            enrolment_check_requested_at,
25            enrolment_checked_at,
26            Utc::now(),
27        )
28}
29
30/// Whether the row's student may ask for a check right now: [`can_request_enrolment_recheck`], and
31/// the row is not [too new](enrolment_checks::is_too_new_for_student_check_request).
32pub fn can_student_request_enrolment_recheck(
33    state: CreditRegistrationState,
34    row_created_at: DateTime<Utc>,
35    enrolment_check_requested_at: Option<DateTime<Utc>>,
36    enrolment_checked_at: Option<DateTime<Utc>>,
37) -> bool {
38    !enrolment_checks::is_too_new_for_student_check_request(row_created_at, Utc::now())
39        && can_request_enrolment_recheck(state, enrolment_check_requested_at, enrolment_checked_at)
40}
41
42/// A row waiting for an enrolment to check, and the completion it registers.
43#[derive(Debug, Clone, Copy)]
44pub struct RecheckTarget {
45    pub registration_id: Uuid,
46    pub course_module_completion_id: Uuid,
47}
48
49/// Asks for an enrolment check of a row waiting for one, and records who asked.
50///
51/// Shared by the student's recheck button, pressing Done and the teacher's recheck, which differ in
52/// `source` and the event. Every kind shares one limit on asking; see
53/// [`enrolment_checks::request_check`]. The completion's check signal is recorded unless the row
54/// turned out not to be waiting, even for a request refused as too soon. A student asks through
55/// [`start_student_enrolment_recheck`] instead.
56pub async fn start_enrolment_recheck(
57    conn: &mut PgConnection,
58    actor_user_id: Uuid,
59    target: RecheckTarget,
60    source: EnrolmentCheckSource,
61    event_kind: CreditRegistrationEventKind,
62    message: &str,
63) -> Result<CheckRequestOutcome, ControllerError> {
64    start_recheck(
65        conn,
66        actor_user_id,
67        target,
68        source,
69        event_kind,
70        message,
71        false,
72    )
73    .await
74}
75
76/// [`start_enrolment_recheck`] for the row's own student, which is refused as too soon while the
77/// row is [too new](enrolment_checks::is_too_new_for_student_check_request).
78pub async fn start_student_enrolment_recheck(
79    conn: &mut PgConnection,
80    student_user_id: Uuid,
81    target: RecheckTarget,
82    registration_created_at: DateTime<Utc>,
83    message: &str,
84) -> Result<CheckRequestOutcome, ControllerError> {
85    let is_too_new =
86        enrolment_checks::is_too_new_for_student_check_request(registration_created_at, Utc::now());
87    start_recheck(
88        conn,
89        student_user_id,
90        target,
91        EnrolmentCheckSource::StudentRequest,
92        CreditRegistrationEventKind::StudentAction,
93        message,
94        is_too_new,
95    )
96    .await
97}
98
99async fn start_recheck(
100    conn: &mut PgConnection,
101    actor_user_id: Uuid,
102    target: RecheckTarget,
103    source: EnrolmentCheckSource,
104    event_kind: CreditRegistrationEventKind,
105    message: &str,
106    is_refused_as_too_soon: bool,
107) -> Result<CheckRequestOutcome, ControllerError> {
108    let registration_id = target.registration_id;
109    let mut tx = conn.begin().await?;
110    let outcome = if is_refused_as_too_soon {
111        CheckRequestOutcome::TooSoon
112    } else {
113        enrolment_checks::request_check(&mut tx, registration_id, source, Utc::now()).await?
114    };
115    if outcome == CheckRequestOutcome::NotWaiting {
116        tx.commit().await?;
117        return Ok(outcome);
118    }
119    credit_registration_enrolment_check_signals::record_check_request(
120        &mut tx,
121        target.course_module_completion_id,
122        source,
123    )
124    .await?;
125    if outcome == CheckRequestOutcome::TooSoon {
126        tx.commit().await?;
127        return Ok(outcome);
128    }
129    models::credit_registration_events::insert(
130        &mut tx,
131        &NewCreditRegistrationEvent {
132            actor_user_id: Some(actor_user_id),
133            message: Some(message.to_string()),
134            ..NewCreditRegistrationEvent::new(registration_id, event_kind)
135        },
136    )
137    .await?;
138    tx.commit().await?;
139    Ok(outcome)
140}