Skip to main content

datasynth_core/templates/
descriptions.rs

1//! Description templates for journal entry header and line text.
2//!
3//! Provides realistic, business-process-specific text templates
4//! for populating header_text and line_text fields.
5
6use crate::models::BusinessProcess;
7use rand::seq::SliceRandom;
8use rand::Rng;
9use serde::{Deserialize, Serialize};
10
11/// Pattern for header text generation.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct HeaderTextPattern {
14    /// The pattern template with placeholders
15    pub template: String,
16    /// Business process this pattern applies to
17    pub business_process: BusinessProcess,
18}
19
20/// Pattern for line text generation.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct LineTextPattern {
23    /// The pattern template
24    pub template: String,
25    /// Account type prefix this applies to (e.g., "1" for assets, "5" for expenses)
26    pub account_prefix: Option<String>,
27}
28
29/// Context for text generation with available placeholders.
30#[derive(Debug, Clone, Default)]
31pub struct DescriptionContext {
32    /// Vendor name for P2P transactions
33    pub vendor_name: Option<String>,
34    /// Customer name for O2C transactions
35    pub customer_name: Option<String>,
36    /// Invoice number reference
37    pub invoice_number: Option<String>,
38    /// PO number reference
39    pub po_number: Option<String>,
40    /// Month name (e.g., "January")
41    pub month_name: Option<String>,
42    /// Year (e.g., "2024")
43    pub year: Option<String>,
44    /// Quarter (e.g., "Q1")
45    pub quarter: Option<String>,
46    /// Asset description
47    pub asset_description: Option<String>,
48    /// Project name
49    pub project_name: Option<String>,
50    /// Department name
51    pub department_name: Option<String>,
52    /// Employee name for H2R
53    pub employee_name: Option<String>,
54    /// Amount for reference
55    pub amount: Option<String>,
56}
57
58impl DescriptionContext {
59    /// Create a context with month and year.
60    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/// Generator for journal entry descriptions.
95#[derive(Debug, Clone)]
96pub struct DescriptionGenerator {
97    /// Header text patterns by business process
98    header_patterns: Vec<HeaderTextPattern>,
99    /// Line text patterns (reserved for future use)
100    #[allow(dead_code)]
101    line_patterns: Vec<LineTextPattern>,
102    /// Expense descriptions
103    expense_descriptions: Vec<&'static str>,
104    /// Revenue descriptions
105    revenue_descriptions: Vec<&'static str>,
106    /// Asset descriptions
107    asset_descriptions: Vec<&'static str>,
108    /// Liability descriptions
109    liability_descriptions: Vec<&'static str>,
110    /// Bank/cash descriptions
111    bank_descriptions: Vec<&'static str>,
112    /// Process-specific line descriptions for P2P
113    p2p_line_descriptions: Vec<&'static str>,
114    /// Process-specific line descriptions for O2C
115    o2c_line_descriptions: Vec<&'static str>,
116    /// Process-specific line descriptions for H2R (Hire to Retire)
117    h2r_line_descriptions: Vec<&'static str>,
118    /// Process-specific line descriptions for R2R (Record to Report)
119    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    /// Create a new description generator with default templates.
130    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            // O2C - Order to Cash
249            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            // P2P - Procure to Pay
282            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            // R2R - Record to Report
315            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            // H2R - Hire to Retire
356            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            // A2R - Acquire to Retire
385            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            // Treasury
410            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            // Tax
427            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            // Intercompany
440            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            // Generic patterns
458            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    /// Generate header text for a business process.
605    pub fn generate_header_text(
606        &self,
607        process: BusinessProcess,
608        context: &DescriptionContext,
609        rng: &mut impl Rng,
610    ) -> String {
611        // Filter patterns for this business process
612        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    /// Generate line text based on account type.
627    pub fn generate_line_text(
628        &self,
629        gl_account: &str,
630        context: &DescriptionContext,
631        rng: &mut impl Rng,
632    ) -> String {
633        // Determine account type from first digit
634        let first_char = gl_account.chars().next().unwrap_or('0');
635
636        match first_char {
637            '1' => {
638                // Assets
639                self.asset_descriptions
640                    .choose(rng)
641                    .unwrap_or(&"Asset posting")
642                    .to_string()
643            }
644            '2' => {
645                // Liabilities
646                self.liability_descriptions
647                    .choose(rng)
648                    .unwrap_or(&"Liability posting")
649                    .to_string()
650            }
651            '3' => {
652                // Equity - use generic
653                "Equity adjustment".to_string()
654            }
655            '4' => {
656                // Revenue
657                self.revenue_descriptions
658                    .choose(rng)
659                    .unwrap_or(&"Revenue posting")
660                    .to_string()
661            }
662            '5' | '6' | '7' => {
663                // Expenses
664                self.expense_descriptions
665                    .choose(rng)
666                    .unwrap_or(&"Expense posting")
667                    .to_string()
668            }
669            '8' | '9' => {
670                // Statistical / Other
671                "Statistical posting".to_string()
672            }
673            '0' => {
674                // Cash/Bank
675                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    /// Generate line text based on business process and account type.
685    ///
686    /// This method provides semantically appropriate line descriptions that match
687    /// the business process context. If a business process is provided, it uses
688    /// process-specific descriptions; otherwise falls back to account-type-based
689    /// descriptions.
690    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 business process is specified, use process-specific descriptions
698        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                    // For other processes, fall back to account-type-based
706                    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        // Fall back to account-type-based descriptions
716        self.generate_line_text_by_account(gl_account, rng)
717    }
718
719    /// Generate line text based solely on account type.
720    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    /// Substitute placeholders in a template string.
756    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        // Substitute all placeholders with context values or defaults
765        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        // Test expense account
945        let expense_text = generator.generate_line_text("500100", &context, &mut rng);
946        assert!(!expense_text.is_empty());
947
948        // Test revenue account
949        let revenue_text = generator.generate_line_text("400100", &context, &mut rng);
950        assert!(!revenue_text.is_empty());
951
952        // Test asset account
953        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        // Should not contain raw placeholders
967        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}