Skip to main content

datasynth_generators/master_data/
entity_registry_manager.rs

1//! Entity Registry Manager for coordinated master data generation.
2//!
3//! This module provides a central manager that coordinates the generation
4//! of all master data entities and maintains the entity registry for
5//! referential integrity in transaction generation.
6
7use chrono::NaiveDate;
8use datasynth_core::models::{
9    CustomerPool, Employee, EmployeePool, EntityId, EntityRegistry, EntityType, FixedAsset,
10    FixedAssetPool, Material, MaterialPool, Vendor, VendorPool,
11};
12
13use super::{
14    AssetGenerator, AssetGeneratorConfig, CustomerGenerator, CustomerGeneratorConfig,
15    EmployeeGenerator, EmployeeGeneratorConfig, MaterialGenerator, MaterialGeneratorConfig,
16    VendorGenerator, VendorGeneratorConfig,
17};
18
19/// Configuration for the entity registry manager.
20#[derive(Debug, Clone, Default)]
21pub struct EntityRegistryManagerConfig {
22    /// Vendor generator configuration
23    pub vendor_config: VendorGeneratorConfig,
24    /// Customer generator configuration
25    pub customer_config: CustomerGeneratorConfig,
26    /// Material generator configuration
27    pub material_config: MaterialGeneratorConfig,
28    /// Asset generator configuration
29    pub asset_config: AssetGeneratorConfig,
30    /// Employee generator configuration
31    pub employee_config: EmployeeGeneratorConfig,
32}
33
34/// Counts for master data generation.
35#[derive(Debug, Clone)]
36pub struct MasterDataCounts {
37    /// Number of vendors
38    pub vendors: usize,
39    /// Number of customers
40    pub customers: usize,
41    /// Number of materials
42    pub materials: usize,
43    /// Number of fixed assets
44    pub assets: usize,
45    /// Number of employees (auto-calculated from departments if None)
46    pub employees: Option<usize>,
47}
48
49impl Default for MasterDataCounts {
50    fn default() -> Self {
51        Self {
52            vendors: 100,
53            customers: 200,
54            materials: 500,
55            assets: 150,
56            employees: None,
57        }
58    }
59}
60
61/// Generated master data result.
62#[derive(Debug)]
63pub struct GeneratedMasterData {
64    /// Vendor pool
65    pub vendors: VendorPool,
66    /// Customer pool
67    pub customers: CustomerPool,
68    /// Material pool
69    pub materials: MaterialPool,
70    /// Fixed asset pool
71    pub assets: FixedAssetPool,
72    /// Employee pool
73    pub employees: EmployeePool,
74    /// Entity registry with all entities
75    pub registry: EntityRegistry,
76}
77
78/// Entity Registry Manager for coordinated master data generation.
79pub struct EntityRegistryManager {
80    seed: u64,
81    config: EntityRegistryManagerConfig,
82    vendor_generator: VendorGenerator,
83    customer_generator: CustomerGenerator,
84    material_generator: MaterialGenerator,
85    asset_generator: AssetGenerator,
86    employee_generator: EmployeeGenerator,
87    registry: EntityRegistry,
88}
89
90impl EntityRegistryManager {
91    /// Create a new entity registry manager.
92    pub fn new(seed: u64) -> Self {
93        Self::with_config(seed, EntityRegistryManagerConfig::default())
94    }
95
96    /// Create a new entity registry manager with custom configuration.
97    pub fn with_config(seed: u64, config: EntityRegistryManagerConfig) -> Self {
98        Self {
99            seed,
100            vendor_generator: VendorGenerator::with_config(seed, config.vendor_config.clone()),
101            customer_generator: CustomerGenerator::with_config(
102                seed + 1,
103                config.customer_config.clone(),
104            ),
105            material_generator: MaterialGenerator::with_config(
106                seed + 2,
107                config.material_config.clone(),
108            ),
109            asset_generator: AssetGenerator::with_config(seed + 3, config.asset_config.clone()),
110            employee_generator: EmployeeGenerator::with_config(
111                seed + 4,
112                config.employee_config.clone(),
113            ),
114            registry: EntityRegistry::new(),
115            config,
116        }
117    }
118
119    /// Generate all master data for a company.
120    pub fn generate_master_data(
121        &mut self,
122        company_code: &str,
123        counts: &MasterDataCounts,
124        effective_date: NaiveDate,
125        date_range: (NaiveDate, NaiveDate),
126    ) -> GeneratedMasterData {
127        // Generate vendors
128        let vendors = self.generate_vendors(company_code, counts.vendors, effective_date);
129
130        // Generate customers
131        let customers = self.generate_customers(company_code, counts.customers, effective_date);
132
133        // Generate materials
134        let materials = self.generate_materials(company_code, counts.materials, effective_date);
135
136        // Generate assets
137        let assets = self.generate_assets(company_code, counts.assets, date_range);
138
139        // Generate employees
140        let employees = self.generate_employees(company_code, date_range);
141
142        GeneratedMasterData {
143            vendors,
144            customers,
145            materials,
146            assets,
147            employees,
148            registry: self.registry.clone(),
149        }
150    }
151
152    /// Generate master data for multiple company codes.
153    pub fn generate_multi_company_master_data(
154        &mut self,
155        company_codes: &[String],
156        counts: &MasterDataCounts,
157        effective_date: NaiveDate,
158        date_range: (NaiveDate, NaiveDate),
159    ) -> Vec<GeneratedMasterData> {
160        let mut results = Vec::new();
161
162        for company_code in company_codes {
163            let data = self.generate_master_data(company_code, counts, effective_date, date_range);
164            results.push(data);
165        }
166
167        results
168    }
169
170    /// Generate master data with intercompany relationships.
171    pub fn generate_master_data_with_ic(
172        &mut self,
173        company_codes: &[String],
174        counts: &MasterDataCounts,
175        effective_date: NaiveDate,
176        date_range: (NaiveDate, NaiveDate),
177    ) -> Vec<GeneratedMasterData> {
178        let mut results = Vec::new();
179
180        for company_code in company_codes {
181            // Get partner company codes (all except current)
182            let partners: Vec<String> = company_codes
183                .iter()
184                .filter(|c| *c != company_code)
185                .cloned()
186                .collect();
187
188            // Generate vendors with IC
189            let vendors = self.vendor_generator.generate_vendor_pool_with_ic(
190                counts.vendors,
191                company_code,
192                &partners,
193                effective_date,
194            );
195
196            // Register vendors
197            for vendor in &vendors.vendors {
198                self.register_vendor(vendor, company_code, effective_date);
199            }
200
201            // Generate customers with IC
202            let customers = self.customer_generator.generate_customer_pool_with_ic(
203                counts.customers,
204                company_code,
205                &partners,
206                effective_date,
207            );
208
209            // Register customers
210            for customer in &customers.customers {
211                self.register_customer(customer, company_code, effective_date);
212            }
213
214            // Generate materials
215            let materials = self.generate_materials(company_code, counts.materials, effective_date);
216
217            // Generate assets
218            let assets = self.generate_assets(company_code, counts.assets, date_range);
219
220            // Generate employees
221            let employees = self.generate_employees(company_code, date_range);
222
223            results.push(GeneratedMasterData {
224                vendors,
225                customers,
226                materials,
227                assets,
228                employees,
229                registry: self.registry.clone(),
230            });
231        }
232
233        results
234    }
235
236    /// Generate vendors.
237    fn generate_vendors(
238        &mut self,
239        company_code: &str,
240        count: usize,
241        effective_date: NaiveDate,
242    ) -> VendorPool {
243        let pool = self
244            .vendor_generator
245            .generate_vendor_pool(count, company_code, effective_date);
246
247        // Register each vendor in the entity registry
248        for vendor in &pool.vendors {
249            self.register_vendor(vendor, company_code, effective_date);
250        }
251
252        pool
253    }
254
255    /// Generate customers.
256    fn generate_customers(
257        &mut self,
258        company_code: &str,
259        count: usize,
260        effective_date: NaiveDate,
261    ) -> CustomerPool {
262        let pool =
263            self.customer_generator
264                .generate_customer_pool(count, company_code, effective_date);
265
266        // Register each customer in the entity registry
267        for customer in &pool.customers {
268            self.register_customer(customer, company_code, effective_date);
269        }
270
271        pool
272    }
273
274    /// Generate materials.
275    fn generate_materials(
276        &mut self,
277        company_code: &str,
278        count: usize,
279        effective_date: NaiveDate,
280    ) -> MaterialPool {
281        let pool = self.material_generator.generate_material_pool_with_bom(
282            count,
283            0.25, // 25% with BOM
284            company_code,
285            effective_date,
286        );
287
288        // Register each material in the entity registry
289        for material in &pool.materials {
290            self.register_material(material, company_code, effective_date);
291        }
292
293        pool
294    }
295
296    /// Generate assets.
297    fn generate_assets(
298        &mut self,
299        company_code: &str,
300        count: usize,
301        date_range: (NaiveDate, NaiveDate),
302    ) -> FixedAssetPool {
303        let pool = self
304            .asset_generator
305            .generate_diverse_pool(count, company_code, date_range);
306
307        // Register each asset in the entity registry
308        for asset in &pool.assets {
309            self.register_asset(asset, company_code, asset.acquisition_date);
310        }
311
312        pool
313    }
314
315    /// Generate employees.
316    fn generate_employees(
317        &mut self,
318        company_code: &str,
319        date_range: (NaiveDate, NaiveDate),
320    ) -> EmployeePool {
321        let pool = self
322            .employee_generator
323            .generate_company_pool(company_code, date_range);
324
325        // Register each employee in the entity registry
326        for employee in &pool.employees {
327            if let Some(hire_date) = employee.hire_date {
328                self.register_employee(employee, company_code, hire_date);
329            }
330        }
331
332        pool
333    }
334
335    /// Register a vendor in the entity registry.
336    fn register_vendor(&mut self, vendor: &Vendor, company_code: &str, effective_date: NaiveDate) {
337        let entity_id = EntityId::vendor(&vendor.vendor_id);
338        let record =
339            datasynth_core::models::EntityRecord::new(entity_id, &vendor.name, effective_date)
340                .with_company_code(company_code);
341        self.registry.register(record);
342    }
343
344    /// Register a customer in the entity registry.
345    fn register_customer(
346        &mut self,
347        customer: &datasynth_core::models::Customer,
348        company_code: &str,
349        effective_date: NaiveDate,
350    ) {
351        let entity_id = EntityId::customer(&customer.customer_id);
352        let record =
353            datasynth_core::models::EntityRecord::new(entity_id, &customer.name, effective_date)
354                .with_company_code(company_code);
355        self.registry.register(record);
356    }
357
358    /// Register a material in the entity registry.
359    fn register_material(
360        &mut self,
361        material: &Material,
362        company_code: &str,
363        effective_date: NaiveDate,
364    ) {
365        let entity_id = EntityId::material(&material.material_id);
366        let record = datasynth_core::models::EntityRecord::new(
367            entity_id,
368            &material.description,
369            effective_date,
370        )
371        .with_company_code(company_code);
372        self.registry.register(record);
373    }
374
375    /// Register an asset in the entity registry.
376    fn register_asset(
377        &mut self,
378        asset: &FixedAsset,
379        company_code: &str,
380        effective_date: NaiveDate,
381    ) {
382        let entity_id = EntityId::fixed_asset(&asset.asset_id);
383        let record = datasynth_core::models::EntityRecord::new(
384            entity_id,
385            &asset.description,
386            effective_date,
387        )
388        .with_company_code(company_code);
389        self.registry.register(record);
390    }
391
392    /// Register an employee in the entity registry.
393    fn register_employee(
394        &mut self,
395        employee: &Employee,
396        company_code: &str,
397        effective_date: NaiveDate,
398    ) {
399        let entity_id = EntityId::employee(&employee.employee_id);
400        let record = datasynth_core::models::EntityRecord::new(
401            entity_id,
402            &employee.display_name,
403            effective_date,
404        )
405        .with_company_code(company_code);
406        self.registry.register(record);
407    }
408
409    /// Get the entity registry.
410    pub fn registry(&self) -> &EntityRegistry {
411        &self.registry
412    }
413
414    /// Validate that an entity exists on a given date.
415    pub fn validate_entity(
416        &self,
417        entity_type: EntityType,
418        entity_id: &str,
419        transaction_date: NaiveDate,
420    ) -> bool {
421        let id = EntityId {
422            entity_type,
423            id: entity_id.to_string(),
424        };
425        self.registry.is_valid_on(&id, transaction_date)
426    }
427
428    /// Get active entities of a type on a given date.
429    pub fn get_active_entities(&self, entity_type: EntityType, date: NaiveDate) -> Vec<EntityId> {
430        self.registry
431            .get_ids_by_type(entity_type)
432            .into_iter()
433            .filter(|id| self.registry.is_valid_on(id, date))
434            .cloned()
435            .collect()
436    }
437
438    /// Get a random active vendor for a company on a date.
439    pub fn get_random_vendor(
440        &self,
441        company_code: &str,
442        date: NaiveDate,
443        rng: &mut impl rand::Rng,
444    ) -> Option<String> {
445        let vendors = self
446            .registry
447            .get_by_company(company_code)
448            .into_iter()
449            .filter(|rec| rec.entity_id.entity_type == EntityType::Vendor)
450            .filter(|rec| self.registry.is_valid(&rec.entity_id, date))
451            .collect::<Vec<_>>();
452
453        if vendors.is_empty() {
454            None
455        } else {
456            use rand::seq::SliceRandom;
457            vendors.choose(rng).map(|rec| rec.entity_id.id.clone())
458        }
459    }
460
461    /// Get a random active customer for a company on a date.
462    pub fn get_random_customer(
463        &self,
464        company_code: &str,
465        date: NaiveDate,
466        rng: &mut impl rand::Rng,
467    ) -> Option<String> {
468        let customers = self
469            .registry
470            .get_by_company(company_code)
471            .into_iter()
472            .filter(|rec| rec.entity_id.entity_type == EntityType::Customer)
473            .filter(|rec| self.registry.is_valid(&rec.entity_id, date))
474            .collect::<Vec<_>>();
475
476        if customers.is_empty() {
477            None
478        } else {
479            use rand::seq::SliceRandom;
480            customers.choose(rng).map(|rec| rec.entity_id.id.clone())
481        }
482    }
483
484    /// Reset all generators.
485    pub fn reset(&mut self) {
486        self.vendor_generator.reset();
487        self.customer_generator.reset();
488        self.material_generator.reset();
489        self.asset_generator.reset();
490        self.employee_generator.reset();
491        self.registry = EntityRegistry::new();
492    }
493}
494
495#[cfg(test)]
496mod tests {
497    use super::*;
498
499    #[test]
500    fn test_manager_creation() {
501        let manager = EntityRegistryManager::new(42);
502        assert_eq!(manager.registry().total_count(), 0);
503    }
504
505    #[test]
506    fn test_master_data_generation() {
507        let mut manager = EntityRegistryManager::new(42);
508        let counts = MasterDataCounts {
509            vendors: 10,
510            customers: 20,
511            materials: 50,
512            assets: 15,
513            employees: None,
514        };
515
516        let data = manager.generate_master_data(
517            "1000",
518            &counts,
519            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
520            (
521                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
522                NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
523            ),
524        );
525
526        assert_eq!(data.vendors.vendors.len(), 10);
527        assert_eq!(data.customers.customers.len(), 20);
528        assert_eq!(data.materials.materials.len(), 50);
529        assert_eq!(data.assets.assets.len(), 15);
530        assert!(!data.employees.employees.is_empty());
531
532        // Registry should have all entities
533        assert!(data.registry.total_count() > 0);
534    }
535
536    #[test]
537    fn test_entity_validation() {
538        let mut manager = EntityRegistryManager::new(42);
539        let counts = MasterDataCounts {
540            vendors: 5,
541            ..Default::default()
542        };
543
544        let data = manager.generate_master_data(
545            "1000",
546            &counts,
547            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
548            (
549                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
550                NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
551            ),
552        );
553
554        // First vendor should be valid
555        let vendor_id = &data.vendors.vendors[0].vendor_id;
556        assert!(manager.validate_entity(
557            EntityType::Vendor,
558            vendor_id,
559            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
560        ));
561
562        // Non-existent vendor should not be valid
563        assert!(!manager.validate_entity(
564            EntityType::Vendor,
565            "V-NONEXISTENT",
566            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
567        ));
568    }
569
570    #[test]
571    fn test_multi_company_generation() {
572        let mut manager = EntityRegistryManager::new(42);
573        let counts = MasterDataCounts {
574            vendors: 5,
575            customers: 10,
576            materials: 20,
577            assets: 5,
578            employees: None,
579        };
580
581        let results = manager.generate_multi_company_master_data(
582            &["1000".to_string(), "2000".to_string()],
583            &counts,
584            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
585            (
586                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
587                NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
588            ),
589        );
590
591        assert_eq!(results.len(), 2);
592    }
593
594    #[test]
595    fn test_intercompany_generation() {
596        let mut manager = EntityRegistryManager::new(42);
597        let counts = MasterDataCounts {
598            vendors: 10,
599            customers: 15,
600            materials: 20,
601            assets: 5,
602            employees: None,
603        };
604
605        let results = manager.generate_master_data_with_ic(
606            &["1000".to_string(), "2000".to_string()],
607            &counts,
608            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
609            (
610                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
611                NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
612            ),
613        );
614
615        // Each company should have IC vendors for the other company
616        let ic_vendors: Vec<_> = results[0]
617            .vendors
618            .vendors
619            .iter()
620            .filter(|v| v.is_intercompany)
621            .collect();
622        assert!(!ic_vendors.is_empty());
623    }
624
625    #[test]
626    fn test_get_random_vendor() {
627        use rand::SeedableRng;
628        use rand_chacha::ChaCha8Rng;
629
630        let mut manager = EntityRegistryManager::new(42);
631        let counts = MasterDataCounts {
632            vendors: 10,
633            ..Default::default()
634        };
635
636        let _data = manager.generate_master_data(
637            "1000",
638            &counts,
639            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
640            (
641                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
642                NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
643            ),
644        );
645
646        let mut rng = ChaCha8Rng::seed_from_u64(42);
647        let vendor = manager.get_random_vendor(
648            "1000",
649            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
650            &mut rng,
651        );
652
653        assert!(vendor.is_some());
654    }
655}