1use rand::prelude::SliceRandom;
7use rand::RngCore;
8use std::sync::Arc;
9
10use super::loader::{MergeStrategy, TemplateData, TemplateLoader};
11use super::names::NameCulture;
12use crate::models::BusinessProcess;
13
14pub trait TemplateProvider: Send + Sync {
22 fn get_person_first_name(
24 &self,
25 culture: NameCulture,
26 is_male: bool,
27 rng: &mut dyn RngCore,
28 ) -> String;
29
30 fn get_person_last_name(&self, culture: NameCulture, rng: &mut dyn RngCore) -> String;
32
33 fn get_vendor_name(&self, category: &str, rng: &mut dyn RngCore) -> String;
35
36 fn get_customer_name(&self, industry: &str, rng: &mut dyn RngCore) -> String;
38
39 fn get_material_description(&self, material_type: &str, rng: &mut dyn RngCore) -> String;
41
42 fn get_asset_description(&self, category: &str, rng: &mut dyn RngCore) -> String;
44
45 fn get_line_text(
47 &self,
48 process: BusinessProcess,
49 account_type: &str,
50 rng: &mut dyn RngCore,
51 ) -> String;
52
53 fn get_header_template(&self, process: BusinessProcess, rng: &mut dyn RngCore) -> String;
55}
56
57pub struct DefaultTemplateProvider {
59 template_data: Option<TemplateData>,
61 merge_strategy: MergeStrategy,
63}
64
65impl DefaultTemplateProvider {
66 pub fn new() -> Self {
68 Self {
69 template_data: None,
70 merge_strategy: MergeStrategy::Extend,
71 }
72 }
73
74 pub fn with_templates(template_data: TemplateData, strategy: MergeStrategy) -> Self {
76 Self {
77 template_data: Some(template_data),
78 merge_strategy: strategy,
79 }
80 }
81
82 pub fn from_file(path: &std::path::Path) -> Result<Self, super::loader::TemplateError> {
84 let data = TemplateLoader::load_from_file(path)?;
85 Ok(Self::with_templates(data, MergeStrategy::Extend))
86 }
87
88 pub fn from_directory(path: &std::path::Path) -> Result<Self, super::loader::TemplateError> {
90 let data = TemplateLoader::load_from_directory(path)?;
91 Ok(Self::with_templates(data, MergeStrategy::Extend))
92 }
93
94 pub fn with_merge_strategy(mut self, strategy: MergeStrategy) -> Self {
96 self.merge_strategy = strategy;
97 self
98 }
99
100 fn embedded_german_first_names_male() -> Vec<&'static str> {
102 vec![
103 "Hans", "Klaus", "Wolfgang", "Dieter", "Michael", "Stefan", "Thomas", "Andreas",
104 "Peter", "Jürgen", "Matthias", "Frank", "Martin", "Bernd",
105 ]
106 }
107
108 fn embedded_german_first_names_female() -> Vec<&'static str> {
109 vec![
110 "Anna",
111 "Maria",
112 "Elisabeth",
113 "Ursula",
114 "Monika",
115 "Petra",
116 "Karin",
117 "Sabine",
118 "Andrea",
119 "Christine",
120 "Gabriele",
121 "Heike",
122 "Birgit",
123 ]
124 }
125
126 fn embedded_german_last_names() -> Vec<&'static str> {
127 vec![
128 "Müller",
129 "Schmidt",
130 "Schneider",
131 "Fischer",
132 "Weber",
133 "Meyer",
134 "Wagner",
135 "Becker",
136 "Schulz",
137 "Hoffmann",
138 "Schäfer",
139 "Koch",
140 "Bauer",
141 "Richter",
142 ]
143 }
144
145 fn embedded_us_first_names_male() -> Vec<&'static str> {
146 vec![
147 "James",
148 "John",
149 "Robert",
150 "Michael",
151 "William",
152 "David",
153 "Richard",
154 "Joseph",
155 "Thomas",
156 "Charles",
157 "Christopher",
158 "Daniel",
159 "Matthew",
160 ]
161 }
162
163 fn embedded_us_first_names_female() -> Vec<&'static str> {
164 vec![
165 "Mary",
166 "Patricia",
167 "Jennifer",
168 "Linda",
169 "Barbara",
170 "Elizabeth",
171 "Susan",
172 "Jessica",
173 "Sarah",
174 "Karen",
175 "Lisa",
176 "Nancy",
177 "Betty",
178 "Margaret",
179 ]
180 }
181
182 fn embedded_us_last_names() -> Vec<&'static str> {
183 vec![
184 "Smith",
185 "Johnson",
186 "Williams",
187 "Brown",
188 "Jones",
189 "Garcia",
190 "Miller",
191 "Davis",
192 "Rodriguez",
193 "Martinez",
194 "Hernandez",
195 "Lopez",
196 "Gonzalez",
197 ]
198 }
199
200 fn embedded_vendor_names_manufacturing() -> Vec<&'static str> {
201 vec![
202 "Precision Parts Inc.",
203 "Industrial Components LLC",
204 "Advanced Materials Corp.",
205 "Steel Solutions GmbH",
206 "Quality Fasteners Ltd.",
207 "Machining Excellence Inc.",
208 ]
209 }
210
211 fn embedded_vendor_names_services() -> Vec<&'static str> {
212 vec![
213 "Consulting Partners LLP",
214 "Technical Services Inc.",
215 "Professional Solutions LLC",
216 "Business Advisory Group",
217 "Strategic Consulting Co.",
218 "Expert Services Ltd.",
219 ]
220 }
221
222 fn embedded_customer_names_automotive() -> Vec<&'static str> {
223 vec![
224 "AutoWerke Industries",
225 "Vehicle Tech Solutions",
226 "Motor Parts Direct",
227 "Automotive Excellence Corp.",
228 "Drive Systems Inc.",
229 "Engine Components Ltd.",
230 ]
231 }
232
233 fn embedded_customer_names_retail() -> Vec<&'static str> {
234 vec![
235 "Retail Solutions Corp.",
236 "Consumer Goods Direct",
237 "Shop Smart Inc.",
238 "Merchandise Holdings LLC",
239 "Retail Distribution Co.",
240 "Store Systems Ltd.",
241 ]
242 }
243
244 fn culture_to_key(culture: NameCulture) -> &'static str {
245 match culture {
246 NameCulture::WesternUs => "us",
247 NameCulture::German => "german",
248 NameCulture::Hispanic => "hispanic",
249 NameCulture::French => "french",
250 NameCulture::Chinese => "chinese",
251 NameCulture::Japanese => "japanese",
252 NameCulture::Indian => "indian",
253 }
254 }
255
256 fn process_to_key(process: BusinessProcess) -> &'static str {
257 match process {
258 BusinessProcess::P2P => "p2p",
259 BusinessProcess::O2C => "o2c",
260 BusinessProcess::H2R => "h2r",
261 BusinessProcess::R2R => "r2r",
262 _ => "other",
263 }
264 }
265}
266
267impl Default for DefaultTemplateProvider {
268 fn default() -> Self {
269 Self::new()
270 }
271}
272
273impl TemplateProvider for DefaultTemplateProvider {
274 fn get_person_first_name(
275 &self,
276 culture: NameCulture,
277 is_male: bool,
278 rng: &mut dyn RngCore,
279 ) -> String {
280 let key = Self::culture_to_key(culture);
281
282 if let Some(ref data) = self.template_data {
284 if let Some(culture_names) = data.person_names.cultures.get(key) {
285 let names = if is_male {
286 &culture_names.male_first_names
287 } else {
288 &culture_names.female_first_names
289 };
290 if !names.is_empty() {
291 if let Some(name) = names.choose(rng) {
292 return name.clone();
293 }
294 }
295 }
296 }
297
298 let embedded = match culture {
300 NameCulture::German => {
301 if is_male {
302 Self::embedded_german_first_names_male()
303 } else {
304 Self::embedded_german_first_names_female()
305 }
306 }
307 _ => {
308 if is_male {
309 Self::embedded_us_first_names_male()
310 } else {
311 Self::embedded_us_first_names_female()
312 }
313 }
314 };
315
316 embedded.choose(rng).unwrap_or(&"Unknown").to_string()
317 }
318
319 fn get_person_last_name(&self, culture: NameCulture, rng: &mut dyn RngCore) -> String {
320 let key = Self::culture_to_key(culture);
321
322 if let Some(ref data) = self.template_data {
324 if let Some(culture_names) = data.person_names.cultures.get(key) {
325 if !culture_names.last_names.is_empty() {
326 if let Some(name) = culture_names.last_names.choose(rng) {
327 return name.clone();
328 }
329 }
330 }
331 }
332
333 let embedded = match culture {
335 NameCulture::German => Self::embedded_german_last_names(),
336 _ => Self::embedded_us_last_names(),
337 };
338
339 embedded.choose(rng).unwrap_or(&"Unknown").to_string()
340 }
341
342 fn get_vendor_name(&self, category: &str, rng: &mut dyn RngCore) -> String {
343 if let Some(ref data) = self.template_data {
345 if let Some(names) = data.vendor_names.categories.get(category) {
346 if !names.is_empty() {
347 if let Some(name) = names.choose(rng) {
348 return name.clone();
349 }
350 }
351 }
352 }
353
354 let embedded = match category {
356 "manufacturing" => Self::embedded_vendor_names_manufacturing(),
357 "services" => Self::embedded_vendor_names_services(),
358 _ => Self::embedded_vendor_names_manufacturing(),
359 };
360
361 embedded
362 .choose(rng)
363 .unwrap_or(&"Unknown Vendor")
364 .to_string()
365 }
366
367 fn get_customer_name(&self, industry: &str, rng: &mut dyn RngCore) -> String {
368 if let Some(ref data) = self.template_data {
370 if let Some(names) = data.customer_names.industries.get(industry) {
371 if !names.is_empty() {
372 if let Some(name) = names.choose(rng) {
373 return name.clone();
374 }
375 }
376 }
377 }
378
379 let embedded = match industry {
381 "automotive" => Self::embedded_customer_names_automotive(),
382 "retail" => Self::embedded_customer_names_retail(),
383 _ => Self::embedded_customer_names_retail(),
384 };
385
386 embedded
387 .choose(rng)
388 .unwrap_or(&"Unknown Customer")
389 .to_string()
390 }
391
392 fn get_material_description(&self, material_type: &str, rng: &mut dyn RngCore) -> String {
393 if let Some(ref data) = self.template_data {
395 if let Some(descs) = data.material_descriptions.by_type.get(material_type) {
396 if !descs.is_empty() {
397 if let Some(desc) = descs.choose(rng) {
398 return desc.clone();
399 }
400 }
401 }
402 }
403
404 format!("{} material", material_type)
406 }
407
408 fn get_asset_description(&self, category: &str, rng: &mut dyn RngCore) -> String {
409 if let Some(ref data) = self.template_data {
411 if let Some(descs) = data.asset_descriptions.by_category.get(category) {
412 if !descs.is_empty() {
413 if let Some(desc) = descs.choose(rng) {
414 return desc.clone();
415 }
416 }
417 }
418 }
419
420 format!("{} asset", category)
422 }
423
424 fn get_line_text(
425 &self,
426 process: BusinessProcess,
427 account_type: &str,
428 rng: &mut dyn RngCore,
429 ) -> String {
430 let key = Self::process_to_key(process);
431
432 if let Some(ref data) = self.template_data {
434 let descs_map = match process {
435 BusinessProcess::P2P => &data.line_item_descriptions.p2p,
436 BusinessProcess::O2C => &data.line_item_descriptions.o2c,
437 BusinessProcess::H2R => &data.line_item_descriptions.h2r,
438 BusinessProcess::R2R => &data.line_item_descriptions.r2r,
439 _ => &data.line_item_descriptions.p2p,
440 };
441
442 if let Some(descs) = descs_map.get(account_type) {
443 if !descs.is_empty() {
444 if let Some(desc) = descs.choose(rng) {
445 return desc.clone();
446 }
447 }
448 }
449 }
450
451 format!("{} posting", key.to_uppercase())
453 }
454
455 fn get_header_template(&self, process: BusinessProcess, rng: &mut dyn RngCore) -> String {
456 let key = Self::process_to_key(process);
457
458 if let Some(ref data) = self.template_data {
460 if let Some(templates) = data.header_text_templates.by_process.get(key) {
461 if !templates.is_empty() {
462 if let Some(template) = templates.choose(rng) {
463 return template.clone();
464 }
465 }
466 }
467 }
468
469 format!("{} Transaction", key.to_uppercase())
471 }
472}
473
474pub type SharedTemplateProvider = Arc<dyn TemplateProvider>;
476
477pub fn default_provider() -> SharedTemplateProvider {
479 Arc::new(DefaultTemplateProvider::new())
480}
481
482pub fn provider_from_file(
484 path: &std::path::Path,
485) -> Result<SharedTemplateProvider, super::loader::TemplateError> {
486 Ok(Arc::new(DefaultTemplateProvider::from_file(path)?))
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492 use rand::SeedableRng;
493 use rand_chacha::ChaCha8Rng;
494
495 #[test]
496 fn test_default_provider() {
497 let provider = DefaultTemplateProvider::new();
498 let mut rng = ChaCha8Rng::seed_from_u64(12345);
499
500 let name = provider.get_person_first_name(NameCulture::German, true, &mut rng);
501 assert!(!name.is_empty());
502
503 let last_name = provider.get_person_last_name(NameCulture::German, &mut rng);
504 assert!(!last_name.is_empty());
505 }
506
507 #[test]
508 fn test_vendor_names() {
509 let provider = DefaultTemplateProvider::new();
510 let mut rng = ChaCha8Rng::seed_from_u64(12345);
511
512 let name = provider.get_vendor_name("manufacturing", &mut rng);
513 assert!(!name.is_empty());
514 assert!(!name.contains("Unknown"));
515 }
516
517 #[test]
518 fn test_shared_provider() {
519 let provider = default_provider();
520 let mut rng = ChaCha8Rng::seed_from_u64(12345);
521
522 let name = provider.get_customer_name("retail", &mut rng);
523 assert!(!name.is_empty());
524 }
525}