use crate::{serde::deserialize_flexible_decimal, types::AccountId};
use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use url::Url;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum AccountKind {
#[serde(alias = "depository")]
Depository,
#[serde(alias = "credit_card")]
CreditCard,
#[serde(alias = "investment")]
Investment,
#[serde(alias = "property")]
Property,
#[serde(alias = "loan")]
Loan,
#[serde(alias = "other_asset")]
OtherAsset,
#[serde(alias = "other_liability")]
OtherLiability,
}
impl std::fmt::Display for AccountKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Depository => "Depository",
Self::CreditCard => "CreditCard",
Self::Investment => "Investment",
Self::Property => "Property",
Self::Loan => "Loan",
Self::OtherAsset => "OtherAsset",
Self::OtherLiability => "OtherLiability",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseAccountKindError(String);
impl std::fmt::Display for ParseAccountKindError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Invalid account kind: {}", self.0)
}
}
impl std::error::Error for ParseAccountKindError {}
impl std::str::FromStr for AccountKind {
type Err = ParseAccountKindError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Depository" => Ok(Self::Depository),
"CreditCard" => Ok(Self::CreditCard),
"Investment" => Ok(Self::Investment),
"Property" => Ok(Self::Property),
"Loan" => Ok(Self::Loan),
"OtherAsset" => Ok(Self::OtherAsset),
"OtherLiability" => Ok(Self::OtherLiability),
_ => Err(ParseAccountKindError(s.to_string())),
}
}
}
impl TryFrom<&str> for AccountKind {
type Error = ParseAccountKindError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
value.parse()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct Account {
pub id: AccountId,
pub name: String,
#[serde(deserialize_with = "deserialize_flexible_decimal")]
pub balance: Decimal,
pub currency: iso_currency::Currency,
pub classification: String,
#[serde(rename = "account_type")]
pub kind: AccountKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct AccountDetail {
pub id: AccountId,
pub name: String,
#[serde(deserialize_with = "deserialize_flexible_decimal")]
pub balance: Decimal,
pub currency: iso_currency::Currency,
pub classification: String,
#[serde(rename = "account_type")]
pub kind: AccountKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtype: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub institution_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub institution_domain: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
pub is_active: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct AccountCollection {
pub accounts: Vec<Account>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub(crate) struct CreateAccountRequest {
pub account: CreateAccountData,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub(crate) struct CreateAccountData {
pub name: String,
#[serde(rename = "accountable_type")]
pub kind: AccountKind,
pub balance: Decimal,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub currency: Option<iso_currency::Currency>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub institution_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub institution_domain: Option<Url>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
pub accountable_attributes: AccountableAttributes,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub(crate) struct UpdateAccountRequest {
pub account: UpdateAccountData,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub(crate) struct UpdateAccountData {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub balance: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub institution_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub institution_domain: Option<Url>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accountable_attributes: Option<AccountableAttributes>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DepositorySubtype {
Checking,
Savings,
Hsa,
Cd,
MoneyMarket,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct DepositoryAttributes {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtype: Option<DepositorySubtype>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locked_attributes: Option<JsonValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum InvestmentSubtype {
Brokerage,
Pension,
Retirement,
#[serde(rename = "401k")]
FourZeroOneK,
#[serde(rename = "roth_401k")]
RothFourZeroOneK,
#[serde(rename = "403b")]
FourZeroThreeB,
Tsp,
#[serde(rename = "529_plan")]
FiveTwoNinePlan,
Hsa,
MutualFund,
Ira,
RothIra,
Angel,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct InvestmentAttributes {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtype: Option<InvestmentSubtype>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locked_attributes: Option<JsonValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct CryptoAttributes {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtype: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locked_attributes: Option<JsonValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PropertySubtype {
SingleFamilyHome,
MultiFamilyHome,
Condominium,
Townhouse,
InvestmentProperty,
SecondHome,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct Address {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub line1: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub line2: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locality: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub region: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub postal_code: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub country: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct PropertyAttributes {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtype: Option<PropertySubtype>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub year_built: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub area_value: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub area_unit: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locked_attributes: Option<JsonValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub address_attributes: Option<Address>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct VehicleAttributes {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub year: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub make: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mileage_value: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mileage_unit: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtype: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locked_attributes: Option<JsonValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct OtherAssetAttributes {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtype: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locked_attributes: Option<JsonValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct CreditCardAttributes {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtype: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub available_credit: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub minimum_payment: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub apr: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expiration_date: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annual_fee: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locked_attributes: Option<JsonValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LoanSubtype {
Mortgage,
Student,
Auto,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LoanRateType {
Fixed,
Variable,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct LoanAttributes {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtype: Option<LoanSubtype>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rate_type: Option<LoanRateType>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub interest_rate: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub term_months: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub initial_balance: Option<Decimal>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locked_attributes: Option<JsonValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "strict", serde(deny_unknown_fields))]
pub struct OtherLiabilityAttributes {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subtype: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub locked_attributes: Option<JsonValue>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AccountableAttributes {
Depository(DepositoryAttributes),
Investment(InvestmentAttributes),
Crypto(CryptoAttributes),
Property(PropertyAttributes),
Vehicle(VehicleAttributes),
OtherAsset(OtherAssetAttributes),
CreditCard(CreditCardAttributes),
Loan(LoanAttributes),
OtherLiability(OtherLiabilityAttributes),
}
impl AccountableAttributes {
pub const fn kind(&self) -> AccountKind {
match self {
Self::Depository(_) => AccountKind::Depository,
Self::Investment(_) => AccountKind::Investment,
Self::Crypto(_) => AccountKind::Property, Self::Property(_) => AccountKind::Property,
Self::Vehicle(_) => AccountKind::Property, Self::OtherAsset(_) => AccountKind::OtherAsset,
Self::CreditCard(_) => AccountKind::CreditCard,
Self::Loan(_) => AccountKind::Loan,
Self::OtherLiability(_) => AccountKind::OtherLiability,
}
}
}