1use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub enum IsaStandard {
12 Isa200,
15 Isa210,
17 Isa220,
19 Isa230,
21 Isa240,
23 Isa250,
25 Isa260,
27 Isa265,
29
30 Isa300,
33 Isa315,
35 Isa320,
37 Isa330,
39
40 Isa402,
43 Isa450,
45
46 Isa500,
49 Isa501,
51 Isa505,
53 Isa510,
55 Isa520,
57 Isa530,
59 Isa540,
61 Isa550,
63 Isa560,
65 Isa570,
67 Isa580,
69
70 Isa600,
73 Isa610,
75 Isa620,
77
78 Isa700,
81 Isa701,
83 Isa705,
85 Isa706,
87 Isa710,
89 Isa720,
91}
92
93impl IsaStandard {
94 pub fn number(&self) -> &'static str {
96 match self {
97 Self::Isa200 => "200",
98 Self::Isa210 => "210",
99 Self::Isa220 => "220",
100 Self::Isa230 => "230",
101 Self::Isa240 => "240",
102 Self::Isa250 => "250",
103 Self::Isa260 => "260",
104 Self::Isa265 => "265",
105 Self::Isa300 => "300",
106 Self::Isa315 => "315",
107 Self::Isa320 => "320",
108 Self::Isa330 => "330",
109 Self::Isa402 => "402",
110 Self::Isa450 => "450",
111 Self::Isa500 => "500",
112 Self::Isa501 => "501",
113 Self::Isa505 => "505",
114 Self::Isa510 => "510",
115 Self::Isa520 => "520",
116 Self::Isa530 => "530",
117 Self::Isa540 => "540",
118 Self::Isa550 => "550",
119 Self::Isa560 => "560",
120 Self::Isa570 => "570",
121 Self::Isa580 => "580",
122 Self::Isa600 => "600",
123 Self::Isa610 => "610",
124 Self::Isa620 => "620",
125 Self::Isa700 => "700",
126 Self::Isa701 => "701",
127 Self::Isa705 => "705",
128 Self::Isa706 => "706",
129 Self::Isa710 => "710",
130 Self::Isa720 => "720",
131 }
132 }
133
134 pub fn title(&self) -> &'static str {
136 match self {
137 Self::Isa200 => "Overall Objectives of the Independent Auditor",
138 Self::Isa210 => "Agreeing the Terms of Audit Engagements",
139 Self::Isa220 => "Quality Management for an Audit of Financial Statements",
140 Self::Isa230 => "Audit Documentation",
141 Self::Isa240 => "The Auditor's Responsibilities Relating to Fraud",
142 Self::Isa250 => "Consideration of Laws and Regulations",
143 Self::Isa260 => "Communication with Those Charged with Governance",
144 Self::Isa265 => "Communicating Deficiencies in Internal Control",
145 Self::Isa300 => "Planning an Audit of Financial Statements",
146 Self::Isa315 => "Identifying and Assessing Risks of Material Misstatement",
147 Self::Isa320 => "Materiality in Planning and Performing an Audit",
148 Self::Isa330 => "The Auditor's Responses to Assessed Risks",
149 Self::Isa402 => "Audit Considerations Relating to Service Organizations",
150 Self::Isa450 => "Evaluation of Misstatements Identified During the Audit",
151 Self::Isa500 => "Audit Evidence",
152 Self::Isa501 => "Audit Evidence - Specific Considerations",
153 Self::Isa505 => "External Confirmations",
154 Self::Isa510 => "Initial Audit Engagements - Opening Balances",
155 Self::Isa520 => "Analytical Procedures",
156 Self::Isa530 => "Audit Sampling",
157 Self::Isa540 => "Auditing Accounting Estimates and Related Disclosures",
158 Self::Isa550 => "Related Parties",
159 Self::Isa560 => "Subsequent Events",
160 Self::Isa570 => "Going Concern",
161 Self::Isa580 => "Written Representations",
162 Self::Isa600 => "Special Considerations - Audits of Group Financial Statements",
163 Self::Isa610 => "Using the Work of Internal Auditors",
164 Self::Isa620 => "Using the Work of an Auditor's Expert",
165 Self::Isa700 => "Forming an Opinion and Reporting on Financial Statements",
166 Self::Isa701 => "Communicating Key Audit Matters",
167 Self::Isa705 => "Modifications to the Opinion",
168 Self::Isa706 => "Emphasis of Matter and Other Matter Paragraphs",
169 Self::Isa710 => "Comparative Information",
170 Self::Isa720 => "The Auditor's Responsibilities Relating to Other Information",
171 }
172 }
173
174 pub fn series(&self) -> IsaSeries {
176 match self {
177 Self::Isa200
178 | Self::Isa210
179 | Self::Isa220
180 | Self::Isa230
181 | Self::Isa240
182 | Self::Isa250
183 | Self::Isa260
184 | Self::Isa265 => IsaSeries::GeneralPrinciples,
185 Self::Isa300 | Self::Isa315 | Self::Isa320 | Self::Isa330 => IsaSeries::RiskAssessment,
186 Self::Isa402 | Self::Isa450 => IsaSeries::InternalControl,
187 Self::Isa500
188 | Self::Isa501
189 | Self::Isa505
190 | Self::Isa510
191 | Self::Isa520
192 | Self::Isa530
193 | Self::Isa540
194 | Self::Isa550
195 | Self::Isa560
196 | Self::Isa570
197 | Self::Isa580 => IsaSeries::AuditEvidence,
198 Self::Isa600 | Self::Isa610 | Self::Isa620 => IsaSeries::UsingWorkOfOthers,
199 Self::Isa700
200 | Self::Isa701
201 | Self::Isa705
202 | Self::Isa706
203 | Self::Isa710
204 | Self::Isa720 => IsaSeries::Reporting,
205 }
206 }
207
208 pub fn all() -> Vec<Self> {
210 vec![
211 Self::Isa200,
212 Self::Isa210,
213 Self::Isa220,
214 Self::Isa230,
215 Self::Isa240,
216 Self::Isa250,
217 Self::Isa260,
218 Self::Isa265,
219 Self::Isa300,
220 Self::Isa315,
221 Self::Isa320,
222 Self::Isa330,
223 Self::Isa402,
224 Self::Isa450,
225 Self::Isa500,
226 Self::Isa501,
227 Self::Isa505,
228 Self::Isa510,
229 Self::Isa520,
230 Self::Isa530,
231 Self::Isa540,
232 Self::Isa550,
233 Self::Isa560,
234 Self::Isa570,
235 Self::Isa580,
236 Self::Isa600,
237 Self::Isa610,
238 Self::Isa620,
239 Self::Isa700,
240 Self::Isa701,
241 Self::Isa705,
242 Self::Isa706,
243 Self::Isa710,
244 Self::Isa720,
245 ]
246 }
247}
248
249impl std::fmt::Display for IsaStandard {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 write!(f, "ISA {}", self.number())
252 }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
257#[serde(rename_all = "snake_case")]
258pub enum IsaSeries {
259 GeneralPrinciples,
261 RiskAssessment,
263 InternalControl,
265 AuditEvidence,
267 UsingWorkOfOthers,
269 Reporting,
271}
272
273impl std::fmt::Display for IsaSeries {
274 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275 match self {
276 Self::GeneralPrinciples => write!(f, "General Principles and Responsibilities"),
277 Self::RiskAssessment => write!(f, "Risk Assessment and Response"),
278 Self::InternalControl => write!(f, "Internal Control"),
279 Self::AuditEvidence => write!(f, "Audit Evidence"),
280 Self::UsingWorkOfOthers => write!(f, "Using Work of Others"),
281 Self::Reporting => write!(f, "Audit Conclusions and Reporting"),
282 }
283 }
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct IsaRequirement {
289 pub standard: IsaStandard,
291
292 pub paragraph: String,
294
295 pub requirement_type: IsaRequirementType,
297
298 pub description: String,
300
301 pub is_mandatory: bool,
303}
304
305impl IsaRequirement {
306 pub fn new(
308 standard: IsaStandard,
309 paragraph: impl Into<String>,
310 requirement_type: IsaRequirementType,
311 description: impl Into<String>,
312 ) -> Self {
313 let is_mandatory = matches!(requirement_type, IsaRequirementType::Requirement);
314 Self {
315 standard,
316 paragraph: paragraph.into(),
317 requirement_type,
318 description: description.into(),
319 is_mandatory,
320 }
321 }
322
323 pub fn reference(&self) -> String {
325 format!("ISA {}.{}", self.standard.number(), self.paragraph)
326 }
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
331#[serde(rename_all = "snake_case")]
332pub enum IsaRequirementType {
333 Objective,
335 Requirement,
337 ApplicationGuidance,
339 Definition,
341}
342
343impl std::fmt::Display for IsaRequirementType {
344 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
345 match self {
346 Self::Objective => write!(f, "Objective"),
347 Self::Requirement => write!(f, "Requirement"),
348 Self::ApplicationGuidance => write!(f, "Application Guidance"),
349 Self::Definition => write!(f, "Definition"),
350 }
351 }
352}
353
354#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct IsaProcedureMapping {
357 pub mapping_id: Uuid,
359
360 pub procedure_id: Uuid,
362
363 pub procedure_description: String,
365
366 pub isa_requirements: Vec<IsaRequirement>,
368
369 pub compliance_status: ComplianceStatus,
371
372 pub documentation_reference: Option<String>,
374
375 pub compliance_notes: String,
377}
378
379impl IsaProcedureMapping {
380 pub fn new(procedure_id: Uuid, procedure_description: impl Into<String>) -> Self {
382 Self {
383 mapping_id: Uuid::now_v7(),
384 procedure_id,
385 procedure_description: procedure_description.into(),
386 isa_requirements: Vec::new(),
387 compliance_status: ComplianceStatus::NotAssessed,
388 documentation_reference: None,
389 compliance_notes: String::new(),
390 }
391 }
392
393 pub fn add_requirement(&mut self, requirement: IsaRequirement) {
395 self.isa_requirements.push(requirement);
396 }
397
398 pub fn standards_covered(&self) -> Vec<IsaStandard> {
400 let mut standards: Vec<_> = self.isa_requirements.iter().map(|r| r.standard).collect();
401 standards.sort_by_key(|s| s.number());
402 standards.dedup();
403 standards
404 }
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
409#[serde(rename_all = "snake_case")]
410pub enum ComplianceStatus {
411 #[default]
413 NotAssessed,
414 Compliant,
416 PartiallyCompliant,
418 NonCompliant,
420 NotApplicable,
422}
423
424impl std::fmt::Display for ComplianceStatus {
425 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426 match self {
427 Self::NotAssessed => write!(f, "Not Assessed"),
428 Self::Compliant => write!(f, "Compliant"),
429 Self::PartiallyCompliant => write!(f, "Partially Compliant"),
430 Self::NonCompliant => write!(f, "Non-Compliant"),
431 Self::NotApplicable => write!(f, "Not Applicable"),
432 }
433 }
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize)]
438pub struct IsaCoverageSummary {
439 pub engagement_id: Uuid,
441
442 pub standards_addressed: Vec<IsaStandard>,
444
445 pub coverage_by_series: Vec<SeriesCoverage>,
447
448 pub overall_compliance_percent: f64,
450
451 pub gaps: Vec<IsaStandard>,
453
454 pub unaddressed_mandatory: Vec<IsaRequirement>,
456}
457
458impl IsaCoverageSummary {
459 pub fn new(engagement_id: Uuid) -> Self {
461 Self {
462 engagement_id,
463 standards_addressed: Vec::new(),
464 coverage_by_series: Vec::new(),
465 overall_compliance_percent: 0.0,
466 gaps: Vec::new(),
467 unaddressed_mandatory: Vec::new(),
468 }
469 }
470
471 pub fn calculate_coverage(&mut self, mappings: &[IsaProcedureMapping]) {
473 self.standards_addressed = mappings
474 .iter()
475 .flat_map(|m| m.standards_covered())
476 .collect();
477 self.standards_addressed.sort_by_key(|s| s.number());
478 self.standards_addressed.dedup();
479
480 self.gaps = IsaStandard::all()
482 .into_iter()
483 .filter(|s| !self.standards_addressed.contains(s))
484 .collect();
485
486 let total = IsaStandard::all().len();
488 let covered = self.standards_addressed.len();
489 self.overall_compliance_percent = (covered as f64 / total as f64) * 100.0;
490 }
491}
492
493#[derive(Debug, Clone, Serialize, Deserialize)]
495pub struct SeriesCoverage {
496 pub series: IsaSeries,
498
499 pub total_standards: usize,
501
502 pub addressed_standards: usize,
504
505 pub coverage_percent: f64,
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512
513 #[test]
514 fn test_isa_standard_number() {
515 assert_eq!(IsaStandard::Isa315.number(), "315");
516 assert_eq!(IsaStandard::Isa700.number(), "700");
517 }
518
519 #[test]
520 fn test_isa_standard_title() {
521 assert_eq!(
522 IsaStandard::Isa315.title(),
523 "Identifying and Assessing Risks of Material Misstatement"
524 );
525 }
526
527 #[test]
528 fn test_isa_standard_series() {
529 assert_eq!(IsaStandard::Isa200.series(), IsaSeries::GeneralPrinciples);
530 assert_eq!(IsaStandard::Isa315.series(), IsaSeries::RiskAssessment);
531 assert_eq!(IsaStandard::Isa500.series(), IsaSeries::AuditEvidence);
532 assert_eq!(IsaStandard::Isa700.series(), IsaSeries::Reporting);
533 }
534
535 #[test]
536 fn test_isa_requirement_reference() {
537 let req = IsaRequirement::new(
538 IsaStandard::Isa315,
539 "25",
540 IsaRequirementType::Requirement,
541 "Identify and assess risks of material misstatement",
542 );
543
544 assert_eq!(req.reference(), "ISA 315.25");
545 assert!(req.is_mandatory);
546 }
547
548 #[test]
549 fn test_procedure_mapping() {
550 let mut mapping =
551 IsaProcedureMapping::new(Uuid::now_v7(), "Test risk assessment procedures");
552
553 mapping.add_requirement(IsaRequirement::new(
554 IsaStandard::Isa315,
555 "25",
556 IsaRequirementType::Requirement,
557 "Risk assessment",
558 ));
559
560 mapping.add_requirement(IsaRequirement::new(
561 IsaStandard::Isa330,
562 "5",
563 IsaRequirementType::Requirement,
564 "Audit responses",
565 ));
566
567 let standards = mapping.standards_covered();
568 assert_eq!(standards.len(), 2);
569 assert!(standards.contains(&IsaStandard::Isa315));
570 assert!(standards.contains(&IsaStandard::Isa330));
571 }
572
573 #[test]
574 fn test_all_standards() {
575 let all = IsaStandard::all();
576 assert_eq!(all.len(), 34); }
578}