1use 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#[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#[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#[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 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#[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#[derive(Debug, Clone, Serialize)]
541#[cfg_attr(feature = "openapi", derive(ToSchema))]
542#[serde(rename_all = "camelCase")]
543pub struct FinancialStatementListResponseDto {
544 pub items: Vec<FinancialStatementResponseDto>,
546 pub total: u64,
548 pub page: u32,
550 pub per_page: u32,
552 pub total_pages: u32,
554 pub has_next: bool,
556 pub has_prev: bool,
558}
559
560impl FinancialStatementListResponseDto {
561 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#[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
592impl 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