1use serde::{Deserialize, Serialize};
7
8use super::user::UserPersona;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum ControlType {
14 Preventive,
16 Detective,
18 Monitoring,
20}
21
22impl std::fmt::Display for ControlType {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 Self::Preventive => write!(f, "Preventive"),
26 Self::Detective => write!(f, "Detective"),
27 Self::Monitoring => write!(f, "Monitoring"),
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum ControlFrequency {
36 Transactional,
38 Daily,
40 Weekly,
42 Monthly,
44 Quarterly,
46 Annual,
48}
49
50impl std::fmt::Display for ControlFrequency {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 match self {
53 Self::Transactional => write!(f, "Transactional"),
54 Self::Daily => write!(f, "Daily"),
55 Self::Weekly => write!(f, "Weekly"),
56 Self::Monthly => write!(f, "Monthly"),
57 Self::Quarterly => write!(f, "Quarterly"),
58 Self::Annual => write!(f, "Annual"),
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum RiskLevel {
67 Low,
69 Medium,
71 High,
73 Critical,
75}
76
77impl std::fmt::Display for RiskLevel {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 Self::Low => write!(f, "Low"),
81 Self::Medium => write!(f, "Medium"),
82 Self::High => write!(f, "High"),
83 Self::Critical => write!(f, "Critical"),
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
90#[serde(rename_all = "snake_case")]
91pub enum SoxAssertion {
92 Existence,
94 Completeness,
96 Valuation,
98 RightsAndObligations,
100 PresentationAndDisclosure,
102}
103
104impl std::fmt::Display for SoxAssertion {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 match self {
107 Self::Existence => write!(f, "Existence"),
108 Self::Completeness => write!(f, "Completeness"),
109 Self::Valuation => write!(f, "Valuation"),
110 Self::RightsAndObligations => write!(f, "RightsAndObligations"),
111 Self::PresentationAndDisclosure => write!(f, "PresentationAndDisclosure"),
112 }
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
118#[serde(rename_all = "snake_case")]
119pub enum ControlStatus {
120 #[default]
122 Effective,
123 Exception,
125 NotTested,
127 Remediated,
129}
130
131impl std::fmt::Display for ControlStatus {
132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133 match self {
134 Self::Effective => write!(f, "Effective"),
135 Self::Exception => write!(f, "Exception"),
136 Self::NotTested => write!(f, "NotTested"),
137 Self::Remediated => write!(f, "Remediated"),
138 }
139 }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct InternalControl {
145 pub control_id: String,
147 pub control_name: String,
149 pub control_type: ControlType,
151 pub objective: String,
153 pub frequency: ControlFrequency,
155 pub owner_role: UserPersona,
157 pub risk_level: RiskLevel,
159 pub description: String,
161 pub is_key_control: bool,
163 pub sox_assertion: SoxAssertion,
165}
166
167impl InternalControl {
168 pub fn new(
170 control_id: impl Into<String>,
171 control_name: impl Into<String>,
172 control_type: ControlType,
173 objective: impl Into<String>,
174 ) -> Self {
175 Self {
176 control_id: control_id.into(),
177 control_name: control_name.into(),
178 control_type,
179 objective: objective.into(),
180 frequency: ControlFrequency::Transactional,
181 owner_role: UserPersona::Controller,
182 risk_level: RiskLevel::Medium,
183 description: String::new(),
184 is_key_control: false,
185 sox_assertion: SoxAssertion::Existence,
186 }
187 }
188
189 pub fn with_frequency(mut self, frequency: ControlFrequency) -> Self {
191 self.frequency = frequency;
192 self
193 }
194
195 pub fn with_owner(mut self, owner: UserPersona) -> Self {
197 self.owner_role = owner;
198 self
199 }
200
201 pub fn with_risk_level(mut self, level: RiskLevel) -> Self {
203 self.risk_level = level;
204 self
205 }
206
207 pub fn with_description(mut self, description: impl Into<String>) -> Self {
209 self.description = description.into();
210 self
211 }
212
213 pub fn as_key_control(mut self) -> Self {
215 self.is_key_control = true;
216 self
217 }
218
219 pub fn with_assertion(mut self, assertion: SoxAssertion) -> Self {
221 self.sox_assertion = assertion;
222 self
223 }
224
225 pub fn standard_controls() -> Vec<Self> {
227 vec![
228 Self::new(
230 "C001",
231 "Cash Account Daily Review",
232 ControlType::Detective,
233 "Review all cash transactions daily for unauthorized activity",
234 )
235 .with_frequency(ControlFrequency::Daily)
236 .with_owner(UserPersona::Controller)
237 .with_risk_level(RiskLevel::High)
238 .as_key_control()
239 .with_assertion(SoxAssertion::Existence)
240 .with_description(
241 "Daily reconciliation of cash accounts with bank statements and review of unusual transactions",
242 ),
243
244 Self::new(
246 "C002",
247 "Large Transaction Multi-Level Approval",
248 ControlType::Preventive,
249 "Transactions over $10,000 require additional approval levels",
250 )
251 .with_frequency(ControlFrequency::Transactional)
252 .with_owner(UserPersona::Manager)
253 .with_risk_level(RiskLevel::High)
254 .as_key_control()
255 .with_assertion(SoxAssertion::Valuation)
256 .with_description(
257 "Multi-level approval workflow for transactions exceeding defined thresholds",
258 ),
259
260 Self::new(
262 "C010",
263 "Three-Way Match",
264 ControlType::Preventive,
265 "Match purchase order, receipt, and invoice before payment",
266 )
267 .with_frequency(ControlFrequency::Transactional)
268 .with_owner(UserPersona::SeniorAccountant)
269 .with_risk_level(RiskLevel::Medium)
270 .as_key_control()
271 .with_assertion(SoxAssertion::Completeness)
272 .with_description(
273 "Automated matching of PO, goods receipt, and vendor invoice prior to payment release",
274 ),
275
276 Self::new(
278 "C011",
279 "Vendor Master Data Maintenance",
280 ControlType::Preventive,
281 "Segregated access for vendor master data changes",
282 )
283 .with_frequency(ControlFrequency::Transactional)
284 .with_owner(UserPersona::SeniorAccountant)
285 .with_risk_level(RiskLevel::High)
286 .as_key_control()
287 .with_assertion(SoxAssertion::Existence)
288 .with_description(
289 "Restricted access to vendor master data with dual-approval for bank account changes",
290 ),
291
292 Self::new(
294 "C020",
295 "Revenue Recognition Review",
296 ControlType::Detective,
297 "Review revenue entries for proper timing and classification",
298 )
299 .with_frequency(ControlFrequency::Monthly)
300 .with_owner(UserPersona::Controller)
301 .with_risk_level(RiskLevel::Critical)
302 .as_key_control()
303 .with_assertion(SoxAssertion::Valuation)
304 .with_description(
305 "Monthly review of revenue recognition to ensure compliance with ASC 606",
306 ),
307
308 Self::new(
310 "C021",
311 "Customer Credit Limit Check",
312 ControlType::Preventive,
313 "Automatic credit limit check before order acceptance",
314 )
315 .with_frequency(ControlFrequency::Transactional)
316 .with_owner(UserPersona::AutomatedSystem)
317 .with_risk_level(RiskLevel::Medium)
318 .with_assertion(SoxAssertion::Valuation)
319 .with_description(
320 "System-enforced credit limit validation at order entry",
321 ),
322
323 Self::new(
325 "C030",
326 "GL Account Reconciliation",
327 ControlType::Detective,
328 "Monthly reconciliation of all balance sheet accounts",
329 )
330 .with_frequency(ControlFrequency::Monthly)
331 .with_owner(UserPersona::SeniorAccountant)
332 .with_risk_level(RiskLevel::High)
333 .as_key_control()
334 .with_assertion(SoxAssertion::Completeness)
335 .with_description(
336 "Complete reconciliation of all balance sheet accounts with supporting documentation",
337 ),
338
339 Self::new(
341 "C031",
342 "Manual Journal Entry Review",
343 ControlType::Detective,
344 "Review of all manual journal entries over threshold",
345 )
346 .with_frequency(ControlFrequency::Daily)
347 .with_owner(UserPersona::Controller)
348 .with_risk_level(RiskLevel::High)
349 .as_key_control()
350 .with_assertion(SoxAssertion::Existence)
351 .with_description(
352 "Daily review of manual journal entries with supporting documentation",
353 ),
354
355 Self::new(
357 "C032",
358 "Period Close Checklist",
359 ControlType::Detective,
360 "Comprehensive checklist for period-end close procedures",
361 )
362 .with_frequency(ControlFrequency::Monthly)
363 .with_owner(UserPersona::Controller)
364 .with_risk_level(RiskLevel::Medium)
365 .with_assertion(SoxAssertion::Completeness)
366 .with_description(
367 "Standardized period-end close checklist ensuring all procedures completed",
368 ),
369
370 Self::new(
372 "C040",
373 "Payroll Processing Review",
374 ControlType::Detective,
375 "Review of payroll processing for accuracy",
376 )
377 .with_frequency(ControlFrequency::Monthly)
378 .with_owner(UserPersona::Controller)
379 .with_risk_level(RiskLevel::High)
380 .as_key_control()
381 .with_assertion(SoxAssertion::Valuation)
382 .with_description(
383 "Monthly review of payroll journals and reconciliation to HR records",
384 ),
385
386 Self::new(
388 "C050",
389 "Fixed Asset Addition Approval",
390 ControlType::Preventive,
391 "Multi-level approval for capital expenditures",
392 )
393 .with_frequency(ControlFrequency::Transactional)
394 .with_owner(UserPersona::Manager)
395 .with_risk_level(RiskLevel::Medium)
396 .with_assertion(SoxAssertion::Existence)
397 .with_description(
398 "Approval workflow for capital asset additions based on dollar thresholds",
399 ),
400
401 Self::new(
403 "C060",
404 "Intercompany Balance Reconciliation",
405 ControlType::Detective,
406 "Monthly reconciliation of intercompany balances",
407 )
408 .with_frequency(ControlFrequency::Monthly)
409 .with_owner(UserPersona::SeniorAccountant)
410 .with_risk_level(RiskLevel::High)
411 .as_key_control()
412 .with_assertion(SoxAssertion::Completeness)
413 .with_description(
414 "Full reconciliation of intercompany accounts between all entities",
415 ),
416 ]
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn test_control_creation() {
426 let control = InternalControl::new(
427 "TEST001",
428 "Test Control",
429 ControlType::Preventive,
430 "Test objective",
431 )
432 .with_frequency(ControlFrequency::Daily)
433 .with_risk_level(RiskLevel::High)
434 .as_key_control();
435
436 assert_eq!(control.control_id, "TEST001");
437 assert_eq!(control.control_type, ControlType::Preventive);
438 assert_eq!(control.frequency, ControlFrequency::Daily);
439 assert_eq!(control.risk_level, RiskLevel::High);
440 assert!(control.is_key_control);
441 }
442
443 #[test]
444 fn test_standard_controls() {
445 let controls = InternalControl::standard_controls();
446 assert!(!controls.is_empty());
447
448 let key_controls: Vec<_> = controls.iter().filter(|c| c.is_key_control).collect();
450 assert!(key_controls.len() >= 5);
451
452 let preventive: Vec<_> = controls
454 .iter()
455 .filter(|c| c.control_type == ControlType::Preventive)
456 .collect();
457 let detective: Vec<_> = controls
458 .iter()
459 .filter(|c| c.control_type == ControlType::Detective)
460 .collect();
461
462 assert!(!preventive.is_empty());
463 assert!(!detective.is_empty());
464 }
465
466 #[test]
467 fn test_control_status_display() {
468 assert_eq!(ControlStatus::Effective.to_string(), "Effective");
469 assert_eq!(ControlStatus::Exception.to_string(), "Exception");
470 }
471}