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. Both the
49/// student's own unlink and the admin unlink call this rather than each doing the soft-delete and
50/// the recompute themselves; callers keep owning the transaction and any admin-action row around it.
51pub async fn unlink_verified_student_number(
52    conn: &mut PgConnection,
53    verified_student_number_id: Uuid,
54    subject_user_id: Uuid,
55    actor_user_id: Option<Uuid>,
56    event_kind: CreditRegistrationEventKind,
57    message: &str,
58) -> ModelResult<i64> {
59    verified_student_numbers::soft_delete(conn, verified_student_number_id).await?;
60    record_student_number_change(conn, subject_user_id, actor_user_id, event_kind, message).await
61}