1use utoipa::ToSchema;
6
7use crate::credit_registrations::CreditRegistrationState;
8use crate::prelude::*;
9
10#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Hash, Type, ToSchema)]
11#[sqlx(
12 type_name = "credit_registration_admin_action",
13 rename_all = "snake_case"
14)]
15#[serde(rename_all = "snake_case")]
16pub enum CreditRegistrationAdminAction {
17 RetryItem,
18 RetryFailedForCourse,
19 ForceRecheck,
20 MarkResolved,
21 RequeueBatch,
22 TransitionItem,
23 CancelRegistration,
24 PauseCourseModule,
25 ResumeCourseModule,
26 PausePhase,
27 ResumePhase,
28 RunPhaseNow,
29 ResendLinkEmail,
30 UnlinkStudentNumber,
31 ManualLinkStudentNumber,
32 OverrideRateCap,
33}
34
35#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, Hash, Type, ToSchema)]
36#[sqlx(
37 type_name = "credit_registration_admin_action_target",
38 rename_all = "snake_case"
39)]
40#[serde(rename_all = "snake_case")]
41pub enum CreditRegistrationAdminActionTarget {
42 CreditRegistration,
43 CourseModule,
44 Course,
45 Phase,
46 VerifiedStudentNumber,
47 StudentNumberVerificationToken,
48}
49
50#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
51pub struct CreditRegistrationAdminActionRecord {
52 pub id: Uuid,
53 pub created_at: DateTime<Utc>,
54 pub updated_at: DateTime<Utc>,
55 pub deleted_at: Option<DateTime<Utc>>,
56 pub action: CreditRegistrationAdminAction,
57 pub target_kind: CreditRegistrationAdminActionTarget,
58 pub target_id: Option<Uuid>,
59 pub target_phase: Option<String>,
60 pub actor_user_id: Uuid,
61 pub actor_role: String,
62 pub actor_course_id: Option<Uuid>,
63 pub reason: Option<String>,
64 pub before_state: Option<CreditRegistrationState>,
65 pub after_state: Option<CreditRegistrationState>,
66 pub details: Option<serde_json::Value>,
67 pub affected_row_count: Option<i32>,
68}
69
70#[derive(Debug, Clone, PartialEq)]
71pub struct NewCreditRegistrationAdminAction {
72 pub action: CreditRegistrationAdminAction,
73 pub target_kind: CreditRegistrationAdminActionTarget,
74 pub target_id: Option<Uuid>,
76 pub target_phase: Option<String>,
77 pub actor_user_id: Uuid,
78 pub actor_role: String,
80 pub actor_course_id: Option<Uuid>,
82 pub reason: Option<String>,
83 pub before_state: Option<CreditRegistrationState>,
84 pub after_state: Option<CreditRegistrationState>,
85 pub details: Option<serde_json::Value>,
87 pub affected_row_count: Option<i32>,
88}
89
90pub async fn record(
92 conn: &mut PgConnection,
93 new: &NewCreditRegistrationAdminAction,
94) -> ModelResult<Uuid> {
95 let res = sqlx::query!(
96 r#"
97INSERT INTO credit_registration_admin_actions (
98 action,
99 target_kind,
100 target_id,
101 target_phase,
102 actor_user_id,
103 actor_role,
104 actor_course_id,
105 reason,
106 before_state,
107 after_state,
108 details,
109 affected_row_count
110 )
111VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
112RETURNING id
113 "#,
114 new.action as CreditRegistrationAdminAction,
115 new.target_kind as CreditRegistrationAdminActionTarget,
116 new.target_id,
117 new.target_phase,
118 new.actor_user_id,
119 new.actor_role,
120 new.actor_course_id,
121 new.reason,
122 new.before_state as Option<CreditRegistrationState>,
123 new.after_state as Option<CreditRegistrationState>,
124 new.details,
125 new.affected_row_count,
126 )
127 .fetch_one(conn)
128 .await?;
129 Ok(res.id)
130}
131
132pub async fn get_recent(
133 conn: &mut PgConnection,
134 limit: i64,
135) -> ModelResult<Vec<CreditRegistrationAdminActionRecord>> {
136 let res = sqlx::query_as!(
137 CreditRegistrationAdminActionRecord,
138 r#"
139SELECT *
140FROM credit_registration_admin_actions
141WHERE deleted_at IS NULL
142ORDER BY created_at DESC
143LIMIT $1
144 "#,
145 limit
146 )
147 .fetch_all(conn)
148 .await?;
149 Ok(res)
150}
151
152pub async fn get_by_target(
153 conn: &mut PgConnection,
154 target_kind: CreditRegistrationAdminActionTarget,
155 target_id: Uuid,
156) -> ModelResult<Vec<CreditRegistrationAdminActionRecord>> {
157 let res = sqlx::query_as!(
158 CreditRegistrationAdminActionRecord,
159 r#"
160SELECT *
161FROM credit_registration_admin_actions
162WHERE target_kind = $1
163 AND target_id = $2
164 AND deleted_at IS NULL
165ORDER BY created_at DESC
166 "#,
167 target_kind as CreditRegistrationAdminActionTarget,
168 target_id,
169 )
170 .fetch_all(conn)
171 .await?;
172 Ok(res)
173}
174
175pub async fn get_by_actor(
176 conn: &mut PgConnection,
177 actor_user_id: Uuid,
178 limit: i64,
179) -> ModelResult<Vec<CreditRegistrationAdminActionRecord>> {
180 let res = sqlx::query_as!(
181 CreditRegistrationAdminActionRecord,
182 r#"
183SELECT *
184FROM credit_registration_admin_actions
185WHERE actor_user_id = $1
186 AND deleted_at IS NULL
187ORDER BY created_at DESC
188LIMIT $2
189 "#,
190 actor_user_id,
191 limit,
192 )
193 .fetch_all(conn)
194 .await?;
195 Ok(res)
196}
197
198pub async fn get_by_actor_course(
200 conn: &mut PgConnection,
201 actor_course_id: Uuid,
202 limit: i64,
203) -> ModelResult<Vec<CreditRegistrationAdminActionRecord>> {
204 let res = sqlx::query_as!(
205 CreditRegistrationAdminActionRecord,
206 r#"
207SELECT *
208FROM credit_registration_admin_actions
209WHERE actor_course_id = $1
210 AND deleted_at IS NULL
211ORDER BY created_at DESC
212LIMIT $2
213 "#,
214 actor_course_id,
215 limit,
216 )
217 .fetch_all(conn)
218 .await?;
219 Ok(res)
220}
221
222pub async fn get_by_phase(
223 conn: &mut PgConnection,
224 target_phase: &str,
225 limit: i64,
226) -> ModelResult<Vec<CreditRegistrationAdminActionRecord>> {
227 let res = sqlx::query_as!(
228 CreditRegistrationAdminActionRecord,
229 r#"
230SELECT *
231FROM credit_registration_admin_actions
232WHERE target_phase = $1
233 AND deleted_at IS NULL
234ORDER BY created_at DESC
235LIMIT $2
236 "#,
237 target_phase,
238 limit,
239 )
240 .fetch_all(conn)
241 .await?;
242 Ok(res)
243}