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