datasynth_group/
resolve.rs1use crate::config::{EntityConfig, GroupConfig};
5use crate::errors::{GroupError, GroupResult};
6
7#[derive(Debug, Clone)]
9pub struct ResolvedEntity {
10 pub code: String,
11 pub name: Option<String>,
12 pub country: String,
13 pub functional_currency: String,
14 pub scoping_profile_name: String,
15 pub consolidation_method: crate::config::ConsolidationMethod,
16 pub ownership_percent: Option<rust_decimal::Decimal>,
17 pub parent_code: Option<String>,
18 pub accounting_framework: String,
19 pub industry: String,
20 pub process_models: Vec<String>,
21 pub rows: Option<u64>,
22 pub merged_config: serde_yaml::Value,
25}
26
27pub fn resolve_entity(cfg: &GroupConfig, code: &str) -> GroupResult<ResolvedEntity> {
29 let entity = cfg
30 .ownership
31 .entities
32 .iter()
33 .find(|e| e.code == code)
34 .ok_or_else(|| GroupError::Config(format!("entity {code} not found")))?;
35 resolve_entity_inner(cfg, entity)
36}
37
38fn resolve_entity_inner(cfg: &GroupConfig, entity: &EntityConfig) -> GroupResult<ResolvedEntity> {
39 let mut merged = cfg.defaults.clone();
41 if merged.is_null() {
42 merged = serde_yaml::Value::Mapping(Default::default());
43 }
44
45 let profile = cfg
47 .scoping_profiles
48 .get(&entity.scoping_profile)
49 .ok_or_else(|| {
50 GroupError::Config(format!(
51 "entity {} references unknown scoping_profile {}",
52 entity.code, entity.scoping_profile
53 ))
54 })?;
55 deep_merge(&mut merged, profile);
56
57 for (k, v) in &entity.overrides {
59 deep_merge_key(&mut merged, k, v);
60 }
61
62 let accounting_framework = entity
64 .accounting_framework
65 .clone()
66 .or_else(|| read_str(&merged, "accounting_framework"))
67 .unwrap_or_else(|| "ifrs".to_string());
68 let industry = entity
69 .industry
70 .clone()
71 .or_else(|| read_str(&merged, "industry"))
72 .unwrap_or_else(|| "manufacturing".to_string());
73 let process_models = read_str_vec(&merged, "process_models").unwrap_or_default();
74
75 Ok(ResolvedEntity {
76 code: entity.code.clone(),
77 name: entity.name.clone(),
78 country: entity.country.clone(),
79 functional_currency: entity.functional_currency.clone(),
80 scoping_profile_name: entity.scoping_profile.clone(),
81 consolidation_method: entity.consolidation_method,
82 ownership_percent: entity.ownership_percent,
83 parent_code: entity.parent_code.clone(),
84 accounting_framework,
85 industry,
86 process_models,
87 rows: entity.rows,
88 merged_config: merged,
89 })
90}
91
92fn deep_merge(base: &mut serde_yaml::Value, overlay: &serde_yaml::Value) {
95 use serde_yaml::Value::Mapping;
96 match (base, overlay) {
97 (Mapping(bm), Mapping(om)) => {
98 for (k, v) in om {
99 if let Some(bv) = bm.get_mut(k) {
100 deep_merge(bv, v);
101 } else {
102 bm.insert(k.clone(), v.clone());
103 }
104 }
105 }
106 (b, o) => *b = o.clone(),
107 }
108}
109
110fn deep_merge_key(base: &mut serde_yaml::Value, key: &str, value: &serde_yaml::Value) {
111 let k = serde_yaml::Value::String(key.into());
112 match base {
113 serde_yaml::Value::Mapping(m) => {
114 if let Some(existing) = m.get_mut(&k) {
115 deep_merge(existing, value);
116 } else {
117 m.insert(k, value.clone());
118 }
119 }
120 _ => {
121 let mut m = serde_yaml::Mapping::new();
122 m.insert(k, value.clone());
123 *base = serde_yaml::Value::Mapping(m);
124 }
125 }
126}
127
128fn read_str(v: &serde_yaml::Value, key: &str) -> Option<String> {
129 v.as_mapping()?
130 .get(serde_yaml::Value::String(key.into()))?
131 .as_str()
132 .map(String::from)
133}
134
135fn read_str_vec(v: &serde_yaml::Value, key: &str) -> Option<Vec<String>> {
136 let seq = v
137 .as_mapping()?
138 .get(serde_yaml::Value::String(key.into()))?
139 .as_sequence()?;
140 Some(
141 seq.iter()
142 .filter_map(|x| x.as_str().map(String::from))
143 .collect(),
144 )
145}