Skip to main content

backbone_accounting/domain/entity/
financial_statement.rs

1use chrono::{DateTime, Utc, NaiveDate};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::StatementType;
8use super::StatementStatus;
9use super::AuditMetadata;
10
11/// Strongly-typed ID for FinancialStatement
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct FinancialStatementId(pub Uuid);
15
16impl FinancialStatementId {
17    pub fn new(id: Uuid) -> Self { Self(id) }
18    pub fn generate() -> Self { Self(Uuid::new_v4()) }
19    pub fn into_inner(self) -> Uuid { self.0 }
20}
21
22impl std::fmt::Display for FinancialStatementId {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        write!(f, "{}", self.0)
25    }
26}
27
28impl std::str::FromStr for FinancialStatementId {
29    type Err = uuid::Error;
30    fn from_str(s: &str) -> Result<Self, Self::Err> {
31        Ok(Self(Uuid::parse_str(s)?))
32    }
33}
34
35impl From<Uuid> for FinancialStatementId {
36    fn from(id: Uuid) -> Self { Self(id) }
37}
38
39impl From<FinancialStatementId> for Uuid {
40    fn from(id: FinancialStatementId) -> Self { id.0 }
41}
42
43impl AsRef<Uuid> for FinancialStatementId {
44    fn as_ref(&self) -> &Uuid { &self.0 }
45}
46
47impl std::ops::Deref for FinancialStatementId {
48    type Target = Uuid;
49    fn deref(&self) -> &Self::Target { &self.0 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
53pub struct FinancialStatement {
54    pub id: Uuid,
55    pub statement_number: String,
56    pub statement_type: StatementType,
57    pub name: String,
58    pub fiscal_period_id: Option<Uuid>,
59    pub fiscal_year: i32,
60    pub fiscal_month: Option<i32>,
61    pub as_of_date: NaiveDate,
62    pub period_start: Option<NaiveDate>,
63    pub period_end: Option<NaiveDate>,
64    pub is_comparative: bool,
65    pub comparative_period_id: Option<Uuid>,
66    pub comparative_as_of_date: Option<NaiveDate>,
67    pub status: StatementStatus,
68    pub total_current_assets: Option<Decimal>,
69    pub total_non_current_assets: Option<Decimal>,
70    pub total_assets: Option<Decimal>,
71    pub total_current_liabilities: Option<Decimal>,
72    pub total_non_current_liabilities: Option<Decimal>,
73    pub total_liabilities: Option<Decimal>,
74    pub total_equity: Option<Decimal>,
75    pub balance_check: bool,
76    pub balance_difference: Option<Decimal>,
77    pub total_revenue: Option<Decimal>,
78    pub total_cogs: Option<Decimal>,
79    pub gross_profit: Option<Decimal>,
80    pub gross_profit_margin: Option<Decimal>,
81    pub total_operating_expenses: Option<Decimal>,
82    pub operating_income: Option<Decimal>,
83    pub operating_margin: Option<Decimal>,
84    pub total_other_income: Option<Decimal>,
85    pub total_other_expenses: Option<Decimal>,
86    pub income_before_tax: Option<Decimal>,
87    pub tax_expense: Option<Decimal>,
88    pub net_income: Option<Decimal>,
89    pub net_profit_margin: Option<Decimal>,
90    pub cash_from_operations: Option<Decimal>,
91    pub cash_from_investing: Option<Decimal>,
92    pub cash_from_financing: Option<Decimal>,
93    pub net_cash_change: Option<Decimal>,
94    pub beginning_cash: Option<Decimal>,
95    pub ending_cash: Option<Decimal>,
96    pub total_debits: Option<Decimal>,
97    pub total_credits: Option<Decimal>,
98    pub trial_balance_check: bool,
99    pub line_items: serde_json::Value,
100    pub comparative_data: serde_json::Value,
101    pub variance_data: serde_json::Value,
102    pub notes: serde_json::Value,
103    pub management_notes: Option<String>,
104    pub generated_at: Option<DateTime<Utc>>,
105    pub generated_by: Option<Uuid>,
106    pub generation_parameters: serde_json::Value,
107    pub reviewed_at: Option<DateTime<Utc>>,
108    pub reviewed_by: Option<Uuid>,
109    pub approved_at: Option<DateTime<Utc>>,
110    pub approved_by: Option<Uuid>,
111    pub published_at: Option<DateTime<Utc>>,
112    pub pdf_url: Option<String>,
113    pub excel_url: Option<String>,
114    pub currency: String,
115    #[serde(default)]
116    #[sqlx(json)]
117    pub metadata: AuditMetadata,
118}
119
120impl FinancialStatement {
121    /// Create a builder for FinancialStatement
122    pub fn builder() -> FinancialStatementBuilder {
123        <FinancialStatementBuilder as Default>::default()
124    }
125
126    /// Create a new FinancialStatement with required fields
127    pub fn new(statement_number: String, statement_type: StatementType, name: String, fiscal_year: i32, as_of_date: NaiveDate, is_comparative: bool, status: StatementStatus, balance_check: bool, trial_balance_check: bool, line_items: serde_json::Value, comparative_data: serde_json::Value, variance_data: serde_json::Value, notes: serde_json::Value, generation_parameters: serde_json::Value, currency: String) -> Self {
128        Self {
129            id: Uuid::new_v4(),
130            statement_number,
131            statement_type,
132            name,
133            fiscal_period_id: None,
134            fiscal_year,
135            fiscal_month: None,
136            as_of_date,
137            period_start: None,
138            period_end: None,
139            is_comparative,
140            comparative_period_id: None,
141            comparative_as_of_date: None,
142            status,
143            total_current_assets: None,
144            total_non_current_assets: None,
145            total_assets: None,
146            total_current_liabilities: None,
147            total_non_current_liabilities: None,
148            total_liabilities: None,
149            total_equity: None,
150            balance_check,
151            balance_difference: None,
152            total_revenue: None,
153            total_cogs: None,
154            gross_profit: None,
155            gross_profit_margin: None,
156            total_operating_expenses: None,
157            operating_income: None,
158            operating_margin: None,
159            total_other_income: None,
160            total_other_expenses: None,
161            income_before_tax: None,
162            tax_expense: None,
163            net_income: None,
164            net_profit_margin: None,
165            cash_from_operations: None,
166            cash_from_investing: None,
167            cash_from_financing: None,
168            net_cash_change: None,
169            beginning_cash: None,
170            ending_cash: None,
171            total_debits: None,
172            total_credits: None,
173            trial_balance_check,
174            line_items,
175            comparative_data,
176            variance_data,
177            notes,
178            management_notes: None,
179            generated_at: None,
180            generated_by: None,
181            generation_parameters,
182            reviewed_at: None,
183            reviewed_by: None,
184            approved_at: None,
185            approved_by: None,
186            published_at: None,
187            pdf_url: None,
188            excel_url: None,
189            currency,
190            metadata: AuditMetadata::default(),
191        }
192    }
193
194    /// Get the entity's unique identifier
195    pub fn id(&self) -> &Uuid {
196        &self.id
197    }
198
199    /// Get a strongly-typed ID for this entity
200    pub fn typed_id(&self) -> FinancialStatementId {
201        FinancialStatementId(self.id)
202    }
203
204    /// Get when this entity was created
205    pub fn created_at(&self) -> Option<&DateTime<Utc>> {
206        self.metadata.created_at.as_ref()
207    }
208
209    /// Get when this entity was last updated
210    pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
211        self.metadata.updated_at.as_ref()
212    }
213
214    /// Check if this entity is soft deleted
215    pub fn is_deleted(&self) -> bool {
216        self.metadata.deleted_at.is_some()
217    }
218
219    /// Check if this entity is active (not deleted)
220    pub fn is_active(&self) -> bool {
221        self.metadata.deleted_at.is_none()
222    }
223
224    /// Get when this entity was deleted
225    pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
226        self.metadata.deleted_at.as_ref()
227    }
228
229    /// Get who created this entity
230    pub fn created_by(&self) -> Option<&Uuid> {
231        self.metadata.created_by.as_ref()
232    }
233
234    /// Get who last updated this entity
235    pub fn updated_by(&self) -> Option<&Uuid> {
236        self.metadata.updated_by.as_ref()
237    }
238
239    /// Get who deleted this entity
240    pub fn deleted_by(&self) -> Option<&Uuid> {
241        self.metadata.deleted_by.as_ref()
242    }
243
244    /// Get the current status
245    pub fn status(&self) -> &StatementStatus {
246        &self.status
247    }
248
249
250    // ==========================================================
251    // Fluent Setters (with_* for optional fields)
252    // ==========================================================
253
254    /// Set the fiscal_period_id field (chainable)
255    pub fn with_fiscal_period_id(mut self, value: Uuid) -> Self {
256        self.fiscal_period_id = Some(value);
257        self
258    }
259
260    /// Set the fiscal_month field (chainable)
261    pub fn with_fiscal_month(mut self, value: i32) -> Self {
262        self.fiscal_month = Some(value);
263        self
264    }
265
266    /// Set the period_start field (chainable)
267    pub fn with_period_start(mut self, value: NaiveDate) -> Self {
268        self.period_start = Some(value);
269        self
270    }
271
272    /// Set the period_end field (chainable)
273    pub fn with_period_end(mut self, value: NaiveDate) -> Self {
274        self.period_end = Some(value);
275        self
276    }
277
278    /// Set the comparative_period_id field (chainable)
279    pub fn with_comparative_period_id(mut self, value: Uuid) -> Self {
280        self.comparative_period_id = Some(value);
281        self
282    }
283
284    /// Set the comparative_as_of_date field (chainable)
285    pub fn with_comparative_as_of_date(mut self, value: NaiveDate) -> Self {
286        self.comparative_as_of_date = Some(value);
287        self
288    }
289
290    /// Set the total_current_assets field (chainable)
291    pub fn with_total_current_assets(mut self, value: Decimal) -> Self {
292        self.total_current_assets = Some(value);
293        self
294    }
295
296    /// Set the total_non_current_assets field (chainable)
297    pub fn with_total_non_current_assets(mut self, value: Decimal) -> Self {
298        self.total_non_current_assets = Some(value);
299        self
300    }
301
302    /// Set the total_assets field (chainable)
303    pub fn with_total_assets(mut self, value: Decimal) -> Self {
304        self.total_assets = Some(value);
305        self
306    }
307
308    /// Set the total_current_liabilities field (chainable)
309    pub fn with_total_current_liabilities(mut self, value: Decimal) -> Self {
310        self.total_current_liabilities = Some(value);
311        self
312    }
313
314    /// Set the total_non_current_liabilities field (chainable)
315    pub fn with_total_non_current_liabilities(mut self, value: Decimal) -> Self {
316        self.total_non_current_liabilities = Some(value);
317        self
318    }
319
320    /// Set the total_liabilities field (chainable)
321    pub fn with_total_liabilities(mut self, value: Decimal) -> Self {
322        self.total_liabilities = Some(value);
323        self
324    }
325
326    /// Set the total_equity field (chainable)
327    pub fn with_total_equity(mut self, value: Decimal) -> Self {
328        self.total_equity = Some(value);
329        self
330    }
331
332    /// Set the balance_difference field (chainable)
333    pub fn with_balance_difference(mut self, value: Decimal) -> Self {
334        self.balance_difference = Some(value);
335        self
336    }
337
338    /// Set the total_revenue field (chainable)
339    pub fn with_total_revenue(mut self, value: Decimal) -> Self {
340        self.total_revenue = Some(value);
341        self
342    }
343
344    /// Set the total_cogs field (chainable)
345    pub fn with_total_cogs(mut self, value: Decimal) -> Self {
346        self.total_cogs = Some(value);
347        self
348    }
349
350    /// Set the gross_profit field (chainable)
351    pub fn with_gross_profit(mut self, value: Decimal) -> Self {
352        self.gross_profit = Some(value);
353        self
354    }
355
356    /// Set the gross_profit_margin field (chainable)
357    pub fn with_gross_profit_margin(mut self, value: Decimal) -> Self {
358        self.gross_profit_margin = Some(value);
359        self
360    }
361
362    /// Set the total_operating_expenses field (chainable)
363    pub fn with_total_operating_expenses(mut self, value: Decimal) -> Self {
364        self.total_operating_expenses = Some(value);
365        self
366    }
367
368    /// Set the operating_income field (chainable)
369    pub fn with_operating_income(mut self, value: Decimal) -> Self {
370        self.operating_income = Some(value);
371        self
372    }
373
374    /// Set the operating_margin field (chainable)
375    pub fn with_operating_margin(mut self, value: Decimal) -> Self {
376        self.operating_margin = Some(value);
377        self
378    }
379
380    /// Set the total_other_income field (chainable)
381    pub fn with_total_other_income(mut self, value: Decimal) -> Self {
382        self.total_other_income = Some(value);
383        self
384    }
385
386    /// Set the total_other_expenses field (chainable)
387    pub fn with_total_other_expenses(mut self, value: Decimal) -> Self {
388        self.total_other_expenses = Some(value);
389        self
390    }
391
392    /// Set the income_before_tax field (chainable)
393    pub fn with_income_before_tax(mut self, value: Decimal) -> Self {
394        self.income_before_tax = Some(value);
395        self
396    }
397
398    /// Set the tax_expense field (chainable)
399    pub fn with_tax_expense(mut self, value: Decimal) -> Self {
400        self.tax_expense = Some(value);
401        self
402    }
403
404    /// Set the net_income field (chainable)
405    pub fn with_net_income(mut self, value: Decimal) -> Self {
406        self.net_income = Some(value);
407        self
408    }
409
410    /// Set the net_profit_margin field (chainable)
411    pub fn with_net_profit_margin(mut self, value: Decimal) -> Self {
412        self.net_profit_margin = Some(value);
413        self
414    }
415
416    /// Set the cash_from_operations field (chainable)
417    pub fn with_cash_from_operations(mut self, value: Decimal) -> Self {
418        self.cash_from_operations = Some(value);
419        self
420    }
421
422    /// Set the cash_from_investing field (chainable)
423    pub fn with_cash_from_investing(mut self, value: Decimal) -> Self {
424        self.cash_from_investing = Some(value);
425        self
426    }
427
428    /// Set the cash_from_financing field (chainable)
429    pub fn with_cash_from_financing(mut self, value: Decimal) -> Self {
430        self.cash_from_financing = Some(value);
431        self
432    }
433
434    /// Set the net_cash_change field (chainable)
435    pub fn with_net_cash_change(mut self, value: Decimal) -> Self {
436        self.net_cash_change = Some(value);
437        self
438    }
439
440    /// Set the beginning_cash field (chainable)
441    pub fn with_beginning_cash(mut self, value: Decimal) -> Self {
442        self.beginning_cash = Some(value);
443        self
444    }
445
446    /// Set the ending_cash field (chainable)
447    pub fn with_ending_cash(mut self, value: Decimal) -> Self {
448        self.ending_cash = Some(value);
449        self
450    }
451
452    /// Set the total_debits field (chainable)
453    pub fn with_total_debits(mut self, value: Decimal) -> Self {
454        self.total_debits = Some(value);
455        self
456    }
457
458    /// Set the total_credits field (chainable)
459    pub fn with_total_credits(mut self, value: Decimal) -> Self {
460        self.total_credits = Some(value);
461        self
462    }
463
464    /// Set the management_notes field (chainable)
465    pub fn with_management_notes(mut self, value: String) -> Self {
466        self.management_notes = Some(value);
467        self
468    }
469
470    /// Set the generated_at field (chainable)
471    pub fn with_generated_at(mut self, value: DateTime<Utc>) -> Self {
472        self.generated_at = Some(value);
473        self
474    }
475
476    /// Set the generated_by field (chainable)
477    pub fn with_generated_by(mut self, value: Uuid) -> Self {
478        self.generated_by = Some(value);
479        self
480    }
481
482    /// Set the reviewed_at field (chainable)
483    pub fn with_reviewed_at(mut self, value: DateTime<Utc>) -> Self {
484        self.reviewed_at = Some(value);
485        self
486    }
487
488    /// Set the reviewed_by field (chainable)
489    pub fn with_reviewed_by(mut self, value: Uuid) -> Self {
490        self.reviewed_by = Some(value);
491        self
492    }
493
494    /// Set the approved_at field (chainable)
495    pub fn with_approved_at(mut self, value: DateTime<Utc>) -> Self {
496        self.approved_at = Some(value);
497        self
498    }
499
500    /// Set the approved_by field (chainable)
501    pub fn with_approved_by(mut self, value: Uuid) -> Self {
502        self.approved_by = Some(value);
503        self
504    }
505
506    /// Set the published_at field (chainable)
507    pub fn with_published_at(mut self, value: DateTime<Utc>) -> Self {
508        self.published_at = Some(value);
509        self
510    }
511
512    /// Set the pdf_url field (chainable)
513    pub fn with_pdf_url(mut self, value: String) -> Self {
514        self.pdf_url = Some(value);
515        self
516    }
517
518    /// Set the excel_url field (chainable)
519    pub fn with_excel_url(mut self, value: String) -> Self {
520        self.excel_url = Some(value);
521        self
522    }
523
524    // ==========================================================
525    // Partial Update
526    // ==========================================================
527
528    /// Apply partial updates from a map of field name to JSON value
529    pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
530        for (key, value) in fields {
531            match key.as_str() {
532                "statement_number" => {
533                    if let Ok(v) = serde_json::from_value(value) { self.statement_number = v; }
534                }
535                "statement_type" => {
536                    if let Ok(v) = serde_json::from_value(value) { self.statement_type = v; }
537                }
538                "name" => {
539                    if let Ok(v) = serde_json::from_value(value) { self.name = v; }
540                }
541                "fiscal_period_id" => {
542                    if let Ok(v) = serde_json::from_value(value) { self.fiscal_period_id = v; }
543                }
544                "fiscal_year" => {
545                    if let Ok(v) = serde_json::from_value(value) { self.fiscal_year = v; }
546                }
547                "fiscal_month" => {
548                    if let Ok(v) = serde_json::from_value(value) { self.fiscal_month = v; }
549                }
550                "as_of_date" => {
551                    if let Ok(v) = serde_json::from_value(value) { self.as_of_date = v; }
552                }
553                "period_start" => {
554                    if let Ok(v) = serde_json::from_value(value) { self.period_start = v; }
555                }
556                "period_end" => {
557                    if let Ok(v) = serde_json::from_value(value) { self.period_end = v; }
558                }
559                "is_comparative" => {
560                    if let Ok(v) = serde_json::from_value(value) { self.is_comparative = v; }
561                }
562                "comparative_period_id" => {
563                    if let Ok(v) = serde_json::from_value(value) { self.comparative_period_id = v; }
564                }
565                "comparative_as_of_date" => {
566                    if let Ok(v) = serde_json::from_value(value) { self.comparative_as_of_date = v; }
567                }
568                "status" => {
569                    if let Ok(v) = serde_json::from_value(value) { self.status = v; }
570                }
571                "total_current_assets" => {
572                    if let Ok(v) = serde_json::from_value(value) { self.total_current_assets = v; }
573                }
574                "total_non_current_assets" => {
575                    if let Ok(v) = serde_json::from_value(value) { self.total_non_current_assets = v; }
576                }
577                "total_assets" => {
578                    if let Ok(v) = serde_json::from_value(value) { self.total_assets = v; }
579                }
580                "total_current_liabilities" => {
581                    if let Ok(v) = serde_json::from_value(value) { self.total_current_liabilities = v; }
582                }
583                "total_non_current_liabilities" => {
584                    if let Ok(v) = serde_json::from_value(value) { self.total_non_current_liabilities = v; }
585                }
586                "total_liabilities" => {
587                    if let Ok(v) = serde_json::from_value(value) { self.total_liabilities = v; }
588                }
589                "total_equity" => {
590                    if let Ok(v) = serde_json::from_value(value) { self.total_equity = v; }
591                }
592                "balance_check" => {
593                    if let Ok(v) = serde_json::from_value(value) { self.balance_check = v; }
594                }
595                "balance_difference" => {
596                    if let Ok(v) = serde_json::from_value(value) { self.balance_difference = v; }
597                }
598                "total_revenue" => {
599                    if let Ok(v) = serde_json::from_value(value) { self.total_revenue = v; }
600                }
601                "total_cogs" => {
602                    if let Ok(v) = serde_json::from_value(value) { self.total_cogs = v; }
603                }
604                "gross_profit" => {
605                    if let Ok(v) = serde_json::from_value(value) { self.gross_profit = v; }
606                }
607                "gross_profit_margin" => {
608                    if let Ok(v) = serde_json::from_value(value) { self.gross_profit_margin = v; }
609                }
610                "total_operating_expenses" => {
611                    if let Ok(v) = serde_json::from_value(value) { self.total_operating_expenses = v; }
612                }
613                "operating_income" => {
614                    if let Ok(v) = serde_json::from_value(value) { self.operating_income = v; }
615                }
616                "operating_margin" => {
617                    if let Ok(v) = serde_json::from_value(value) { self.operating_margin = v; }
618                }
619                "total_other_income" => {
620                    if let Ok(v) = serde_json::from_value(value) { self.total_other_income = v; }
621                }
622                "total_other_expenses" => {
623                    if let Ok(v) = serde_json::from_value(value) { self.total_other_expenses = v; }
624                }
625                "income_before_tax" => {
626                    if let Ok(v) = serde_json::from_value(value) { self.income_before_tax = v; }
627                }
628                "tax_expense" => {
629                    if let Ok(v) = serde_json::from_value(value) { self.tax_expense = v; }
630                }
631                "net_income" => {
632                    if let Ok(v) = serde_json::from_value(value) { self.net_income = v; }
633                }
634                "net_profit_margin" => {
635                    if let Ok(v) = serde_json::from_value(value) { self.net_profit_margin = v; }
636                }
637                "cash_from_operations" => {
638                    if let Ok(v) = serde_json::from_value(value) { self.cash_from_operations = v; }
639                }
640                "cash_from_investing" => {
641                    if let Ok(v) = serde_json::from_value(value) { self.cash_from_investing = v; }
642                }
643                "cash_from_financing" => {
644                    if let Ok(v) = serde_json::from_value(value) { self.cash_from_financing = v; }
645                }
646                "net_cash_change" => {
647                    if let Ok(v) = serde_json::from_value(value) { self.net_cash_change = v; }
648                }
649                "beginning_cash" => {
650                    if let Ok(v) = serde_json::from_value(value) { self.beginning_cash = v; }
651                }
652                "ending_cash" => {
653                    if let Ok(v) = serde_json::from_value(value) { self.ending_cash = v; }
654                }
655                "total_debits" => {
656                    if let Ok(v) = serde_json::from_value(value) { self.total_debits = v; }
657                }
658                "total_credits" => {
659                    if let Ok(v) = serde_json::from_value(value) { self.total_credits = v; }
660                }
661                "trial_balance_check" => {
662                    if let Ok(v) = serde_json::from_value(value) { self.trial_balance_check = v; }
663                }
664                "line_items" => {
665                    if let Ok(v) = serde_json::from_value(value) { self.line_items = v; }
666                }
667                "comparative_data" => {
668                    if let Ok(v) = serde_json::from_value(value) { self.comparative_data = v; }
669                }
670                "variance_data" => {
671                    if let Ok(v) = serde_json::from_value(value) { self.variance_data = v; }
672                }
673                "notes" => {
674                    if let Ok(v) = serde_json::from_value(value) { self.notes = v; }
675                }
676                "management_notes" => {
677                    if let Ok(v) = serde_json::from_value(value) { self.management_notes = v; }
678                }
679                "generated_at" => {
680                    if let Ok(v) = serde_json::from_value(value) { self.generated_at = v; }
681                }
682                "generated_by" => {
683                    if let Ok(v) = serde_json::from_value(value) { self.generated_by = v; }
684                }
685                "generation_parameters" => {
686                    if let Ok(v) = serde_json::from_value(value) { self.generation_parameters = v; }
687                }
688                "reviewed_at" => {
689                    if let Ok(v) = serde_json::from_value(value) { self.reviewed_at = v; }
690                }
691                "reviewed_by" => {
692                    if let Ok(v) = serde_json::from_value(value) { self.reviewed_by = v; }
693                }
694                "approved_at" => {
695                    if let Ok(v) = serde_json::from_value(value) { self.approved_at = v; }
696                }
697                "approved_by" => {
698                    if let Ok(v) = serde_json::from_value(value) { self.approved_by = v; }
699                }
700                "published_at" => {
701                    if let Ok(v) = serde_json::from_value(value) { self.published_at = v; }
702                }
703                "pdf_url" => {
704                    if let Ok(v) = serde_json::from_value(value) { self.pdf_url = v; }
705                }
706                "excel_url" => {
707                    if let Ok(v) = serde_json::from_value(value) { self.excel_url = v; }
708                }
709                "currency" => {
710                    if let Ok(v) = serde_json::from_value(value) { self.currency = v; }
711                }
712                _ => {} // ignore unknown fields
713            }
714        }
715    }
716
717}
718
719impl super::Entity for FinancialStatement {
720    type Id = Uuid;
721
722    fn entity_id(&self) -> &Self::Id {
723        &self.id
724    }
725
726    fn entity_type() -> &'static str {
727        "FinancialStatement"
728    }
729}
730
731impl backbone_core::PersistentEntity for FinancialStatement {
732    fn entity_id(&self) -> String {
733        self.id.to_string()
734    }
735    fn set_entity_id(&mut self, id: String) {
736        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
737            self.id = uuid;
738        }
739    }
740    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
741        self.metadata.created_at
742    }
743    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
744        self.metadata.created_at = Some(ts);
745    }
746    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
747        self.metadata.updated_at
748    }
749    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
750        self.metadata.updated_at = Some(ts);
751    }
752    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
753        self.metadata.deleted_at
754    }
755    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
756        self.metadata.deleted_at = ts;
757    }
758}
759
760impl backbone_orm::EntityRepoMeta for FinancialStatement {
761    fn column_types() -> std::collections::HashMap<String, String> {
762        let mut m = std::collections::HashMap::new();
763        m.insert("id".to_string(), "uuid".to_string());
764        m.insert("fiscal_period_id".to_string(), "uuid".to_string());
765        m.insert("comparative_period_id".to_string(), "uuid".to_string());
766        m.insert("statement_type".to_string(), "statement_type".to_string());
767        m.insert("status".to_string(), "statement_status".to_string());
768        m.insert("as_of_date".to_string(), "date".to_string());
769        m.insert("period_start".to_string(), "date".to_string());
770        m.insert("period_end".to_string(), "date".to_string());
771        m.insert("comparative_as_of_date".to_string(), "date".to_string());
772        m.insert("generated_at".to_string(), "timestamptz".to_string());
773        m.insert("reviewed_at".to_string(), "timestamptz".to_string());
774        m.insert("approved_at".to_string(), "timestamptz".to_string());
775        m.insert("published_at".to_string(), "timestamptz".to_string());
776        m
777    }
778    fn search_fields() -> &'static [&'static str] {
779        &["statement_number", "name", "currency"]
780    }
781    fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
782        &[("fiscalPeriod", "fiscal_periods", "fiscalPeriodId"), ("comparativePeriod", "fiscal_periods", "comparativePeriodId")]
783    }
784}
785
786/// Builder for FinancialStatement entity
787///
788/// Provides a fluent API for constructing FinancialStatement instances.
789/// System fields (id, metadata, timestamps) are auto-initialized.
790#[derive(Debug, Clone, Default)]
791pub struct FinancialStatementBuilder {
792    statement_number: Option<String>,
793    statement_type: Option<StatementType>,
794    name: Option<String>,
795    fiscal_period_id: Option<Uuid>,
796    fiscal_year: Option<i32>,
797    fiscal_month: Option<i32>,
798    as_of_date: Option<NaiveDate>,
799    period_start: Option<NaiveDate>,
800    period_end: Option<NaiveDate>,
801    is_comparative: Option<bool>,
802    comparative_period_id: Option<Uuid>,
803    comparative_as_of_date: Option<NaiveDate>,
804    status: Option<StatementStatus>,
805    total_current_assets: Option<Decimal>,
806    total_non_current_assets: Option<Decimal>,
807    total_assets: Option<Decimal>,
808    total_current_liabilities: Option<Decimal>,
809    total_non_current_liabilities: Option<Decimal>,
810    total_liabilities: Option<Decimal>,
811    total_equity: Option<Decimal>,
812    balance_check: Option<bool>,
813    balance_difference: Option<Decimal>,
814    total_revenue: Option<Decimal>,
815    total_cogs: Option<Decimal>,
816    gross_profit: Option<Decimal>,
817    gross_profit_margin: Option<Decimal>,
818    total_operating_expenses: Option<Decimal>,
819    operating_income: Option<Decimal>,
820    operating_margin: Option<Decimal>,
821    total_other_income: Option<Decimal>,
822    total_other_expenses: Option<Decimal>,
823    income_before_tax: Option<Decimal>,
824    tax_expense: Option<Decimal>,
825    net_income: Option<Decimal>,
826    net_profit_margin: Option<Decimal>,
827    cash_from_operations: Option<Decimal>,
828    cash_from_investing: Option<Decimal>,
829    cash_from_financing: Option<Decimal>,
830    net_cash_change: Option<Decimal>,
831    beginning_cash: Option<Decimal>,
832    ending_cash: Option<Decimal>,
833    total_debits: Option<Decimal>,
834    total_credits: Option<Decimal>,
835    trial_balance_check: Option<bool>,
836    line_items: Option<serde_json::Value>,
837    comparative_data: Option<serde_json::Value>,
838    variance_data: Option<serde_json::Value>,
839    notes: Option<serde_json::Value>,
840    management_notes: Option<String>,
841    generated_at: Option<DateTime<Utc>>,
842    generated_by: Option<Uuid>,
843    generation_parameters: Option<serde_json::Value>,
844    reviewed_at: Option<DateTime<Utc>>,
845    reviewed_by: Option<Uuid>,
846    approved_at: Option<DateTime<Utc>>,
847    approved_by: Option<Uuid>,
848    published_at: Option<DateTime<Utc>>,
849    pdf_url: Option<String>,
850    excel_url: Option<String>,
851    currency: Option<String>,
852}
853
854impl FinancialStatementBuilder {
855    /// Set the statement_number field (required)
856    pub fn statement_number(mut self, value: String) -> Self {
857        self.statement_number = Some(value);
858        self
859    }
860
861    /// Set the statement_type field (required)
862    pub fn statement_type(mut self, value: StatementType) -> Self {
863        self.statement_type = Some(value);
864        self
865    }
866
867    /// Set the name field (required)
868    pub fn name(mut self, value: String) -> Self {
869        self.name = Some(value);
870        self
871    }
872
873    /// Set the fiscal_period_id field (optional)
874    pub fn fiscal_period_id(mut self, value: Uuid) -> Self {
875        self.fiscal_period_id = Some(value);
876        self
877    }
878
879    /// Set the fiscal_year field (required)
880    pub fn fiscal_year(mut self, value: i32) -> Self {
881        self.fiscal_year = Some(value);
882        self
883    }
884
885    /// Set the fiscal_month field (optional)
886    pub fn fiscal_month(mut self, value: i32) -> Self {
887        self.fiscal_month = Some(value);
888        self
889    }
890
891    /// Set the as_of_date field (required)
892    pub fn as_of_date(mut self, value: NaiveDate) -> Self {
893        self.as_of_date = Some(value);
894        self
895    }
896
897    /// Set the period_start field (optional)
898    pub fn period_start(mut self, value: NaiveDate) -> Self {
899        self.period_start = Some(value);
900        self
901    }
902
903    /// Set the period_end field (optional)
904    pub fn period_end(mut self, value: NaiveDate) -> Self {
905        self.period_end = Some(value);
906        self
907    }
908
909    /// Set the is_comparative field (default: `false`)
910    pub fn is_comparative(mut self, value: bool) -> Self {
911        self.is_comparative = Some(value);
912        self
913    }
914
915    /// Set the comparative_period_id field (optional)
916    pub fn comparative_period_id(mut self, value: Uuid) -> Self {
917        self.comparative_period_id = Some(value);
918        self
919    }
920
921    /// Set the comparative_as_of_date field (optional)
922    pub fn comparative_as_of_date(mut self, value: NaiveDate) -> Self {
923        self.comparative_as_of_date = Some(value);
924        self
925    }
926
927    /// Set the status field (default: `StatementStatus::default()`)
928    pub fn status(mut self, value: StatementStatus) -> Self {
929        self.status = Some(value);
930        self
931    }
932
933    /// Set the total_current_assets field (optional)
934    pub fn total_current_assets(mut self, value: Decimal) -> Self {
935        self.total_current_assets = Some(value);
936        self
937    }
938
939    /// Set the total_non_current_assets field (optional)
940    pub fn total_non_current_assets(mut self, value: Decimal) -> Self {
941        self.total_non_current_assets = Some(value);
942        self
943    }
944
945    /// Set the total_assets field (optional)
946    pub fn total_assets(mut self, value: Decimal) -> Self {
947        self.total_assets = Some(value);
948        self
949    }
950
951    /// Set the total_current_liabilities field (optional)
952    pub fn total_current_liabilities(mut self, value: Decimal) -> Self {
953        self.total_current_liabilities = Some(value);
954        self
955    }
956
957    /// Set the total_non_current_liabilities field (optional)
958    pub fn total_non_current_liabilities(mut self, value: Decimal) -> Self {
959        self.total_non_current_liabilities = Some(value);
960        self
961    }
962
963    /// Set the total_liabilities field (optional)
964    pub fn total_liabilities(mut self, value: Decimal) -> Self {
965        self.total_liabilities = Some(value);
966        self
967    }
968
969    /// Set the total_equity field (optional)
970    pub fn total_equity(mut self, value: Decimal) -> Self {
971        self.total_equity = Some(value);
972        self
973    }
974
975    /// Set the balance_check field (default: `false`)
976    pub fn balance_check(mut self, value: bool) -> Self {
977        self.balance_check = Some(value);
978        self
979    }
980
981    /// Set the balance_difference field (optional)
982    pub fn balance_difference(mut self, value: Decimal) -> Self {
983        self.balance_difference = Some(value);
984        self
985    }
986
987    /// Set the total_revenue field (optional)
988    pub fn total_revenue(mut self, value: Decimal) -> Self {
989        self.total_revenue = Some(value);
990        self
991    }
992
993    /// Set the total_cogs field (optional)
994    pub fn total_cogs(mut self, value: Decimal) -> Self {
995        self.total_cogs = Some(value);
996        self
997    }
998
999    /// Set the gross_profit field (optional)
1000    pub fn gross_profit(mut self, value: Decimal) -> Self {
1001        self.gross_profit = Some(value);
1002        self
1003    }
1004
1005    /// Set the gross_profit_margin field (optional)
1006    pub fn gross_profit_margin(mut self, value: Decimal) -> Self {
1007        self.gross_profit_margin = Some(value);
1008        self
1009    }
1010
1011    /// Set the total_operating_expenses field (optional)
1012    pub fn total_operating_expenses(mut self, value: Decimal) -> Self {
1013        self.total_operating_expenses = Some(value);
1014        self
1015    }
1016
1017    /// Set the operating_income field (optional)
1018    pub fn operating_income(mut self, value: Decimal) -> Self {
1019        self.operating_income = Some(value);
1020        self
1021    }
1022
1023    /// Set the operating_margin field (optional)
1024    pub fn operating_margin(mut self, value: Decimal) -> Self {
1025        self.operating_margin = Some(value);
1026        self
1027    }
1028
1029    /// Set the total_other_income field (optional)
1030    pub fn total_other_income(mut self, value: Decimal) -> Self {
1031        self.total_other_income = Some(value);
1032        self
1033    }
1034
1035    /// Set the total_other_expenses field (optional)
1036    pub fn total_other_expenses(mut self, value: Decimal) -> Self {
1037        self.total_other_expenses = Some(value);
1038        self
1039    }
1040
1041    /// Set the income_before_tax field (optional)
1042    pub fn income_before_tax(mut self, value: Decimal) -> Self {
1043        self.income_before_tax = Some(value);
1044        self
1045    }
1046
1047    /// Set the tax_expense field (optional)
1048    pub fn tax_expense(mut self, value: Decimal) -> Self {
1049        self.tax_expense = Some(value);
1050        self
1051    }
1052
1053    /// Set the net_income field (optional)
1054    pub fn net_income(mut self, value: Decimal) -> Self {
1055        self.net_income = Some(value);
1056        self
1057    }
1058
1059    /// Set the net_profit_margin field (optional)
1060    pub fn net_profit_margin(mut self, value: Decimal) -> Self {
1061        self.net_profit_margin = Some(value);
1062        self
1063    }
1064
1065    /// Set the cash_from_operations field (optional)
1066    pub fn cash_from_operations(mut self, value: Decimal) -> Self {
1067        self.cash_from_operations = Some(value);
1068        self
1069    }
1070
1071    /// Set the cash_from_investing field (optional)
1072    pub fn cash_from_investing(mut self, value: Decimal) -> Self {
1073        self.cash_from_investing = Some(value);
1074        self
1075    }
1076
1077    /// Set the cash_from_financing field (optional)
1078    pub fn cash_from_financing(mut self, value: Decimal) -> Self {
1079        self.cash_from_financing = Some(value);
1080        self
1081    }
1082
1083    /// Set the net_cash_change field (optional)
1084    pub fn net_cash_change(mut self, value: Decimal) -> Self {
1085        self.net_cash_change = Some(value);
1086        self
1087    }
1088
1089    /// Set the beginning_cash field (optional)
1090    pub fn beginning_cash(mut self, value: Decimal) -> Self {
1091        self.beginning_cash = Some(value);
1092        self
1093    }
1094
1095    /// Set the ending_cash field (optional)
1096    pub fn ending_cash(mut self, value: Decimal) -> Self {
1097        self.ending_cash = Some(value);
1098        self
1099    }
1100
1101    /// Set the total_debits field (optional)
1102    pub fn total_debits(mut self, value: Decimal) -> Self {
1103        self.total_debits = Some(value);
1104        self
1105    }
1106
1107    /// Set the total_credits field (optional)
1108    pub fn total_credits(mut self, value: Decimal) -> Self {
1109        self.total_credits = Some(value);
1110        self
1111    }
1112
1113    /// Set the trial_balance_check field (default: `false`)
1114    pub fn trial_balance_check(mut self, value: bool) -> Self {
1115        self.trial_balance_check = Some(value);
1116        self
1117    }
1118
1119    /// Set the line_items field (default: `serde_json::json!([])`)
1120    pub fn line_items(mut self, value: serde_json::Value) -> Self {
1121        self.line_items = Some(value);
1122        self
1123    }
1124
1125    /// Set the comparative_data field (default: `serde_json::json!({})`)
1126    pub fn comparative_data(mut self, value: serde_json::Value) -> Self {
1127        self.comparative_data = Some(value);
1128        self
1129    }
1130
1131    /// Set the variance_data field (default: `serde_json::json!({})`)
1132    pub fn variance_data(mut self, value: serde_json::Value) -> Self {
1133        self.variance_data = Some(value);
1134        self
1135    }
1136
1137    /// Set the notes field (default: `serde_json::json!([])`)
1138    pub fn notes(mut self, value: serde_json::Value) -> Self {
1139        self.notes = Some(value);
1140        self
1141    }
1142
1143    /// Set the management_notes field (optional)
1144    pub fn management_notes(mut self, value: String) -> Self {
1145        self.management_notes = Some(value);
1146        self
1147    }
1148
1149    /// Set the generated_at field (optional)
1150    pub fn generated_at(mut self, value: DateTime<Utc>) -> Self {
1151        self.generated_at = Some(value);
1152        self
1153    }
1154
1155    /// Set the generated_by field (optional)
1156    pub fn generated_by(mut self, value: Uuid) -> Self {
1157        self.generated_by = Some(value);
1158        self
1159    }
1160
1161    /// Set the generation_parameters field (default: `serde_json::json!({})`)
1162    pub fn generation_parameters(mut self, value: serde_json::Value) -> Self {
1163        self.generation_parameters = Some(value);
1164        self
1165    }
1166
1167    /// Set the reviewed_at field (optional)
1168    pub fn reviewed_at(mut self, value: DateTime<Utc>) -> Self {
1169        self.reviewed_at = Some(value);
1170        self
1171    }
1172
1173    /// Set the reviewed_by field (optional)
1174    pub fn reviewed_by(mut self, value: Uuid) -> Self {
1175        self.reviewed_by = Some(value);
1176        self
1177    }
1178
1179    /// Set the approved_at field (optional)
1180    pub fn approved_at(mut self, value: DateTime<Utc>) -> Self {
1181        self.approved_at = Some(value);
1182        self
1183    }
1184
1185    /// Set the approved_by field (optional)
1186    pub fn approved_by(mut self, value: Uuid) -> Self {
1187        self.approved_by = Some(value);
1188        self
1189    }
1190
1191    /// Set the published_at field (optional)
1192    pub fn published_at(mut self, value: DateTime<Utc>) -> Self {
1193        self.published_at = Some(value);
1194        self
1195    }
1196
1197    /// Set the pdf_url field (optional)
1198    pub fn pdf_url(mut self, value: String) -> Self {
1199        self.pdf_url = Some(value);
1200        self
1201    }
1202
1203    /// Set the excel_url field (optional)
1204    pub fn excel_url(mut self, value: String) -> Self {
1205        self.excel_url = Some(value);
1206        self
1207    }
1208
1209    /// Set the currency field (default: `"IDR".to_string()`)
1210    pub fn currency(mut self, value: String) -> Self {
1211        self.currency = Some(value);
1212        self
1213    }
1214
1215    /// Build the FinancialStatement entity
1216    ///
1217    /// Returns Err if any required field without a default is missing.
1218    pub fn build(self) -> Result<FinancialStatement, String> {
1219        let statement_number = self.statement_number.ok_or_else(|| "statement_number is required".to_string())?;
1220        let statement_type = self.statement_type.ok_or_else(|| "statement_type is required".to_string())?;
1221        let name = self.name.ok_or_else(|| "name is required".to_string())?;
1222        let fiscal_year = self.fiscal_year.ok_or_else(|| "fiscal_year is required".to_string())?;
1223        let as_of_date = self.as_of_date.ok_or_else(|| "as_of_date is required".to_string())?;
1224
1225        Ok(FinancialStatement {
1226            id: Uuid::new_v4(),
1227            statement_number,
1228            statement_type,
1229            name,
1230            fiscal_period_id: self.fiscal_period_id,
1231            fiscal_year,
1232            fiscal_month: self.fiscal_month,
1233            as_of_date,
1234            period_start: self.period_start,
1235            period_end: self.period_end,
1236            is_comparative: self.is_comparative.unwrap_or(false),
1237            comparative_period_id: self.comparative_period_id,
1238            comparative_as_of_date: self.comparative_as_of_date,
1239            status: self.status.unwrap_or_default(),
1240            total_current_assets: self.total_current_assets,
1241            total_non_current_assets: self.total_non_current_assets,
1242            total_assets: self.total_assets,
1243            total_current_liabilities: self.total_current_liabilities,
1244            total_non_current_liabilities: self.total_non_current_liabilities,
1245            total_liabilities: self.total_liabilities,
1246            total_equity: self.total_equity,
1247            balance_check: self.balance_check.unwrap_or(false),
1248            balance_difference: self.balance_difference,
1249            total_revenue: self.total_revenue,
1250            total_cogs: self.total_cogs,
1251            gross_profit: self.gross_profit,
1252            gross_profit_margin: self.gross_profit_margin,
1253            total_operating_expenses: self.total_operating_expenses,
1254            operating_income: self.operating_income,
1255            operating_margin: self.operating_margin,
1256            total_other_income: self.total_other_income,
1257            total_other_expenses: self.total_other_expenses,
1258            income_before_tax: self.income_before_tax,
1259            tax_expense: self.tax_expense,
1260            net_income: self.net_income,
1261            net_profit_margin: self.net_profit_margin,
1262            cash_from_operations: self.cash_from_operations,
1263            cash_from_investing: self.cash_from_investing,
1264            cash_from_financing: self.cash_from_financing,
1265            net_cash_change: self.net_cash_change,
1266            beginning_cash: self.beginning_cash,
1267            ending_cash: self.ending_cash,
1268            total_debits: self.total_debits,
1269            total_credits: self.total_credits,
1270            trial_balance_check: self.trial_balance_check.unwrap_or(false),
1271            line_items: self.line_items.unwrap_or(serde_json::json!([])),
1272            comparative_data: self.comparative_data.unwrap_or(serde_json::json!({})),
1273            variance_data: self.variance_data.unwrap_or(serde_json::json!({})),
1274            notes: self.notes.unwrap_or(serde_json::json!([])),
1275            management_notes: self.management_notes,
1276            generated_at: self.generated_at,
1277            generated_by: self.generated_by,
1278            generation_parameters: self.generation_parameters.unwrap_or(serde_json::json!({})),
1279            reviewed_at: self.reviewed_at,
1280            reviewed_by: self.reviewed_by,
1281            approved_at: self.approved_at,
1282            approved_by: self.approved_by,
1283            published_at: self.published_at,
1284            pdf_url: self.pdf_url,
1285            excel_url: self.excel_url,
1286            currency: self.currency.unwrap_or("IDR".to_string()),
1287            metadata: AuditMetadata::default(),
1288        })
1289    }
1290}