Skip to main content

datasynth_generators/period_close/
dividend_generator.rs

1//! Dividend declaration generator.
2//!
3//! Generates dividend declarations with paired journal entries:
4//! - Declaration JE: DR Retained Earnings / CR Dividends Payable
5//! - Payment JE: DR Dividends Payable / CR Operating Cash
6
7use chrono::NaiveDate;
8use datasynth_core::accounts::{cash_accounts, dividend_accounts, equity_accounts};
9use datasynth_core::models::{DividendDeclaration, JournalEntry, JournalEntryLine};
10use datasynth_core::utils::seeded_rng;
11use datasynth_core::uuid_factory::{DeterministicUuidFactory, GeneratorType};
12use rand_chacha::ChaCha8Rng;
13use rust_decimal::Decimal;
14
15/// Result of generating a dividend cycle.
16pub struct DividendResult {
17    /// Dividend declarations generated.
18    pub declarations: Vec<DividendDeclaration>,
19    /// Journal entries (declaration JE first, then payment JE).
20    pub journal_entries: Vec<JournalEntry>,
21}
22
23/// Generates dividend declarations with paired journal entries.
24pub struct DividendGenerator {
25    /// RNG for any future randomised fields.
26    #[allow(dead_code)]
27    rng: ChaCha8Rng,
28    uuid_factory: DeterministicUuidFactory,
29}
30
31impl DividendGenerator {
32    /// Create a new dividend generator with the given seed.
33    pub fn new(seed: u64) -> Self {
34        Self {
35            rng: seeded_rng(seed, 0),
36            uuid_factory: DeterministicUuidFactory::new(seed, GeneratorType::PeriodClose),
37        }
38    }
39
40    /// Generate a dividend declaration and its paired journal entries.
41    ///
42    /// # Arguments
43    /// * `entity_code`       – Company code declaring the dividend.
44    /// * `declaration_date`  – Board declaration date.
45    /// * `per_share_amount`  – Dividend per share.
46    /// * `total_amount`      – Total cash dividend declared.
47    /// * `currency`          – ISO 4217 currency code.
48    pub fn generate(
49        &mut self,
50        entity_code: &str,
51        declaration_date: NaiveDate,
52        per_share_amount: Decimal,
53        total_amount: Decimal,
54        currency: &str,
55    ) -> DividendResult {
56        let id = self.uuid_factory.next().to_string();
57
58        let record_date = declaration_date + chrono::Duration::days(14);
59        let payment_date = record_date + chrono::Duration::days(30);
60
61        let declaration = DividendDeclaration {
62            id: id.clone(),
63            entity_code: entity_code.to_string(),
64            declaration_date,
65            record_date,
66            payment_date,
67            per_share_amount,
68            total_amount,
69            currency: currency.to_string(),
70        };
71
72        // --- Declaration JE ---------------------------------------------------
73        // DR Retained Earnings ("3200")  |  CR Dividends Payable ("2170")
74        let decl_doc_id = format!("JE-DIV-DECL-{id}");
75        let mut decl_je = JournalEntry::new_simple(
76            decl_doc_id,
77            entity_code.to_string(),
78            declaration_date,
79            format!("Dividend declaration – {entity_code}"),
80        );
81        decl_je.add_line(JournalEntryLine {
82            line_number: 1,
83            gl_account: equity_accounts::RETAINED_EARNINGS.to_string(),
84            debit_amount: total_amount,
85            credit_amount: Decimal::ZERO,
86            reference: Some(id.clone()),
87            ..Default::default()
88        });
89        decl_je.add_line(JournalEntryLine {
90            line_number: 2,
91            gl_account: dividend_accounts::DIVIDENDS_PAYABLE.to_string(),
92            debit_amount: Decimal::ZERO,
93            credit_amount: total_amount,
94            reference: Some(id.clone()),
95            ..Default::default()
96        });
97
98        // --- Payment JE -------------------------------------------------------
99        // DR Dividends Payable ("2170")  |  CR Operating Cash ("1000")
100        let pay_doc_id = format!("JE-DIV-PAY-{id}");
101        let mut pay_je = JournalEntry::new_simple(
102            pay_doc_id,
103            entity_code.to_string(),
104            payment_date,
105            format!("Dividend payment – {entity_code}"),
106        );
107        pay_je.add_line(JournalEntryLine {
108            line_number: 1,
109            gl_account: dividend_accounts::DIVIDENDS_PAYABLE.to_string(),
110            debit_amount: total_amount,
111            credit_amount: Decimal::ZERO,
112            reference: Some(id.clone()),
113            ..Default::default()
114        });
115        pay_je.add_line(JournalEntryLine {
116            line_number: 2,
117            gl_account: cash_accounts::OPERATING_CASH.to_string(),
118            debit_amount: Decimal::ZERO,
119            credit_amount: total_amount,
120            reference: Some(id.clone()),
121            ..Default::default()
122        });
123
124        DividendResult {
125            declarations: vec![declaration],
126            journal_entries: vec![decl_je, pay_je],
127        }
128    }
129}