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)]
496#[allow(clippy::unwrap_used)]
497mod tests {
498    use super::*;
499
500    #[test]
501    fn test_manager_creation() {
502        let manager = EntityRegistryManager::new(42);
503        assert_eq!(manager.registry().total_count(), 0);
504    }
505
506    #[test]
507    fn test_master_data_generation() {
508        let mut manager = EntityRegistryManager::new(42);
509        let counts = MasterDataCounts {
510            vendors: 10,
511            customers: 20,
512            materials: 50,
513            assets: 15,
514            employees: None,
515        };
516
517        let data = manager.generate_master_data(
518            "1000",
519            &counts,
520            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
521            (
522                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
523                NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
524            ),
525        );
526
527        assert_eq!(data.vendors.vendors.len(), 10);
528        assert_eq!(data.customers.customers.len(), 20);
529        assert_eq!(data.materials.materials.len(), 50);
530        assert_eq!(data.assets.assets.len(), 15);
531        assert!(!data.employees.employees.is_empty());
532
533        // Registry should have all entities
534        assert!(data.registry.total_count() > 0);
535    }
536
537    #[test]
538    fn test_entity_validation() {
539        let mut manager = EntityRegistryManager::new(42);
540        let counts = MasterDataCounts {
541            vendors: 5,
542            ..Default::default()
543        };
544
545        let data = manager.generate_master_data(
546            "1000",
547            &counts,
548            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
549            (
550                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
551                NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
552            ),
553        );
554
555        // First vendor should be valid
556        let vendor_id = &data.vendors.vendors[0].vendor_id;
557        assert!(manager.validate_entity(
558            EntityType::Vendor,
559            vendor_id,
560            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
561        ));
562
563        // Non-existent vendor should not be valid
564        assert!(!manager.validate_entity(
565            EntityType::Vendor,
566            "V-NONEXISTENT",
567            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
568        ));
569    }
570
571    #[test]
572    fn test_multi_company_generation() {
573        let mut manager = EntityRegistryManager::new(42);
574        let counts = MasterDataCounts {
575            vendors: 5,
576            customers: 10,
577            materials: 20,
578            assets: 5,
579            employees: None,
580        };
581
582        let results = manager.generate_multi_company_master_data(
583            &["1000".to_string(), "2000".to_string()],
584            &counts,
585            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
586            (
587                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
588                NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
589            ),
590        );
591
592        assert_eq!(results.len(), 2);
593    }
594
595    #[test]
596    fn test_intercompany_generation() {
597        let mut manager = EntityRegistryManager::new(42);
598        let counts = MasterDataCounts {
599            vendors: 10,
600            customers: 15,
601            materials: 20,
602            assets: 5,
603            employees: None,
604        };
605
606        let results = manager.generate_master_data_with_ic(
607            &["1000".to_string(), "2000".to_string()],
608            &counts,
609            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
610            (
611                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
612                NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
613            ),
614        );
615
616        // Each company should have IC vendors for the other company
617        let ic_vendors: Vec<_> = results[0]
618            .vendors
619            .vendors
620            .iter()
621            .filter(|v| v.is_intercompany)
622            .collect();
623        assert!(!ic_vendors.is_empty());
624    }
625
626    #[test]
627    fn test_get_random_vendor() {
628        use rand::SeedableRng;
629        use rand_chacha::ChaCha8Rng;
630
631        let mut manager = EntityRegistryManager::new(42);
632        let counts = MasterDataCounts {
633            vendors: 10,
634            ..Default::default()
635        };
636
637        let _data = manager.generate_master_data(
638            "1000",
639            &counts,
640            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
641            (
642                NaiveDate::from_ymd_opt(2020, 1, 1).unwrap(),
643                NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
644            ),
645        );
646
647        let mut rng = ChaCha8Rng::seed_from_u64(42);
648        let vendor = manager.get_random_vendor(
649            "1000",
650            NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
651            &mut rng,
652        );
653
654        assert!(vendor.is_some());
655    }
656}