use crate::database::models::DBUserId; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::{query, query_scalar}; use std::fmt; #[derive( Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, utoipa::ToSchema, )] pub enum FormType { #[serde(rename = "W-8BEN")] ForeignIndividual, #[serde(rename = "W-8BEN-E")] ForeignEntity, #[default] #[serde(rename = "W-9")] DomesticPerson, } impl FormType { pub fn as_str(&self) -> &'static str { match self { FormType::ForeignIndividual => "W8-BEN", FormType::ForeignEntity => "W8-BEN-E", FormType::DomesticPerson => "W-9", } } pub fn from_str_or_default(s: &str) -> Self { match s { "W8-BEN" => FormType::ForeignIndividual, "W8-BEN-E" => FormType::ForeignEntity, "W-9" => FormType::DomesticPerson, _ => FormType::default(), } } pub fn requires_domestic_tin_match(self) -> bool { match self { FormType::ForeignIndividual | FormType::ForeignEntity => false, FormType::DomesticPerson => true, } } } impl fmt::Display for FormType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } #[derive(Debug)] pub struct UserCompliance { pub id: i64, pub user_id: DBUserId, pub requested: DateTime, pub signed: Option>, pub e_delivery_consented: bool, pub tin_matched: bool, pub last_checked: DateTime, pub external_request_id: String, pub reference_id: String, pub form_type: Option, pub requires_manual_review: bool, } impl UserCompliance { pub async fn get_by_user_id<'a, E>( exec: E, id: DBUserId, ) -> sqlx::Result> where E: sqlx::PgExecutor<'a>, { let maybe_compliance = query!( r#"SELECT * FROM users_compliance WHERE user_id = $1"#, id.0 ) .fetch_optional(exec) .await? .map(|row| UserCompliance { id: row.id, user_id: id, requested: row.requested, signed: row.signed, e_delivery_consented: row.e_delivery_consented, tin_matched: row.tin_matched, last_checked: row.last_checked, external_request_id: row.external_request_id, reference_id: row.reference_id, form_type: row .form_type .as_deref() .map(FormType::from_str_or_default), requires_manual_review: row.requires_manual_review, }); Ok(maybe_compliance) } /// This either inserts the row into the table or updates the row, *except the requires_manual_review* column. pub async fn upsert_partial<'a, E>(&mut self, exec: E) -> sqlx::Result<()> where E: sqlx::PgExecutor<'a>, { let id = query_scalar!( r#" INSERT INTO users_compliance ( user_id, requested, signed, e_delivery_consented, tin_matched, last_checked, external_request_id, reference_id, form_type, requires_manual_review ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (user_id) DO UPDATE SET requested = EXCLUDED.requested, signed = EXCLUDED.signed, e_delivery_consented = EXCLUDED.e_delivery_consented, tin_matched = EXCLUDED.tin_matched, last_checked = EXCLUDED.last_checked, external_request_id = EXCLUDED.external_request_id, reference_id = EXCLUDED.reference_id, form_type = EXCLUDED.form_type RETURNING id "#, self.user_id.0, self.requested, self.signed, self.e_delivery_consented, self.tin_matched, self.last_checked, self.external_request_id, self.reference_id, self.form_type.map(|s| s.as_str()), self.requires_manual_review, ) .fetch_one(exec) .await?; self.id = id; Ok(()) } pub async fn update<'a, E>(&self, exec: E) -> sqlx::Result<()> where E: sqlx::PgExecutor<'a>, { query!( r#" UPDATE users_compliance SET requested = $2, signed = $3, e_delivery_consented = $4, tin_matched = $5, last_checked = $6, external_request_id = $7, reference_id = $8, form_type = $9, requires_manual_review = $10 WHERE id = $1 "#, self.id, self.requested, self.signed, self.e_delivery_consented, self.tin_matched, self.last_checked, self.external_request_id, self.reference_id, self.form_type.map(|s| s.as_str()), self.requires_manual_review, ) .execute(exec) .await?; Ok(()) } }