1use chrono::{DateTime, NaiveDate, Utc};
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
13#[serde(rename_all = "snake_case")]
14pub enum ReportingLine {
15 #[default]
17 AuditCommittee,
18 Board,
20 CFO,
22 CEO,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
28#[serde(rename_all = "snake_case")]
29pub enum IaAssessment {
30 FullyEffective,
32 #[default]
34 LargelyEffective,
35 PartiallyEffective,
37 Ineffective,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
43#[serde(rename_all = "snake_case")]
44pub enum ObjectivityRating {
45 #[default]
47 High,
48 Moderate,
50 Low,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
56#[serde(rename_all = "snake_case")]
57pub enum CompetenceRating {
58 High,
60 #[default]
62 Moderate,
63 Low,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
69#[serde(rename_all = "snake_case")]
70pub enum RelianceExtent {
71 NoReliance,
73 #[default]
75 LimitedReliance,
76 SignificantReliance,
78 FullReliance,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
84#[serde(rename_all = "snake_case")]
85pub enum IaReportRating {
86 #[default]
88 Satisfactory,
89 NeedsImprovement,
91 Unsatisfactory,
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
97#[serde(rename_all = "snake_case")]
98pub enum IaReportStatus {
99 #[default]
101 Draft,
102 Final,
104 Retracted,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
110#[serde(rename_all = "snake_case")]
111pub enum RecommendationPriority {
112 Critical,
114 High,
116 #[default]
118 Medium,
119 Low,
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
125#[serde(rename_all = "snake_case")]
126pub enum ActionPlanStatus {
127 #[default]
129 Open,
130 InProgress,
132 Implemented,
134 Overdue,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
140#[serde(rename_all = "snake_case")]
141pub enum IaWorkAssessment {
142 Reliable,
144 #[default]
146 PartiallyReliable,
147 Unreliable,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct InternalAuditFunction {
154 pub function_id: Uuid,
156 pub function_ref: String,
158 pub engagement_id: Uuid,
160
161 pub department_name: String,
164 pub reporting_line: ReportingLine,
166 pub head_of_ia: String,
168 pub head_of_ia_qualifications: Vec<String>,
170 pub staff_count: u32,
172 pub annual_plan_coverage: f64,
174 pub quality_assurance: bool,
176
177 pub isa_610_assessment: IaAssessment,
180 pub objectivity_rating: ObjectivityRating,
182 pub competence_rating: CompetenceRating,
184 pub systematic_discipline: bool,
186
187 pub reliance_extent: RelianceExtent,
190 pub reliance_areas: Vec<String>,
192 pub direct_assistance: bool,
194
195 #[serde(with = "crate::serde_timestamp::utc")]
197 pub created_at: DateTime<Utc>,
198 #[serde(with = "crate::serde_timestamp::utc")]
199 pub updated_at: DateTime<Utc>,
200}
201
202impl InternalAuditFunction {
203 pub fn new(
205 engagement_id: Uuid,
206 department_name: impl Into<String>,
207 head_of_ia: impl Into<String>,
208 ) -> Self {
209 let now = Utc::now();
210 let id = Uuid::new_v4();
211 let function_ref = format!("IAF-{}", &id.simple().to_string()[..8]);
212 Self {
213 function_id: id,
214 function_ref,
215 engagement_id,
216 department_name: department_name.into(),
217 reporting_line: ReportingLine::AuditCommittee,
218 head_of_ia: head_of_ia.into(),
219 head_of_ia_qualifications: Vec::new(),
220 staff_count: 0,
221 annual_plan_coverage: 0.0,
222 quality_assurance: false,
223 isa_610_assessment: IaAssessment::LargelyEffective,
224 objectivity_rating: ObjectivityRating::High,
225 competence_rating: CompetenceRating::Moderate,
226 systematic_discipline: true,
227 reliance_extent: RelianceExtent::LimitedReliance,
228 reliance_areas: Vec::new(),
229 direct_assistance: false,
230 created_at: now,
231 updated_at: now,
232 }
233 }
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct IaRecommendation {
239 pub recommendation_id: Uuid,
241 pub description: String,
243 pub priority: RecommendationPriority,
245 pub management_response: Option<String>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251pub struct ActionPlan {
252 pub plan_id: Uuid,
254 pub recommendation_id: Uuid,
256 pub description: String,
258 pub responsible_party: String,
260 pub target_date: NaiveDate,
262 pub status: ActionPlanStatus,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct InternalAuditReport {
269 pub report_id: Uuid,
271 pub report_ref: String,
273 pub engagement_id: Uuid,
275 pub ia_function_id: Uuid,
277
278 pub report_title: String,
281 pub audit_area: String,
283 pub report_date: NaiveDate,
285 pub period_start: NaiveDate,
287 pub period_end: NaiveDate,
289
290 pub scope_description: String,
293 pub methodology: String,
295
296 pub overall_rating: IaReportRating,
299 pub findings_count: u32,
301 pub high_risk_findings: u32,
303 pub recommendations: Vec<IaRecommendation>,
305 pub management_action_plans: Vec<ActionPlan>,
307
308 pub status: IaReportStatus,
311
312 pub external_auditor_assessment: Option<IaWorkAssessment>,
315
316 #[serde(with = "crate::serde_timestamp::utc")]
318 pub created_at: DateTime<Utc>,
319 #[serde(with = "crate::serde_timestamp::utc")]
320 pub updated_at: DateTime<Utc>,
321}
322
323impl InternalAuditReport {
324 pub fn new(
326 engagement_id: Uuid,
327 ia_function_id: Uuid,
328 report_title: impl Into<String>,
329 audit_area: impl Into<String>,
330 report_date: NaiveDate,
331 period_start: NaiveDate,
332 period_end: NaiveDate,
333 ) -> Self {
334 let now = Utc::now();
335 let id = Uuid::new_v4();
336 let report_ref = format!("IAR-{}", &id.simple().to_string()[..8]);
337 Self {
338 report_id: id,
339 report_ref,
340 engagement_id,
341 ia_function_id,
342 report_title: report_title.into(),
343 audit_area: audit_area.into(),
344 report_date,
345 period_start,
346 period_end,
347 scope_description: String::new(),
348 methodology: String::new(),
349 overall_rating: IaReportRating::Satisfactory,
350 findings_count: 0,
351 high_risk_findings: 0,
352 recommendations: Vec::new(),
353 management_action_plans: Vec::new(),
354 status: IaReportStatus::Draft,
355 external_auditor_assessment: None,
356 created_at: now,
357 updated_at: now,
358 }
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 fn sample_date(year: i32, month: u32, day: u32) -> NaiveDate {
367 NaiveDate::from_ymd_opt(year, month, day).unwrap()
368 }
369
370 #[test]
371 fn test_new_ia_function() {
372 let eng = Uuid::new_v4();
373 let iaf = InternalAuditFunction::new(eng, "Group Internal Audit", "Jane Smith");
374
375 assert_eq!(iaf.engagement_id, eng);
376 assert_eq!(iaf.department_name, "Group Internal Audit");
377 assert_eq!(iaf.head_of_ia, "Jane Smith");
378 assert_eq!(iaf.reporting_line, ReportingLine::AuditCommittee);
379 assert_eq!(iaf.isa_610_assessment, IaAssessment::LargelyEffective);
380 assert_eq!(iaf.objectivity_rating, ObjectivityRating::High);
381 assert_eq!(iaf.competence_rating, CompetenceRating::Moderate);
382 assert_eq!(iaf.reliance_extent, RelianceExtent::LimitedReliance);
383 assert!(iaf.systematic_discipline);
384 assert!(!iaf.direct_assistance);
385 assert!(iaf.function_ref.starts_with("IAF-"));
386 assert_eq!(iaf.function_ref.len(), 12); }
388
389 #[test]
390 fn test_new_ia_report() {
391 let eng = Uuid::new_v4();
392 let func = Uuid::new_v4();
393 let report = InternalAuditReport::new(
394 eng,
395 func,
396 "Procurement Process Review",
397 "Procurement",
398 sample_date(2025, 3, 31),
399 sample_date(2025, 1, 1),
400 sample_date(2025, 12, 31),
401 );
402
403 assert_eq!(report.engagement_id, eng);
404 assert_eq!(report.ia_function_id, func);
405 assert_eq!(report.report_title, "Procurement Process Review");
406 assert_eq!(report.audit_area, "Procurement");
407 assert_eq!(report.overall_rating, IaReportRating::Satisfactory);
408 assert_eq!(report.status, IaReportStatus::Draft);
409 assert_eq!(report.findings_count, 0);
410 assert!(report.recommendations.is_empty());
411 assert!(report.external_auditor_assessment.is_none());
412 assert!(report.report_ref.starts_with("IAR-"));
413 assert_eq!(report.report_ref.len(), 12); }
415
416 #[test]
417 fn test_reporting_line_serde() {
418 let variants = [
419 ReportingLine::AuditCommittee,
420 ReportingLine::Board,
421 ReportingLine::CFO,
422 ReportingLine::CEO,
423 ];
424 for v in variants {
425 let json = serde_json::to_string(&v).unwrap();
426 let rt: ReportingLine = serde_json::from_str(&json).unwrap();
427 assert_eq!(v, rt);
428 }
429 assert_eq!(
430 serde_json::to_string(&ReportingLine::AuditCommittee).unwrap(),
431 "\"audit_committee\""
432 );
433 }
434
435 #[test]
436 fn test_ia_assessment_serde() {
437 let variants = [
438 IaAssessment::FullyEffective,
439 IaAssessment::LargelyEffective,
440 IaAssessment::PartiallyEffective,
441 IaAssessment::Ineffective,
442 ];
443 for v in variants {
444 let json = serde_json::to_string(&v).unwrap();
445 let rt: IaAssessment = serde_json::from_str(&json).unwrap();
446 assert_eq!(v, rt);
447 }
448 assert_eq!(
449 serde_json::to_string(&IaAssessment::FullyEffective).unwrap(),
450 "\"fully_effective\""
451 );
452 }
453
454 #[test]
455 fn test_reliance_extent_serde() {
456 let variants = [
457 RelianceExtent::NoReliance,
458 RelianceExtent::LimitedReliance,
459 RelianceExtent::SignificantReliance,
460 RelianceExtent::FullReliance,
461 ];
462 for v in variants {
463 let json = serde_json::to_string(&v).unwrap();
464 let rt: RelianceExtent = serde_json::from_str(&json).unwrap();
465 assert_eq!(v, rt);
466 }
467 assert_eq!(
468 serde_json::to_string(&RelianceExtent::SignificantReliance).unwrap(),
469 "\"significant_reliance\""
470 );
471 }
472
473 #[test]
474 fn test_ia_report_status_serde() {
475 let variants = [
476 IaReportStatus::Draft,
477 IaReportStatus::Final,
478 IaReportStatus::Retracted,
479 ];
480 for v in variants {
481 let json = serde_json::to_string(&v).unwrap();
482 let rt: IaReportStatus = serde_json::from_str(&json).unwrap();
483 assert_eq!(v, rt);
484 }
485 assert_eq!(
486 serde_json::to_string(&IaReportStatus::Final).unwrap(),
487 "\"final\""
488 );
489 }
490
491 #[test]
492 fn test_ia_report_rating_serde() {
493 let variants = [
494 IaReportRating::Satisfactory,
495 IaReportRating::NeedsImprovement,
496 IaReportRating::Unsatisfactory,
497 ];
498 for v in variants {
499 let json = serde_json::to_string(&v).unwrap();
500 let rt: IaReportRating = serde_json::from_str(&json).unwrap();
501 assert_eq!(v, rt);
502 }
503 assert_eq!(
504 serde_json::to_string(&IaReportRating::NeedsImprovement).unwrap(),
505 "\"needs_improvement\""
506 );
507 }
508
509 #[test]
510 fn test_recommendation_priority_serde() {
511 let variants = [
512 RecommendationPriority::Critical,
513 RecommendationPriority::High,
514 RecommendationPriority::Medium,
515 RecommendationPriority::Low,
516 ];
517 for v in variants {
518 let json = serde_json::to_string(&v).unwrap();
519 let rt: RecommendationPriority = serde_json::from_str(&json).unwrap();
520 assert_eq!(v, rt);
521 }
522 assert_eq!(
523 serde_json::to_string(&RecommendationPriority::Critical).unwrap(),
524 "\"critical\""
525 );
526 }
527
528 #[test]
529 fn test_action_plan_status_serde() {
530 let variants = [
531 ActionPlanStatus::Open,
532 ActionPlanStatus::InProgress,
533 ActionPlanStatus::Implemented,
534 ActionPlanStatus::Overdue,
535 ];
536 for v in variants {
537 let json = serde_json::to_string(&v).unwrap();
538 let rt: ActionPlanStatus = serde_json::from_str(&json).unwrap();
539 assert_eq!(v, rt);
540 }
541 assert_eq!(
542 serde_json::to_string(&ActionPlanStatus::InProgress).unwrap(),
543 "\"in_progress\""
544 );
545 }
546
547 #[test]
548 fn test_ia_work_assessment_serde() {
549 let variants = [
550 IaWorkAssessment::Reliable,
551 IaWorkAssessment::PartiallyReliable,
552 IaWorkAssessment::Unreliable,
553 ];
554 for v in variants {
555 let json = serde_json::to_string(&v).unwrap();
556 let rt: IaWorkAssessment = serde_json::from_str(&json).unwrap();
557 assert_eq!(v, rt);
558 }
559 assert_eq!(
560 serde_json::to_string(&IaWorkAssessment::PartiallyReliable).unwrap(),
561 "\"partially_reliable\""
562 );
563 }
564}