backbone_banking/domain/entity/
bank_reconciliation.rs1use chrono::{DateTime, Utc, NaiveDate};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::ReconStatus;
8use super::AuditMetadata;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(transparent)]
13pub struct BankReconciliationId(pub Uuid);
14
15impl BankReconciliationId {
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 BankReconciliationId {
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 BankReconciliationId {
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 BankReconciliationId {
35 fn from(id: Uuid) -> Self { Self(id) }
36}
37
38impl From<BankReconciliationId> for Uuid {
39 fn from(id: BankReconciliationId) -> Self { id.0 }
40}
41
42impl AsRef<Uuid> for BankReconciliationId {
43 fn as_ref(&self) -> &Uuid { &self.0 }
44}
45
46impl std::ops::Deref for BankReconciliationId {
47 type Target = Uuid;
48 fn deref(&self) -> &Self::Target { &self.0 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
52pub struct BankReconciliation {
53 pub id: Uuid,
54 pub bank_account_id: Uuid,
55 pub from_date: NaiveDate,
56 pub to_date: NaiveDate,
57 pub statement_closing_balance: Decimal,
58 pub ledger_balance: Decimal,
59 pub computed_difference: Decimal,
60 pub unreconciled_count: i32,
61 pub status: ReconStatus,
62 pub preset_id: Option<Uuid>,
63 #[serde(default)]
64 #[sqlx(json)]
65 pub metadata: AuditMetadata,
66}
67
68impl BankReconciliation {
69 pub fn builder() -> BankReconciliationBuilder {
71 <BankReconciliationBuilder as Default>::default()
72 }
73
74 pub fn new(bank_account_id: Uuid, from_date: NaiveDate, to_date: NaiveDate, statement_closing_balance: Decimal, ledger_balance: Decimal, computed_difference: Decimal, unreconciled_count: i32, status: ReconStatus) -> Self {
76 Self {
77 id: Uuid::new_v4(),
78 bank_account_id,
79 from_date,
80 to_date,
81 statement_closing_balance,
82 ledger_balance,
83 computed_difference,
84 unreconciled_count,
85 status,
86 preset_id: None,
87 metadata: AuditMetadata::default(),
88 }
89 }
90
91 pub fn id(&self) -> &Uuid {
93 &self.id
94 }
95
96 pub fn typed_id(&self) -> BankReconciliationId {
98 BankReconciliationId(self.id)
99 }
100
101 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
103 self.metadata.created_at.as_ref()
104 }
105
106 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
108 self.metadata.updated_at.as_ref()
109 }
110
111 pub fn is_deleted(&self) -> bool {
113 self.metadata.deleted_at.is_some()
114 }
115
116 pub fn is_active(&self) -> bool {
118 self.metadata.deleted_at.is_none()
119 }
120
121 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
123 self.metadata.deleted_at.as_ref()
124 }
125
126 pub fn created_by(&self) -> Option<&Uuid> {
128 self.metadata.created_by.as_ref()
129 }
130
131 pub fn updated_by(&self) -> Option<&Uuid> {
133 self.metadata.updated_by.as_ref()
134 }
135
136 pub fn deleted_by(&self) -> Option<&Uuid> {
138 self.metadata.deleted_by.as_ref()
139 }
140
141 pub fn status(&self) -> &ReconStatus {
143 &self.status
144 }
145
146
147 pub fn with_preset_id(mut self, value: Uuid) -> Self {
153 self.preset_id = Some(value);
154 self
155 }
156
157 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
163 for (key, value) in fields {
164 match key.as_str() {
165 "bank_account_id" => {
166 if let Ok(v) = serde_json::from_value(value) { self.bank_account_id = v; }
167 }
168 "from_date" => {
169 if let Ok(v) = serde_json::from_value(value) { self.from_date = v; }
170 }
171 "to_date" => {
172 if let Ok(v) = serde_json::from_value(value) { self.to_date = v; }
173 }
174 "statement_closing_balance" => {
175 if let Ok(v) = serde_json::from_value(value) { self.statement_closing_balance = v; }
176 }
177 "ledger_balance" => {
178 if let Ok(v) = serde_json::from_value(value) { self.ledger_balance = v; }
179 }
180 "computed_difference" => {
181 if let Ok(v) = serde_json::from_value(value) { self.computed_difference = v; }
182 }
183 "unreconciled_count" => {
184 if let Ok(v) = serde_json::from_value(value) { self.unreconciled_count = v; }
185 }
186 "status" => {
187 if let Ok(v) = serde_json::from_value(value) { self.status = v; }
188 }
189 "preset_id" => {
190 if let Ok(v) = serde_json::from_value(value) { self.preset_id = v; }
191 }
192 _ => {} }
194 }
195 }
196
197}
198
199impl super::Entity for BankReconciliation {
200 type Id = Uuid;
201
202 fn entity_id(&self) -> &Self::Id {
203 &self.id
204 }
205
206 fn entity_type() -> &'static str {
207 "BankReconciliation"
208 }
209}
210
211impl backbone_core::PersistentEntity for BankReconciliation {
212 fn entity_id(&self) -> String {
213 self.id.to_string()
214 }
215 fn set_entity_id(&mut self, id: String) {
216 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
217 self.id = uuid;
218 }
219 }
220 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
221 self.metadata.created_at
222 }
223 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
224 self.metadata.created_at = Some(ts);
225 }
226 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
227 self.metadata.updated_at
228 }
229 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
230 self.metadata.updated_at = Some(ts);
231 }
232 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
233 self.metadata.deleted_at
234 }
235 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
236 self.metadata.deleted_at = ts;
237 }
238}
239
240impl backbone_orm::EntityRepoMeta for BankReconciliation {
241 fn column_types() -> std::collections::HashMap<String, String> {
242 let mut m = std::collections::HashMap::new();
243 m.insert("id".to_string(), "uuid".to_string());
244 m.insert("bank_account_id".to_string(), "uuid".to_string());
245 m.insert("preset_id".to_string(), "uuid".to_string());
246 m.insert("status".to_string(), "recon_status".to_string());
247 m.insert("from_date".to_string(), "date".to_string());
248 m.insert("to_date".to_string(), "date".to_string());
249 m
250 }
251 fn search_fields() -> &'static [&'static str] {
252 &[]
253 }
254}
255
256#[derive(Debug, Clone, Default)]
261pub struct BankReconciliationBuilder {
262 bank_account_id: Option<Uuid>,
263 from_date: Option<NaiveDate>,
264 to_date: Option<NaiveDate>,
265 statement_closing_balance: Option<Decimal>,
266 ledger_balance: Option<Decimal>,
267 computed_difference: Option<Decimal>,
268 unreconciled_count: Option<i32>,
269 status: Option<ReconStatus>,
270 preset_id: Option<Uuid>,
271}
272
273impl BankReconciliationBuilder {
274 pub fn bank_account_id(mut self, value: Uuid) -> Self {
276 self.bank_account_id = Some(value);
277 self
278 }
279
280 pub fn from_date(mut self, value: NaiveDate) -> Self {
282 self.from_date = Some(value);
283 self
284 }
285
286 pub fn to_date(mut self, value: NaiveDate) -> Self {
288 self.to_date = Some(value);
289 self
290 }
291
292 pub fn statement_closing_balance(mut self, value: Decimal) -> Self {
294 self.statement_closing_balance = Some(value);
295 self
296 }
297
298 pub fn ledger_balance(mut self, value: Decimal) -> Self {
300 self.ledger_balance = Some(value);
301 self
302 }
303
304 pub fn computed_difference(mut self, value: Decimal) -> Self {
306 self.computed_difference = Some(value);
307 self
308 }
309
310 pub fn unreconciled_count(mut self, value: i32) -> Self {
312 self.unreconciled_count = Some(value);
313 self
314 }
315
316 pub fn status(mut self, value: ReconStatus) -> Self {
318 self.status = Some(value);
319 self
320 }
321
322 pub fn preset_id(mut self, value: Uuid) -> Self {
324 self.preset_id = Some(value);
325 self
326 }
327
328 pub fn build(self) -> Result<BankReconciliation, String> {
332 let bank_account_id = self.bank_account_id.ok_or_else(|| "bank_account_id is required".to_string())?;
333 let from_date = self.from_date.ok_or_else(|| "from_date is required".to_string())?;
334 let to_date = self.to_date.ok_or_else(|| "to_date is required".to_string())?;
335
336 Ok(BankReconciliation {
337 id: Uuid::new_v4(),
338 bank_account_id,
339 from_date,
340 to_date,
341 statement_closing_balance: self.statement_closing_balance.unwrap_or(Decimal::from(0)),
342 ledger_balance: self.ledger_balance.unwrap_or(Decimal::from(0)),
343 computed_difference: self.computed_difference.unwrap_or(Decimal::from(0)),
344 unreconciled_count: self.unreconciled_count.unwrap_or(0),
345 status: self.status.unwrap_or_default(),
346 preset_id: self.preset_id,
347 metadata: AuditMetadata::default(),
348 })
349 }
350}