headless_lms_credit_registration/registry/
requests.rs1use chrono::{DateTime, Utc};
4use headless_lms_models::library::credit_registration::grade_mapping::MappedGrade;
5
6use super::answers::{EnrolmentAnswer, ImportAnswer, PersonAnswer, VerificationAnswer};
7use super::batch::{BatchEntry, BatchOptions, BatchReply, BatchRequest};
8use super::ids::{AttainmentId, CourseCode, StudentNumber};
9use super::{RegistryOperation, StudyRegistry};
10
11pub(crate) struct PersonLookup {
13 pub student_number: StudentNumber,
14}
15
16impl BatchRequest for PersonLookup {
17 const OPERATION: RegistryOperation = RegistryOperation::ResolvePersons;
18 type Answer = PersonAnswer;
19
20 async fn send<Registry: StudyRegistry, K>(
21 registry: &mut Registry,
22 entries: Vec<BatchEntry<K, Self>>,
23 options: BatchOptions,
24 ) -> BatchReply<K, Self, PersonAnswer> {
25 registry.resolve_persons(entries, options).await
26 }
27}
28
29pub(crate) struct EnrolmentLookup {
31 pub student_number: StudentNumber,
32 pub course_code: CourseCode,
33}
34
35impl BatchRequest for EnrolmentLookup {
36 const OPERATION: RegistryOperation = RegistryOperation::ResolveEnrolments;
37 type Answer = EnrolmentAnswer;
38
39 async fn send<Registry: StudyRegistry, K>(
40 registry: &mut Registry,
41 entries: Vec<BatchEntry<K, Self>>,
42 options: BatchOptions,
43 ) -> BatchReply<K, Self, EnrolmentAnswer> {
44 registry.resolve_enrolments(entries, options).await
45 }
46}
47
48pub(crate) struct AttainmentSubmission {
50 pub student_number: StudentNumber,
51 pub course_code: CourseCode,
52 pub enrolment_id: String,
53 pub attained_at: DateTime<Utc>,
54 pub attainment_language: String,
55 pub grade: MappedGrade,
56 pub credits: Credits,
57}
58
59impl BatchRequest for AttainmentSubmission {
60 const OPERATION: RegistryOperation = RegistryOperation::ImportAttainments;
61 type Answer = ImportAnswer;
62
63 async fn send<Registry: StudyRegistry, K>(
64 registry: &mut Registry,
65 entries: Vec<BatchEntry<K, Self>>,
66 options: BatchOptions,
67 ) -> BatchReply<K, Self, ImportAnswer> {
68 registry.import_attainments(entries, options).await
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq)]
75pub(crate) struct Credits(f64);
76
77impl Credits {
78 pub(crate) fn from_stored(credits: f32) -> Option<Self> {
81 credits
82 .is_finite()
83 .then(|| Self((f64::from(credits) * 10.0).round() / 10.0))
84 }
85
86 pub(crate) fn as_f64(self) -> f64 {
87 self.0
88 }
89}
90
91pub(crate) struct VerificationRequest {
93 pub submitted_attainment_id: AttainmentId,
94}
95
96impl BatchRequest for VerificationRequest {
97 const OPERATION: RegistryOperation = RegistryOperation::VerifyAttainments;
98 type Answer = VerificationAnswer;
99
100 async fn send<Registry: StudyRegistry, K>(
101 registry: &mut Registry,
102 entries: Vec<BatchEntry<K, Self>>,
103 options: BatchOptions,
104 ) -> BatchReply<K, Self, VerificationAnswer> {
105 registry.verify_attainments(entries, options).await
106 }
107}
108
109#[derive(Clone)]
111pub(crate) struct RosterCode {
112 pub course_code: CourseCode,
113 pub is_fetched_alone: bool,
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn stored_credits_go_out_rounded_to_a_tenth_and_only_when_finite() {
123 assert_eq!(Credits::from_stored(2.7).map(Credits::as_f64), Some(2.7));
124 assert_eq!(Credits::from_stored(f32::NAN), None);
125 assert_eq!(Credits::from_stored(f32::INFINITY), None);
126 }
127}