1use crate::{serde::deserialize_flexible_decimal, types::AccountId};
2use chrono::{DateTime, Utc};
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5use serde_json::Value as JsonValue;
6use url::Url;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "PascalCase")]
11pub enum AccountKind {
12 #[serde(alias = "depository")]
14 Depository,
15 #[serde(alias = "credit_card")]
17 CreditCard,
18 #[serde(alias = "investment")]
20 Investment,
21 #[serde(alias = "property")]
23 Property,
24 #[serde(alias = "loan")]
26 Loan,
27 #[serde(alias = "other_asset")]
29 OtherAsset,
30 #[serde(alias = "other_liability")]
32 OtherLiability,
33}
34
35impl std::fmt::Display for AccountKind {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 let s = match self {
38 Self::Depository => "Depository",
39 Self::CreditCard => "CreditCard",
40 Self::Investment => "Investment",
41 Self::Property => "Property",
42 Self::Loan => "Loan",
43 Self::OtherAsset => "OtherAsset",
44 Self::OtherLiability => "OtherLiability",
45 };
46 write!(f, "{}", s)
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct ParseAccountKindError(String);
53
54impl std::fmt::Display for ParseAccountKindError {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "Invalid account kind: {}", self.0)
57 }
58}
59
60impl std::error::Error for ParseAccountKindError {}
61
62impl std::str::FromStr for AccountKind {
63 type Err = ParseAccountKindError;
64
65 fn from_str(s: &str) -> Result<Self, Self::Err> {
66 match s {
67 "Depository" => Ok(Self::Depository),
68 "CreditCard" => Ok(Self::CreditCard),
69 "Investment" => Ok(Self::Investment),
70 "Property" => Ok(Self::Property),
71 "Loan" => Ok(Self::Loan),
72 "OtherAsset" => Ok(Self::OtherAsset),
73 "OtherLiability" => Ok(Self::OtherLiability),
74 _ => Err(ParseAccountKindError(s.to_string())),
75 }
76 }
77}
78
79impl TryFrom<&str> for AccountKind {
80 type Error = ParseAccountKindError;
81
82 fn try_from(value: &str) -> Result<Self, Self::Error> {
83 value.parse()
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
90pub struct Account {
91 pub id: AccountId,
93 pub name: String,
95 #[serde(deserialize_with = "deserialize_flexible_decimal")]
97 pub balance: Decimal,
98 pub currency: iso_currency::Currency,
100 pub classification: String,
102 #[serde(rename = "account_type")]
104 pub kind: AccountKind,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
110pub struct AccountDetail {
111 pub id: AccountId,
113 pub name: String,
115 #[serde(deserialize_with = "deserialize_flexible_decimal")]
117 pub balance: Decimal,
118 pub currency: iso_currency::Currency,
120 pub classification: String,
122 #[serde(rename = "account_type")]
124 pub kind: AccountKind,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
127 pub subtype: Option<String>,
128 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub institution_name: Option<String>,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub institution_domain: Option<String>,
134 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub notes: Option<String>,
137 pub is_active: bool,
139 pub created_at: DateTime<Utc>,
141 pub updated_at: DateTime<Utc>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
148pub struct AccountCollection {
149 pub accounts: Vec<Account>,
151}
152
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
155#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
156pub(crate) struct CreateAccountRequest {
157 pub account: CreateAccountData,
159}
160
161#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
163#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
164pub(crate) struct CreateAccountData {
165 pub name: String,
167 #[serde(rename = "accountable_type")]
169 pub kind: AccountKind,
170 pub balance: Decimal,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub currency: Option<iso_currency::Currency>,
175 #[serde(default, skip_serializing_if = "Option::is_none")]
177 pub institution_name: Option<String>,
178 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub institution_domain: Option<Url>,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub notes: Option<String>,
184 pub accountable_attributes: AccountableAttributes,
186}
187
188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
190#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
191pub(crate) struct UpdateAccountRequest {
192 pub account: UpdateAccountData,
194}
195
196#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
198#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
199pub(crate) struct UpdateAccountData {
200 #[serde(default, skip_serializing_if = "Option::is_none")]
202 pub name: Option<String>,
203 #[serde(default, skip_serializing_if = "Option::is_none")]
205 pub balance: Option<Decimal>,
206 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub institution_name: Option<String>,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
211 pub institution_domain: Option<Url>,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub notes: Option<String>,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
217 pub accountable_attributes: Option<AccountableAttributes>,
218}
219
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
224#[serde(rename_all = "snake_case")]
225pub enum DepositorySubtype {
226 Checking,
228 Savings,
230 Hsa,
232 Cd,
234 MoneyMarket,
236}
237
238#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
240#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
241pub struct DepositoryAttributes {
242 #[serde(default, skip_serializing_if = "Option::is_none")]
244 pub subtype: Option<DepositorySubtype>,
245 #[serde(default, skip_serializing_if = "Option::is_none")]
247 pub locked_attributes: Option<JsonValue>,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
252#[serde(rename_all = "snake_case")]
253pub enum InvestmentSubtype {
254 Brokerage,
256 Pension,
258 Retirement,
260 #[serde(rename = "401k")]
262 FourZeroOneK,
263 #[serde(rename = "roth_401k")]
265 RothFourZeroOneK,
266 #[serde(rename = "403b")]
268 FourZeroThreeB,
269 Tsp,
271 #[serde(rename = "529_plan")]
273 FiveTwoNinePlan,
274 Hsa,
276 MutualFund,
278 Ira,
280 RothIra,
282 Angel,
284}
285
286#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
289pub struct InvestmentAttributes {
290 #[serde(default, skip_serializing_if = "Option::is_none")]
292 pub subtype: Option<InvestmentSubtype>,
293 #[serde(default, skip_serializing_if = "Option::is_none")]
295 pub locked_attributes: Option<JsonValue>,
296}
297
298#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
300#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
301pub struct CryptoAttributes {
302 #[serde(default, skip_serializing_if = "Option::is_none")]
304 pub subtype: Option<String>,
305 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub locked_attributes: Option<JsonValue>,
308}
309
310#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
312#[serde(rename_all = "snake_case")]
313pub enum PropertySubtype {
314 SingleFamilyHome,
316 MultiFamilyHome,
318 Condominium,
320 Townhouse,
322 InvestmentProperty,
324 SecondHome,
326}
327
328#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
330#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
331pub struct Address {
332 #[serde(default, skip_serializing_if = "Option::is_none")]
334 pub line1: Option<String>,
335 #[serde(default, skip_serializing_if = "Option::is_none")]
337 pub line2: Option<String>,
338 #[serde(default, skip_serializing_if = "Option::is_none")]
340 pub locality: Option<String>,
341 #[serde(default, skip_serializing_if = "Option::is_none")]
343 pub region: Option<String>,
344 #[serde(default, skip_serializing_if = "Option::is_none")]
346 pub postal_code: Option<String>,
347 #[serde(default, skip_serializing_if = "Option::is_none")]
349 pub country: Option<String>,
350}
351
352#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
354#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
355pub struct PropertyAttributes {
356 #[serde(default, skip_serializing_if = "Option::is_none")]
358 pub subtype: Option<PropertySubtype>,
359 #[serde(default, skip_serializing_if = "Option::is_none")]
361 pub year_built: Option<i32>,
362 #[serde(default, skip_serializing_if = "Option::is_none")]
364 pub area_value: Option<i32>,
365 #[serde(default, skip_serializing_if = "Option::is_none")]
367 pub area_unit: Option<String>,
368 #[serde(default, skip_serializing_if = "Option::is_none")]
370 pub locked_attributes: Option<JsonValue>,
371 #[serde(default, skip_serializing_if = "Option::is_none")]
373 pub address_attributes: Option<Address>,
374}
375
376#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
378#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
379pub struct VehicleAttributes {
380 #[serde(default, skip_serializing_if = "Option::is_none")]
382 pub year: Option<i32>,
383 #[serde(default, skip_serializing_if = "Option::is_none")]
385 pub make: Option<String>,
386 #[serde(default, skip_serializing_if = "Option::is_none")]
388 pub model: Option<String>,
389 #[serde(default, skip_serializing_if = "Option::is_none")]
391 pub mileage_value: Option<i32>,
392 #[serde(default, skip_serializing_if = "Option::is_none")]
394 pub mileage_unit: Option<String>,
395 #[serde(default, skip_serializing_if = "Option::is_none")]
397 pub subtype: Option<String>,
398 #[serde(default, skip_serializing_if = "Option::is_none")]
400 pub locked_attributes: Option<JsonValue>,
401}
402
403#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
405#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
406pub struct OtherAssetAttributes {
407 #[serde(default, skip_serializing_if = "Option::is_none")]
409 pub subtype: Option<String>,
410 #[serde(default, skip_serializing_if = "Option::is_none")]
412 pub locked_attributes: Option<JsonValue>,
413}
414
415#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
417#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
418pub struct CreditCardAttributes {
419 #[serde(default, skip_serializing_if = "Option::is_none")]
421 pub subtype: Option<String>,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
424 pub available_credit: Option<Decimal>,
425 #[serde(default, skip_serializing_if = "Option::is_none")]
427 pub minimum_payment: Option<Decimal>,
428 #[serde(default, skip_serializing_if = "Option::is_none")]
430 pub apr: Option<Decimal>,
431 #[serde(default, skip_serializing_if = "Option::is_none")]
433 pub expiration_date: Option<DateTime<Utc>>,
434 #[serde(default, skip_serializing_if = "Option::is_none")]
436 pub annual_fee: Option<Decimal>,
437 #[serde(default, skip_serializing_if = "Option::is_none")]
439 pub locked_attributes: Option<JsonValue>,
440}
441
442#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
444#[serde(rename_all = "snake_case")]
445pub enum LoanSubtype {
446 Mortgage,
448 Student,
450 Auto,
452 Other,
454}
455
456#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
458#[serde(rename_all = "snake_case")]
459pub enum LoanRateType {
460 Fixed,
462 Variable,
464}
465
466#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
468#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
469pub struct LoanAttributes {
470 #[serde(default, skip_serializing_if = "Option::is_none")]
472 pub subtype: Option<LoanSubtype>,
473 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub rate_type: Option<LoanRateType>,
476 #[serde(default, skip_serializing_if = "Option::is_none")]
478 pub interest_rate: Option<Decimal>,
479 #[serde(default, skip_serializing_if = "Option::is_none")]
481 pub term_months: Option<i32>,
482 #[serde(default, skip_serializing_if = "Option::is_none")]
484 pub initial_balance: Option<Decimal>,
485 #[serde(default, skip_serializing_if = "Option::is_none")]
487 pub locked_attributes: Option<JsonValue>,
488}
489
490#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
492#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
493pub struct OtherLiabilityAttributes {
494 #[serde(default, skip_serializing_if = "Option::is_none")]
496 pub subtype: Option<String>,
497 #[serde(default, skip_serializing_if = "Option::is_none")]
499 pub locked_attributes: Option<JsonValue>,
500}
501
502#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
506#[serde(untagged)]
507pub enum AccountableAttributes {
508 Depository(DepositoryAttributes),
510 Investment(InvestmentAttributes),
512 Crypto(CryptoAttributes),
514 Property(PropertyAttributes),
516 Vehicle(VehicleAttributes),
518 OtherAsset(OtherAssetAttributes),
520 CreditCard(CreditCardAttributes),
522 Loan(LoanAttributes),
524 OtherLiability(OtherLiabilityAttributes),
526}
527
528impl AccountableAttributes {
529 pub const fn kind(&self) -> AccountKind {
531 match self {
532 Self::Depository(_) => AccountKind::Depository,
533 Self::Investment(_) => AccountKind::Investment,
534 Self::Crypto(_) => AccountKind::Property, Self::Property(_) => AccountKind::Property,
536 Self::Vehicle(_) => AccountKind::Property, Self::OtherAsset(_) => AccountKind::OtherAsset,
538 Self::CreditCard(_) => AccountKind::CreditCard,
539 Self::Loan(_) => AccountKind::Loan,
540 Self::OtherLiability(_) => AccountKind::OtherLiability,
541 }
542 }
543}