use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ScotSignificance {
High,
Medium,
Low,
}
impl std::fmt::Display for ScotSignificance {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::High => "High",
Self::Medium => "Medium",
Self::Low => "Low",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ScotTransactionType {
Routine,
NonRoutine,
Estimation,
}
impl std::fmt::Display for ScotTransactionType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Routine => "Routine",
Self::NonRoutine => "Non-Routine",
Self::Estimation => "Estimation",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProcessingMethod {
FullyAutomated,
SemiAutomated,
Manual,
}
impl std::fmt::Display for ProcessingMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::FullyAutomated => "Fully Automated",
Self::SemiAutomated => "Semi-Automated",
Self::Manual => "Manual",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EstimationComplexity {
Simple,
Moderate,
Complex,
}
impl std::fmt::Display for EstimationComplexity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Simple => "Simple",
Self::Moderate => "Moderate",
Self::Complex => "Complex",
};
write!(f, "{s}")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CriticalPathStage {
pub stage_name: String,
pub description: String,
pub is_automated: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_control_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignificantClassOfTransactions {
pub id: String,
pub entity_code: String,
pub scot_name: String,
pub business_process: String,
pub significance_level: ScotSignificance,
pub transaction_type: ScotTransactionType,
pub processing_method: ProcessingMethod,
pub volume: usize,
#[serde(with = "crate::serde_decimal")]
pub monetary_value: Decimal,
pub critical_path: Vec<CriticalPathStage>,
pub relevant_assertions: Vec<String>,
pub related_account_areas: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub estimation_complexity: Option<EstimationComplexity>,
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn scot_display_impls() {
assert_eq!(ScotSignificance::High.to_string(), "High");
assert_eq!(ScotTransactionType::Estimation.to_string(), "Estimation");
assert_eq!(
ProcessingMethod::FullyAutomated.to_string(),
"Fully Automated"
);
assert_eq!(EstimationComplexity::Complex.to_string(), "Complex");
}
#[test]
fn scot_structure() {
let scot = SignificantClassOfTransactions {
id: "SCOT-C001-REVENUE_PRODUCT_SALES".into(),
entity_code: "C001".into(),
scot_name: "Revenue — Product Sales".into(),
business_process: "O2C".into(),
significance_level: ScotSignificance::High,
transaction_type: ScotTransactionType::Routine,
processing_method: ProcessingMethod::SemiAutomated,
volume: 5_000,
monetary_value: dec!(10_000_000),
critical_path: vec![
CriticalPathStage {
stage_name: "Initiation".into(),
description: "Sales order created by customer / sales team".into(),
is_automated: false,
key_control_id: Some("C001".into()),
},
CriticalPathStage {
stage_name: "Recording".into(),
description: "System records SO upon credit approval".into(),
is_automated: true,
key_control_id: None,
},
],
relevant_assertions: vec!["Occurrence".into(), "Accuracy".into()],
related_account_areas: vec!["Revenue".into(), "Trade Receivables".into()],
estimation_complexity: None,
};
assert_eq!(scot.critical_path.len(), 2);
assert!(scot.estimation_complexity.is_none());
assert_eq!(scot.significance_level, ScotSignificance::High);
}
#[test]
fn estimation_scot_has_complexity() {
let scot = SignificantClassOfTransactions {
id: "SCOT-C001-ECL_BAD_DEBT".into(),
entity_code: "C001".into(),
scot_name: "ECL / Bad Debt Provision".into(),
business_process: "R2R".into(),
significance_level: ScotSignificance::High,
transaction_type: ScotTransactionType::Estimation,
processing_method: ProcessingMethod::Manual,
volume: 12,
monetary_value: dec!(250_000),
critical_path: Vec::new(),
relevant_assertions: vec!["ValuationAndAllocation".into()],
related_account_areas: vec!["Trade Receivables".into(), "Provisions".into()],
estimation_complexity: Some(EstimationComplexity::Moderate),
};
assert!(scot.estimation_complexity.is_some());
assert_eq!(
scot.estimation_complexity.unwrap(),
EstimationComplexity::Moderate
);
}
}