datasynth_core/models/
internal_control.rs

1//! Internal Controls System (ICS) definitions for SOX compliance.
2//!
3//! Provides structures for modeling internal controls, control testing,
4//! and SOX 404 compliance markers in synthetic accounting data.
5
6use serde::{Deserialize, Serialize};
7
8use super::user::UserPersona;
9
10/// Control type based on SOX 404 framework.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum ControlType {
14    /// Prevents errors/fraud before they occur
15    Preventive,
16    /// Detects errors/fraud after they occur
17    Detective,
18    /// Continuous monitoring and analytics
19    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/// Control testing frequency.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum ControlFrequency {
36    /// Applied to every transaction
37    Transactional,
38    /// Performed daily
39    Daily,
40    /// Performed weekly
41    Weekly,
42    /// Performed monthly
43    Monthly,
44    /// Performed quarterly
45    Quarterly,
46    /// Performed annually
47    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/// Risk level for controls and control deficiencies.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum RiskLevel {
67    /// Low risk - minor impact
68    Low,
69    /// Medium risk - moderate impact
70    Medium,
71    /// High risk - significant impact
72    High,
73    /// Critical risk - material impact on financial statements
74    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/// SOX 404 financial statement assertions.
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
90#[serde(rename_all = "snake_case")]
91pub enum SoxAssertion {
92    /// Transactions and events have been recorded
93    Existence,
94    /// All transactions have been recorded
95    Completeness,
96    /// Amounts are recorded at appropriate values
97    Valuation,
98    /// Entity has rights to assets and obligations for liabilities
99    RightsAndObligations,
100    /// Components are properly classified and disclosed
101    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/// Control status for transaction-level tracking.
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
118#[serde(rename_all = "snake_case")]
119pub enum ControlStatus {
120    /// Control operating effectively
121    #[default]
122    Effective,
123    /// Control exception/deficiency found
124    Exception,
125    /// Control not yet tested
126    NotTested,
127    /// Exception has been remediated
128    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/// Internal control definition.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct InternalControl {
145    /// Unique control identifier (e.g., "C001", "C010")
146    pub control_id: String,
147    /// Control name/title
148    pub control_name: String,
149    /// Type of control (Preventive, Detective, Monitoring)
150    pub control_type: ControlType,
151    /// Control objective description
152    pub objective: String,
153    /// How often the control is performed
154    pub frequency: ControlFrequency,
155    /// Role responsible for executing/owning the control
156    pub owner_role: UserPersona,
157    /// Risk level associated with control failure
158    pub risk_level: RiskLevel,
159    /// Detailed description of the control procedure
160    pub description: String,
161    /// Whether this is a SOX 404 key control
162    pub is_key_control: bool,
163    /// SOX assertion this control addresses
164    pub sox_assertion: SoxAssertion,
165}
166
167impl InternalControl {
168    /// Create a new internal control.
169    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    /// Builder method to set frequency.
190    pub fn with_frequency(mut self, frequency: ControlFrequency) -> Self {
191        self.frequency = frequency;
192        self
193    }
194
195    /// Builder method to set owner role.
196    pub fn with_owner(mut self, owner: UserPersona) -> Self {
197        self.owner_role = owner;
198        self
199    }
200
201    /// Builder method to set risk level.
202    pub fn with_risk_level(mut self, level: RiskLevel) -> Self {
203        self.risk_level = level;
204        self
205    }
206
207    /// Builder method to set description.
208    pub fn with_description(mut self, description: impl Into<String>) -> Self {
209        self.description = description.into();
210        self
211    }
212
213    /// Builder method to mark as key control.
214    pub fn as_key_control(mut self) -> Self {
215        self.is_key_control = true;
216        self
217    }
218
219    /// Builder method to set SOX assertion.
220    pub fn with_assertion(mut self, assertion: SoxAssertion) -> Self {
221        self.sox_assertion = assertion;
222        self
223    }
224
225    /// Generate standard controls for a typical organization.
226    pub fn standard_controls() -> Vec<Self> {
227        vec![
228            // Cash controls
229            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            // Large transaction approval
245            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            // P2P Three-Way Match
261            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            // Vendor Master Maintenance
277            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            // O2C Revenue Recognition
293            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            // Credit Limit Enforcement
309            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            // GL Account Reconciliation
324            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            // Journal Entry Review
340            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            // Period Close Review
356            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            // Payroll Processing
371            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            // Fixed Asset Additions
387            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            // Intercompany Reconciliation
402            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        // Verify key controls exist
449        let key_controls: Vec<_> = controls.iter().filter(|c| c.is_key_control).collect();
450        assert!(key_controls.len() >= 5);
451
452        // Verify different control types exist
453        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}