backbone_banking/domain/entity/
bank_transaction.rs1use chrono::{DateTime, Utc, NaiveDate};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::TxnStatus;
8use super::AuditMetadata;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(transparent)]
13pub struct BankTransactionId(pub Uuid);
14
15impl BankTransactionId {
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 BankTransactionId {
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 BankTransactionId {
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 BankTransactionId {
35 fn from(id: Uuid) -> Self { Self(id) }
36}
37
38impl From<BankTransactionId> for Uuid {
39 fn from(id: BankTransactionId) -> Self { id.0 }
40}
41
42impl AsRef<Uuid> for BankTransactionId {
43 fn as_ref(&self) -> &Uuid { &self.0 }
44}
45
46impl std::ops::Deref for BankTransactionId {
47 type Target = Uuid;
48 fn deref(&self) -> &Self::Target { &self.0 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
52pub struct BankTransaction {
53 pub id: Uuid,
54 pub bank_account_id: Uuid,
55 pub import_id: Uuid,
56 pub txn_date: NaiveDate,
57 pub value_date: Option<NaiveDate>,
58 pub description: Option<String>,
59 pub reference_no: Option<String>,
60 pub deposit: Decimal,
61 pub withdrawal: Decimal,
62 pub currency: String,
63 pub status: TxnStatus,
64 pub allocated_amount: Decimal,
65 #[serde(default)]
66 #[sqlx(json)]
67 pub metadata: AuditMetadata,
68}
69
70impl BankTransaction {
71 pub fn builder() -> BankTransactionBuilder {
73 <BankTransactionBuilder as Default>::default()
74 }
75
76 pub fn new(bank_account_id: Uuid, import_id: Uuid, txn_date: NaiveDate, deposit: Decimal, withdrawal: Decimal, currency: String, status: TxnStatus, allocated_amount: Decimal) -> Self {
78 Self {
79 id: Uuid::new_v4(),
80 bank_account_id,
81 import_id,
82 txn_date,
83 value_date: None,
84 description: None,
85 reference_no: None,
86 deposit,
87 withdrawal,
88 currency,
89 status,
90 allocated_amount,
91 metadata: AuditMetadata::default(),
92 }
93 }
94
95 pub fn id(&self) -> &Uuid {
97 &self.id
98 }
99
100 pub fn typed_id(&self) -> BankTransactionId {
102 BankTransactionId(self.id)
103 }
104
105 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
107 self.metadata.created_at.as_ref()
108 }
109
110 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
112 self.metadata.updated_at.as_ref()
113 }
114
115 pub fn is_deleted(&self) -> bool {
117 self.metadata.deleted_at.is_some()
118 }
119
120 pub fn is_active(&self) -> bool {
122 self.metadata.deleted_at.is_none()
123 }
124
125 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
127 self.metadata.deleted_at.as_ref()
128 }
129
130 pub fn created_by(&self) -> Option<&Uuid> {
132 self.metadata.created_by.as_ref()
133 }
134
135 pub fn updated_by(&self) -> Option<&Uuid> {
137 self.metadata.updated_by.as_ref()
138 }
139
140 pub fn deleted_by(&self) -> Option<&Uuid> {
142 self.metadata.deleted_by.as_ref()
143 }
144
145 pub fn status(&self) -> &TxnStatus {
147 &self.status
148 }
149
150
151 pub fn with_value_date(mut self, value: NaiveDate) -> Self {
157 self.value_date = Some(value);
158 self
159 }
160
161 pub fn with_description(mut self, value: String) -> Self {
163 self.description = Some(value);
164 self
165 }
166
167 pub fn with_reference_no(mut self, value: String) -> Self {
169 self.reference_no = Some(value);
170 self
171 }
172
173 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
179 for (key, value) in fields {
180 match key.as_str() {
181 "bank_account_id" => {
182 if let Ok(v) = serde_json::from_value(value) { self.bank_account_id = v; }
183 }
184 "import_id" => {
185 if let Ok(v) = serde_json::from_value(value) { self.import_id = v; }
186 }
187 "txn_date" => {
188 if let Ok(v) = serde_json::from_value(value) { self.txn_date = v; }
189 }
190 "value_date" => {
191 if let Ok(v) = serde_json::from_value(value) { self.value_date = v; }
192 }
193 "description" => {
194 if let Ok(v) = serde_json::from_value(value) { self.description = v; }
195 }
196 "reference_no" => {
197 if let Ok(v) = serde_json::from_value(value) { self.reference_no = v; }
198 }
199 "deposit" => {
200 if let Ok(v) = serde_json::from_value(value) { self.deposit = v; }
201 }
202 "withdrawal" => {
203 if let Ok(v) = serde_json::from_value(value) { self.withdrawal = v; }
204 }
205 "currency" => {
206 if let Ok(v) = serde_json::from_value(value) { self.currency = v; }
207 }
208 "status" => {
209 if let Ok(v) = serde_json::from_value(value) { self.status = v; }
210 }
211 "allocated_amount" => {
212 if let Ok(v) = serde_json::from_value(value) { self.allocated_amount = v; }
213 }
214 _ => {} }
216 }
217 }
218
219}
220
221impl super::Entity for BankTransaction {
222 type Id = Uuid;
223
224 fn entity_id(&self) -> &Self::Id {
225 &self.id
226 }
227
228 fn entity_type() -> &'static str {
229 "BankTransaction"
230 }
231}
232
233impl backbone_core::PersistentEntity for BankTransaction {
234 fn entity_id(&self) -> String {
235 self.id.to_string()
236 }
237 fn set_entity_id(&mut self, id: String) {
238 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
239 self.id = uuid;
240 }
241 }
242 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
243 self.metadata.created_at
244 }
245 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
246 self.metadata.created_at = Some(ts);
247 }
248 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
249 self.metadata.updated_at
250 }
251 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
252 self.metadata.updated_at = Some(ts);
253 }
254 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
255 self.metadata.deleted_at
256 }
257 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
258 self.metadata.deleted_at = ts;
259 }
260}
261
262impl backbone_orm::EntityRepoMeta for BankTransaction {
263 fn column_types() -> std::collections::HashMap<String, String> {
264 let mut m = std::collections::HashMap::new();
265 m.insert("id".to_string(), "uuid".to_string());
266 m.insert("bank_account_id".to_string(), "uuid".to_string());
267 m.insert("import_id".to_string(), "uuid".to_string());
268 m.insert("status".to_string(), "txn_status".to_string());
269 m.insert("txn_date".to_string(), "date".to_string());
270 m.insert("value_date".to_string(), "date".to_string());
271 m
272 }
273 fn search_fields() -> &'static [&'static str] {
274 &["currency"]
275 }
276 fn relations() -> &'static [(&'static str, &'static str, &'static str)] {
277 &[("import", "bank_statement_imports", "importId")]
278 }
279}
280
281#[derive(Debug, Clone, Default)]
286pub struct BankTransactionBuilder {
287 bank_account_id: Option<Uuid>,
288 import_id: Option<Uuid>,
289 txn_date: Option<NaiveDate>,
290 value_date: Option<NaiveDate>,
291 description: Option<String>,
292 reference_no: Option<String>,
293 deposit: Option<Decimal>,
294 withdrawal: Option<Decimal>,
295 currency: Option<String>,
296 status: Option<TxnStatus>,
297 allocated_amount: Option<Decimal>,
298}
299
300impl BankTransactionBuilder {
301 pub fn bank_account_id(mut self, value: Uuid) -> Self {
303 self.bank_account_id = Some(value);
304 self
305 }
306
307 pub fn import_id(mut self, value: Uuid) -> Self {
309 self.import_id = Some(value);
310 self
311 }
312
313 pub fn txn_date(mut self, value: NaiveDate) -> Self {
315 self.txn_date = Some(value);
316 self
317 }
318
319 pub fn value_date(mut self, value: NaiveDate) -> Self {
321 self.value_date = Some(value);
322 self
323 }
324
325 pub fn description(mut self, value: String) -> Self {
327 self.description = Some(value);
328 self
329 }
330
331 pub fn reference_no(mut self, value: String) -> Self {
333 self.reference_no = Some(value);
334 self
335 }
336
337 pub fn deposit(mut self, value: Decimal) -> Self {
339 self.deposit = Some(value);
340 self
341 }
342
343 pub fn withdrawal(mut self, value: Decimal) -> Self {
345 self.withdrawal = Some(value);
346 self
347 }
348
349 pub fn currency(mut self, value: String) -> Self {
351 self.currency = Some(value);
352 self
353 }
354
355 pub fn status(mut self, value: TxnStatus) -> Self {
357 self.status = Some(value);
358 self
359 }
360
361 pub fn allocated_amount(mut self, value: Decimal) -> Self {
363 self.allocated_amount = Some(value);
364 self
365 }
366
367 pub fn build(self) -> Result<BankTransaction, String> {
371 let bank_account_id = self.bank_account_id.ok_or_else(|| "bank_account_id is required".to_string())?;
372 let import_id = self.import_id.ok_or_else(|| "import_id is required".to_string())?;
373 let txn_date = self.txn_date.ok_or_else(|| "txn_date is required".to_string())?;
374
375 Ok(BankTransaction {
376 id: Uuid::new_v4(),
377 bank_account_id,
378 import_id,
379 txn_date,
380 value_date: self.value_date,
381 description: self.description,
382 reference_no: self.reference_no,
383 deposit: self.deposit.unwrap_or(Decimal::from(0)),
384 withdrawal: self.withdrawal.unwrap_or(Decimal::from(0)),
385 currency: self.currency.unwrap_or("IDR".to_string()),
386 status: self.status.unwrap_or_default(),
387 allocated_amount: self.allocated_amount.unwrap_or(Decimal::from(0)),
388 metadata: AuditMetadata::default(),
389 })
390 }
391}