Skip to main content

backbone_accounting/presentation/dto/
financial_statement_dto.rs

1//! FinancialStatement DTOs (Data Transfer Objects)
2//!
3//! Generated by metaphor-schema. Do not edit manually.
4//!
5//! DTOs provide a clean separation between domain entities and API
6//! representations, with OpenAPI documentation support.
7
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10use chrono::{DateTime, Utc, NaiveDate};
11use rust_decimal::Decimal;
12
13#[cfg(feature = "openapi")]
14#[cfg(feature = "openapi")]
15use utoipa::ToSchema;
16
17use crate::domain::entity::FinancialStatement;
18use crate::domain::entity::AuditMetadata;
19use crate::domain::entity::StatementStatus;
20use crate::domain::entity::StatementType;
21
22// =============================================================================
23// Create DTO
24// =============================================================================
25
26/// Request DTO for creating a new FinancialStatement
27///
28/// Used in POST requests to create new entities.
29/// Excludes auto-generated fields (id, created_at, updated_at, deleted_at).
30#[derive(Debug, Clone, Deserialize)]
31#[cfg_attr(feature = "openapi", derive(ToSchema))]
32#[serde(rename_all = "camelCase")]
33pub struct CreateFinancialStatementDto {
34    #[cfg_attr(feature = "openapi", schema(example = "example"))]
35    #[serde(alias = "statement_number")]
36    pub statement_number: String,
37    #[serde(alias = "statement_type")]
38    pub statement_type: StatementType,
39    #[cfg_attr(feature = "openapi", schema(example = "example"))]
40    pub name: String,
41    #[serde(default, skip_serializing_if = "Option::is_none", alias = "fiscal_period_id")]
42    pub fiscal_period_id: Option<Uuid>,
43    #[cfg_attr(feature = "openapi", schema(example = 42))]
44    #[serde(alias = "fiscal_year")]
45    pub fiscal_year: i32,
46    #[serde(default, skip_serializing_if = "Option::is_none", alias = "fiscal_month")]
47    pub fiscal_month: Option<i32>,
48    #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
49    #[serde(alias = "as_of_date")]
50    pub as_of_date: NaiveDate,
51    #[serde(default, skip_serializing_if = "Option::is_none", alias = "period_start")]
52    pub period_start: Option<NaiveDate>,
53    #[serde(default, skip_serializing_if = "Option::is_none", alias = "period_end")]
54    pub period_end: Option<NaiveDate>,
55    #[cfg_attr(feature = "openapi", schema(example = true))]
56    #[serde(alias = "is_comparative")]
57    pub is_comparative: bool,
58    #[serde(default, skip_serializing_if = "Option::is_none", alias = "comparative_period_id")]
59    pub comparative_period_id: Option<Uuid>,
60    #[serde(default, skip_serializing_if = "Option::is_none", alias = "comparative_as_of_date")]
61    pub comparative_as_of_date: Option<NaiveDate>,
62    pub status: StatementStatus,
63    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_current_assets")]
64    pub total_current_assets: Option<Decimal>,
65    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_non_current_assets")]
66    pub total_non_current_assets: Option<Decimal>,
67    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_assets")]
68    pub total_assets: Option<Decimal>,
69    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_current_liabilities")]
70    pub total_current_liabilities: Option<Decimal>,
71    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_non_current_liabilities")]
72    pub total_non_current_liabilities: Option<Decimal>,
73    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_liabilities")]
74    pub total_liabilities: Option<Decimal>,
75    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_equity")]
76    pub total_equity: Option<Decimal>,
77    #[cfg_attr(feature = "openapi", schema(example = true))]
78    #[serde(alias = "balance_check")]
79    pub balance_check: bool,
80    #[serde(default, skip_serializing_if = "Option::is_none", alias = "balance_difference")]
81    pub balance_difference: Option<Decimal>,
82    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_revenue")]
83    pub total_revenue: Option<Decimal>,
84    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_cogs")]
85    pub total_cogs: Option<Decimal>,
86    #[serde(default, skip_serializing_if = "Option::is_none", alias = "gross_profit")]
87    pub gross_profit: Option<Decimal>,
88    #[serde(default, skip_serializing_if = "Option::is_none", alias = "gross_profit_margin")]
89    pub gross_profit_margin: Option<Decimal>,
90    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_operating_expenses")]
91    pub total_operating_expenses: Option<Decimal>,
92    #[serde(default, skip_serializing_if = "Option::is_none", alias = "operating_income")]
93    pub operating_income: Option<Decimal>,
94    #[serde(default, skip_serializing_if = "Option::is_none", alias = "operating_margin")]
95    pub operating_margin: Option<Decimal>,
96    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_other_income")]
97    pub total_other_income: Option<Decimal>,
98    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_other_expenses")]
99    pub total_other_expenses: Option<Decimal>,
100    #[serde(default, skip_serializing_if = "Option::is_none", alias = "income_before_tax")]
101    pub income_before_tax: Option<Decimal>,
102    #[serde(default, skip_serializing_if = "Option::is_none", alias = "tax_expense")]
103    pub tax_expense: Option<Decimal>,
104    #[serde(default, skip_serializing_if = "Option::is_none", alias = "net_income")]
105    pub net_income: Option<Decimal>,
106    #[serde(default, skip_serializing_if = "Option::is_none", alias = "net_profit_margin")]
107    pub net_profit_margin: Option<Decimal>,
108    #[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_operations")]
109    pub cash_from_operations: Option<Decimal>,
110    #[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_investing")]
111    pub cash_from_investing: Option<Decimal>,
112    #[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_financing")]
113    pub cash_from_financing: Option<Decimal>,
114    #[serde(default, skip_serializing_if = "Option::is_none", alias = "net_cash_change")]
115    pub net_cash_change: Option<Decimal>,
116    #[serde(default, skip_serializing_if = "Option::is_none", alias = "beginning_cash")]
117    pub beginning_cash: Option<Decimal>,
118    #[serde(default, skip_serializing_if = "Option::is_none", alias = "ending_cash")]
119    pub ending_cash: Option<Decimal>,
120    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_debits")]
121    pub total_debits: Option<Decimal>,
122    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_credits")]
123    pub total_credits: Option<Decimal>,
124    #[cfg_attr(feature = "openapi", schema(example = true))]
125    #[serde(alias = "trial_balance_check")]
126    pub trial_balance_check: bool,
127    #[serde(alias = "line_items")]
128    pub line_items: serde_json::Value,
129    #[serde(alias = "comparative_data")]
130    pub comparative_data: serde_json::Value,
131    #[serde(alias = "variance_data")]
132    pub variance_data: serde_json::Value,
133    pub notes: serde_json::Value,
134    #[serde(default, skip_serializing_if = "Option::is_none", alias = "management_notes")]
135    pub management_notes: Option<String>,
136    #[serde(default, skip_serializing_if = "Option::is_none", alias = "generated_at")]
137    pub generated_at: Option<DateTime<Utc>>,
138    #[serde(default, skip_serializing_if = "Option::is_none", alias = "generated_by")]
139    pub generated_by: Option<Uuid>,
140    #[serde(alias = "generation_parameters")]
141    pub generation_parameters: serde_json::Value,
142    #[serde(default, skip_serializing_if = "Option::is_none", alias = "reviewed_at")]
143    pub reviewed_at: Option<DateTime<Utc>>,
144    #[serde(default, skip_serializing_if = "Option::is_none", alias = "reviewed_by")]
145    pub reviewed_by: Option<Uuid>,
146    #[serde(default, skip_serializing_if = "Option::is_none", alias = "approved_at")]
147    pub approved_at: Option<DateTime<Utc>>,
148    #[serde(default, skip_serializing_if = "Option::is_none", alias = "approved_by")]
149    pub approved_by: Option<Uuid>,
150    #[serde(default, skip_serializing_if = "Option::is_none", alias = "published_at")]
151    pub published_at: Option<DateTime<Utc>>,
152    #[serde(default, skip_serializing_if = "Option::is_none", alias = "pdf_url")]
153    pub pdf_url: Option<String>,
154    #[serde(default, skip_serializing_if = "Option::is_none", alias = "excel_url")]
155    pub excel_url: Option<String>,
156    #[cfg_attr(feature = "openapi", schema(example = "example"))]
157    pub currency: String,
158}
159
160// =============================================================================
161// Update DTO
162// =============================================================================
163
164/// Request DTO for full update of a FinancialStatement
165///
166/// Used in PUT requests for full entity replacement.
167/// All fields are required (except auto-generated ones).
168#[derive(Debug, Clone, Deserialize)]
169#[cfg_attr(feature = "openapi", derive(ToSchema))]
170#[serde(rename_all = "camelCase")]
171pub struct UpdateFinancialStatementDto {
172    #[cfg_attr(feature = "openapi", schema(example = "example"))]
173    #[serde(alias = "statement_number")]
174    pub statement_number: String,
175    #[serde(alias = "statement_type")]
176    pub statement_type: StatementType,
177    #[cfg_attr(feature = "openapi", schema(example = "example"))]
178    pub name: String,
179    #[serde(default, skip_serializing_if = "Option::is_none", alias = "fiscal_period_id")]
180    pub fiscal_period_id: Option<Uuid>,
181    #[cfg_attr(feature = "openapi", schema(example = 42))]
182    #[serde(alias = "fiscal_year")]
183    pub fiscal_year: i32,
184    #[serde(default, skip_serializing_if = "Option::is_none", alias = "fiscal_month")]
185    pub fiscal_month: Option<i32>,
186    #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
187    #[serde(alias = "as_of_date")]
188    pub as_of_date: NaiveDate,
189    #[serde(default, skip_serializing_if = "Option::is_none", alias = "period_start")]
190    pub period_start: Option<NaiveDate>,
191    #[serde(default, skip_serializing_if = "Option::is_none", alias = "period_end")]
192    pub period_end: Option<NaiveDate>,
193    #[cfg_attr(feature = "openapi", schema(example = true))]
194    #[serde(alias = "is_comparative")]
195    pub is_comparative: bool,
196    #[serde(default, skip_serializing_if = "Option::is_none", alias = "comparative_period_id")]
197    pub comparative_period_id: Option<Uuid>,
198    #[serde(default, skip_serializing_if = "Option::is_none", alias = "comparative_as_of_date")]
199    pub comparative_as_of_date: Option<NaiveDate>,
200    pub status: StatementStatus,
201    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_current_assets")]
202    pub total_current_assets: Option<Decimal>,
203    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_non_current_assets")]
204    pub total_non_current_assets: Option<Decimal>,
205    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_assets")]
206    pub total_assets: Option<Decimal>,
207    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_current_liabilities")]
208    pub total_current_liabilities: Option<Decimal>,
209    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_non_current_liabilities")]
210    pub total_non_current_liabilities: Option<Decimal>,
211    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_liabilities")]
212    pub total_liabilities: Option<Decimal>,
213    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_equity")]
214    pub total_equity: Option<Decimal>,
215    #[cfg_attr(feature = "openapi", schema(example = true))]
216    #[serde(alias = "balance_check")]
217    pub balance_check: bool,
218    #[serde(default, skip_serializing_if = "Option::is_none", alias = "balance_difference")]
219    pub balance_difference: Option<Decimal>,
220    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_revenue")]
221    pub total_revenue: Option<Decimal>,
222    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_cogs")]
223    pub total_cogs: Option<Decimal>,
224    #[serde(default, skip_serializing_if = "Option::is_none", alias = "gross_profit")]
225    pub gross_profit: Option<Decimal>,
226    #[serde(default, skip_serializing_if = "Option::is_none", alias = "gross_profit_margin")]
227    pub gross_profit_margin: Option<Decimal>,
228    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_operating_expenses")]
229    pub total_operating_expenses: Option<Decimal>,
230    #[serde(default, skip_serializing_if = "Option::is_none", alias = "operating_income")]
231    pub operating_income: Option<Decimal>,
232    #[serde(default, skip_serializing_if = "Option::is_none", alias = "operating_margin")]
233    pub operating_margin: Option<Decimal>,
234    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_other_income")]
235    pub total_other_income: Option<Decimal>,
236    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_other_expenses")]
237    pub total_other_expenses: Option<Decimal>,
238    #[serde(default, skip_serializing_if = "Option::is_none", alias = "income_before_tax")]
239    pub income_before_tax: Option<Decimal>,
240    #[serde(default, skip_serializing_if = "Option::is_none", alias = "tax_expense")]
241    pub tax_expense: Option<Decimal>,
242    #[serde(default, skip_serializing_if = "Option::is_none", alias = "net_income")]
243    pub net_income: Option<Decimal>,
244    #[serde(default, skip_serializing_if = "Option::is_none", alias = "net_profit_margin")]
245    pub net_profit_margin: Option<Decimal>,
246    #[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_operations")]
247    pub cash_from_operations: Option<Decimal>,
248    #[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_investing")]
249    pub cash_from_investing: Option<Decimal>,
250    #[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_financing")]
251    pub cash_from_financing: Option<Decimal>,
252    #[serde(default, skip_serializing_if = "Option::is_none", alias = "net_cash_change")]
253    pub net_cash_change: Option<Decimal>,
254    #[serde(default, skip_serializing_if = "Option::is_none", alias = "beginning_cash")]
255    pub beginning_cash: Option<Decimal>,
256    #[serde(default, skip_serializing_if = "Option::is_none", alias = "ending_cash")]
257    pub ending_cash: Option<Decimal>,
258    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_debits")]
259    pub total_debits: Option<Decimal>,
260    #[serde(default, skip_serializing_if = "Option::is_none", alias = "total_credits")]
261    pub total_credits: Option<Decimal>,
262    #[cfg_attr(feature = "openapi", schema(example = true))]
263    #[serde(alias = "trial_balance_check")]
264    pub trial_balance_check: bool,
265    #[serde(alias = "line_items")]
266    pub line_items: serde_json::Value,
267    #[serde(alias = "comparative_data")]
268    pub comparative_data: serde_json::Value,
269    #[serde(alias = "variance_data")]
270    pub variance_data: serde_json::Value,
271    pub notes: serde_json::Value,
272    #[serde(default, skip_serializing_if = "Option::is_none", alias = "management_notes")]
273    pub management_notes: Option<String>,
274    #[serde(default, skip_serializing_if = "Option::is_none", alias = "generated_at")]
275    pub generated_at: Option<DateTime<Utc>>,
276    #[serde(default, skip_serializing_if = "Option::is_none", alias = "generated_by")]
277    pub generated_by: Option<Uuid>,
278    #[serde(alias = "generation_parameters")]
279    pub generation_parameters: serde_json::Value,
280    #[serde(default, skip_serializing_if = "Option::is_none", alias = "reviewed_at")]
281    pub reviewed_at: Option<DateTime<Utc>>,
282    #[serde(default, skip_serializing_if = "Option::is_none", alias = "reviewed_by")]
283    pub reviewed_by: Option<Uuid>,
284    #[serde(default, skip_serializing_if = "Option::is_none", alias = "approved_at")]
285    pub approved_at: Option<DateTime<Utc>>,
286    #[serde(default, skip_serializing_if = "Option::is_none", alias = "approved_by")]
287    pub approved_by: Option<Uuid>,
288    #[serde(default, skip_serializing_if = "Option::is_none", alias = "published_at")]
289    pub published_at: Option<DateTime<Utc>>,
290    #[serde(default, skip_serializing_if = "Option::is_none", alias = "pdf_url")]
291    pub pdf_url: Option<String>,
292    #[serde(default, skip_serializing_if = "Option::is_none", alias = "excel_url")]
293    pub excel_url: Option<String>,
294    #[cfg_attr(feature = "openapi", schema(example = "example"))]
295    pub currency: String,
296}
297
298// =============================================================================
299// Patch DTO
300// =============================================================================
301
302/// Request DTO for partial update of a FinancialStatement
303///
304/// Used in PATCH requests for partial updates.
305/// All fields are optional - only provided fields will be updated.
306#[derive(Debug, Clone, Default, Deserialize)]
307#[cfg_attr(feature = "openapi", derive(ToSchema))]
308#[serde(rename_all = "camelCase")]
309pub struct PatchFinancialStatementDto {
310    #[cfg_attr(feature = "openapi", schema(example = "example"))]
311    #[serde(skip_serializing_if = "Option::is_none", alias = "statement_number")]
312    pub statement_number: Option<String>,
313    #[serde(skip_serializing_if = "Option::is_none", alias = "statement_type")]
314    pub statement_type: Option<StatementType>,
315    #[cfg_attr(feature = "openapi", schema(example = "example"))]
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub name: Option<String>,
318    #[serde(skip_serializing_if = "Option::is_none", alias = "fiscal_period_id")]
319    pub fiscal_period_id: Option<Uuid>,
320    #[cfg_attr(feature = "openapi", schema(example = 42))]
321    #[serde(skip_serializing_if = "Option::is_none", alias = "fiscal_year")]
322    pub fiscal_year: Option<i32>,
323    #[serde(skip_serializing_if = "Option::is_none", alias = "fiscal_month")]
324    pub fiscal_month: Option<i32>,
325    #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
326    #[serde(skip_serializing_if = "Option::is_none", alias = "as_of_date")]
327    pub as_of_date: Option<NaiveDate>,
328    #[serde(skip_serializing_if = "Option::is_none", alias = "period_start")]
329    pub period_start: Option<NaiveDate>,
330    #[serde(skip_serializing_if = "Option::is_none", alias = "period_end")]
331    pub period_end: Option<NaiveDate>,
332    #[cfg_attr(feature = "openapi", schema(example = true))]
333    #[serde(skip_serializing_if = "Option::is_none", alias = "is_comparative")]
334    pub is_comparative: Option<bool>,
335    #[serde(skip_serializing_if = "Option::is_none", alias = "comparative_period_id")]
336    pub comparative_period_id: Option<Uuid>,
337    #[serde(skip_serializing_if = "Option::is_none", alias = "comparative_as_of_date")]
338    pub comparative_as_of_date: Option<NaiveDate>,
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub status: Option<StatementStatus>,
341    #[serde(skip_serializing_if = "Option::is_none", alias = "total_current_assets")]
342    pub total_current_assets: Option<Decimal>,
343    #[serde(skip_serializing_if = "Option::is_none", alias = "total_non_current_assets")]
344    pub total_non_current_assets: Option<Decimal>,
345    #[serde(skip_serializing_if = "Option::is_none", alias = "total_assets")]
346    pub total_assets: Option<Decimal>,
347    #[serde(skip_serializing_if = "Option::is_none", alias = "total_current_liabilities")]
348    pub total_current_liabilities: Option<Decimal>,
349    #[serde(skip_serializing_if = "Option::is_none", alias = "total_non_current_liabilities")]
350    pub total_non_current_liabilities: Option<Decimal>,
351    #[serde(skip_serializing_if = "Option::is_none", alias = "total_liabilities")]
352    pub total_liabilities: Option<Decimal>,
353    #[serde(skip_serializing_if = "Option::is_none", alias = "total_equity")]
354    pub total_equity: Option<Decimal>,
355    #[cfg_attr(feature = "openapi", schema(example = true))]
356    #[serde(skip_serializing_if = "Option::is_none", alias = "balance_check")]
357    pub balance_check: Option<bool>,
358    #[serde(skip_serializing_if = "Option::is_none", alias = "balance_difference")]
359    pub balance_difference: Option<Decimal>,
360    #[serde(skip_serializing_if = "Option::is_none", alias = "total_revenue")]
361    pub total_revenue: Option<Decimal>,
362    #[serde(skip_serializing_if = "Option::is_none", alias = "total_cogs")]
363    pub total_cogs: Option<Decimal>,
364    #[serde(skip_serializing_if = "Option::is_none", alias = "gross_profit")]
365    pub gross_profit: Option<Decimal>,
366    #[serde(skip_serializing_if = "Option::is_none", alias = "gross_profit_margin")]
367    pub gross_profit_margin: Option<Decimal>,
368    #[serde(skip_serializing_if = "Option::is_none", alias = "total_operating_expenses")]
369    pub total_operating_expenses: Option<Decimal>,
370    #[serde(skip_serializing_if = "Option::is_none", alias = "operating_income")]
371    pub operating_income: Option<Decimal>,
372    #[serde(skip_serializing_if = "Option::is_none", alias = "operating_margin")]
373    pub operating_margin: Option<Decimal>,
374    #[serde(skip_serializing_if = "Option::is_none", alias = "total_other_income")]
375    pub total_other_income: Option<Decimal>,
376    #[serde(skip_serializing_if = "Option::is_none", alias = "total_other_expenses")]
377    pub total_other_expenses: Option<Decimal>,
378    #[serde(skip_serializing_if = "Option::is_none", alias = "income_before_tax")]
379    pub income_before_tax: Option<Decimal>,
380    #[serde(skip_serializing_if = "Option::is_none", alias = "tax_expense")]
381    pub tax_expense: Option<Decimal>,
382    #[serde(skip_serializing_if = "Option::is_none", alias = "net_income")]
383    pub net_income: Option<Decimal>,
384    #[serde(skip_serializing_if = "Option::is_none", alias = "net_profit_margin")]
385    pub net_profit_margin: Option<Decimal>,
386    #[serde(skip_serializing_if = "Option::is_none", alias = "cash_from_operations")]
387    pub cash_from_operations: Option<Decimal>,
388    #[serde(skip_serializing_if = "Option::is_none", alias = "cash_from_investing")]
389    pub cash_from_investing: Option<Decimal>,
390    #[serde(skip_serializing_if = "Option::is_none", alias = "cash_from_financing")]
391    pub cash_from_financing: Option<Decimal>,
392    #[serde(skip_serializing_if = "Option::is_none", alias = "net_cash_change")]
393    pub net_cash_change: Option<Decimal>,
394    #[serde(skip_serializing_if = "Option::is_none", alias = "beginning_cash")]
395    pub beginning_cash: Option<Decimal>,
396    #[serde(skip_serializing_if = "Option::is_none", alias = "ending_cash")]
397    pub ending_cash: Option<Decimal>,
398    #[serde(skip_serializing_if = "Option::is_none", alias = "total_debits")]
399    pub total_debits: Option<Decimal>,
400    #[serde(skip_serializing_if = "Option::is_none", alias = "total_credits")]
401    pub total_credits: Option<Decimal>,
402    #[cfg_attr(feature = "openapi", schema(example = true))]
403    #[serde(skip_serializing_if = "Option::is_none", alias = "trial_balance_check")]
404    pub trial_balance_check: Option<bool>,
405    #[serde(skip_serializing_if = "Option::is_none", alias = "line_items")]
406    pub line_items: Option<serde_json::Value>,
407    #[serde(skip_serializing_if = "Option::is_none", alias = "comparative_data")]
408    pub comparative_data: Option<serde_json::Value>,
409    #[serde(skip_serializing_if = "Option::is_none", alias = "variance_data")]
410    pub variance_data: Option<serde_json::Value>,
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub notes: Option<serde_json::Value>,
413    #[serde(skip_serializing_if = "Option::is_none", alias = "management_notes")]
414    pub management_notes: Option<String>,
415    #[serde(skip_serializing_if = "Option::is_none", alias = "generated_at")]
416    pub generated_at: Option<DateTime<Utc>>,
417    #[serde(skip_serializing_if = "Option::is_none", alias = "generated_by")]
418    pub generated_by: Option<Uuid>,
419    #[serde(skip_serializing_if = "Option::is_none", alias = "generation_parameters")]
420    pub generation_parameters: Option<serde_json::Value>,
421    #[serde(skip_serializing_if = "Option::is_none", alias = "reviewed_at")]
422    pub reviewed_at: Option<DateTime<Utc>>,
423    #[serde(skip_serializing_if = "Option::is_none", alias = "reviewed_by")]
424    pub reviewed_by: Option<Uuid>,
425    #[serde(skip_serializing_if = "Option::is_none", alias = "approved_at")]
426    pub approved_at: Option<DateTime<Utc>>,
427    #[serde(skip_serializing_if = "Option::is_none", alias = "approved_by")]
428    pub approved_by: Option<Uuid>,
429    #[serde(skip_serializing_if = "Option::is_none", alias = "published_at")]
430    pub published_at: Option<DateTime<Utc>>,
431    #[serde(skip_serializing_if = "Option::is_none", alias = "pdf_url")]
432    pub pdf_url: Option<String>,
433    #[serde(skip_serializing_if = "Option::is_none", alias = "excel_url")]
434    pub excel_url: Option<String>,
435    #[cfg_attr(feature = "openapi", schema(example = "example"))]
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub currency: Option<String>,
438}
439
440impl PatchFinancialStatementDto {
441    /// Check if any field is set
442    pub fn has_changes(&self) -> bool {
443        self.statement_number.is_some() || self.statement_type.is_some() || self.name.is_some() || self.fiscal_period_id.is_some() || self.fiscal_year.is_some() || self.fiscal_month.is_some() || self.as_of_date.is_some() || self.period_start.is_some() || self.period_end.is_some() || self.is_comparative.is_some() || self.comparative_period_id.is_some() || self.comparative_as_of_date.is_some() || self.status.is_some() || self.total_current_assets.is_some() || self.total_non_current_assets.is_some() || self.total_assets.is_some() || self.total_current_liabilities.is_some() || self.total_non_current_liabilities.is_some() || self.total_liabilities.is_some() || self.total_equity.is_some() || self.balance_check.is_some() || self.balance_difference.is_some() || self.total_revenue.is_some() || self.total_cogs.is_some() || self.gross_profit.is_some() || self.gross_profit_margin.is_some() || self.total_operating_expenses.is_some() || self.operating_income.is_some() || self.operating_margin.is_some() || self.total_other_income.is_some() || self.total_other_expenses.is_some() || self.income_before_tax.is_some() || self.tax_expense.is_some() || self.net_income.is_some() || self.net_profit_margin.is_some() || self.cash_from_operations.is_some() || self.cash_from_investing.is_some() || self.cash_from_financing.is_some() || self.net_cash_change.is_some() || self.beginning_cash.is_some() || self.ending_cash.is_some() || self.total_debits.is_some() || self.total_credits.is_some() || self.trial_balance_check.is_some() || self.line_items.is_some() || self.comparative_data.is_some() || self.variance_data.is_some() || self.notes.is_some() || self.management_notes.is_some() || self.generated_at.is_some() || self.generated_by.is_some() || self.generation_parameters.is_some() || self.reviewed_at.is_some() || self.reviewed_by.is_some() || self.approved_at.is_some() || self.approved_by.is_some() || self.published_at.is_some() || self.pdf_url.is_some() || self.excel_url.is_some() || self.currency.is_some()
444    }
445}
446
447// =============================================================================
448// Response DTO
449// =============================================================================
450
451/// Response DTO for FinancialStatement entity
452///
453/// Used in API responses for single entity.
454/// Includes all fields including metadata.
455#[derive(Debug, Clone, Serialize)]
456#[cfg_attr(feature = "openapi", derive(ToSchema))]
457#[serde(rename_all = "camelCase")]
458pub struct FinancialStatementResponseDto {
459    #[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
460    pub id: Uuid,
461    #[cfg_attr(feature = "openapi", schema(example = "example"))]
462    pub statement_number: String,
463    pub statement_type: StatementType,
464    #[cfg_attr(feature = "openapi", schema(example = "example"))]
465    pub name: String,
466    pub fiscal_period_id: Option<Uuid>,
467    #[cfg_attr(feature = "openapi", schema(example = 42))]
468    pub fiscal_year: i32,
469    pub fiscal_month: Option<i32>,
470    #[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
471    pub as_of_date: NaiveDate,
472    pub period_start: Option<NaiveDate>,
473    pub period_end: Option<NaiveDate>,
474    #[cfg_attr(feature = "openapi", schema(example = true))]
475    pub is_comparative: bool,
476    pub comparative_period_id: Option<Uuid>,
477    pub comparative_as_of_date: Option<NaiveDate>,
478    pub status: StatementStatus,
479    pub total_current_assets: Option<Decimal>,
480    pub total_non_current_assets: Option<Decimal>,
481    pub total_assets: Option<Decimal>,
482    pub total_current_liabilities: Option<Decimal>,
483    pub total_non_current_liabilities: Option<Decimal>,
484    pub total_liabilities: Option<Decimal>,
485    pub total_equity: Option<Decimal>,
486    #[cfg_attr(feature = "openapi", schema(example = true))]
487    pub balance_check: bool,
488    pub balance_difference: Option<Decimal>,
489    pub total_revenue: Option<Decimal>,
490    pub total_cogs: Option<Decimal>,
491    pub gross_profit: Option<Decimal>,
492    pub gross_profit_margin: Option<Decimal>,
493    pub total_operating_expenses: Option<Decimal>,
494    pub operating_income: Option<Decimal>,
495    pub operating_margin: Option<Decimal>,
496    pub total_other_income: Option<Decimal>,
497    pub total_other_expenses: Option<Decimal>,
498    pub income_before_tax: Option<Decimal>,
499    pub tax_expense: Option<Decimal>,
500    pub net_income: Option<Decimal>,
501    pub net_profit_margin: Option<Decimal>,
502    pub cash_from_operations: Option<Decimal>,
503    pub cash_from_investing: Option<Decimal>,
504    pub cash_from_financing: Option<Decimal>,
505    pub net_cash_change: Option<Decimal>,
506    pub beginning_cash: Option<Decimal>,
507    pub ending_cash: Option<Decimal>,
508    pub total_debits: Option<Decimal>,
509    pub total_credits: Option<Decimal>,
510    #[cfg_attr(feature = "openapi", schema(example = true))]
511    pub trial_balance_check: bool,
512    pub line_items: serde_json::Value,
513    pub comparative_data: serde_json::Value,
514    pub variance_data: serde_json::Value,
515    pub notes: serde_json::Value,
516    pub management_notes: Option<String>,
517    pub generated_at: Option<DateTime<Utc>>,
518    pub generated_by: Option<Uuid>,
519    pub generation_parameters: serde_json::Value,
520    pub reviewed_at: Option<DateTime<Utc>>,
521    pub reviewed_by: Option<Uuid>,
522    pub approved_at: Option<DateTime<Utc>>,
523    pub approved_by: Option<Uuid>,
524    pub published_at: Option<DateTime<Utc>>,
525    pub pdf_url: Option<String>,
526    pub excel_url: Option<String>,
527    #[cfg_attr(feature = "openapi", schema(example = "example"))]
528    pub currency: String,
529    pub metadata: AuditMetadata,
530}
531
532// =============================================================================
533// List Response DTO
534// =============================================================================
535
536/// Paginated list response for FinancialStatement entities
537///
538/// Used in API responses for list endpoints.
539/// Includes pagination metadata.
540#[derive(Debug, Clone, Serialize)]
541#[cfg_attr(feature = "openapi", derive(ToSchema))]
542#[serde(rename_all = "camelCase")]
543pub struct FinancialStatementListResponseDto {
544    /// List of items
545    pub items: Vec<FinancialStatementResponseDto>,
546    /// Total number of items (across all pages)
547    pub total: u64,
548    /// Current page number (1-indexed)
549    pub page: u32,
550    /// Number of items per page
551    pub per_page: u32,
552    /// Total number of pages
553    pub total_pages: u32,
554    /// Whether there is a next page
555    pub has_next: bool,
556    /// Whether there is a previous page
557    pub has_prev: bool,
558}
559
560impl FinancialStatementListResponseDto {
561    /// Create a new list response from items and pagination info
562    pub fn new(items: Vec<FinancialStatementResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
563        let total_pages = if per_page > 0 {
564            ((total as f64) / (per_page as f64)).ceil() as u32
565        } else {
566            0
567        };
568        Self {
569            items,
570            total,
571            page,
572            per_page,
573            total_pages,
574            has_next: page < total_pages,
575            has_prev: page > 1,
576        }
577    }
578}
579
580/// Summary DTO for list views (compact version)
581#[derive(Debug, Clone, Serialize)]
582#[cfg_attr(feature = "openapi", derive(ToSchema))]
583#[serde(rename_all = "camelCase")]
584pub struct FinancialStatementSummaryDto {
585    pub id: Uuid,
586    pub statement_number: String,
587    pub statement_type: StatementType,
588    pub name: String,
589    pub created_at: Option<DateTime<Utc>>,
590}
591
592// =============================================================================
593// Conversions
594// =============================================================================
595
596impl From<FinancialStatement> for FinancialStatementResponseDto {
597    fn from(entity: FinancialStatement) -> Self {
598        Self {
599            id: entity.id,
600            statement_number: entity.statement_number,
601            statement_type: entity.statement_type,
602            name: entity.name,
603            fiscal_period_id: entity.fiscal_period_id,
604            fiscal_year: entity.fiscal_year,
605            fiscal_month: entity.fiscal_month,
606            as_of_date: entity.as_of_date,
607            period_start: entity.period_start,
608            period_end: entity.period_end,
609            is_comparative: entity.is_comparative,
610            comparative_period_id: entity.comparative_period_id,
611            comparative_as_of_date: entity.comparative_as_of_date,
612            status: entity.status,
613            total_current_assets: entity.total_current_assets,
614            total_non_current_assets: entity.total_non_current_assets,
615            total_assets: entity.total_assets,
616            total_current_liabilities: entity.total_current_liabilities,
617            total_non_current_liabilities: entity.total_non_current_liabilities,
618            total_liabilities: entity.total_liabilities,
619            total_equity: entity.total_equity,
620            balance_check: entity.balance_check,
621            balance_difference: entity.balance_difference,
622            total_revenue: entity.total_revenue,
623            total_cogs: entity.total_cogs,
624            gross_profit: entity.gross_profit,
625            gross_profit_margin: entity.gross_profit_margin,
626            total_operating_expenses: entity.total_operating_expenses,
627            operating_income: entity.operating_income,
628            operating_margin: entity.operating_margin,
629            total_other_income: entity.total_other_income,
630            total_other_expenses: entity.total_other_expenses,
631            income_before_tax: entity.income_before_tax,
632            tax_expense: entity.tax_expense,
633            net_income: entity.net_income,
634            net_profit_margin: entity.net_profit_margin,
635            cash_from_operations: entity.cash_from_operations,
636            cash_from_investing: entity.cash_from_investing,
637            cash_from_financing: entity.cash_from_financing,
638            net_cash_change: entity.net_cash_change,
639            beginning_cash: entity.beginning_cash,
640            ending_cash: entity.ending_cash,
641            total_debits: entity.total_debits,
642            total_credits: entity.total_credits,
643            trial_balance_check: entity.trial_balance_check,
644            line_items: entity.line_items,
645            comparative_data: entity.comparative_data,
646            variance_data: entity.variance_data,
647            notes: entity.notes,
648            management_notes: entity.management_notes,
649            generated_at: entity.generated_at,
650            generated_by: entity.generated_by,
651            generation_parameters: entity.generation_parameters,
652            reviewed_at: entity.reviewed_at,
653            reviewed_by: entity.reviewed_by,
654            approved_at: entity.approved_at,
655            approved_by: entity.approved_by,
656            published_at: entity.published_at,
657            pdf_url: entity.pdf_url,
658            excel_url: entity.excel_url,
659            currency: entity.currency,
660            metadata: entity.metadata,
661        }
662    }
663}
664
665impl From<FinancialStatement> for FinancialStatementSummaryDto {
666    fn from(entity: FinancialStatement) -> Self {
667        let created_at = backbone_core::PersistentEntity::created_at(&entity);
668        Self {
669            id: entity.id,
670            statement_number: entity.statement_number,
671            statement_type: entity.statement_type,
672            name: entity.name,
673            created_at,
674        }
675    }
676}
677
678impl From<CreateFinancialStatementDto> for FinancialStatement {
679    fn from(dto: CreateFinancialStatementDto) -> Self {
680        Self {
681            id: Uuid::new_v4(),
682            statement_number: dto.statement_number,
683            statement_type: dto.statement_type,
684            name: dto.name,
685            fiscal_period_id: dto.fiscal_period_id,
686            fiscal_year: dto.fiscal_year,
687            fiscal_month: dto.fiscal_month,
688            as_of_date: dto.as_of_date,
689            period_start: dto.period_start,
690            period_end: dto.period_end,
691            is_comparative: dto.is_comparative,
692            comparative_period_id: dto.comparative_period_id,
693            comparative_as_of_date: dto.comparative_as_of_date,
694            status: dto.status,
695            total_current_assets: dto.total_current_assets,
696            total_non_current_assets: dto.total_non_current_assets,
697            total_assets: dto.total_assets,
698            total_current_liabilities: dto.total_current_liabilities,
699            total_non_current_liabilities: dto.total_non_current_liabilities,
700            total_liabilities: dto.total_liabilities,
701            total_equity: dto.total_equity,
702            balance_check: dto.balance_check,
703            balance_difference: dto.balance_difference,
704            total_revenue: dto.total_revenue,
705            total_cogs: dto.total_cogs,
706            gross_profit: dto.gross_profit,
707            gross_profit_margin: dto.gross_profit_margin,
708            total_operating_expenses: dto.total_operating_expenses,
709            operating_income: dto.operating_income,
710            operating_margin: dto.operating_margin,
711            total_other_income: dto.total_other_income,
712            total_other_expenses: dto.total_other_expenses,
713            income_before_tax: dto.income_before_tax,
714            tax_expense: dto.tax_expense,
715            net_income: dto.net_income,
716            net_profit_margin: dto.net_profit_margin,
717            cash_from_operations: dto.cash_from_operations,
718            cash_from_investing: dto.cash_from_investing,
719            cash_from_financing: dto.cash_from_financing,
720            net_cash_change: dto.net_cash_change,
721            beginning_cash: dto.beginning_cash,
722            ending_cash: dto.ending_cash,
723            total_debits: dto.total_debits,
724            total_credits: dto.total_credits,
725            trial_balance_check: dto.trial_balance_check,
726            line_items: dto.line_items,
727            comparative_data: dto.comparative_data,
728            variance_data: dto.variance_data,
729            notes: dto.notes,
730            management_notes: dto.management_notes,
731            generated_at: dto.generated_at,
732            generated_by: dto.generated_by,
733            generation_parameters: dto.generation_parameters,
734            reviewed_at: dto.reviewed_at,
735            reviewed_by: dto.reviewed_by,
736            approved_at: dto.approved_at,
737            approved_by: dto.approved_by,
738            published_at: dto.published_at,
739            pdf_url: dto.pdf_url,
740            excel_url: dto.excel_url,
741            currency: dto.currency,
742            metadata: AuditMetadata::default(),
743        }
744    }
745}
746
747impl From<&FinancialStatement> for FinancialStatementResponseDto {
748    fn from(entity: &FinancialStatement) -> Self {
749        Self {
750            id: entity.id.clone(),
751            statement_number: entity.statement_number.clone(),
752            statement_type: entity.statement_type.clone(),
753            name: entity.name.clone(),
754            fiscal_period_id: entity.fiscal_period_id.clone(),
755            fiscal_year: entity.fiscal_year.clone(),
756            fiscal_month: entity.fiscal_month.clone(),
757            as_of_date: entity.as_of_date.clone(),
758            period_start: entity.period_start.clone(),
759            period_end: entity.period_end.clone(),
760            is_comparative: entity.is_comparative.clone(),
761            comparative_period_id: entity.comparative_period_id.clone(),
762            comparative_as_of_date: entity.comparative_as_of_date.clone(),
763            status: entity.status.clone(),
764            total_current_assets: entity.total_current_assets.clone(),
765            total_non_current_assets: entity.total_non_current_assets.clone(),
766            total_assets: entity.total_assets.clone(),
767            total_current_liabilities: entity.total_current_liabilities.clone(),
768            total_non_current_liabilities: entity.total_non_current_liabilities.clone(),
769            total_liabilities: entity.total_liabilities.clone(),
770            total_equity: entity.total_equity.clone(),
771            balance_check: entity.balance_check.clone(),
772            balance_difference: entity.balance_difference.clone(),
773            total_revenue: entity.total_revenue.clone(),
774            total_cogs: entity.total_cogs.clone(),
775            gross_profit: entity.gross_profit.clone(),
776            gross_profit_margin: entity.gross_profit_margin.clone(),
777            total_operating_expenses: entity.total_operating_expenses.clone(),
778            operating_income: entity.operating_income.clone(),
779            operating_margin: entity.operating_margin.clone(),
780            total_other_income: entity.total_other_income.clone(),
781            total_other_expenses: entity.total_other_expenses.clone(),
782            income_before_tax: entity.income_before_tax.clone(),
783            tax_expense: entity.tax_expense.clone(),
784            net_income: entity.net_income.clone(),
785            net_profit_margin: entity.net_profit_margin.clone(),
786            cash_from_operations: entity.cash_from_operations.clone(),
787            cash_from_investing: entity.cash_from_investing.clone(),
788            cash_from_financing: entity.cash_from_financing.clone(),
789            net_cash_change: entity.net_cash_change.clone(),
790            beginning_cash: entity.beginning_cash.clone(),
791            ending_cash: entity.ending_cash.clone(),
792            total_debits: entity.total_debits.clone(),
793            total_credits: entity.total_credits.clone(),
794            trial_balance_check: entity.trial_balance_check.clone(),
795            line_items: entity.line_items.clone(),
796            comparative_data: entity.comparative_data.clone(),
797            variance_data: entity.variance_data.clone(),
798            notes: entity.notes.clone(),
799            management_notes: entity.management_notes.clone(),
800            generated_at: entity.generated_at.clone(),
801            generated_by: entity.generated_by.clone(),
802            generation_parameters: entity.generation_parameters.clone(),
803            reviewed_at: entity.reviewed_at.clone(),
804            reviewed_by: entity.reviewed_by.clone(),
805            approved_at: entity.approved_at.clone(),
806            approved_by: entity.approved_by.clone(),
807            published_at: entity.published_at.clone(),
808            pdf_url: entity.pdf_url.clone(),
809            excel_url: entity.excel_url.clone(),
810            currency: entity.currency.clone(),
811            metadata: entity.metadata.clone(),
812        }
813    }
814}
815
816impl backbone_core::FromCreateDto<CreateFinancialStatementDto> for FinancialStatement {
817    fn from_create_dto(dto: CreateFinancialStatementDto) -> backbone_core::ServiceResult<Self> {
818        Ok(FinancialStatement::from(dto))
819    }
820}
821
822impl backbone_core::ApplyUpdateDto<UpdateFinancialStatementDto> for FinancialStatement {
823    fn apply_update(mut self, dto: UpdateFinancialStatementDto) -> backbone_core::ServiceResult<Self> {
824        self.statement_number = dto.statement_number;
825        self.statement_type = dto.statement_type;
826        self.name = dto.name;
827        self.fiscal_period_id = dto.fiscal_period_id;
828        self.fiscal_year = dto.fiscal_year;
829        self.fiscal_month = dto.fiscal_month;
830        self.as_of_date = dto.as_of_date;
831        self.period_start = dto.period_start;
832        self.period_end = dto.period_end;
833        self.is_comparative = dto.is_comparative;
834        self.comparative_period_id = dto.comparative_period_id;
835        self.comparative_as_of_date = dto.comparative_as_of_date;
836        self.status = dto.status;
837        self.total_current_assets = dto.total_current_assets;
838        self.total_non_current_assets = dto.total_non_current_assets;
839        self.total_assets = dto.total_assets;
840        self.total_current_liabilities = dto.total_current_liabilities;
841        self.total_non_current_liabilities = dto.total_non_current_liabilities;
842        self.total_liabilities = dto.total_liabilities;
843        self.total_equity = dto.total_equity;
844        self.balance_check = dto.balance_check;
845        self.balance_difference = dto.balance_difference;
846        self.total_revenue = dto.total_revenue;
847        self.total_cogs = dto.total_cogs;
848        self.gross_profit = dto.gross_profit;
849        self.gross_profit_margin = dto.gross_profit_margin;
850        self.total_operating_expenses = dto.total_operating_expenses;
851        self.operating_income = dto.operating_income;
852        self.operating_margin = dto.operating_margin;
853        self.total_other_income = dto.total_other_income;
854        self.total_other_expenses = dto.total_other_expenses;
855        self.income_before_tax = dto.income_before_tax;
856        self.tax_expense = dto.tax_expense;
857        self.net_income = dto.net_income;
858        self.net_profit_margin = dto.net_profit_margin;
859        self.cash_from_operations = dto.cash_from_operations;
860        self.cash_from_investing = dto.cash_from_investing;
861        self.cash_from_financing = dto.cash_from_financing;
862        self.net_cash_change = dto.net_cash_change;
863        self.beginning_cash = dto.beginning_cash;
864        self.ending_cash = dto.ending_cash;
865        self.total_debits = dto.total_debits;
866        self.total_credits = dto.total_credits;
867        self.trial_balance_check = dto.trial_balance_check;
868        self.line_items = dto.line_items;
869        self.comparative_data = dto.comparative_data;
870        self.variance_data = dto.variance_data;
871        self.notes = dto.notes;
872        self.management_notes = dto.management_notes;
873        self.generated_at = dto.generated_at;
874        self.generated_by = dto.generated_by;
875        self.generation_parameters = dto.generation_parameters;
876        self.reviewed_at = dto.reviewed_at;
877        self.reviewed_by = dto.reviewed_by;
878        self.approved_at = dto.approved_at;
879        self.approved_by = dto.approved_by;
880        self.published_at = dto.published_at;
881        self.pdf_url = dto.pdf_url;
882        self.excel_url = dto.excel_url;
883        self.currency = dto.currency;
884        Ok(self)
885    }
886}
887
888// =============================================================================
889// Custom DTOs
890// =============================================================================
891
892// Add custom DTOs specific to FinancialStatement here.
893// This section will be preserved during regeneration.
894// >>> END CUSTOM DTOs
895