//! FinancialStatement DTOs (Data Transfer Objects)
//!
//! Generated by metaphor-schema. Do not edit manually.
//!
//! DTOs provide a clean separation between domain entities and API
//! representations, with OpenAPI documentation support.
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc, NaiveDate};
use rust_decimal::Decimal;
#[cfg(feature = "openapi")]
#[cfg(feature = "openapi")]
use utoipa::ToSchema;
use crate::domain::entity::FinancialStatement;
use crate::domain::entity::AuditMetadata;
use crate::domain::entity::StatementStatus;
use crate::domain::entity::StatementType;
// =============================================================================
// Create DTO
// =============================================================================
/// Request DTO for creating a new FinancialStatement
///
/// Used in POST requests to create new entities.
/// Excludes auto-generated fields (id, created_at, updated_at, deleted_at).
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct CreateFinancialStatementDto {
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(alias = "statement_number")]
pub statement_number: String,
#[serde(alias = "statement_type")]
pub statement_type: StatementType,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "fiscal_period_id")]
pub fiscal_period_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = 42))]
#[serde(alias = "fiscal_year")]
pub fiscal_year: i32,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "fiscal_month")]
pub fiscal_month: Option<i32>,
#[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
#[serde(alias = "as_of_date")]
pub as_of_date: NaiveDate,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "period_start")]
pub period_start: Option<NaiveDate>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "period_end")]
pub period_end: Option<NaiveDate>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "is_comparative")]
pub is_comparative: bool,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "comparative_period_id")]
pub comparative_period_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "comparative_as_of_date")]
pub comparative_as_of_date: Option<NaiveDate>,
pub status: StatementStatus,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_current_assets")]
pub total_current_assets: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_non_current_assets")]
pub total_non_current_assets: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_assets")]
pub total_assets: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_current_liabilities")]
pub total_current_liabilities: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_non_current_liabilities")]
pub total_non_current_liabilities: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_liabilities")]
pub total_liabilities: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_equity")]
pub total_equity: Option<Decimal>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "balance_check")]
pub balance_check: bool,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "balance_difference")]
pub balance_difference: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_revenue")]
pub total_revenue: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_cogs")]
pub total_cogs: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "gross_profit")]
pub gross_profit: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "gross_profit_margin")]
pub gross_profit_margin: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_operating_expenses")]
pub total_operating_expenses: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "operating_income")]
pub operating_income: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "operating_margin")]
pub operating_margin: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_other_income")]
pub total_other_income: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_other_expenses")]
pub total_other_expenses: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "income_before_tax")]
pub income_before_tax: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "tax_expense")]
pub tax_expense: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "net_income")]
pub net_income: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "net_profit_margin")]
pub net_profit_margin: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_operations")]
pub cash_from_operations: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_investing")]
pub cash_from_investing: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_financing")]
pub cash_from_financing: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "net_cash_change")]
pub net_cash_change: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "beginning_cash")]
pub beginning_cash: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "ending_cash")]
pub ending_cash: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_debits")]
pub total_debits: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_credits")]
pub total_credits: Option<Decimal>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "trial_balance_check")]
pub trial_balance_check: bool,
#[serde(alias = "line_items")]
pub line_items: serde_json::Value,
#[serde(alias = "comparative_data")]
pub comparative_data: serde_json::Value,
#[serde(alias = "variance_data")]
pub variance_data: serde_json::Value,
pub notes: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "management_notes")]
pub management_notes: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "generated_at")]
pub generated_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "generated_by")]
pub generated_by: Option<Uuid>,
#[serde(alias = "generation_parameters")]
pub generation_parameters: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "reviewed_at")]
pub reviewed_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "reviewed_by")]
pub reviewed_by: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "approved_at")]
pub approved_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "approved_by")]
pub approved_by: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "published_at")]
pub published_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "pdf_url")]
pub pdf_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "excel_url")]
pub excel_url: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub currency: String,
}
// =============================================================================
// Update DTO
// =============================================================================
/// Request DTO for full update of a FinancialStatement
///
/// Used in PUT requests for full entity replacement.
/// All fields are required (except auto-generated ones).
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct UpdateFinancialStatementDto {
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(alias = "statement_number")]
pub statement_number: String,
#[serde(alias = "statement_type")]
pub statement_type: StatementType,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "fiscal_period_id")]
pub fiscal_period_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = 42))]
#[serde(alias = "fiscal_year")]
pub fiscal_year: i32,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "fiscal_month")]
pub fiscal_month: Option<i32>,
#[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
#[serde(alias = "as_of_date")]
pub as_of_date: NaiveDate,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "period_start")]
pub period_start: Option<NaiveDate>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "period_end")]
pub period_end: Option<NaiveDate>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "is_comparative")]
pub is_comparative: bool,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "comparative_period_id")]
pub comparative_period_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "comparative_as_of_date")]
pub comparative_as_of_date: Option<NaiveDate>,
pub status: StatementStatus,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_current_assets")]
pub total_current_assets: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_non_current_assets")]
pub total_non_current_assets: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_assets")]
pub total_assets: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_current_liabilities")]
pub total_current_liabilities: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_non_current_liabilities")]
pub total_non_current_liabilities: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_liabilities")]
pub total_liabilities: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_equity")]
pub total_equity: Option<Decimal>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "balance_check")]
pub balance_check: bool,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "balance_difference")]
pub balance_difference: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_revenue")]
pub total_revenue: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_cogs")]
pub total_cogs: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "gross_profit")]
pub gross_profit: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "gross_profit_margin")]
pub gross_profit_margin: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_operating_expenses")]
pub total_operating_expenses: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "operating_income")]
pub operating_income: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "operating_margin")]
pub operating_margin: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_other_income")]
pub total_other_income: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_other_expenses")]
pub total_other_expenses: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "income_before_tax")]
pub income_before_tax: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "tax_expense")]
pub tax_expense: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "net_income")]
pub net_income: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "net_profit_margin")]
pub net_profit_margin: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_operations")]
pub cash_from_operations: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_investing")]
pub cash_from_investing: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "cash_from_financing")]
pub cash_from_financing: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "net_cash_change")]
pub net_cash_change: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "beginning_cash")]
pub beginning_cash: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "ending_cash")]
pub ending_cash: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_debits")]
pub total_debits: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "total_credits")]
pub total_credits: Option<Decimal>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(alias = "trial_balance_check")]
pub trial_balance_check: bool,
#[serde(alias = "line_items")]
pub line_items: serde_json::Value,
#[serde(alias = "comparative_data")]
pub comparative_data: serde_json::Value,
#[serde(alias = "variance_data")]
pub variance_data: serde_json::Value,
pub notes: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "management_notes")]
pub management_notes: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "generated_at")]
pub generated_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "generated_by")]
pub generated_by: Option<Uuid>,
#[serde(alias = "generation_parameters")]
pub generation_parameters: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "reviewed_at")]
pub reviewed_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "reviewed_by")]
pub reviewed_by: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "approved_at")]
pub approved_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "approved_by")]
pub approved_by: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "published_at")]
pub published_at: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "pdf_url")]
pub pdf_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "excel_url")]
pub excel_url: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub currency: String,
}
// =============================================================================
// Patch DTO
// =============================================================================
/// Request DTO for partial update of a FinancialStatement
///
/// Used in PATCH requests for partial updates.
/// All fields are optional - only provided fields will be updated.
#[derive(Debug, Clone, Default, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct PatchFinancialStatementDto {
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "statement_number")]
pub statement_number: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "statement_type")]
pub statement_type: Option<StatementType>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "fiscal_period_id")]
pub fiscal_period_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = 42))]
#[serde(skip_serializing_if = "Option::is_none", alias = "fiscal_year")]
pub fiscal_year: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none", alias = "fiscal_month")]
pub fiscal_month: Option<i32>,
#[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
#[serde(skip_serializing_if = "Option::is_none", alias = "as_of_date")]
pub as_of_date: Option<NaiveDate>,
#[serde(skip_serializing_if = "Option::is_none", alias = "period_start")]
pub period_start: Option<NaiveDate>,
#[serde(skip_serializing_if = "Option::is_none", alias = "period_end")]
pub period_end: Option<NaiveDate>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(skip_serializing_if = "Option::is_none", alias = "is_comparative")]
pub is_comparative: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", alias = "comparative_period_id")]
pub comparative_period_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "comparative_as_of_date")]
pub comparative_as_of_date: Option<NaiveDate>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<StatementStatus>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_current_assets")]
pub total_current_assets: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_non_current_assets")]
pub total_non_current_assets: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_assets")]
pub total_assets: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_current_liabilities")]
pub total_current_liabilities: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_non_current_liabilities")]
pub total_non_current_liabilities: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_liabilities")]
pub total_liabilities: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_equity")]
pub total_equity: Option<Decimal>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(skip_serializing_if = "Option::is_none", alias = "balance_check")]
pub balance_check: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", alias = "balance_difference")]
pub balance_difference: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_revenue")]
pub total_revenue: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_cogs")]
pub total_cogs: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "gross_profit")]
pub gross_profit: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "gross_profit_margin")]
pub gross_profit_margin: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_operating_expenses")]
pub total_operating_expenses: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "operating_income")]
pub operating_income: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "operating_margin")]
pub operating_margin: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_other_income")]
pub total_other_income: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_other_expenses")]
pub total_other_expenses: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "income_before_tax")]
pub income_before_tax: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "tax_expense")]
pub tax_expense: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "net_income")]
pub net_income: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "net_profit_margin")]
pub net_profit_margin: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "cash_from_operations")]
pub cash_from_operations: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "cash_from_investing")]
pub cash_from_investing: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "cash_from_financing")]
pub cash_from_financing: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "net_cash_change")]
pub net_cash_change: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "beginning_cash")]
pub beginning_cash: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "ending_cash")]
pub ending_cash: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_debits")]
pub total_debits: Option<Decimal>,
#[serde(skip_serializing_if = "Option::is_none", alias = "total_credits")]
pub total_credits: Option<Decimal>,
#[cfg_attr(feature = "openapi", schema(example = true))]
#[serde(skip_serializing_if = "Option::is_none", alias = "trial_balance_check")]
pub trial_balance_check: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", alias = "line_items")]
pub line_items: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none", alias = "comparative_data")]
pub comparative_data: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none", alias = "variance_data")]
pub variance_data: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none", alias = "management_notes")]
pub management_notes: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "generated_at")]
pub generated_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none", alias = "generated_by")]
pub generated_by: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "generation_parameters")]
pub generation_parameters: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none", alias = "reviewed_at")]
pub reviewed_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none", alias = "reviewed_by")]
pub reviewed_by: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "approved_at")]
pub approved_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none", alias = "approved_by")]
pub approved_by: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none", alias = "published_at")]
pub published_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none", alias = "pdf_url")]
pub pdf_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", alias = "excel_url")]
pub excel_url: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub currency: Option<String>,
}
impl PatchFinancialStatementDto {
/// Check if any field is set
pub fn has_changes(&self) -> bool {
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()
}
}
// =============================================================================
// Response DTO
// =============================================================================
/// Response DTO for FinancialStatement entity
///
/// Used in API responses for single entity.
/// Includes all fields including metadata.
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct FinancialStatementResponseDto {
#[cfg_attr(feature = "openapi", schema(example = "550e8400-e29b-41d4-a716-446655440000"))]
pub id: Uuid,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub statement_number: String,
pub statement_type: StatementType,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub name: String,
pub fiscal_period_id: Option<Uuid>,
#[cfg_attr(feature = "openapi", schema(example = 42))]
pub fiscal_year: i32,
pub fiscal_month: Option<i32>,
#[cfg_attr(feature = "openapi", schema(example = "2024-01-01"))]
pub as_of_date: NaiveDate,
pub period_start: Option<NaiveDate>,
pub period_end: Option<NaiveDate>,
#[cfg_attr(feature = "openapi", schema(example = true))]
pub is_comparative: bool,
pub comparative_period_id: Option<Uuid>,
pub comparative_as_of_date: Option<NaiveDate>,
pub status: StatementStatus,
pub total_current_assets: Option<Decimal>,
pub total_non_current_assets: Option<Decimal>,
pub total_assets: Option<Decimal>,
pub total_current_liabilities: Option<Decimal>,
pub total_non_current_liabilities: Option<Decimal>,
pub total_liabilities: Option<Decimal>,
pub total_equity: Option<Decimal>,
#[cfg_attr(feature = "openapi", schema(example = true))]
pub balance_check: bool,
pub balance_difference: Option<Decimal>,
pub total_revenue: Option<Decimal>,
pub total_cogs: Option<Decimal>,
pub gross_profit: Option<Decimal>,
pub gross_profit_margin: Option<Decimal>,
pub total_operating_expenses: Option<Decimal>,
pub operating_income: Option<Decimal>,
pub operating_margin: Option<Decimal>,
pub total_other_income: Option<Decimal>,
pub total_other_expenses: Option<Decimal>,
pub income_before_tax: Option<Decimal>,
pub tax_expense: Option<Decimal>,
pub net_income: Option<Decimal>,
pub net_profit_margin: Option<Decimal>,
pub cash_from_operations: Option<Decimal>,
pub cash_from_investing: Option<Decimal>,
pub cash_from_financing: Option<Decimal>,
pub net_cash_change: Option<Decimal>,
pub beginning_cash: Option<Decimal>,
pub ending_cash: Option<Decimal>,
pub total_debits: Option<Decimal>,
pub total_credits: Option<Decimal>,
#[cfg_attr(feature = "openapi", schema(example = true))]
pub trial_balance_check: bool,
pub line_items: serde_json::Value,
pub comparative_data: serde_json::Value,
pub variance_data: serde_json::Value,
pub notes: serde_json::Value,
pub management_notes: Option<String>,
pub generated_at: Option<DateTime<Utc>>,
pub generated_by: Option<Uuid>,
pub generation_parameters: serde_json::Value,
pub reviewed_at: Option<DateTime<Utc>>,
pub reviewed_by: Option<Uuid>,
pub approved_at: Option<DateTime<Utc>>,
pub approved_by: Option<Uuid>,
pub published_at: Option<DateTime<Utc>>,
pub pdf_url: Option<String>,
pub excel_url: Option<String>,
#[cfg_attr(feature = "openapi", schema(example = "example"))]
pub currency: String,
pub metadata: AuditMetadata,
}
// =============================================================================
// List Response DTO
// =============================================================================
/// Paginated list response for FinancialStatement entities
///
/// Used in API responses for list endpoints.
/// Includes pagination metadata.
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct FinancialStatementListResponseDto {
/// List of items
pub items: Vec<FinancialStatementResponseDto>,
/// Total number of items (across all pages)
pub total: u64,
/// Current page number (1-indexed)
pub page: u32,
/// Number of items per page
pub per_page: u32,
/// Total number of pages
pub total_pages: u32,
/// Whether there is a next page
pub has_next: bool,
/// Whether there is a previous page
pub has_prev: bool,
}
impl FinancialStatementListResponseDto {
/// Create a new list response from items and pagination info
pub fn new(items: Vec<FinancialStatementResponseDto>, total: u64, page: u32, per_page: u32) -> Self {
let total_pages = if per_page > 0 {
((total as f64) / (per_page as f64)).ceil() as u32
} else {
0
};
Self {
items,
total,
page,
per_page,
total_pages,
has_next: page < total_pages,
has_prev: page > 1,
}
}
}
/// Summary DTO for list views (compact version)
#[derive(Debug, Clone, Serialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct FinancialStatementSummaryDto {
pub id: Uuid,
pub statement_number: String,
pub statement_type: StatementType,
pub name: String,
pub created_at: Option<DateTime<Utc>>,
}
// =============================================================================
// Conversions
// =============================================================================
impl From<FinancialStatement> for FinancialStatementResponseDto {
fn from(entity: FinancialStatement) -> Self {
Self {
id: entity.id,
statement_number: entity.statement_number,
statement_type: entity.statement_type,
name: entity.name,
fiscal_period_id: entity.fiscal_period_id,
fiscal_year: entity.fiscal_year,
fiscal_month: entity.fiscal_month,
as_of_date: entity.as_of_date,
period_start: entity.period_start,
period_end: entity.period_end,
is_comparative: entity.is_comparative,
comparative_period_id: entity.comparative_period_id,
comparative_as_of_date: entity.comparative_as_of_date,
status: entity.status,
total_current_assets: entity.total_current_assets,
total_non_current_assets: entity.total_non_current_assets,
total_assets: entity.total_assets,
total_current_liabilities: entity.total_current_liabilities,
total_non_current_liabilities: entity.total_non_current_liabilities,
total_liabilities: entity.total_liabilities,
total_equity: entity.total_equity,
balance_check: entity.balance_check,
balance_difference: entity.balance_difference,
total_revenue: entity.total_revenue,
total_cogs: entity.total_cogs,
gross_profit: entity.gross_profit,
gross_profit_margin: entity.gross_profit_margin,
total_operating_expenses: entity.total_operating_expenses,
operating_income: entity.operating_income,
operating_margin: entity.operating_margin,
total_other_income: entity.total_other_income,
total_other_expenses: entity.total_other_expenses,
income_before_tax: entity.income_before_tax,
tax_expense: entity.tax_expense,
net_income: entity.net_income,
net_profit_margin: entity.net_profit_margin,
cash_from_operations: entity.cash_from_operations,
cash_from_investing: entity.cash_from_investing,
cash_from_financing: entity.cash_from_financing,
net_cash_change: entity.net_cash_change,
beginning_cash: entity.beginning_cash,
ending_cash: entity.ending_cash,
total_debits: entity.total_debits,
total_credits: entity.total_credits,
trial_balance_check: entity.trial_balance_check,
line_items: entity.line_items,
comparative_data: entity.comparative_data,
variance_data: entity.variance_data,
notes: entity.notes,
management_notes: entity.management_notes,
generated_at: entity.generated_at,
generated_by: entity.generated_by,
generation_parameters: entity.generation_parameters,
reviewed_at: entity.reviewed_at,
reviewed_by: entity.reviewed_by,
approved_at: entity.approved_at,
approved_by: entity.approved_by,
published_at: entity.published_at,
pdf_url: entity.pdf_url,
excel_url: entity.excel_url,
currency: entity.currency,
metadata: entity.metadata,
}
}
}
impl From<FinancialStatement> for FinancialStatementSummaryDto {
fn from(entity: FinancialStatement) -> Self {
let created_at = backbone_core::PersistentEntity::created_at(&entity);
Self {
id: entity.id,
statement_number: entity.statement_number,
statement_type: entity.statement_type,
name: entity.name,
created_at,
}
}
}
impl From<CreateFinancialStatementDto> for FinancialStatement {
fn from(dto: CreateFinancialStatementDto) -> Self {
Self {
id: Uuid::new_v4(),
statement_number: dto.statement_number,
statement_type: dto.statement_type,
name: dto.name,
fiscal_period_id: dto.fiscal_period_id,
fiscal_year: dto.fiscal_year,
fiscal_month: dto.fiscal_month,
as_of_date: dto.as_of_date,
period_start: dto.period_start,
period_end: dto.period_end,
is_comparative: dto.is_comparative,
comparative_period_id: dto.comparative_period_id,
comparative_as_of_date: dto.comparative_as_of_date,
status: dto.status,
total_current_assets: dto.total_current_assets,
total_non_current_assets: dto.total_non_current_assets,
total_assets: dto.total_assets,
total_current_liabilities: dto.total_current_liabilities,
total_non_current_liabilities: dto.total_non_current_liabilities,
total_liabilities: dto.total_liabilities,
total_equity: dto.total_equity,
balance_check: dto.balance_check,
balance_difference: dto.balance_difference,
total_revenue: dto.total_revenue,
total_cogs: dto.total_cogs,
gross_profit: dto.gross_profit,
gross_profit_margin: dto.gross_profit_margin,
total_operating_expenses: dto.total_operating_expenses,
operating_income: dto.operating_income,
operating_margin: dto.operating_margin,
total_other_income: dto.total_other_income,
total_other_expenses: dto.total_other_expenses,
income_before_tax: dto.income_before_tax,
tax_expense: dto.tax_expense,
net_income: dto.net_income,
net_profit_margin: dto.net_profit_margin,
cash_from_operations: dto.cash_from_operations,
cash_from_investing: dto.cash_from_investing,
cash_from_financing: dto.cash_from_financing,
net_cash_change: dto.net_cash_change,
beginning_cash: dto.beginning_cash,
ending_cash: dto.ending_cash,
total_debits: dto.total_debits,
total_credits: dto.total_credits,
trial_balance_check: dto.trial_balance_check,
line_items: dto.line_items,
comparative_data: dto.comparative_data,
variance_data: dto.variance_data,
notes: dto.notes,
management_notes: dto.management_notes,
generated_at: dto.generated_at,
generated_by: dto.generated_by,
generation_parameters: dto.generation_parameters,
reviewed_at: dto.reviewed_at,
reviewed_by: dto.reviewed_by,
approved_at: dto.approved_at,
approved_by: dto.approved_by,
published_at: dto.published_at,
pdf_url: dto.pdf_url,
excel_url: dto.excel_url,
currency: dto.currency,
metadata: AuditMetadata::default(),
}
}
}
impl From<&FinancialStatement> for FinancialStatementResponseDto {
fn from(entity: &FinancialStatement) -> Self {
Self {
id: entity.id.clone(),
statement_number: entity.statement_number.clone(),
statement_type: entity.statement_type.clone(),
name: entity.name.clone(),
fiscal_period_id: entity.fiscal_period_id.clone(),
fiscal_year: entity.fiscal_year.clone(),
fiscal_month: entity.fiscal_month.clone(),
as_of_date: entity.as_of_date.clone(),
period_start: entity.period_start.clone(),
period_end: entity.period_end.clone(),
is_comparative: entity.is_comparative.clone(),
comparative_period_id: entity.comparative_period_id.clone(),
comparative_as_of_date: entity.comparative_as_of_date.clone(),
status: entity.status.clone(),
total_current_assets: entity.total_current_assets.clone(),
total_non_current_assets: entity.total_non_current_assets.clone(),
total_assets: entity.total_assets.clone(),
total_current_liabilities: entity.total_current_liabilities.clone(),
total_non_current_liabilities: entity.total_non_current_liabilities.clone(),
total_liabilities: entity.total_liabilities.clone(),
total_equity: entity.total_equity.clone(),
balance_check: entity.balance_check.clone(),
balance_difference: entity.balance_difference.clone(),
total_revenue: entity.total_revenue.clone(),
total_cogs: entity.total_cogs.clone(),
gross_profit: entity.gross_profit.clone(),
gross_profit_margin: entity.gross_profit_margin.clone(),
total_operating_expenses: entity.total_operating_expenses.clone(),
operating_income: entity.operating_income.clone(),
operating_margin: entity.operating_margin.clone(),
total_other_income: entity.total_other_income.clone(),
total_other_expenses: entity.total_other_expenses.clone(),
income_before_tax: entity.income_before_tax.clone(),
tax_expense: entity.tax_expense.clone(),
net_income: entity.net_income.clone(),
net_profit_margin: entity.net_profit_margin.clone(),
cash_from_operations: entity.cash_from_operations.clone(),
cash_from_investing: entity.cash_from_investing.clone(),
cash_from_financing: entity.cash_from_financing.clone(),
net_cash_change: entity.net_cash_change.clone(),
beginning_cash: entity.beginning_cash.clone(),
ending_cash: entity.ending_cash.clone(),
total_debits: entity.total_debits.clone(),
total_credits: entity.total_credits.clone(),
trial_balance_check: entity.trial_balance_check.clone(),
line_items: entity.line_items.clone(),
comparative_data: entity.comparative_data.clone(),
variance_data: entity.variance_data.clone(),
notes: entity.notes.clone(),
management_notes: entity.management_notes.clone(),
generated_at: entity.generated_at.clone(),
generated_by: entity.generated_by.clone(),
generation_parameters: entity.generation_parameters.clone(),
reviewed_at: entity.reviewed_at.clone(),
reviewed_by: entity.reviewed_by.clone(),
approved_at: entity.approved_at.clone(),
approved_by: entity.approved_by.clone(),
published_at: entity.published_at.clone(),
pdf_url: entity.pdf_url.clone(),
excel_url: entity.excel_url.clone(),
currency: entity.currency.clone(),
metadata: entity.metadata.clone(),
}
}
}
impl backbone_core::FromCreateDto<CreateFinancialStatementDto> for FinancialStatement {
fn from_create_dto(dto: CreateFinancialStatementDto) -> backbone_core::ServiceResult<Self> {
Ok(FinancialStatement::from(dto))
}
}
impl backbone_core::ApplyUpdateDto<UpdateFinancialStatementDto> for FinancialStatement {
fn apply_update(mut self, dto: UpdateFinancialStatementDto) -> backbone_core::ServiceResult<Self> {
self.statement_number = dto.statement_number;
self.statement_type = dto.statement_type;
self.name = dto.name;
self.fiscal_period_id = dto.fiscal_period_id;
self.fiscal_year = dto.fiscal_year;
self.fiscal_month = dto.fiscal_month;
self.as_of_date = dto.as_of_date;
self.period_start = dto.period_start;
self.period_end = dto.period_end;
self.is_comparative = dto.is_comparative;
self.comparative_period_id = dto.comparative_period_id;
self.comparative_as_of_date = dto.comparative_as_of_date;
self.status = dto.status;
self.total_current_assets = dto.total_current_assets;
self.total_non_current_assets = dto.total_non_current_assets;
self.total_assets = dto.total_assets;
self.total_current_liabilities = dto.total_current_liabilities;
self.total_non_current_liabilities = dto.total_non_current_liabilities;
self.total_liabilities = dto.total_liabilities;
self.total_equity = dto.total_equity;
self.balance_check = dto.balance_check;
self.balance_difference = dto.balance_difference;
self.total_revenue = dto.total_revenue;
self.total_cogs = dto.total_cogs;
self.gross_profit = dto.gross_profit;
self.gross_profit_margin = dto.gross_profit_margin;
self.total_operating_expenses = dto.total_operating_expenses;
self.operating_income = dto.operating_income;
self.operating_margin = dto.operating_margin;
self.total_other_income = dto.total_other_income;
self.total_other_expenses = dto.total_other_expenses;
self.income_before_tax = dto.income_before_tax;
self.tax_expense = dto.tax_expense;
self.net_income = dto.net_income;
self.net_profit_margin = dto.net_profit_margin;
self.cash_from_operations = dto.cash_from_operations;
self.cash_from_investing = dto.cash_from_investing;
self.cash_from_financing = dto.cash_from_financing;
self.net_cash_change = dto.net_cash_change;
self.beginning_cash = dto.beginning_cash;
self.ending_cash = dto.ending_cash;
self.total_debits = dto.total_debits;
self.total_credits = dto.total_credits;
self.trial_balance_check = dto.trial_balance_check;
self.line_items = dto.line_items;
self.comparative_data = dto.comparative_data;
self.variance_data = dto.variance_data;
self.notes = dto.notes;
self.management_notes = dto.management_notes;
self.generated_at = dto.generated_at;
self.generated_by = dto.generated_by;
self.generation_parameters = dto.generation_parameters;
self.reviewed_at = dto.reviewed_at;
self.reviewed_by = dto.reviewed_by;
self.approved_at = dto.approved_at;
self.approved_by = dto.approved_by;
self.published_at = dto.published_at;
self.pdf_url = dto.pdf_url;
self.excel_url = dto.excel_url;
self.currency = dto.currency;
Ok(self)
}
}
// =============================================================================
// Custom DTOs
// =============================================================================
// Add custom DTOs specific to FinancialStatement here.
// This section will be preserved during regeneration.
// >>> END CUSTOM DTOs