Skip to main content

backbone_banking/domain/entity/
bank_account.rs

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