datasynth_banking/models/
account.rs1use chrono::{DateTime, NaiveDate, Utc};
4use datasynth_core::models::banking::{AccountFeatures, AccountStatus, BankAccountType};
5use rust_decimal::Decimal;
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct BankAccount {
12 pub account_id: Uuid,
14 pub account_number: String,
16 pub account_type: BankAccountType,
18 pub primary_owner_id: Uuid,
20 pub joint_owner_ids: Vec<Uuid>,
22 pub status: AccountStatus,
24 pub currency: String,
26 pub opening_date: NaiveDate,
28 pub closing_date: Option<NaiveDate>,
30 #[serde(with = "rust_decimal::serde::str")]
32 pub current_balance: Decimal,
33 #[serde(with = "rust_decimal::serde::str")]
35 pub available_balance: Decimal,
36 pub features: AccountFeatures,
38 pub iban: Option<String>,
40 pub swift_bic: Option<String>,
42 pub routing_number: Option<String>,
44 pub branch_code: Option<String>,
46 pub interest_rate: Option<Decimal>,
48 #[serde(with = "rust_decimal::serde::str")]
50 pub overdraft_limit: Decimal,
51 pub last_activity: Option<DateTime<Utc>>,
53 pub days_dormant: u32,
55 pub is_nominee: bool,
57 pub linked_cards: Vec<String>,
59 pub declared_purpose: Option<String>,
61 pub funding_source_account: Option<Uuid>,
63 pub gl_account: Option<String>,
66
67 pub is_mule_account: bool,
70 pub is_funnel_account: bool,
72 pub case_id: Option<String>,
74}
75
76impl BankAccount {
77 pub fn new(
79 account_id: Uuid,
80 account_number: String,
81 account_type: BankAccountType,
82 primary_owner_id: Uuid,
83 currency: &str,
84 opening_date: NaiveDate,
85 ) -> Self {
86 let features = match account_type {
87 BankAccountType::Checking => AccountFeatures::retail_standard(),
88 BankAccountType::BusinessOperating => AccountFeatures::business_standard(),
89 _ => AccountFeatures::default(),
90 };
91
92 Self {
93 account_id,
94 account_number,
95 account_type,
96 primary_owner_id,
97 joint_owner_ids: Vec::new(),
98 status: AccountStatus::Active,
99 currency: currency.to_string(),
100 opening_date,
101 closing_date: None,
102 current_balance: Decimal::ZERO,
103 available_balance: Decimal::ZERO,
104 features,
105 iban: None,
106 swift_bic: None,
107 routing_number: None,
108 branch_code: None,
109 interest_rate: None,
110 overdraft_limit: Decimal::ZERO,
111 last_activity: None,
112 days_dormant: 0,
113 is_nominee: false,
114 linked_cards: Vec::new(),
115 declared_purpose: None,
116 funding_source_account: None,
117 is_mule_account: false,
118 is_funnel_account: false,
119 case_id: None,
120 gl_account: None,
121 }
122 }
123
124 pub fn can_transact(&self) -> bool {
126 self.status.allows_transactions()
127 }
128
129 pub fn has_sufficient_funds(&self, amount: Decimal) -> bool {
131 self.available_balance + self.overdraft_limit >= amount
132 }
133
134 pub fn apply_debit(&mut self, amount: Decimal, timestamp: DateTime<Utc>) -> bool {
136 if !self.has_sufficient_funds(amount) {
137 return false;
138 }
139 self.current_balance -= amount;
140 self.available_balance -= amount;
141 self.last_activity = Some(timestamp);
142 self.days_dormant = 0;
143 true
144 }
145
146 pub fn apply_credit(&mut self, amount: Decimal, timestamp: DateTime<Utc>) {
148 self.current_balance += amount;
149 self.available_balance += amount;
150 self.last_activity = Some(timestamp);
151 self.days_dormant = 0;
152 }
153
154 pub fn place_hold(&mut self, amount: Decimal) {
156 self.available_balance -= amount;
157 }
158
159 pub fn release_hold(&mut self, amount: Decimal) {
161 self.available_balance += amount;
162 }
163
164 pub fn close(&mut self, close_date: NaiveDate) {
166 self.status = AccountStatus::Closed;
167 self.closing_date = Some(close_date);
168 }
169
170 pub fn freeze(&mut self) {
172 self.status = AccountStatus::Frozen;
173 }
174
175 pub fn mark_dormant(&mut self, days: u32) {
177 self.days_dormant = days;
178 if days > 365 {
179 self.status = AccountStatus::Dormant;
180 }
181 }
182
183 pub fn add_joint_owner(&mut self, owner_id: Uuid) {
185 if !self.joint_owner_ids.contains(&owner_id) {
186 self.joint_owner_ids.push(owner_id);
187 }
188 }
189
190 pub fn all_owner_ids(&self) -> Vec<Uuid> {
192 let mut owners = vec![self.primary_owner_id];
193 owners.extend(&self.joint_owner_ids);
194 owners
195 }
196
197 pub fn calculate_risk_score(&self) -> u8 {
199 let mut score = self.account_type.risk_weight() * 30.0;
200
201 score += self.status.risk_indicator() * 20.0;
203
204 if self.features.international_transfers {
206 score += 10.0;
207 }
208 if self.features.wire_transfers {
209 score += 5.0;
210 }
211 if self.features.cash_deposits {
212 score += 5.0;
213 }
214
215 if self.is_mule_account {
217 score += 50.0;
218 }
219 if self.is_funnel_account {
220 score += 40.0;
221 }
222
223 score.min(100.0) as u8
224 }
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct AccountHolder {
230 pub customer_id: Uuid,
232 pub holder_type: AccountHolderType,
234 pub ownership_percent: Option<u8>,
236 pub added_date: NaiveDate,
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
242#[serde(rename_all = "snake_case")]
243pub enum AccountHolderType {
244 Primary,
246 JointOwner,
248 AuthorizedSigner,
250 Beneficiary,
252 PowerOfAttorney,
254}
255
256#[cfg(test)]
257#[allow(clippy::unwrap_used)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_account_creation() {
263 let account = BankAccount::new(
264 Uuid::new_v4(),
265 "****1234".to_string(),
266 BankAccountType::Checking,
267 Uuid::new_v4(),
268 "USD",
269 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
270 );
271
272 assert!(account.can_transact());
273 assert_eq!(account.current_balance, Decimal::ZERO);
274 }
275
276 #[test]
277 fn test_account_transactions() {
278 let mut account = BankAccount::new(
279 Uuid::new_v4(),
280 "****1234".to_string(),
281 BankAccountType::Checking,
282 Uuid::new_v4(),
283 "USD",
284 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
285 );
286
287 let now = Utc::now();
288
289 account.apply_credit(Decimal::from(1000), now);
291 assert_eq!(account.current_balance, Decimal::from(1000));
292
293 assert!(account.apply_debit(Decimal::from(500), now));
295 assert_eq!(account.current_balance, Decimal::from(500));
296
297 assert!(!account.apply_debit(Decimal::from(1000), now));
299 }
300
301 #[test]
302 fn test_account_freeze() {
303 let mut account = BankAccount::new(
304 Uuid::new_v4(),
305 "****1234".to_string(),
306 BankAccountType::Checking,
307 Uuid::new_v4(),
308 "USD",
309 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
310 );
311
312 account.freeze();
313 assert!(!account.can_transact());
314 }
315
316 #[test]
317 fn test_joint_owners() {
318 let mut account = BankAccount::new(
319 Uuid::new_v4(),
320 "****1234".to_string(),
321 BankAccountType::Checking,
322 Uuid::new_v4(),
323 "USD",
324 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
325 );
326
327 let joint_owner = Uuid::new_v4();
328 account.add_joint_owner(joint_owner);
329
330 let owners = account.all_owner_ids();
331 assert_eq!(owners.len(), 2);
332 }
333}