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
64 pub is_mule_account: bool,
67 pub is_funnel_account: bool,
69 pub case_id: Option<String>,
71}
72
73impl BankAccount {
74 pub fn new(
76 account_id: Uuid,
77 account_number: String,
78 account_type: BankAccountType,
79 primary_owner_id: Uuid,
80 currency: &str,
81 opening_date: NaiveDate,
82 ) -> Self {
83 let features = match account_type {
84 BankAccountType::Checking => AccountFeatures::retail_standard(),
85 BankAccountType::BusinessOperating => AccountFeatures::business_standard(),
86 _ => AccountFeatures::default(),
87 };
88
89 Self {
90 account_id,
91 account_number,
92 account_type,
93 primary_owner_id,
94 joint_owner_ids: Vec::new(),
95 status: AccountStatus::Active,
96 currency: currency.to_string(),
97 opening_date,
98 closing_date: None,
99 current_balance: Decimal::ZERO,
100 available_balance: Decimal::ZERO,
101 features,
102 iban: None,
103 swift_bic: None,
104 routing_number: None,
105 branch_code: None,
106 interest_rate: None,
107 overdraft_limit: Decimal::ZERO,
108 last_activity: None,
109 days_dormant: 0,
110 is_nominee: false,
111 linked_cards: Vec::new(),
112 declared_purpose: None,
113 funding_source_account: None,
114 is_mule_account: false,
115 is_funnel_account: false,
116 case_id: None,
117 }
118 }
119
120 pub fn can_transact(&self) -> bool {
122 self.status.allows_transactions()
123 }
124
125 pub fn has_sufficient_funds(&self, amount: Decimal) -> bool {
127 self.available_balance + self.overdraft_limit >= amount
128 }
129
130 pub fn apply_debit(&mut self, amount: Decimal, timestamp: DateTime<Utc>) -> bool {
132 if !self.has_sufficient_funds(amount) {
133 return false;
134 }
135 self.current_balance -= amount;
136 self.available_balance -= amount;
137 self.last_activity = Some(timestamp);
138 self.days_dormant = 0;
139 true
140 }
141
142 pub fn apply_credit(&mut self, amount: Decimal, timestamp: DateTime<Utc>) {
144 self.current_balance += amount;
145 self.available_balance += amount;
146 self.last_activity = Some(timestamp);
147 self.days_dormant = 0;
148 }
149
150 pub fn place_hold(&mut self, amount: Decimal) {
152 self.available_balance -= amount;
153 }
154
155 pub fn release_hold(&mut self, amount: Decimal) {
157 self.available_balance += amount;
158 }
159
160 pub fn close(&mut self, close_date: NaiveDate) {
162 self.status = AccountStatus::Closed;
163 self.closing_date = Some(close_date);
164 }
165
166 pub fn freeze(&mut self) {
168 self.status = AccountStatus::Frozen;
169 }
170
171 pub fn mark_dormant(&mut self, days: u32) {
173 self.days_dormant = days;
174 if days > 365 {
175 self.status = AccountStatus::Dormant;
176 }
177 }
178
179 pub fn add_joint_owner(&mut self, owner_id: Uuid) {
181 if !self.joint_owner_ids.contains(&owner_id) {
182 self.joint_owner_ids.push(owner_id);
183 }
184 }
185
186 pub fn all_owner_ids(&self) -> Vec<Uuid> {
188 let mut owners = vec![self.primary_owner_id];
189 owners.extend(&self.joint_owner_ids);
190 owners
191 }
192
193 pub fn calculate_risk_score(&self) -> u8 {
195 let mut score = self.account_type.risk_weight() * 30.0;
196
197 score += self.status.risk_indicator() * 20.0;
199
200 if self.features.international_transfers {
202 score += 10.0;
203 }
204 if self.features.wire_transfers {
205 score += 5.0;
206 }
207 if self.features.cash_deposits {
208 score += 5.0;
209 }
210
211 if self.is_mule_account {
213 score += 50.0;
214 }
215 if self.is_funnel_account {
216 score += 40.0;
217 }
218
219 score.min(100.0) as u8
220 }
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct AccountHolder {
226 pub customer_id: Uuid,
228 pub holder_type: AccountHolderType,
230 pub ownership_percent: Option<u8>,
232 pub added_date: NaiveDate,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
238#[serde(rename_all = "snake_case")]
239pub enum AccountHolderType {
240 Primary,
242 JointOwner,
244 AuthorizedSigner,
246 Beneficiary,
248 PowerOfAttorney,
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_account_creation() {
258 let account = BankAccount::new(
259 Uuid::new_v4(),
260 "****1234".to_string(),
261 BankAccountType::Checking,
262 Uuid::new_v4(),
263 "USD",
264 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
265 );
266
267 assert!(account.can_transact());
268 assert_eq!(account.current_balance, Decimal::ZERO);
269 }
270
271 #[test]
272 fn test_account_transactions() {
273 let mut account = BankAccount::new(
274 Uuid::new_v4(),
275 "****1234".to_string(),
276 BankAccountType::Checking,
277 Uuid::new_v4(),
278 "USD",
279 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
280 );
281
282 let now = Utc::now();
283
284 account.apply_credit(Decimal::from(1000), now);
286 assert_eq!(account.current_balance, Decimal::from(1000));
287
288 assert!(account.apply_debit(Decimal::from(500), now));
290 assert_eq!(account.current_balance, Decimal::from(500));
291
292 assert!(!account.apply_debit(Decimal::from(1000), now));
294 }
295
296 #[test]
297 fn test_account_freeze() {
298 let mut account = BankAccount::new(
299 Uuid::new_v4(),
300 "****1234".to_string(),
301 BankAccountType::Checking,
302 Uuid::new_v4(),
303 "USD",
304 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
305 );
306
307 account.freeze();
308 assert!(!account.can_transact());
309 }
310
311 #[test]
312 fn test_joint_owners() {
313 let mut account = BankAccount::new(
314 Uuid::new_v4(),
315 "****1234".to_string(),
316 BankAccountType::Checking,
317 Uuid::new_v4(),
318 "USD",
319 NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
320 );
321
322 let joint_owner = Uuid::new_v4();
323 account.add_joint_owner(joint_owner);
324
325 let owners = account.all_owner_ids();
326 assert_eq!(owners.len(), 2);
327 }
328}