use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditOpinion {
pub opinion_id: Uuid,
pub engagement_id: Uuid,
pub opinion_date: NaiveDate,
pub opinion_type: OpinionType,
pub key_audit_matters: Vec<KeyAuditMatter>,
pub modification: Option<OpinionModification>,
pub emphasis_of_matter: Vec<EmphasisOfMatter>,
pub other_matter: Vec<OtherMatter>,
pub going_concern_conclusion: GoingConcernConclusion,
pub material_uncertainty_going_concern: bool,
pub pcaob_compliance: Option<PcaobOpinionElements>,
pub period_end_date: NaiveDate,
pub entity_name: String,
pub auditor_name: String,
pub engagement_partner: String,
pub eqcr_performed: bool,
}
impl AuditOpinion {
pub fn new(
engagement_id: Uuid,
opinion_date: NaiveDate,
opinion_type: OpinionType,
entity_name: impl Into<String>,
period_end_date: NaiveDate,
) -> Self {
Self {
opinion_id: Uuid::now_v7(),
engagement_id,
opinion_date,
opinion_type,
key_audit_matters: Vec::new(),
modification: None,
emphasis_of_matter: Vec::new(),
other_matter: Vec::new(),
going_concern_conclusion: GoingConcernConclusion::default(),
material_uncertainty_going_concern: false,
pcaob_compliance: None,
period_end_date,
entity_name: entity_name.into(),
auditor_name: String::new(),
engagement_partner: String::new(),
eqcr_performed: false,
}
}
pub fn is_unmodified(&self) -> bool {
matches!(self.opinion_type, OpinionType::Unmodified)
}
pub fn is_modified(&self) -> bool {
!self.is_unmodified()
}
pub fn add_kam(&mut self, kam: KeyAuditMatter) {
self.key_audit_matters.push(kam);
}
pub fn add_eom(&mut self, eom: EmphasisOfMatter) {
self.emphasis_of_matter.push(eom);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum OpinionType {
#[default]
Unmodified,
Qualified,
Adverse,
Disclaimer,
}
impl std::fmt::Display for OpinionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unmodified => write!(f, "Unmodified"),
Self::Qualified => write!(f, "Qualified"),
Self::Adverse => write!(f, "Adverse"),
Self::Disclaimer => write!(f, "Disclaimer"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyAuditMatter {
pub kam_id: Uuid,
pub title: String,
pub significance_explanation: String,
pub audit_response: String,
pub financial_statement_area: String,
pub romm_level: RiskLevel,
pub related_finding_ids: Vec<Uuid>,
pub workpaper_references: Vec<String>,
}
impl KeyAuditMatter {
pub fn new(
title: impl Into<String>,
significance_explanation: impl Into<String>,
audit_response: impl Into<String>,
financial_statement_area: impl Into<String>,
) -> Self {
Self {
kam_id: Uuid::now_v7(),
title: title.into(),
significance_explanation: significance_explanation.into(),
audit_response: audit_response.into(),
financial_statement_area: financial_statement_area.into(),
romm_level: RiskLevel::High,
related_finding_ids: Vec::new(),
workpaper_references: Vec::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum RiskLevel {
Low,
#[default]
Medium,
High,
VeryHigh,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpinionModification {
pub basis: ModificationBasis,
pub matter_description: String,
pub financial_effects: FinancialEffects,
pub is_pervasive: bool,
pub affected_areas: Vec<String>,
pub misstatement_amount: Option<rust_decimal::Decimal>,
pub relates_to_prior_period: bool,
pub relates_to_going_concern: bool,
}
impl OpinionModification {
pub fn new(basis: ModificationBasis, matter_description: impl Into<String>) -> Self {
Self {
basis,
matter_description: matter_description.into(),
financial_effects: FinancialEffects::default(),
is_pervasive: false,
affected_areas: Vec::new(),
misstatement_amount: None,
relates_to_prior_period: false,
relates_to_going_concern: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ModificationBasis {
MaterialMisstatement,
InabilityToObtainEvidence,
Both,
}
impl std::fmt::Display for ModificationBasis {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MaterialMisstatement => write!(f, "Material Misstatement"),
Self::InabilityToObtainEvidence => write!(f, "Inability to Obtain Evidence"),
Self::Both => write!(f, "Material Misstatement and Scope Limitation"),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FinancialEffects {
pub assets_effect: String,
pub liabilities_effect: String,
pub equity_effect: String,
pub revenue_effect: String,
pub expenses_effect: String,
pub cash_flows_effect: String,
pub disclosures_effect: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmphasisOfMatter {
pub eom_id: Uuid,
pub matter: EomMatter,
pub description: String,
pub note_reference: String,
pub appropriately_presented: bool,
}
impl EmphasisOfMatter {
pub fn new(matter: EomMatter, description: impl Into<String>) -> Self {
Self {
eom_id: Uuid::now_v7(),
matter,
description: description.into(),
note_reference: String::new(),
appropriately_presented: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EomMatter {
GoingConcern,
MajorCatastrophe,
RelatedPartyTransactions,
SubsequentEvent,
AccountingPolicyChange,
NewStandardAdoption,
Litigation,
RegulatoryAction,
Other,
}
impl std::fmt::Display for EomMatter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::GoingConcern => write!(f, "Going Concern"),
Self::MajorCatastrophe => write!(f, "Major Catastrophe"),
Self::RelatedPartyTransactions => write!(f, "Related Party Transactions"),
Self::SubsequentEvent => write!(f, "Subsequent Event"),
Self::AccountingPolicyChange => write!(f, "Accounting Policy Change"),
Self::NewStandardAdoption => write!(f, "New Standard Adoption"),
Self::Litigation => write!(f, "Litigation"),
Self::RegulatoryAction => write!(f, "Regulatory Action"),
Self::Other => write!(f, "Other"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OtherMatter {
pub om_id: Uuid,
pub matter_type: OtherMatterType,
pub description: String,
}
impl OtherMatter {
pub fn new(matter_type: OtherMatterType, description: impl Into<String>) -> Self {
Self {
om_id: Uuid::now_v7(),
matter_type,
description: description.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OtherMatterType {
PredecessorAuditor,
PriorPeriodNotAudited,
SupplementaryInformation,
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct GoingConcernConclusion {
pub conclusion: GoingConcernAssessment,
pub material_uncertainty_exists: bool,
pub adequately_disclosed: bool,
pub events_conditions: Vec<String>,
pub management_plans: String,
pub auditor_evaluation: String,
pub opinion_impact: Option<OpinionType>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum GoingConcernAssessment {
#[default]
NoMaterialUncertainty,
MaterialUncertaintyAdequatelyDisclosed,
MaterialUncertaintyNotAdequatelyDisclosed,
GoingConcernInappropriate,
}
impl std::fmt::Display for GoingConcernAssessment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoMaterialUncertainty => write!(f, "No Material Uncertainty"),
Self::MaterialUncertaintyAdequatelyDisclosed => {
write!(f, "Material Uncertainty - Adequately Disclosed")
}
Self::MaterialUncertaintyNotAdequatelyDisclosed => {
write!(f, "Material Uncertainty - Not Adequately Disclosed")
}
Self::GoingConcernInappropriate => write!(f, "Going Concern Basis Inappropriate"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PcaobOpinionElements {
pub is_integrated_audit: bool,
pub icfr_opinion: Option<IcfrOpinion>,
pub critical_audit_matters: Vec<KeyAuditMatter>,
pub auditor_tenure_years: Option<u32>,
pub pcaob_registration_number: Option<String>,
}
impl PcaobOpinionElements {
pub fn new(is_integrated_audit: bool) -> Self {
Self {
is_integrated_audit,
icfr_opinion: None,
critical_audit_matters: Vec::new(),
auditor_tenure_years: None,
pcaob_registration_number: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IcfrOpinion {
pub opinion_type: IcfrOpinionType,
pub material_weaknesses: Vec<MaterialWeakness>,
pub significant_deficiencies: Vec<String>,
pub scope_limitations: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum IcfrOpinionType {
#[default]
Effective,
Adverse,
Disclaimer,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaterialWeakness {
pub weakness_id: Uuid,
pub description: String,
pub affected_controls: Vec<String>,
pub affected_accounts: Vec<String>,
pub potential_misstatement: String,
}
impl MaterialWeakness {
pub fn new(description: impl Into<String>) -> Self {
Self {
weakness_id: Uuid::now_v7(),
description: description.into(),
affected_controls: Vec::new(),
affected_accounts: Vec::new(),
potential_misstatement: String::new(),
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_audit_opinion_creation() {
let opinion = AuditOpinion::new(
Uuid::now_v7(),
NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(),
OpinionType::Unmodified,
"Test Company Inc.",
NaiveDate::from_ymd_opt(2023, 12, 31).unwrap(),
);
assert!(opinion.is_unmodified());
assert!(!opinion.is_modified());
assert!(opinion.key_audit_matters.is_empty());
}
#[test]
fn test_modified_opinion() {
let mut opinion = AuditOpinion::new(
Uuid::now_v7(),
NaiveDate::from_ymd_opt(2024, 3, 15).unwrap(),
OpinionType::Qualified,
"Test Company Inc.",
NaiveDate::from_ymd_opt(2023, 12, 31).unwrap(),
);
opinion.modification = Some(OpinionModification::new(
ModificationBasis::MaterialMisstatement,
"Inventory was not properly valued",
));
assert!(opinion.is_modified());
assert!(opinion.modification.is_some());
}
#[test]
fn test_key_audit_matter() {
let kam = KeyAuditMatter::new(
"Revenue Recognition",
"Complex multi-element arrangements require significant judgment",
"We tested controls over revenue recognition and performed substantive testing",
"Revenue",
);
assert_eq!(kam.title, "Revenue Recognition");
assert_eq!(kam.romm_level, RiskLevel::High);
}
#[test]
fn test_going_concern() {
let gc = GoingConcernConclusion {
conclusion: GoingConcernAssessment::MaterialUncertaintyAdequatelyDisclosed,
material_uncertainty_exists: true,
adequately_disclosed: true,
..Default::default()
};
assert!(gc.material_uncertainty_exists);
assert!(gc.adequately_disclosed);
}
#[test]
fn test_pcaob_elements() {
let mut pcaob = PcaobOpinionElements::new(true);
pcaob.auditor_tenure_years = Some(5);
pcaob.icfr_opinion = Some(IcfrOpinion {
opinion_type: IcfrOpinionType::Effective,
material_weaknesses: Vec::new(),
significant_deficiencies: Vec::new(),
scope_limitations: Vec::new(),
});
assert!(pcaob.is_integrated_audit);
assert!(pcaob.icfr_opinion.is_some());
}
}