headless_lms_models/library/credit_registration/
student_progress.rs1use utoipa::ToSchema;
5
6use crate::credit_registrations::CreditRegistrationState;
7use crate::prelude::*;
8
9#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Hash, ToSchema)]
11#[serde(rename_all = "snake_case")]
12pub enum StudentRegistrationProgressStep {
13 EnrolmentFound,
14 SentToSisu,
15 AcceptedBySisu,
16 PartiallyRegistered,
18}
19
20impl StudentRegistrationProgressStep {
21 const IN_ORDER: [Self; 4] = [
22 Self::EnrolmentFound,
23 Self::SentToSisu,
24 Self::AcceptedBySisu,
25 Self::PartiallyRegistered,
26 ];
27}
28
29#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
30pub struct CompletedProgressStep {
31 pub step: StudentRegistrationProgressStep,
32 pub reached_at: Option<DateTime<Utc>>,
34}
35
36#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
37pub struct StudentRegistrationProgress {
38 pub current_step: Option<StudentRegistrationProgressStep>,
40 pub completed_steps: Vec<CompletedProgressStep>,
42 pub submission_uncertain: bool,
45}
46
47pub const ENROLMENT_FOUND_STATE: CreditRegistrationState =
50 CreditRegistrationState::CheckingEnrolment;
51
52#[derive(Debug, Clone, Copy)]
54pub struct ProgressFacts {
55 pub state: CreditRegistrationState,
56 pub has_usable_enrolment: bool,
58 pub enrolment_found_at: Option<DateTime<Utc>>,
60 pub has_submitted_attainment: bool,
62 pub submitted_at: Option<DateTime<Utc>>,
63 pub partially_registered_at: Option<DateTime<Utc>>,
64 pub registered_at: Option<DateTime<Utc>>,
65}
66
67enum Position {
68 At(StudentRegistrationProgressStep),
69 Finished,
70 Off,
72}
73
74fn position(facts: &ProgressFacts) -> Position {
75 use CreditRegistrationState as S;
76 use StudentRegistrationProgressStep as Step;
77 match facts.state {
78 S::Pending
80 | S::ReadyToSubmit
81 | S::ResolvingEnrolment
82 | S::CheckingEnrolment
83 | S::Submitting
84 | S::SubmissionUncertain => Position::At(Step::SentToSisu),
85 S::FailedRetryable if facts.partially_registered_at.is_some() => {
87 Position::At(Step::PartiallyRegistered)
88 }
89 S::FailedRetryable if facts.has_submitted_attainment => Position::At(Step::AcceptedBySisu),
90 S::FailedRetryable => Position::At(Step::SentToSisu),
91 S::AwaitingVerification => Position::At(Step::AcceptedBySisu),
92 S::PartiallyRegistered => Position::At(Step::PartiallyRegistered),
93 S::Registered | S::Duplicate | S::NotImproved => Position::Finished,
94 S::NoUsableEnrolment
95 | S::Misregistered
96 | S::FailedPermanent
97 | S::Blocked
98 | S::Cancelled => Position::Off,
99 }
100}
101
102impl StudentRegistrationProgress {
103 pub fn of(facts: ProgressFacts) -> Option<Self> {
105 use StudentRegistrationProgressStep as Step;
106 if !facts.has_usable_enrolment {
107 return None;
108 }
109 let current_step = match position(&facts) {
110 Position::At(step) => Some(step),
111 Position::Finished => None,
112 Position::Off => return None,
113 };
114 let completed_steps = Step::IN_ORDER
115 .into_iter()
116 .take_while(|step| Some(*step) != current_step)
117 .map(|step| CompletedProgressStep {
118 step,
119 reached_at: match step {
120 Step::EnrolmentFound => facts.enrolment_found_at,
121 Step::SentToSisu => facts.submitted_at,
122 Step::AcceptedBySisu => facts.partially_registered_at.or(facts.registered_at),
123 Step::PartiallyRegistered => facts.partially_registered_at,
124 },
125 })
126 .collect();
127 Some(Self {
128 current_step,
129 completed_steps,
130 submission_uncertain: facts.state == CreditRegistrationState::SubmissionUncertain,
131 })
132 }
133}