Skip to main content

backbone_banking/domain/entity/
bank_statement_import.rs

1use chrono::{DateTime, Utc, NaiveDate};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::SourceFormat;
8use super::ImportStatus;
9use super::AuditMetadata;
10
11/// Strongly-typed ID for BankStatementImport
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct BankStatementImportId(pub Uuid);
15
16impl BankStatementImportId {
17    pub fn new(id: Uuid) -> Self { Self(id) }
18    pub fn generate() -> Self { Self(Uuid::new_v4()) }
19    pub fn into_inner(self) -> Uuid { self.0 }
20}
21
22impl std::fmt::Display for BankStatementImportId {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        write!(f, "{}", self.0)
25    }
26}
27
28impl std::str::FromStr for BankStatementImportId {
29    type Err = uuid::Error;
30    fn from_str(s: &str) -> Result<Self, Self::Err> {
31        Ok(Self(Uuid::parse_str(s)?))
32    }
33}
34
35impl From<Uuid> for BankStatementImportId {
36    fn from(id: Uuid) -> Self { Self(id) }
37}
38
39impl From<BankStatementImportId> for Uuid {
40    fn from(id: BankStatementImportId) -> Self { id.0 }
41}
42
43impl AsRef<Uuid> for BankStatementImportId {
44    fn as_ref(&self) -> &Uuid { &self.0 }
45}
46
47impl std::ops::Deref for BankStatementImportId {
48    type Target = Uuid;
49    fn deref(&self) -> &Self::Target { &self.0 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
53pub struct BankStatementImport {
54    pub id: Uuid,
55    pub bank_account_id: Uuid,
56    pub source_format: SourceFormat,
57    pub statement_period_start: NaiveDate,
58    pub statement_period_end: NaiveDate,
59    pub opening_balance: Decimal,
60    pub closing_balance: Decimal,
61    pub file_ref: Option<String>,
62    pub status: ImportStatus,
63    pub row_count: i32,
64    #[serde(default)]
65    #[sqlx(json)]
66    pub metadata: AuditMetadata,
67}
68
69impl BankStatementImport {
70    /// Create a builder for BankStatementImport
71    pub fn builder() -> BankStatementImportBuilder {
72        <BankStatementImportBuilder as Default>::default()
73    }
74
75    /// Create a new BankStatementImport with required fields
76    pub fn new(bank_account_id: Uuid, source_format: SourceFormat, statement_period_start: NaiveDate, statement_period_end: NaiveDate, opening_balance: Decimal, closing_balance: Decimal, status: ImportStatus, row_count: i32) -> Self {
77        Self {
78            id: Uuid::new_v4(),
79            bank_account_id,
80            source_format,
81            statement_period_start,
82            statement_period_end,
83            opening_balance,
84            closing_balance,
85            file_ref: None,
86            status,
87            row_count,
88            metadata: AuditMetadata::default(),
89        }
90    }
91
92    /// Get the entity's unique identifier
93    pub fn id(&self) -> &Uuid {
94        &self.id
95    }
96
97    /// Get a strongly-typed ID for this entity
98    pub fn typed_id(&self) -> BankStatementImportId {
99        BankStatementImportId(self.id)
100    }
101
102    /// Get when this entity was created
103    pub fn created_at(&self) -> Option<&DateTime<Utc>> {
104        self.metadata.created_at.as_ref()
105    }
106
107    /// Get when this entity was last updated
108    pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
109        self.metadata.updated_at.as_ref()
110    }
111
112    /// Check if this entity is soft deleted
113    pub fn is_deleted(&self) -> bool {
114        self.metadata.deleted_at.is_some()
115    }
116
117    /// Check if this entity is active (not deleted)
118    pub fn is_active(&self) -> bool {
119        self.metadata.deleted_at.is_none()
120    }
121
122    /// Get when this entity was deleted
123    pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
124        self.metadata.deleted_at.as_ref()
125    }
126
127    /// Get who created this entity
128    pub fn created_by(&self) -> Option<&Uuid> {
129        self.metadata.created_by.as_ref()
130    }
131
132    /// Get who last updated this entity
133    pub fn updated_by(&self) -> Option<&Uuid> {
134        self.metadata.updated_by.as_ref()
135    }
136
137    /// Get who deleted this entity
138    pub fn deleted_by(&self) -> Option<&Uuid> {
139        self.metadata.deleted_by.as_ref()
140    }
141
142    /// Get the current status
143    pub fn status(&self) -> &ImportStatus {
144        &self.status
145    }
146
147
148    // ==========================================================
149    // Fluent Setters (with_* for optional fields)
150    // ==========================================================
151
152    /// Set the file_ref field (chainable)
153    pub fn with_file_ref(mut self, value: String) -> Self {
154        self.file_ref = Some(value);
155        self
156    }
157
158    // ==========================================================
159    // Partial Update
160    // ==========================================================
161
162    /// Apply partial updates from a map of field name to JSON value
163    pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
164        for (key, value) in fields {
165            match key.as_str() {
166                "bank_account_id" => {
167                    if let Ok(v) = serde_json::from_value(value) { self.bank_account_id = v; }
168                }
169                "source_format" => {
170                    if let Ok(v) = serde_json::from_value(value) { self.source_format = v; }
171                }
172                "statement_period_start" => {
173                    if let Ok(v) = serde_json::from_value(value) { self.statement_period_start = v; }
174                }
175                "statement_period_end" => {
176                    if let Ok(v) = serde_json::from_value(value) { self.statement_period_end = v; }
177                }
178                "opening_balance" => {
179                    if let Ok(v) = serde_json::from_value(value) { self.opening_balance = v; }
180                }
181                "closing_balance" => {
182                    if let Ok(v) = serde_json::from_value(value) { self.closing_balance = v; }
183                }
184                "file_ref" => {
185                    if let Ok(v) = serde_json::from_value(value) { self.file_ref = v; }
186                }
187                "status" => {
188                    if let Ok(v) = serde_json::from_value(value) { self.status = v; }
189                }
190                "row_count" => {
191                    if let Ok(v) = serde_json::from_value(value) { self.row_count = v; }
192                }
193                _ => {} // ignore unknown fields
194            }
195        }
196    }
197
198}
199
200impl super::Entity for BankStatementImport {
201    type Id = Uuid;
202
203    fn entity_id(&self) -> &Self::Id {
204        &self.id
205    }
206
207    fn entity_type() -> &'static str {
208        "BankStatementImport"
209    }
210}
211
212impl backbone_core::PersistentEntity for BankStatementImport {
213    fn entity_id(&self) -> String {
214        self.id.to_string()
215    }
216    fn set_entity_id(&mut self, id: String) {
217        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
218            self.id = uuid;
219        }
220    }
221    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
222        self.metadata.created_at
223    }
224    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
225        self.metadata.created_at = Some(ts);
226    }
227    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
228        self.metadata.updated_at
229    }
230    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
231        self.metadata.updated_at = Some(ts);
232    }
233    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
234        self.metadata.deleted_at
235    }
236    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
237        self.metadata.deleted_at = ts;
238    }
239}
240
241impl backbone_orm::EntityRepoMeta for BankStatementImport {
242    fn column_types() -> std::collections::HashMap<String, String> {
243        let mut m = std::collections::HashMap::new();
244        m.insert("id".to_string(), "uuid".to_string());
245        m.insert("bank_account_id".to_string(), "uuid".to_string());
246        m.insert("source_format".to_string(), "source_format".to_string());
247        m.insert("status".to_string(), "import_status".to_string());
248        m.insert("statement_period_start".to_string(), "date".to_string());
249        m.insert("statement_period_end".to_string(), "date".to_string());
250        m
251    }
252    fn search_fields() -> &'static [&'static str] {
253        &[]
254    }
255    fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
256        &[("bankAccount", "bank_accounts", "bankAccountId")]
257    }
258}
259
260/// Builder for BankStatementImport entity
261///
262/// Provides a fluent API for constructing BankStatementImport instances.
263/// System fields (id, metadata, timestamps) are auto-initialized.
264#[derive(Debug, Clone, Default)]
265pub struct BankStatementImportBuilder {
266    bank_account_id: Option<Uuid>,
267    source_format: Option<SourceFormat>,
268    statement_period_start: Option<NaiveDate>,
269    statement_period_end: Option<NaiveDate>,
270    opening_balance: Option<Decimal>,
271    closing_balance: Option<Decimal>,
272    file_ref: Option<String>,
273    status: Option<ImportStatus>,
274    row_count: Option<i32>,
275}
276
277impl BankStatementImportBuilder {
278    /// Set the bank_account_id field (required)
279    pub fn bank_account_id(mut self, value: Uuid) -> Self {
280        self.bank_account_id = Some(value);
281        self
282    }
283
284    /// Set the source_format field (default: `SourceFormat::default()`)
285    pub fn source_format(mut self, value: SourceFormat) -> Self {
286        self.source_format = Some(value);
287        self
288    }
289
290    /// Set the statement_period_start field (required)
291    pub fn statement_period_start(mut self, value: NaiveDate) -> Self {
292        self.statement_period_start = Some(value);
293        self
294    }
295
296    /// Set the statement_period_end field (required)
297    pub fn statement_period_end(mut self, value: NaiveDate) -> Self {
298        self.statement_period_end = Some(value);
299        self
300    }
301
302    /// Set the opening_balance field (default: `Decimal::from(0)`)
303    pub fn opening_balance(mut self, value: Decimal) -> Self {
304        self.opening_balance = Some(value);
305        self
306    }
307
308    /// Set the closing_balance field (default: `Decimal::from(0)`)
309    pub fn closing_balance(mut self, value: Decimal) -> Self {
310        self.closing_balance = Some(value);
311        self
312    }
313
314    /// Set the file_ref field (optional)
315    pub fn file_ref(mut self, value: String) -> Self {
316        self.file_ref = Some(value);
317        self
318    }
319
320    /// Set the status field (default: `ImportStatus::default()`)
321    pub fn status(mut self, value: ImportStatus) -> Self {
322        self.status = Some(value);
323        self
324    }
325
326    /// Set the row_count field (default: `0`)
327    pub fn row_count(mut self, value: i32) -> Self {
328        self.row_count = Some(value);
329        self
330    }
331
332    /// Build the BankStatementImport entity
333    ///
334    /// Returns Err if any required field without a default is missing.
335    pub fn build(self) -> Result<BankStatementImport, String> {
336        let bank_account_id = self.bank_account_id.ok_or_else(|| "bank_account_id is required".to_string())?;
337        let statement_period_start = self.statement_period_start.ok_or_else(|| "statement_period_start is required".to_string())?;
338        let statement_period_end = self.statement_period_end.ok_or_else(|| "statement_period_end is required".to_string())?;
339
340        Ok(BankStatementImport {
341            id: Uuid::new_v4(),
342            bank_account_id,
343            source_format: self.source_format.unwrap_or_default(),
344            statement_period_start,
345            statement_period_end,
346            opening_balance: self.opening_balance.unwrap_or(Decimal::from(0)),
347            closing_balance: self.closing_balance.unwrap_or(Decimal::from(0)),
348            file_ref: self.file_ref,
349            status: self.status.unwrap_or_default(),
350            row_count: self.row_count.unwrap_or(0),
351            metadata: AuditMetadata::default(),
352        })
353    }
354}