headless_lms_models/library/credit_registration/
study_registry.rs1use chrono::NaiveDate;
5use secrecy::SecretString;
6
7use super::grade_mapping::MappedGrade;
8use crate::prelude::*;
9
10pub const ATTAINMENT_TYPE_COURSE_UNIT: &str = "CourseUnitAttainment";
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
16pub struct LocalizedName {
17 pub fi: Option<String>,
18 pub sv: Option<String>,
19 pub en: Option<String>,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct DatePeriod {
25 pub start_date: Option<NaiveDate>,
26 pub end_date: Option<NaiveDate>,
27}
28
29impl DatePeriod {
30 pub fn contains(&self, date: NaiveDate) -> bool {
33 self.start_date.is_none_or(|start| start <= date)
34 && self.end_date.is_none_or(|end| date < end)
35 }
36}
37
38#[derive(Debug, Clone, PartialEq)]
40pub struct CreditRange {
41 pub min: Option<f64>,
42 pub max: Option<f64>,
43}
44
45#[derive(Debug, Clone, PartialEq)]
48pub struct RegistryEnrolment {
49 pub id: String,
50 pub state: Option<String>,
51 pub kind: Option<String>,
52 pub course_unit_realisation_id: Option<String>,
53 pub course_unit_realisation_name: Option<LocalizedName>,
54 pub activity_period: Option<DatePeriod>,
55 pub grade_scale_id: Option<String>,
57 pub credits: Option<CreditRange>,
59 pub study_right_validity_period: Option<DatePeriod>,
61 pub enrolment_date_time: Option<DateTime<Utc>>,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct RegistryAttainment {
68 pub id: String,
69 pub attainment_type: String,
70 pub state: Option<String>,
71 pub attainment_date: Option<NaiveDate>,
72 pub registration_date: Option<NaiveDate>,
73 pub grade_scale_id: Option<String>,
74 pub grade_id: Option<String>,
75}
76
77impl RegistryAttainment {
78 pub fn held_grade(&self) -> Option<MappedGrade> {
80 MappedGrade::from_columns(self.grade_scale_id.as_deref(), self.grade_id.as_deref())
81 }
82
83 pub fn display_grade(&self) -> Option<String> {
86 let grade_id = self.grade_id.as_deref()?;
87 Some(match self.grade_scale_id.as_deref() {
88 Some(scale) => format!("{grade_id} on {scale}"),
89 None => grade_id.to_string(),
90 })
91 }
92}
93
94#[derive(Debug, Clone)]
96pub struct RosterPerson {
97 pub student_number: SecretString,
98 pub person_id: SecretString,
99 pub first_names: Option<SecretString>,
100 pub last_name: Option<SecretString>,
101 pub primary_email: Option<SecretString>,
102 pub secondary_email: Option<SecretString>,
103 pub enrolment: Option<RosterEnrolment>,
104}
105
106impl RosterPerson {
107 pub fn enrolled_at(&self) -> Option<DateTime<Utc>> {
109 self.enrolment
110 .as_ref()
111 .and_then(|enrolment| enrolment.enrolment_date_time)
112 }
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
117pub struct RosterEnrolment {
118 pub id: Option<String>,
119 pub course_unit_realisation_id: Option<String>,
120 pub state: Option<String>,
121 pub enrolment_date_time: Option<DateTime<Utc>>,
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
127pub enum RegistryOperation {
128 ResolvePersons,
129 ResolveEnrolments,
130 ImportAttainments,
131 VerifyAttainments,
132 ListCourseRoster,
133 ValidateCourseCodes,
134}
135
136impl RegistryOperation {
137 pub fn creates_attainments(self) -> bool {
140 matches!(self, Self::ImportAttainments)
141 }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum RegistryErrorKind {
148 AuthenticationFailure,
150 MalformedRequest,
152 RejectedRequest,
154 TemporarilyUnavailable,
156 ServerError,
157 NotDelivered,
159 NoAnswer,
161 ProtocolViolation,
163}
164
165impl RegistryErrorKind {
166 pub fn may_have_been_acted_on(self) -> bool {
169 !matches!(
170 self,
171 Self::AuthenticationFailure
172 | Self::MalformedRequest
173 | Self::RejectedRequest
174 | Self::TemporarilyUnavailable
175 | Self::NotDelivered
176 )
177 }
178
179 pub fn is_outage(self) -> bool {
182 matches!(
183 self,
184 Self::TemporarilyUnavailable | Self::ServerError | Self::NotDelivered | Self::NoAnswer
185 )
186 }
187}