use crate::error::EvalResult;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentChainEvaluation {
pub p2p_total: usize,
pub p2p_complete: usize,
pub p2p_completion_rate: f64,
pub p2p_po_created: usize,
pub p2p_gr_created: usize,
pub p2p_invoice_created: usize,
pub p2p_payment_created: usize,
pub p2p_three_way_match_passed: usize,
pub p2p_three_way_match_rate: f64,
pub o2c_total: usize,
pub o2c_complete: usize,
pub o2c_completion_rate: f64,
pub o2c_so_created: usize,
pub o2c_delivery_created: usize,
pub o2c_invoice_created: usize,
pub o2c_receipt_created: usize,
pub o2c_credit_check_passed: usize,
pub o2c_credit_check_rate: f64,
pub orphan_documents: usize,
pub broken_references: usize,
pub reference_integrity_score: f64,
}
#[derive(Debug, Clone)]
pub struct P2PChainData {
pub is_complete: bool,
pub has_po: bool,
pub has_gr: bool,
pub has_invoice: bool,
pub has_payment: bool,
pub three_way_match_passed: bool,
}
#[derive(Debug, Clone)]
pub struct O2CChainData {
pub is_complete: bool,
pub has_so: bool,
pub has_delivery: bool,
pub has_invoice: bool,
pub has_receipt: bool,
pub credit_check_passed: bool,
}
#[derive(Debug, Clone)]
pub struct DocumentReferenceData {
pub total_references: usize,
pub valid_references: usize,
pub orphan_count: usize,
}
pub struct DocumentChainEvaluator;
impl DocumentChainEvaluator {
pub fn new() -> Self {
Self
}
pub fn evaluate(
&self,
p2p_chains: &[P2PChainData],
o2c_chains: &[O2CChainData],
references: &DocumentReferenceData,
) -> EvalResult<DocumentChainEvaluation> {
let p2p_total = p2p_chains.len();
let p2p_complete = p2p_chains.iter().filter(|c| c.is_complete).count();
let p2p_completion_rate = if p2p_total > 0 {
p2p_complete as f64 / p2p_total as f64
} else {
1.0
};
let p2p_po_created = p2p_chains.iter().filter(|c| c.has_po).count();
let p2p_gr_created = p2p_chains.iter().filter(|c| c.has_gr).count();
let p2p_invoice_created = p2p_chains.iter().filter(|c| c.has_invoice).count();
let p2p_payment_created = p2p_chains.iter().filter(|c| c.has_payment).count();
let p2p_three_way_match_passed = p2p_chains
.iter()
.filter(|c| c.three_way_match_passed)
.count();
let p2p_three_way_match_rate = if p2p_total > 0 {
p2p_three_way_match_passed as f64 / p2p_total as f64
} else {
1.0
};
let o2c_total = o2c_chains.len();
let o2c_complete = o2c_chains.iter().filter(|c| c.is_complete).count();
let o2c_completion_rate = if o2c_total > 0 {
o2c_complete as f64 / o2c_total as f64
} else {
1.0
};
let o2c_so_created = o2c_chains.iter().filter(|c| c.has_so).count();
let o2c_delivery_created = o2c_chains.iter().filter(|c| c.has_delivery).count();
let o2c_invoice_created = o2c_chains.iter().filter(|c| c.has_invoice).count();
let o2c_receipt_created = o2c_chains.iter().filter(|c| c.has_receipt).count();
let o2c_credit_check_passed = o2c_chains.iter().filter(|c| c.credit_check_passed).count();
let o2c_credit_check_rate = if o2c_total > 0 {
o2c_credit_check_passed as f64 / o2c_total as f64
} else {
1.0
};
let broken_references = references.total_references - references.valid_references;
let reference_integrity_score = if references.total_references > 0 {
references.valid_references as f64 / references.total_references as f64
} else {
1.0
};
Ok(DocumentChainEvaluation {
p2p_total,
p2p_complete,
p2p_completion_rate,
p2p_po_created,
p2p_gr_created,
p2p_invoice_created,
p2p_payment_created,
p2p_three_way_match_passed,
p2p_three_way_match_rate,
o2c_total,
o2c_complete,
o2c_completion_rate,
o2c_so_created,
o2c_delivery_created,
o2c_invoice_created,
o2c_receipt_created,
o2c_credit_check_passed,
o2c_credit_check_rate,
orphan_documents: references.orphan_count,
broken_references,
reference_integrity_score,
})
}
}
impl Default for DocumentChainEvaluator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_complete_p2p_chains() {
let p2p_chains = vec![
P2PChainData {
is_complete: true,
has_po: true,
has_gr: true,
has_invoice: true,
has_payment: true,
three_way_match_passed: true,
},
P2PChainData {
is_complete: true,
has_po: true,
has_gr: true,
has_invoice: true,
has_payment: true,
three_way_match_passed: true,
},
];
let o2c_chains = vec![];
let references = DocumentReferenceData {
total_references: 10,
valid_references: 10,
orphan_count: 0,
};
let evaluator = DocumentChainEvaluator::new();
let result = evaluator
.evaluate(&p2p_chains, &o2c_chains, &references)
.unwrap();
assert_eq!(result.p2p_total, 2);
assert_eq!(result.p2p_complete, 2);
assert_eq!(result.p2p_completion_rate, 1.0);
assert_eq!(result.p2p_three_way_match_rate, 1.0);
}
#[test]
fn test_incomplete_chains() {
let p2p_chains = vec![P2PChainData {
is_complete: false,
has_po: true,
has_gr: true,
has_invoice: false,
has_payment: false,
three_way_match_passed: false,
}];
let o2c_chains = vec![];
let references = DocumentReferenceData {
total_references: 5,
valid_references: 4,
orphan_count: 1,
};
let evaluator = DocumentChainEvaluator::new();
let result = evaluator
.evaluate(&p2p_chains, &o2c_chains, &references)
.unwrap();
assert_eq!(result.p2p_completion_rate, 0.0);
assert_eq!(result.broken_references, 1);
assert_eq!(result.reference_integrity_score, 0.8);
}
}