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
106#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct RosterEnrolment {
109 pub id: Option<String>,
110 pub course_unit_realisation_id: Option<String>,
111 pub state: Option<String>,
112 pub enrolment_date_time: Option<DateTime<Utc>>,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
118pub enum RegistryOperation {
119 ResolvePersons,
120 ResolveEnrolments,
121 ImportAttainments,
122 VerifyAttainments,
123 ListCourseRoster,
124 ValidateCourseCodes,
125}
126
127impl RegistryOperation {
128 pub fn creates_attainments(self) -> bool {
131 matches!(self, Self::ImportAttainments)
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum RegistryErrorKind {
139 AuthenticationFailure,
141 MalformedRequest,
143 RejectedRequest,
145 TemporarilyUnavailable,
147 ServerError,
148 NotDelivered,
150 NoAnswer,
152 ProtocolViolation,
154}
155
156impl RegistryErrorKind {
157 pub fn may_have_been_acted_on(self) -> bool {
160 !matches!(
161 self,
162 Self::AuthenticationFailure
163 | Self::MalformedRequest
164 | Self::RejectedRequest
165 | Self::TemporarilyUnavailable
166 | Self::NotDelivered
167 )
168 }
169
170 pub fn is_outage(self) -> bool {
173 matches!(
174 self,
175 Self::TemporarilyUnavailable | Self::ServerError | Self::NotDelivered | Self::NoAnswer
176 )
177 }
178}