Skip to main content

headless_lms_models/library/credit_registration/
student_number_change.rs

1//! What changing an account's linked student number does to its registrations: both the student's
2//! own unlink/claim and an admin's unlink/manual-link go through this, so the audit trail and the
3//! recompute stay in one place regardless of who acted.
4
5use crate::credit_registration_events::CreditRegistrationEventKind;
6use crate::credit_registrations::RegistrationScope;
7use crate::prelude::*;
8use crate::verified_student_numbers;
9
10use super::preconditions::{PRECONDITIONS_LIMIT, recompute_preconditions};
11
12/// Audits a change to `subject_user_id`'s linked student number on every registration it can affect,
13/// then applies it. Returns how many of the account's registrations the recompute moved.
14///
15/// `actor_user_id` is `None` when a worker made the change and no person decided it.
16pub async fn record_student_number_change(
17    conn: &mut PgConnection,
18    subject_user_id: Uuid,
19    actor_user_id: Option<Uuid>,
20    event_kind: CreditRegistrationEventKind,
21    message: &str,
22) -> ModelResult<i64> {
23    let affected: Vec<Uuid> = crate::credit_registrations::get_by_user_id(conn, subject_user_id)
24        .await?
25        .into_iter()
26        .filter(|row| row.superseded_by_id.is_none() && row.terminal_at.is_none())
27        .map(|row| row.id)
28        .collect();
29    crate::credit_registration_events::insert_many(
30        conn,
31        &affected,
32        event_kind,
33        actor_user_id,
34        Some(message),
35    )
36    .await?;
37    recompute_preconditions(
38        conn,
39        &RegistrationScope {
40            user_id: Some(subject_user_id),
41            ..RegistrationScope::default()
42        },
43        PRECONDITIONS_LIMIT,
44    )
45    .await
46}
47
48/// Retires a verified link and reopens every registration it had gated, as one step, and retires
49/// the person's unused linking mails so a new one can go out. Both the student's own unlink and the
50/// admin unlink call this rather than each doing the soft-delete and the recompute themselves;
51/// callers keep owning the transaction and any admin-action row around it.
52pub async fn unlink_verified_student_number(
53    conn: &mut PgConnection,
54    verified_student_number_id: Uuid,
55    subject_user_id: Uuid,
56    actor_user_id: Option<Uuid>,
57    event_kind: CreditRegistrationEventKind,
58    message: &str,
59) -> ModelResult<i64> {
60    crate::credit_registration_account_linking_emails::retire_unused_for_link(
61        conn,
62        verified_student_number_id,
63    )
64    .await?;
65    verified_student_numbers::soft_delete(conn, verified_student_number_id).await?;
66    record_student_number_change(conn, subject_user_id, actor_user_id, event_kind, message).await
67}