1use crate::models::BusinessProcess;
7use rand::seq::IndexedRandom;
8use rand::Rng;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct HeaderTextPattern {
14 pub template: String,
16 pub business_process: BusinessProcess,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct LineTextPattern {
23 pub template: String,
25 pub account_prefix: Option<String>,
27}
28
29#[derive(Debug, Clone, Default)]
31pub struct DescriptionContext {
32 pub vendor_name: Option<String>,
34 pub customer_name: Option<String>,
36 pub invoice_number: Option<String>,
38 pub po_number: Option<String>,
40 pub month_name: Option<String>,
42 pub year: Option<String>,
44 pub quarter: Option<String>,
46 pub asset_description: Option<String>,
48 pub project_name: Option<String>,
50 pub department_name: Option<String>,
52 pub employee_name: Option<String>,
54 pub amount: Option<String>,
56}
57
58impl DescriptionContext {
59 pub fn with_period(month: u32, year: i32) -> Self {
61 let month_name = match month {
62 1 => "January",
63 2 => "February",
64 3 => "March",
65 4 => "April",
66 5 => "May",
67 6 => "June",
68 7 => "July",
69 8 => "August",
70 9 => "September",
71 10 => "October",
72 11 => "November",
73 12 => "December",
74 _ => "Unknown",
75 };
76
77 let quarter = match month {
78 1..=3 => "Q1",
79 4..=6 => "Q2",
80 7..=9 => "Q3",
81 10..=12 => "Q4",
82 _ => "Q1",
83 };
84
85 Self {
86 month_name: Some(month_name.to_string()),
87 year: Some(year.to_string()),
88 quarter: Some(quarter.to_string()),
89 ..Default::default()
90 }
91 }
92}
93
94#[derive(Debug, Clone)]
96pub struct DescriptionGenerator {
97 header_patterns: Vec<HeaderTextPattern>,
99 expense_descriptions: Vec<&'static str>,
101 revenue_descriptions: Vec<&'static str>,
103 asset_descriptions: Vec<&'static str>,
105 liability_descriptions: Vec<&'static str>,
107 bank_descriptions: Vec<&'static str>,
109 p2p_line_descriptions: Vec<&'static str>,
111 o2c_line_descriptions: Vec<&'static str>,
113 h2r_line_descriptions: Vec<&'static str>,
115 r2r_line_descriptions: Vec<&'static str>,
117}
118
119impl Default for DescriptionGenerator {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125impl DescriptionGenerator {
126 pub fn new() -> Self {
128 Self {
129 header_patterns: Self::default_header_patterns(),
130 expense_descriptions: Self::default_expense_descriptions(),
131 revenue_descriptions: Self::default_revenue_descriptions(),
132 asset_descriptions: Self::default_asset_descriptions(),
133 liability_descriptions: Self::default_liability_descriptions(),
134 bank_descriptions: Self::default_bank_descriptions(),
135 p2p_line_descriptions: Self::default_p2p_line_descriptions(),
136 o2c_line_descriptions: Self::default_o2c_line_descriptions(),
137 h2r_line_descriptions: Self::default_h2r_line_descriptions(),
138 r2r_line_descriptions: Self::default_r2r_line_descriptions(),
139 }
140 }
141
142 fn default_p2p_line_descriptions() -> Vec<&'static str> {
143 vec![
144 "Inventory purchase",
145 "Raw materials receipt",
146 "Goods received - standard",
147 "Vendor invoice posting",
148 "AP invoice match",
149 "Purchase goods receipt",
150 "Material receipt",
151 "Components inventory",
152 "Supplies procurement",
153 "Service receipt",
154 "Purchase payment",
155 "Vendor payment",
156 "AP settlement",
157 "GR/IR clearing",
158 "Price variance adjustment",
159 "Quantity variance",
160 "Procurement expense",
161 "Freight charges",
162 "Customs duties",
163 "Import taxes",
164 ]
165 }
166
167 fn default_o2c_line_descriptions() -> Vec<&'static str> {
168 vec![
169 "Product sales",
170 "Service delivery",
171 "Customer invoice",
172 "Revenue recognition",
173 "Sales order fulfillment",
174 "Goods shipped",
175 "Delivery completion",
176 "Customer receipt",
177 "AR receipt",
178 "Cash application",
179 "Sales discount given",
180 "Trade discount",
181 "Early payment discount",
182 "Finished goods sale",
183 "Merchandise sale",
184 "Contract revenue",
185 "Subscription revenue",
186 "License fee revenue",
187 "Commission earned",
188 "COGS recognition",
189 ]
190 }
191
192 fn default_h2r_line_descriptions() -> Vec<&'static str> {
193 vec![
194 "Salary expense",
195 "Wages allocation",
196 "Benefits expense",
197 "Payroll taxes",
198 "Commission payment",
199 "Bonus accrual",
200 "Vacation accrual",
201 "Health insurance",
202 "Retirement contribution",
203 "Training expense",
204 "Recruitment costs",
205 "Relocation expense",
206 "Employee reimbursement",
207 "Contractor payment",
208 "Temporary staff",
209 "Overtime payment",
210 "Shift differential",
211 "On-call allowance",
212 "Travel reimbursement",
213 "Expense report",
214 ]
215 }
216
217 fn default_r2r_line_descriptions() -> Vec<&'static str> {
218 vec![
219 "Period close adjustment",
220 "Depreciation expense",
221 "Amortization expense",
222 "Accrual entry",
223 "Accrual reversal",
224 "Reclassification entry",
225 "Intercompany elimination",
226 "Currency translation",
227 "FX revaluation",
228 "Reserve adjustment",
229 "Provision update",
230 "Impairment charge",
231 "Bad debt provision",
232 "Inventory adjustment",
233 "Valuation adjustment",
234 "Consolidation entry",
235 "Manual adjustment",
236 "Year-end closing",
237 "Opening balance",
238 "Trial balance adjustment",
239 ]
240 }
241
242 fn default_header_patterns() -> Vec<HeaderTextPattern> {
243 vec![
244 HeaderTextPattern {
246 template: "Customer Invoice - {CustomerName}".to_string(),
247 business_process: BusinessProcess::O2C,
248 },
249 HeaderTextPattern {
250 template: "Sales Order Fulfillment - {CustomerName}".to_string(),
251 business_process: BusinessProcess::O2C,
252 },
253 HeaderTextPattern {
254 template: "Revenue Recognition - {Month} {Year}".to_string(),
255 business_process: BusinessProcess::O2C,
256 },
257 HeaderTextPattern {
258 template: "Customer Payment Receipt - {CustomerName}".to_string(),
259 business_process: BusinessProcess::O2C,
260 },
261 HeaderTextPattern {
262 template: "AR Collection - {InvoiceNumber}".to_string(),
263 business_process: BusinessProcess::O2C,
264 },
265 HeaderTextPattern {
266 template: "Credit Memo - {CustomerName}".to_string(),
267 business_process: BusinessProcess::O2C,
268 },
269 HeaderTextPattern {
270 template: "Deferred Revenue Release - {Month}".to_string(),
271 business_process: BusinessProcess::O2C,
272 },
273 HeaderTextPattern {
274 template: "Sales Commission Accrual - {Quarter}".to_string(),
275 business_process: BusinessProcess::O2C,
276 },
277 HeaderTextPattern {
279 template: "Vendor Invoice - {VendorName}".to_string(),
280 business_process: BusinessProcess::P2P,
281 },
282 HeaderTextPattern {
283 template: "Purchase Order - {PONumber}".to_string(),
284 business_process: BusinessProcess::P2P,
285 },
286 HeaderTextPattern {
287 template: "AP Payment Run - {Month} {Year}".to_string(),
288 business_process: BusinessProcess::P2P,
289 },
290 HeaderTextPattern {
291 template: "Vendor Payment - {VendorName}".to_string(),
292 business_process: BusinessProcess::P2P,
293 },
294 HeaderTextPattern {
295 template: "Expense Accrual - {Month} {Year}".to_string(),
296 business_process: BusinessProcess::P2P,
297 },
298 HeaderTextPattern {
299 template: "Travel Expense Report - {EmployeeName}".to_string(),
300 business_process: BusinessProcess::P2P,
301 },
302 HeaderTextPattern {
303 template: "Utility Bill - {VendorName}".to_string(),
304 business_process: BusinessProcess::P2P,
305 },
306 HeaderTextPattern {
307 template: "Goods Receipt - {PONumber}".to_string(),
308 business_process: BusinessProcess::P2P,
309 },
310 HeaderTextPattern {
312 template: "Month End Close - {Month} {Year}".to_string(),
313 business_process: BusinessProcess::R2R,
314 },
315 HeaderTextPattern {
316 template: "Depreciation - {Month} {Year}".to_string(),
317 business_process: BusinessProcess::R2R,
318 },
319 HeaderTextPattern {
320 template: "Amortization - {Month} {Year}".to_string(),
321 business_process: BusinessProcess::R2R,
322 },
323 HeaderTextPattern {
324 template: "Accrual Reversal - {Month}".to_string(),
325 business_process: BusinessProcess::R2R,
326 },
327 HeaderTextPattern {
328 template: "Prepaid Expense Release - {Month}".to_string(),
329 business_process: BusinessProcess::R2R,
330 },
331 HeaderTextPattern {
332 template: "FX Revaluation - {Month} {Year}".to_string(),
333 business_process: BusinessProcess::R2R,
334 },
335 HeaderTextPattern {
336 template: "Bank Reconciliation Adjustment".to_string(),
337 business_process: BusinessProcess::R2R,
338 },
339 HeaderTextPattern {
340 template: "Manual Journal Entry - {Department}".to_string(),
341 business_process: BusinessProcess::R2R,
342 },
343 HeaderTextPattern {
344 template: "Intercompany Allocation - {Month}".to_string(),
345 business_process: BusinessProcess::R2R,
346 },
347 HeaderTextPattern {
348 template: "Cost Allocation - {Quarter} {Year}".to_string(),
349 business_process: BusinessProcess::R2R,
350 },
351 HeaderTextPattern {
353 template: "Payroll - {Month} {Year}".to_string(),
354 business_process: BusinessProcess::H2R,
355 },
356 HeaderTextPattern {
357 template: "Benefits Accrual - {Month}".to_string(),
358 business_process: BusinessProcess::H2R,
359 },
360 HeaderTextPattern {
361 template: "Bonus Accrual - {Quarter} {Year}".to_string(),
362 business_process: BusinessProcess::H2R,
363 },
364 HeaderTextPattern {
365 template: "Pension Contribution - {Month}".to_string(),
366 business_process: BusinessProcess::H2R,
367 },
368 HeaderTextPattern {
369 template: "Stock Compensation - {Month} {Year}".to_string(),
370 business_process: BusinessProcess::H2R,
371 },
372 HeaderTextPattern {
373 template: "Payroll Tax Remittance - {Month}".to_string(),
374 business_process: BusinessProcess::H2R,
375 },
376 HeaderTextPattern {
377 template: "401k Contribution - {Month} {Year}".to_string(),
378 business_process: BusinessProcess::H2R,
379 },
380 HeaderTextPattern {
382 template: "Asset Acquisition - {AssetDescription}".to_string(),
383 business_process: BusinessProcess::A2R,
384 },
385 HeaderTextPattern {
386 template: "Capital Project - {ProjectName}".to_string(),
387 business_process: BusinessProcess::A2R,
388 },
389 HeaderTextPattern {
390 template: "Asset Disposal - {AssetDescription}".to_string(),
391 business_process: BusinessProcess::A2R,
392 },
393 HeaderTextPattern {
394 template: "Asset Transfer - {AssetDescription}".to_string(),
395 business_process: BusinessProcess::A2R,
396 },
397 HeaderTextPattern {
398 template: "CIP Settlement - {ProjectName}".to_string(),
399 business_process: BusinessProcess::A2R,
400 },
401 HeaderTextPattern {
402 template: "Impairment Write-down - {Quarter} {Year}".to_string(),
403 business_process: BusinessProcess::A2R,
404 },
405 HeaderTextPattern {
407 template: "Bank Transfer - {Month} {Year}".to_string(),
408 business_process: BusinessProcess::Treasury,
409 },
410 HeaderTextPattern {
411 template: "Cash Pooling - {Month}".to_string(),
412 business_process: BusinessProcess::Treasury,
413 },
414 HeaderTextPattern {
415 template: "Investment Transaction".to_string(),
416 business_process: BusinessProcess::Treasury,
417 },
418 HeaderTextPattern {
419 template: "Loan Interest Payment - {Month}".to_string(),
420 business_process: BusinessProcess::Treasury,
421 },
422 HeaderTextPattern {
424 template: "Tax Provision - {Quarter} {Year}".to_string(),
425 business_process: BusinessProcess::Tax,
426 },
427 HeaderTextPattern {
428 template: "VAT/GST Remittance - {Month}".to_string(),
429 business_process: BusinessProcess::Tax,
430 },
431 HeaderTextPattern {
432 template: "Withholding Tax - {Month} {Year}".to_string(),
433 business_process: BusinessProcess::Tax,
434 },
435 HeaderTextPattern {
437 template: "IC Service Charge - {Month} {Year}".to_string(),
438 business_process: BusinessProcess::Intercompany,
439 },
440 HeaderTextPattern {
441 template: "IC Management Fee - {Quarter}".to_string(),
442 business_process: BusinessProcess::Intercompany,
443 },
444 HeaderTextPattern {
445 template: "IC Goods Transfer".to_string(),
446 business_process: BusinessProcess::Intercompany,
447 },
448 ]
449 }
450
451 fn default_expense_descriptions() -> Vec<&'static str> {
452 vec![
453 "Office supplies and materials",
454 "Software subscription - monthly",
455 "Professional services fee",
456 "Travel expense - airfare",
457 "Travel expense - hotel",
458 "Travel expense - meals",
459 "Conference registration fee",
460 "Equipment maintenance",
461 "Telecommunication services",
462 "Internet and data services",
463 "Insurance premium",
464 "Legal services",
465 "Consulting services",
466 "Marketing materials",
467 "Advertising expense",
468 "Training and development",
469 "Membership and subscriptions",
470 "Postage and shipping",
471 "Utilities expense",
472 "Rent expense - monthly",
473 "Cleaning services",
474 "Security services",
475 "Repair and maintenance",
476 "Vehicle expense",
477 "Fuel expense",
478 "Bank charges",
479 "Credit card processing fees",
480 "Recruitment expense",
481 "Employee benefits",
482 "Medical insurance contribution",
483 "Office refreshments",
484 "Team building event",
485 "Client entertainment",
486 "Research materials",
487 "Cloud computing services",
488 "Data storage services",
489 "Audit fees",
490 "Tax preparation services",
491 "License and permits",
492 "Bad debt expense",
493 ]
494 }
495
496 fn default_revenue_descriptions() -> Vec<&'static str> {
497 vec![
498 "Product sales revenue",
499 "Service revenue",
500 "Consulting revenue",
501 "Subscription revenue - monthly",
502 "License fee revenue",
503 "Maintenance contract revenue",
504 "Support services revenue",
505 "Training revenue",
506 "Commission income",
507 "Referral fee income",
508 "Rental income",
509 "Interest income",
510 "Dividend income",
511 "Royalty income",
512 "Grant revenue",
513 "Milestone payment",
514 "Setup fee revenue",
515 "Implementation revenue",
516 "Project completion payment",
517 "Retainer fee",
518 ]
519 }
520
521 fn default_asset_descriptions() -> Vec<&'static str> {
522 vec![
523 "Cash receipt",
524 "Bank deposit",
525 "AR collection",
526 "Prepaid expense",
527 "Security deposit",
528 "Inventory receipt",
529 "Fixed asset addition",
530 "Computer equipment",
531 "Office furniture",
532 "Leasehold improvement",
533 "Software license",
534 "Patent acquisition",
535 "Investment purchase",
536 "Loan receivable",
537 "Intercompany receivable",
538 "Other current asset",
539 "Deferred tax asset",
540 "Work in progress",
541 "Raw materials",
542 "Finished goods",
543 ]
544 }
545
546 fn default_liability_descriptions() -> Vec<&'static str> {
547 vec![
548 "AP - vendor invoice",
549 "Accrued expense",
550 "Accrued payroll",
551 "Accrued bonus",
552 "Deferred revenue",
553 "Customer deposit",
554 "Sales tax payable",
555 "VAT payable",
556 "Income tax payable",
557 "Withholding tax",
558 "Pension liability",
559 "Lease liability",
560 "Loan payable - current",
561 "Loan payable - long term",
562 "Intercompany payable",
563 "Accrued interest",
564 "Warranty reserve",
565 "Legal reserve",
566 "Other accrued liability",
567 "Gift card liability",
568 ]
569 }
570
571 fn default_bank_descriptions() -> Vec<&'static str> {
572 vec![
573 "Wire transfer",
574 "ACH payment",
575 "Check deposit",
576 "Cash withdrawal",
577 "Bank fee",
578 "Interest earned",
579 "Transfer between accounts",
580 "Direct deposit",
581 "ATM withdrawal",
582 "Credit card payment",
583 ]
584 }
585
586 pub fn generate_header_text(
588 &self,
589 process: BusinessProcess,
590 context: &DescriptionContext,
591 rng: &mut impl Rng,
592 ) -> String {
593 let matching: Vec<_> = self
595 .header_patterns
596 .iter()
597 .filter(|p| p.business_process == process)
598 .collect();
599
600 if matching.is_empty() {
601 return format!("{:?} Transaction", process);
602 }
603
604 let pattern = matching.choose(rng).expect("non-empty collection");
605 self.substitute_placeholders(&pattern.template, context, rng)
606 }
607
608 pub fn generate_line_text(
610 &self,
611 gl_account: &str,
612 context: &DescriptionContext,
613 rng: &mut impl Rng,
614 ) -> String {
615 let first_char = gl_account.chars().next().unwrap_or('0');
617
618 match first_char {
619 '1' => {
620 self.asset_descriptions
622 .choose(rng)
623 .unwrap_or(&"Asset posting")
624 .to_string()
625 }
626 '2' => {
627 self.liability_descriptions
629 .choose(rng)
630 .unwrap_or(&"Liability posting")
631 .to_string()
632 }
633 '3' => {
634 "Equity adjustment".to_string()
636 }
637 '4' => {
638 self.revenue_descriptions
640 .choose(rng)
641 .unwrap_or(&"Revenue posting")
642 .to_string()
643 }
644 '5' | '6' | '7' => {
645 self.expense_descriptions
647 .choose(rng)
648 .unwrap_or(&"Expense posting")
649 .to_string()
650 }
651 '8' | '9' => {
652 "Statistical posting".to_string()
654 }
655 '0' => {
656 self.bank_descriptions
658 .choose(rng)
659 .unwrap_or(&"Bank transaction")
660 .to_string()
661 }
662 _ => self.substitute_placeholders("Transaction posting", context, rng),
663 }
664 }
665
666 pub fn generate_line_text_for_process(
673 &self,
674 gl_account: &str,
675 business_process: Option<BusinessProcess>,
676 _context: &DescriptionContext,
677 rng: &mut impl Rng,
678 ) -> String {
679 if let Some(process) = business_process {
681 let pool = match process {
682 BusinessProcess::P2P => &self.p2p_line_descriptions,
683 BusinessProcess::O2C => &self.o2c_line_descriptions,
684 BusinessProcess::H2R => &self.h2r_line_descriptions,
685 BusinessProcess::R2R => &self.r2r_line_descriptions,
686 _ => {
687 return self.generate_line_text_by_account(gl_account, rng);
689 }
690 };
691
692 if let Some(desc) = pool.choose(rng) {
693 return (*desc).to_string();
694 }
695 }
696
697 self.generate_line_text_by_account(gl_account, rng)
699 }
700
701 fn generate_line_text_by_account(&self, gl_account: &str, rng: &mut impl Rng) -> String {
703 let first_char = gl_account.chars().next().unwrap_or('0');
704
705 match first_char {
706 '1' => self
707 .asset_descriptions
708 .choose(rng)
709 .unwrap_or(&"Asset posting")
710 .to_string(),
711 '2' => self
712 .liability_descriptions
713 .choose(rng)
714 .unwrap_or(&"Liability posting")
715 .to_string(),
716 '3' => "Equity adjustment".to_string(),
717 '4' => self
718 .revenue_descriptions
719 .choose(rng)
720 .unwrap_or(&"Revenue posting")
721 .to_string(),
722 '5' | '6' | '7' => self
723 .expense_descriptions
724 .choose(rng)
725 .unwrap_or(&"Expense posting")
726 .to_string(),
727 '8' | '9' => "Statistical posting".to_string(),
728 '0' => self
729 .bank_descriptions
730 .choose(rng)
731 .unwrap_or(&"Bank transaction")
732 .to_string(),
733 _ => "Transaction posting".to_string(),
734 }
735 }
736
737 fn substitute_placeholders(
739 &self,
740 template: &str,
741 context: &DescriptionContext,
742 rng: &mut impl Rng,
743 ) -> String {
744 let mut result = template.to_string();
745
746 if let Some(ref val) = context.vendor_name {
748 result = result.replace("{VendorName}", val);
749 } else {
750 result = result.replace("{VendorName}", &self.generate_vendor_name(rng));
751 }
752
753 if let Some(ref val) = context.customer_name {
754 result = result.replace("{CustomerName}", val);
755 } else {
756 result = result.replace("{CustomerName}", &self.generate_customer_name(rng));
757 }
758
759 if let Some(ref val) = context.invoice_number {
760 result = result.replace("{InvoiceNumber}", val);
761 } else {
762 result = result.replace(
763 "{InvoiceNumber}",
764 &format!("INV-{:06}", rng.random_range(1..999999)),
765 );
766 }
767
768 if let Some(ref val) = context.po_number {
769 result = result.replace("{PONumber}", val);
770 } else {
771 result = result.replace(
772 "{PONumber}",
773 &format!("PO-{:06}", rng.random_range(1..999999)),
774 );
775 }
776
777 if let Some(ref val) = context.month_name {
778 result = result.replace("{Month}", val);
779 } else {
780 result = result.replace("{Month}", "January");
781 }
782
783 if let Some(ref val) = context.year {
784 result = result.replace("{Year}", val);
785 } else {
786 result = result.replace("{Year}", "2024");
787 }
788
789 if let Some(ref val) = context.quarter {
790 result = result.replace("{Quarter}", val);
791 } else {
792 result = result.replace("{Quarter}", "Q1");
793 }
794
795 if let Some(ref val) = context.asset_description {
796 result = result.replace("{AssetDescription}", val);
797 } else {
798 result = result.replace("{AssetDescription}", &self.generate_asset_description(rng));
799 }
800
801 if let Some(ref val) = context.project_name {
802 result = result.replace("{ProjectName}", val);
803 } else {
804 result = result.replace("{ProjectName}", &self.generate_project_name(rng));
805 }
806
807 if let Some(ref val) = context.department_name {
808 result = result.replace("{Department}", val);
809 } else {
810 result = result.replace("{Department}", "Finance");
811 }
812
813 if let Some(ref val) = context.employee_name {
814 result = result.replace("{EmployeeName}", val);
815 } else {
816 result = result.replace("{EmployeeName}", "Employee");
817 }
818
819 result
820 }
821
822 fn generate_vendor_name(&self, rng: &mut impl Rng) -> String {
823 let vendors = [
824 "Acme Supplies Inc",
825 "Global Tech Solutions",
826 "Office Depot",
827 "Amazon Business",
828 "Dell Technologies",
829 "Microsoft Corporation",
830 "Adobe Systems",
831 "Salesforce Inc",
832 "Oracle Corporation",
833 "ServiceNow Inc",
834 "Workday Inc",
835 "SAP America",
836 "IBM Corporation",
837 "Cisco Systems",
838 "HP Inc",
839 "Lenovo Group",
840 "Apple Inc",
841 "Google Cloud",
842 "AWS Inc",
843 "Zoom Communications",
844 ];
845 vendors.choose(rng).unwrap_or(&"Vendor").to_string()
846 }
847
848 fn generate_customer_name(&self, rng: &mut impl Rng) -> String {
849 let customers = [
850 "Northwind Traders",
851 "Contoso Ltd",
852 "Adventure Works",
853 "Fabrikam Inc",
854 "Tailspin Toys",
855 "Wide World Importers",
856 "Proseware Inc",
857 "Coho Vineyard",
858 "Alpine Ski House",
859 "Bellows College",
860 "Datum Corporation",
861 "Litware Inc",
862 "Lucerne Publishing",
863 "Margie Travel",
864 "Trey Research",
865 "Fourth Coffee",
866 "Graphic Design Institute",
867 "School of Fine Art",
868 "VanArsdel Ltd",
869 "Wingtip Toys",
870 ];
871 customers.choose(rng).unwrap_or(&"Customer").to_string()
872 }
873
874 fn generate_asset_description(&self, rng: &mut impl Rng) -> String {
875 let assets = [
876 "Server Equipment",
877 "Network Infrastructure",
878 "Office Renovation",
879 "Manufacturing Equipment",
880 "Delivery Vehicle",
881 "Computer Hardware",
882 "Software License",
883 "Building Improvement",
884 "Lab Equipment",
885 "Security System",
886 ];
887 assets.choose(rng).unwrap_or(&"Asset").to_string()
888 }
889
890 fn generate_project_name(&self, rng: &mut impl Rng) -> String {
891 let projects = [
892 "Digital Transformation",
893 "ERP Implementation",
894 "Data Center Upgrade",
895 "Office Expansion",
896 "Process Automation",
897 "Cloud Migration",
898 "Security Enhancement",
899 "Customer Portal",
900 "Mobile App Development",
901 "Infrastructure Modernization",
902 ];
903 projects.choose(rng).unwrap_or(&"Project").to_string()
904 }
905}
906
907#[cfg(test)]
908#[allow(clippy::unwrap_used)]
909mod tests {
910 use super::*;
911 use rand::SeedableRng;
912 use rand_chacha::ChaCha8Rng;
913
914 #[test]
915 fn test_header_text_generation() {
916 let mut rng = ChaCha8Rng::seed_from_u64(42);
917 let generator = DescriptionGenerator::new();
918 let context = DescriptionContext::with_period(3, 2024);
919
920 let text = generator.generate_header_text(BusinessProcess::P2P, &context, &mut rng);
921 assert!(!text.is_empty());
922 }
923
924 #[test]
925 fn test_line_text_generation() {
926 let mut rng = ChaCha8Rng::seed_from_u64(42);
927 let generator = DescriptionGenerator::new();
928 let context = DescriptionContext::default();
929
930 let expense_text = generator.generate_line_text("500100", &context, &mut rng);
932 assert!(!expense_text.is_empty());
933
934 let revenue_text = generator.generate_line_text("400100", &context, &mut rng);
936 assert!(!revenue_text.is_empty());
937
938 let asset_text = generator.generate_line_text("100000", &context, &mut rng);
940 assert!(!asset_text.is_empty());
941 }
942
943 #[test]
944 fn test_placeholder_substitution() {
945 let mut rng = ChaCha8Rng::seed_from_u64(42);
946 let generator = DescriptionGenerator::new();
947 let mut context = DescriptionContext::with_period(6, 2024);
948 context.vendor_name = Some("Test Vendor Inc".to_string());
949
950 let text = generator.generate_header_text(BusinessProcess::P2P, &context, &mut rng);
951
952 assert!(!text.contains('{'));
954 assert!(!text.contains('}'));
955 }
956
957 #[test]
958 fn test_all_business_processes() {
959 let mut rng = ChaCha8Rng::seed_from_u64(42);
960 let generator = DescriptionGenerator::new();
961 let context = DescriptionContext::with_period(1, 2024);
962
963 let processes = [
964 BusinessProcess::O2C,
965 BusinessProcess::P2P,
966 BusinessProcess::R2R,
967 BusinessProcess::H2R,
968 BusinessProcess::A2R,
969 BusinessProcess::Treasury,
970 BusinessProcess::Tax,
971 BusinessProcess::Intercompany,
972 ];
973
974 for process in processes {
975 let text = generator.generate_header_text(process, &context, &mut rng);
976 assert!(!text.is_empty(), "Empty text for {:?}", process);
977 }
978 }
979}