1use crate::{course_module_completions, prelude::*};
2use rand::Rng;
3
4#[derive(Clone, PartialEq, Deserialize, Serialize)]
5pub struct CourseModuleCompletionRegisteredToStudyRegistry {
6 pub id: Uuid,
7 pub created_at: DateTime<Utc>,
8 pub updated_at: DateTime<Utc>,
9 pub deleted_at: Option<DateTime<Utc>>,
10 pub course_id: Uuid,
11 pub course_module_completion_id: Uuid,
12 pub course_module_id: Uuid,
13 pub study_registry_registrar_id: Uuid,
14 pub user_id: Uuid,
15 pub real_student_number: String,
16}
17
18#[derive(Clone, PartialEq, Deserialize, Serialize)]
19pub struct NewCourseModuleCompletionRegisteredToStudyRegistry {
20 pub course_id: Uuid,
21 pub course_module_completion_id: Uuid,
22 pub course_module_id: Uuid,
23 pub study_registry_registrar_id: Uuid,
24 pub user_id: Uuid,
25 pub real_student_number: String,
26}
27
28pub async fn insert(
29 conn: &mut PgConnection,
30 pkey_policy: PKeyPolicy<Uuid>,
31 new_completion_registration: &NewCourseModuleCompletionRegisteredToStudyRegistry,
32) -> ModelResult<Uuid> {
33 let res = sqlx::query!(
34 "
35INSERT INTO course_module_completion_registered_to_study_registries (
36 id,
37 course_id,
38 course_module_completion_id,
39 course_module_id,
40 study_registry_registrar_id,
41 user_id,
42 real_student_number
43 )
44VALUES (
45 $1,
46 $2,
47 $3,
48 $4,
49 $5,
50 $6,
51 $7
52 )
53RETURNING id
54 ",
55 pkey_policy.into_uuid(),
56 new_completion_registration.course_id,
57 new_completion_registration.course_module_completion_id,
58 new_completion_registration.course_module_id,
59 new_completion_registration.study_registry_registrar_id,
60 new_completion_registration.user_id,
61 new_completion_registration.real_student_number,
62 )
63 .fetch_one(conn)
64 .await?;
65 Ok(res.id)
66}
67
68pub async fn insert_bulk(
69 conn: &mut PgConnection,
70 new_completion_registrations: Vec<NewCourseModuleCompletionRegisteredToStudyRegistry>,
71) -> ModelResult<Vec<Uuid>> {
72 if new_completion_registrations.is_empty() {
73 return Ok(vec![]);
74 }
75
76 let ids: Vec<Uuid> = (0..new_completion_registrations.len())
78 .map(|_| Uuid::new_v4())
79 .collect();
80 let course_ids: Vec<Uuid> = new_completion_registrations
81 .iter()
82 .map(|r| r.course_id)
83 .collect();
84 let completion_ids: Vec<Uuid> = new_completion_registrations
85 .iter()
86 .map(|r| r.course_module_completion_id)
87 .collect();
88 let module_ids: Vec<Uuid> = new_completion_registrations
89 .iter()
90 .map(|r| r.course_module_id)
91 .collect();
92 let registrar_ids: Vec<Uuid> = new_completion_registrations
93 .iter()
94 .map(|r| r.study_registry_registrar_id)
95 .collect();
96 let user_ids: Vec<Uuid> = new_completion_registrations
97 .iter()
98 .map(|r| r.user_id)
99 .collect();
100 let student_numbers: Vec<String> = new_completion_registrations
101 .iter()
102 .map(|r| r.real_student_number.clone())
103 .collect();
104
105 let res = sqlx::query!(
106 r#"
107INSERT INTO course_module_completion_registered_to_study_registries (
108 id,
109 course_id,
110 course_module_completion_id,
111 course_module_id,
112 study_registry_registrar_id,
113 user_id,
114 real_student_number
115)
116SELECT * FROM UNNEST(
117 $1::uuid[],
118 $2::uuid[],
119 $3::uuid[],
120 $4::uuid[],
121 $5::uuid[],
122 $6::uuid[],
123 $7::text[]
124)
125RETURNING id
126 "#,
127 &ids[..],
128 &course_ids[..],
129 &completion_ids[..],
130 &module_ids[..],
131 ®istrar_ids[..],
132 &user_ids[..],
133 &student_numbers[..],
134 )
135 .fetch_all(conn)
136 .await?;
137
138 Ok(res.into_iter().map(|r| r.id).collect())
139}
140
141#[derive(Clone, PartialEq, Eq, Deserialize, Serialize, Debug)]
142pub struct RegisteredCompletion {
144 pub completion_id: Uuid,
146 pub student_number: String,
148 pub registration_date: DateTime<Utc>,
150}
151
152pub async fn mark_completions_as_registered_to_study_registry(
153 conn: &mut PgConnection,
154 completions: Vec<RegisteredCompletion>,
155 study_registry_registrar_id: Uuid,
156) -> ModelResult<()> {
157 if completions.is_empty() {
158 return Ok(());
159 }
160
161 let ids: Vec<Uuid> = completions.iter().map(|x| x.completion_id).collect();
162 let completions_by_id = course_module_completions::get_by_ids_as_map(conn, &ids).await?;
163
164 for completion in &completions {
166 if !completions_by_id.contains_key(&completion.completion_id) {
167 return Err(ModelError::new(
168 ModelErrorType::PreconditionFailed,
169 format!(
170 "Cannot find completion with id: {}. This completion does not exist in the database.",
171 completion.completion_id
172 ),
173 None,
174 ));
175 }
176 }
177
178 let new_registrations = completions
179 .into_iter()
180 .map(|completion| {
181 let module_completion = completions_by_id
182 .get(&completion.completion_id)
183 .ok_or_else(|| {
184 ModelError::new(
185 ModelErrorType::PreconditionFailed,
186 format!(
187 "Completion with id {} not found after validation - this should never happen",
188 completion.completion_id
189 ),
190 None,
191 )
192 })?;
193 Ok(NewCourseModuleCompletionRegisteredToStudyRegistry {
194 course_id: module_completion.course_id,
195 course_module_completion_id: completion.completion_id,
196 course_module_id: module_completion.course_module_id,
197 study_registry_registrar_id,
198 user_id: module_completion.user_id,
199 real_student_number: completion.student_number,
200 })
201 })
202 .collect::<ModelResult<Vec<_>>>()?;
203
204 let mut tx = conn.begin().await?;
205
206 insert_bulk(&mut tx, new_registrations).await?;
207
208 let mut rng = rand::rng();
211 let delete_all = rng.random_range(0..50) == 0; if delete_all {
214 delete_all_duplicates(&mut tx).await?;
215 } else {
216 delete_duplicates_for_specific_completions(&mut tx, &ids).await?;
217 }
218
219 tx.commit().await?;
220
221 Ok(())
222}
223
224pub async fn get_by_id(
225 conn: &mut PgConnection,
226 id: Uuid,
227) -> ModelResult<CourseModuleCompletionRegisteredToStudyRegistry> {
228 let res = sqlx::query_as!(
229 CourseModuleCompletionRegisteredToStudyRegistry,
230 "
231SELECT *
232FROM course_module_completion_registered_to_study_registries
233WHERE id = $1
234 AND deleted_at IS NULL
235 ",
236 id,
237 )
238 .fetch_one(conn)
239 .await?;
240 Ok(res)
241}
242
243pub async fn delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
244 sqlx::query!(
245 "
246UPDATE course_module_completion_registered_to_study_registries
247SET deleted_at = now()
248WHERE id = $1
249AND deleted_at IS NULL
250 ",
251 id
252 )
253 .execute(conn)
254 .await?;
255 Ok(())
256}
257
258pub async fn get_count_of_distinct_users_with_registrations_by_course_id(
260 conn: &mut PgConnection,
261 course_id: Uuid,
262) -> ModelResult<i64> {
263 let res = sqlx::query!(
264 "
265SELECT COUNT(DISTINCT user_id) as count
266FROM course_module_completion_registered_to_study_registries
267WHERE course_id = $1
268 AND deleted_at IS NULL
269",
270 course_id,
271 )
272 .fetch_one(conn)
273 .await?;
274 Ok(res.count.unwrap_or(0))
275}
276
277pub async fn get_by_completion_id_and_registrar_id(
278 conn: &mut PgConnection,
279 completion_id: Uuid,
280 study_registry_registrar_id: Uuid,
281) -> ModelResult<Vec<CourseModuleCompletionRegisteredToStudyRegistry>> {
282 let registrations = sqlx::query_as!(
283 CourseModuleCompletionRegisteredToStudyRegistry,
284 r#"
285 SELECT *
286 FROM course_module_completion_registered_to_study_registries
287 WHERE course_module_completion_id = $1 AND study_registry_registrar_id = $2
288 AND deleted_at IS NULL
289 "#,
290 completion_id,
291 study_registry_registrar_id
292 )
293 .fetch_all(conn)
294 .await?;
295
296 Ok(registrations)
297}
298
299async fn delete_duplicates_for_specific_completions(
300 conn: &mut PgConnection,
301 completion_ids: &[Uuid],
302) -> ModelResult<i64> {
303 let res = sqlx::query!(
304 r#"
305WITH duplicate_rows AS (
306 SELECT id,
307 ROW_NUMBER() OVER (
308 PARTITION BY course_module_completion_id
309 ORDER BY created_at ASC -- Keep the oldest, delete the rest
310 ) AS rn
311 FROM course_module_completion_registered_to_study_registries
312 WHERE deleted_at IS NULL
313 AND course_module_completion_id = ANY($1)
314)
315UPDATE course_module_completion_registered_to_study_registries
316SET deleted_at = NOW()
317WHERE id IN (
318 SELECT id
319 FROM duplicate_rows
320 WHERE rn > 1
321 )
322RETURNING id
323 "#,
324 completion_ids,
325 )
326 .fetch_all(conn)
327 .await?;
328
329 Ok(res.len() as i64)
330}
331
332async fn delete_all_duplicates(conn: &mut PgConnection) -> ModelResult<i64> {
333 let res = sqlx::query!(
334 r#"
335WITH duplicate_rows AS (
336 SELECT id,
337 ROW_NUMBER() OVER (
338 PARTITION BY course_module_completion_id
339 ORDER BY created_at ASC -- Keep the oldest, delete the rest
340 ) AS rn
341 FROM course_module_completion_registered_to_study_registries
342 WHERE deleted_at IS NULL
343)
344UPDATE course_module_completion_registered_to_study_registries
345SET deleted_at = NOW()
346WHERE id IN (
347 SELECT id
348 FROM duplicate_rows
349 WHERE rn > 1
350 )
351RETURNING id
352 "#
353 )
354 .fetch_all(conn)
355 .await?;
356
357 Ok(res.len() as i64)
358}
359
360pub async fn insert_record(
361 conn: &mut PgConnection,
362 course_id: Uuid,
363 completion_id: Uuid,
364 module_id: Uuid,
365 registrar_id: Uuid,
366 user_id: Uuid,
367 real_student_number: &str,
368) -> ModelResult<()> {
369 sqlx::query!(
370 r#"
371 INSERT INTO course_module_completion_registered_to_study_registries (
372 course_id,
373 course_module_completion_id,
374 course_module_id,
375 study_registry_registrar_id,
376 user_id,
377 real_student_number
378 )
379 VALUES ($1,$2,$3,$4,$5,$6)
380 ON CONFLICT DO NOTHING
381 "#,
382 course_id,
383 completion_id,
384 module_id,
385 registrar_id,
386 user_id,
387 real_student_number
388 )
389 .execute(conn)
390 .await?;
391
392 Ok(())
393}
394
395#[cfg(test)]
396mod test {
397 use super::*;
398 use crate::{course_module_completions::CourseModuleCompletionGranter, test_helper::*};
399
400 #[tokio::test]
401 async fn bulk_insert_works() {
402 insert_data!(:tx, :user, :org, :course, instance: _instance, :course_module);
403
404 let registrar_id = crate::study_registry_registrars::insert(
405 tx.as_mut(),
406 PKeyPolicy::Generate,
407 "Test Registrar",
408 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
409 )
410 .await
411 .unwrap();
412
413 let new_completion_id = crate::course_module_completions::insert(
414 tx.as_mut(),
415 PKeyPolicy::Generate,
416 &crate::course_module_completions::NewCourseModuleCompletion {
417 course_id: course,
418 course_module_id: course_module.id,
419 user_id: user,
420 completion_date: Utc::now(),
421 completion_registration_attempt_date: None,
422 completion_language: "en-US".to_string(),
423 eligible_for_ects: true,
424 email: "test@example.com".to_string(),
425 grade: Some(10),
426 passed: true,
427 },
428 CourseModuleCompletionGranter::User(user),
429 )
430 .await
431 .unwrap();
432
433 let registrations = vec![
434 NewCourseModuleCompletionRegisteredToStudyRegistry {
435 course_id: course,
436 course_module_completion_id: new_completion_id.id,
437 course_module_id: course_module.id,
438 study_registry_registrar_id: registrar_id,
439 user_id: user,
440 real_student_number: "12345".to_string(),
441 },
442 NewCourseModuleCompletionRegisteredToStudyRegistry {
443 course_id: course,
444 course_module_completion_id: new_completion_id.id,
445 course_module_id: course_module.id,
446 study_registry_registrar_id: registrar_id,
447 user_id: user,
448 real_student_number: "67890".to_string(),
449 },
450 ];
451
452 let inserted_ids = insert_bulk(tx.as_mut(), registrations).await.unwrap();
453 assert_eq!(inserted_ids.len(), 2);
454
455 for id in inserted_ids {
457 let registration = get_by_id(tx.as_mut(), id).await.unwrap();
458 assert_eq!(registration.course_id, course);
459 assert_eq!(
460 registration.course_module_completion_id,
461 new_completion_id.id
462 );
463 assert_eq!(registration.course_module_id, course_module.id);
464 assert_eq!(registration.study_registry_registrar_id, registrar_id);
465 assert_eq!(registration.user_id, user);
466 }
467 }
468
469 #[tokio::test]
470 async fn bulk_insert_empty_vec_works() {
471 insert_data!(:tx);
472
473 let empty_vec = vec![];
474 let result = insert_bulk(tx.as_mut(), empty_vec).await.unwrap();
475 assert!(result.is_empty());
476 }
477
478 #[tokio::test]
479 async fn insert_completions_works() {
480 insert_data!(:tx, :user, :org, :course, instance: _instance, :course_module);
481
482 let registrar_id = crate::study_registry_registrars::insert(
483 tx.as_mut(),
484 PKeyPolicy::Generate,
485 "Test Registrar",
486 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
487 )
488 .await
489 .unwrap();
490
491 let completion = crate::course_module_completions::insert(
492 tx.as_mut(),
493 PKeyPolicy::Generate,
494 &crate::course_module_completions::NewCourseModuleCompletion {
495 course_id: course,
496 course_module_id: course_module.id,
497 user_id: user,
498 completion_date: Utc::now(),
499 completion_registration_attempt_date: None,
500 completion_language: "en-US".to_string(),
501 eligible_for_ects: true,
502 email: "test@example.com".to_string(),
503 grade: Some(5),
504 passed: true,
505 },
506 CourseModuleCompletionGranter::User(user),
507 )
508 .await
509 .unwrap();
510
511 let registered_completions = vec![RegisteredCompletion {
512 completion_id: completion.id,
513 student_number: "12345".to_string(),
514 registration_date: Utc::now(),
515 }];
516
517 mark_completions_as_registered_to_study_registry(
518 tx.as_mut(),
519 registered_completions,
520 registrar_id,
521 )
522 .await
523 .unwrap();
524
525 let registrations =
526 get_by_completion_id_and_registrar_id(tx.as_mut(), completion.id, registrar_id)
527 .await
528 .unwrap();
529
530 assert_eq!(registrations.len(), 1);
531 assert_eq!(registrations[0].course_id, course);
532 assert_eq!(registrations[0].course_module_id, course_module.id);
533 assert_eq!(registrations[0].user_id, user);
534 assert_eq!(registrations[0].real_student_number, "12345");
535 }
536
537 #[tokio::test]
538 async fn insert_completions_with_invalid_completion_id_fails() {
539 insert_data!(:tx);
540
541 let registrar_id = crate::study_registry_registrars::insert(
542 tx.as_mut(),
543 PKeyPolicy::Generate,
544 "Test Registrar",
545 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
546 )
547 .await
548 .unwrap();
549
550 let invalid_uuid = Uuid::new_v4(); let registered_completions = vec![RegisteredCompletion {
552 completion_id: invalid_uuid,
553 student_number: "12345".to_string(),
554 registration_date: Utc::now(),
555 }];
556
557 let result = mark_completions_as_registered_to_study_registry(
559 tx.as_mut(),
560 registered_completions,
561 registrar_id,
562 )
563 .await;
564
565 assert!(result.is_err());
566 let error = result.unwrap_err();
567 assert_eq!(*error.error_type(), ModelErrorType::PreconditionFailed);
568 assert!(error.message().contains("Cannot find completion with id"));
569 assert!(error.message().contains(&invalid_uuid.to_string()));
570 }
571
572 #[tokio::test]
573 async fn delete_duplicate_registrations_works() {
574 insert_data!(:tx, :user, :org, :course, instance: _instance, :course_module);
575
576 let registrar_id = crate::study_registry_registrars::insert(
577 tx.as_mut(),
578 PKeyPolicy::Generate,
579 "Test Registrar",
580 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
581 )
582 .await
583 .unwrap();
584
585 let completion = crate::course_module_completions::insert(
586 tx.as_mut(),
587 PKeyPolicy::Generate,
588 &crate::course_module_completions::NewCourseModuleCompletion {
589 course_id: course,
590 course_module_id: course_module.id,
591 user_id: user,
592 completion_date: Utc::now(),
593 completion_registration_attempt_date: None,
594 completion_language: "en-US".to_string(),
595 eligible_for_ects: true,
596 email: "test@example.com".to_string(),
597 grade: Some(5),
598 passed: true,
599 },
600 CourseModuleCompletionGranter::User(user),
601 )
602 .await
603 .unwrap();
604
605 let first_registration = NewCourseModuleCompletionRegisteredToStudyRegistry {
607 course_id: course,
608 course_module_completion_id: completion.id,
609 course_module_id: course_module.id,
610 study_registry_registrar_id: registrar_id,
611 user_id: user,
612 real_student_number: "12345".to_string(),
613 };
614 let first_id = insert(tx.as_mut(), PKeyPolicy::Generate, &first_registration)
615 .await
616 .unwrap();
617
618 let later_registrations = vec![
620 NewCourseModuleCompletionRegisteredToStudyRegistry {
621 course_id: course,
622 course_module_completion_id: completion.id,
623 course_module_id: course_module.id,
624 study_registry_registrar_id: registrar_id,
625 user_id: user,
626 real_student_number: "67890".to_string(),
627 },
628 NewCourseModuleCompletionRegisteredToStudyRegistry {
629 course_id: course,
630 course_module_completion_id: completion.id,
631 course_module_id: course_module.id,
632 study_registry_registrar_id: registrar_id,
633 user_id: user,
634 real_student_number: "54321".to_string(),
635 },
636 ];
637 insert_bulk(tx.as_mut(), later_registrations).await.unwrap();
638
639 let before_registrations =
640 get_by_completion_id_and_registrar_id(tx.as_mut(), completion.id, registrar_id)
641 .await
642 .unwrap();
643 assert_eq!(before_registrations.len(), 3);
644
645 let deleted_count =
646 delete_duplicates_for_specific_completions(tx.as_mut(), &[completion.id])
647 .await
648 .unwrap();
649 assert_eq!(deleted_count, 2); let after_registrations =
652 get_by_completion_id_and_registrar_id(tx.as_mut(), completion.id, registrar_id)
653 .await
654 .unwrap();
655 assert_eq!(after_registrations.len(), 1);
656
657 assert_eq!(after_registrations[0].id, first_id);
659 assert_eq!(after_registrations[0].real_student_number, "12345");
660 }
661
662 #[tokio::test]
663 async fn delete_duplicate_registrations_with_no_duplicates() {
664 insert_data!(:tx, :user, :org, :course, instance: _instance, :course_module);
665
666 let registrar_id = crate::study_registry_registrars::insert(
667 tx.as_mut(),
668 PKeyPolicy::Generate,
669 "Test Registrar",
670 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
671 )
672 .await
673 .unwrap();
674
675 let completion1 = crate::course_module_completions::insert(
676 tx.as_mut(),
677 PKeyPolicy::Generate,
678 &crate::course_module_completions::NewCourseModuleCompletion {
679 course_id: course,
680 course_module_id: course_module.id,
681 user_id: user,
682 completion_date: Utc::now(),
683 completion_registration_attempt_date: None,
684 completion_language: "en-US".to_string(),
685 eligible_for_ects: true,
686 email: "test1@example.com".to_string(),
687 grade: Some(5),
688 passed: true,
689 },
690 CourseModuleCompletionGranter::User(user),
691 )
692 .await
693 .unwrap();
694
695 let completion2 = crate::course_module_completions::insert(
696 tx.as_mut(),
697 PKeyPolicy::Generate,
698 &crate::course_module_completions::NewCourseModuleCompletion {
699 course_id: course,
700 course_module_id: course_module.id,
701 user_id: user,
702 completion_date: Utc::now(),
703 completion_registration_attempt_date: None,
704 completion_language: "en-US".to_string(),
705 eligible_for_ects: true,
706 email: "test2@example.com".to_string(),
707 grade: Some(4),
708 passed: true,
709 },
710 CourseModuleCompletionGranter::User(user),
711 )
712 .await
713 .unwrap();
714
715 let registrations = vec![
717 NewCourseModuleCompletionRegisteredToStudyRegistry {
718 course_id: course,
719 course_module_completion_id: completion1.id,
720 course_module_id: course_module.id,
721 study_registry_registrar_id: registrar_id,
722 user_id: user,
723 real_student_number: "12345".to_string(),
724 },
725 NewCourseModuleCompletionRegisteredToStudyRegistry {
726 course_id: course,
727 course_module_completion_id: completion2.id,
728 course_module_id: course_module.id,
729 study_registry_registrar_id: registrar_id,
730 user_id: user,
731 real_student_number: "67890".to_string(),
732 },
733 ];
734 insert_bulk(tx.as_mut(), registrations).await.unwrap();
735
736 let before_reg1 =
738 get_by_completion_id_and_registrar_id(tx.as_mut(), completion1.id, registrar_id)
739 .await
740 .unwrap();
741 let before_reg2 =
742 get_by_completion_id_and_registrar_id(tx.as_mut(), completion2.id, registrar_id)
743 .await
744 .unwrap();
745 assert_eq!(before_reg1.len(), 1);
746 assert_eq!(before_reg2.len(), 1);
747
748 let deleted_count = delete_all_duplicates(tx.as_mut()).await.unwrap();
750 assert_eq!(deleted_count, 0); let after_reg1 =
754 get_by_completion_id_and_registrar_id(tx.as_mut(), completion1.id, registrar_id)
755 .await
756 .unwrap();
757 let after_reg2 =
758 get_by_completion_id_and_registrar_id(tx.as_mut(), completion2.id, registrar_id)
759 .await
760 .unwrap();
761 assert_eq!(after_reg1.len(), 1);
762 assert_eq!(after_reg2.len(), 1);
763 }
764
765 #[tokio::test]
766 async fn delete_duplicate_registrations_filters_by_completion_id() {
767 insert_data!(:tx, :user, :org, :course, instance: _instance, :course_module);
768
769 let registrar_id = crate::study_registry_registrars::insert(
770 tx.as_mut(),
771 PKeyPolicy::Generate,
772 "Test Registrar",
773 "test_123131231231231231231231231231238971283718927389172893718923712893129837189273891278317892378193971289",
774 )
775 .await
776 .unwrap();
777
778 let completion1 = crate::course_module_completions::insert(
780 tx.as_mut(),
781 PKeyPolicy::Generate,
782 &crate::course_module_completions::NewCourseModuleCompletion {
783 course_id: course,
784 course_module_id: course_module.id,
785 user_id: user,
786 completion_date: Utc::now(),
787 completion_registration_attempt_date: None,
788 completion_language: "en-US".to_string(),
789 eligible_for_ects: true,
790 email: "test1@example.com".to_string(),
791 grade: Some(5),
792 passed: true,
793 },
794 CourseModuleCompletionGranter::User(user),
795 )
796 .await
797 .unwrap();
798
799 let completion2 = crate::course_module_completions::insert(
800 tx.as_mut(),
801 PKeyPolicy::Generate,
802 &crate::course_module_completions::NewCourseModuleCompletion {
803 course_id: course,
804 course_module_id: course_module.id,
805 user_id: user,
806 completion_date: Utc::now(),
807 completion_registration_attempt_date: None,
808 completion_language: "en-US".to_string(),
809 eligible_for_ects: true,
810 email: "test2@example.com".to_string(),
811 grade: Some(5),
812 passed: true,
813 },
814 CourseModuleCompletionGranter::User(user),
815 )
816 .await
817 .unwrap();
818
819 let first_registrations = vec![
821 NewCourseModuleCompletionRegisteredToStudyRegistry {
822 course_id: course,
823 course_module_completion_id: completion1.id,
824 course_module_id: course_module.id,
825 study_registry_registrar_id: registrar_id,
826 user_id: user,
827 real_student_number: "12345-1".to_string(),
828 },
829 NewCourseModuleCompletionRegisteredToStudyRegistry {
830 course_id: course,
831 course_module_completion_id: completion2.id,
832 course_module_id: course_module.id,
833 study_registry_registrar_id: registrar_id,
834 user_id: user,
835 real_student_number: "12345-2".to_string(),
836 },
837 ];
838 insert_bulk(tx.as_mut(), first_registrations).await.unwrap();
839
840 let second_registrations = vec![
842 NewCourseModuleCompletionRegisteredToStudyRegistry {
843 course_id: course,
844 course_module_completion_id: completion1.id,
845 course_module_id: course_module.id,
846 study_registry_registrar_id: registrar_id,
847 user_id: user,
848 real_student_number: "67890-1".to_string(),
849 },
850 NewCourseModuleCompletionRegisteredToStudyRegistry {
851 course_id: course,
852 course_module_completion_id: completion2.id,
853 course_module_id: course_module.id,
854 study_registry_registrar_id: registrar_id,
855 user_id: user,
856 real_student_number: "67890-2".to_string(),
857 },
858 ];
859 insert_bulk(tx.as_mut(), second_registrations)
860 .await
861 .unwrap();
862
863 let before_reg1 =
865 get_by_completion_id_and_registrar_id(tx.as_mut(), completion1.id, registrar_id)
866 .await
867 .unwrap();
868 let before_reg2 =
869 get_by_completion_id_and_registrar_id(tx.as_mut(), completion2.id, registrar_id)
870 .await
871 .unwrap();
872 assert_eq!(before_reg1.len(), 2);
873 assert_eq!(before_reg2.len(), 2);
874
875 let deleted_count =
877 delete_duplicates_for_specific_completions(tx.as_mut(), &[completion1.id])
878 .await
879 .unwrap();
880 assert_eq!(deleted_count, 1); let after_reg1 =
884 get_by_completion_id_and_registrar_id(tx.as_mut(), completion1.id, registrar_id)
885 .await
886 .unwrap();
887 let after_reg2 =
888 get_by_completion_id_and_registrar_id(tx.as_mut(), completion2.id, registrar_id)
889 .await
890 .unwrap();
891 assert_eq!(after_reg1.len(), 1);
892 assert_eq!(after_reg2.len(), 2);
893
894 assert_eq!(after_reg1[0].real_student_number, "12345-1");
896 }
897}