feat: basic import of user activity

This commit is contained in:
Matthieu Bessat 2023-12-27 01:03:33 +01:00
parent 853a3be680
commit 1fcf35eaa7
4 changed files with 122 additions and 20 deletions

18
TODO.md Normal file
View file

@ -0,0 +1,18 @@
Following the dev
like rossman said, you need to split up things
# schedule
## 2023-12-23
- have a rust client to fetch list of users from sql
## 2023-12-27
- Normalize address, cities
all of the normalization work should not have been done here,
we should have created our own platform so people can register and pay later (either in cash, or using 3rd party payment like stripe)

View file

@ -262,12 +262,13 @@ async fn launch_adapter() -> Result<()> {
} }
// get summary of users // get summary of users
let existing_users = paheko_client.get_users().await.context("Get users")?; let mut existing_users = paheko_client.get_users().await.context("Get users")?;
// get summary of transactions for that year // get summary of transactions for that year
let existing_transactions = paheko_client.get_transactions(1).await.context("Get transactions")?; let existing_transactions = paheko_client.get_transactions(1).await.context("Get transactions")?;
// TODO: before creating any users, get the current maximum id of the users table to predict // TODO: before creating any users, get the current maximum id of the users table to predict
// the next auto-incrementing id. // the next auto-incrementing id.
let mut pk_next_id = paheko_client.get_user_next_id().await.context("Get paheko users next id")?;
for answer in answers { for answer in answers {
eprintln!("Processing answer:"); eprintln!("Processing answer:");
@ -275,8 +276,8 @@ async fn launch_adapter() -> Result<()> {
eprintln!(" email: {:?}", email); eprintln!(" email: {:?}", email);
let paheko_user = paheko::User { let mut pk_user = paheko::User {
id: generate_id(), id: utils::Id(0),
first_name: Some(normalize_str(answer.user.first_name.clone())), first_name: Some(normalize_str(answer.user.first_name.clone())),
last_name: normalize_str(answer.user.last_name.clone()), last_name: normalize_str(answer.user.last_name.clone()),
email, email,
@ -295,6 +296,13 @@ async fn launch_adapter() -> Result<()> {
register_time: answer.order.inception_time, register_time: answer.order.inception_time,
}; };
// apply custom user override
// this particular answer had duplicate phone and email from another answer
if answer.id == 64756582 {
pk_user.email = None;
pk_user.phone = None;
}
// check for existing transactions // check for existing transactions
if let Some(_) = existing_transactions.iter().find( if let Some(_) = existing_transactions.iter().find(
|summary| summary.reference == format!("HA/{}", answer.id) |summary| summary.reference == format!("HA/{}", answer.id)
@ -303,18 +311,19 @@ async fn launch_adapter() -> Result<()> {
continue; continue;
} }
let existing_user_opt = existing_users.iter().find(|user| user.email == pk_user.email).cloned();
// check for existing paheko user, or create paheko user // check for existing paheko user, or create paheko user
let paheko_user_summary = match existing_users.iter().find(|user| user.email == paheko_user.email) { let pk_user_summary = match existing_user_opt.clone() {
Some(user) => user.clone(), Some(user) => user,
None => { None => {
let c = paheko_client.create_user(&paheko_user).await.context("Expected to create paheko user")?; let c = paheko_client.create_user(
&pk_user, pk_next_id.clone()
).await.context("Expected to create paheko user")?;
eprintln!(" Created paheko user"); eprintln!(" Created paheko user");
UserSummary { pk_next_id += 1;
id: utils::Id(0), existing_users.push(c.clone());
first_name: paheko_user.first_name.clone(), c
last_name: paheko_user.last_name.clone(),
email: paheko_user.email.clone()
}
} }
}; };
@ -323,7 +332,7 @@ async fn launch_adapter() -> Result<()> {
campaign: "".to_string(), campaign: "".to_string(),
inception_time: Utc::now(), inception_time: Utc::now(),
mode: helloasso_to_paheko_membership(&answer.mode), mode: helloasso_to_paheko_membership(&answer.mode),
users: vec![paheko_user.id.clone()], users: vec![pk_user.id.clone()],
external_references: paheko::ExternalReferences { external_references: paheko::ExternalReferences {
helloasso_ref: paheko::HelloassoReferences { helloasso_ref: paheko::HelloassoReferences {
answer_id: answer.id, answer_id: answer.id,
@ -331,12 +340,11 @@ async fn launch_adapter() -> Result<()> {
} }
} }
}; };
dbg!(&pk_membership.users);
// then create optional linked user // then create optional linked user
if answer.mode == helloasso::MembershipMode::Couple { if answer.mode == helloasso::MembershipMode::Couple {
let mut second_pk_user = paheko_user.clone(); let mut second_pk_user = pk_user.clone();
second_pk_user.id = generate_id(); second_pk_user.id = utils::Id(0);
second_pk_user.email = None; second_pk_user.email = None;
second_pk_user.phone = None; second_pk_user.phone = None;
second_pk_user.skills = None; second_pk_user.skills = None;
@ -354,10 +362,22 @@ async fn launch_adapter() -> Result<()> {
} }
} }
if existing_user_opt.is_none() {
let second_pk_user_summary = paheko_client.create_user(&second_pk_user, pk_next_id)
.await.context("Expected to create second paheko user")?;
eprintln!(" Created conjoint paheko user");
pk_next_id += 1;
}
// TODO: get existing linked user from previous year
pk_membership.users.push(second_pk_user.id.clone()); pk_membership.users.push(second_pk_user.id.clone());
pk_users.push(second_pk_user); pk_users.push(second_pk_user);
} }
pk_users.push(paheko_user);
// add activity
paheko_client.register_user_to_service(&pk_user_summary).await.context("Registering user to paheko server")?;
pk_users.push(pk_user);
pk_memberships.push(pk_membership); pk_memberships.push(pk_membership);
} }
dbg!(&pk_users); dbg!(&pk_users);

View file

@ -237,6 +237,23 @@ impl AuthentifiedClient {
Ok(serde_json::from_value(users_val.results)?) Ok(serde_json::from_value(users_val.results)?)
} }
pub async fn get_user_next_id(&self) -> Result<u64> {
let query: String = r#"
SELECT id FROM users ORDER BY id DESC LIMIT 1
"#.to_string();
let users_id_val = self.sql_query(query).await.context("Fetching users")?;
#[derive(Deserialize)]
struct UserIdEntry {
id: u64
}
let users_ids: Vec<UserIdEntry> = serde_json::from_value(users_id_val.results)?;
Ok(users_ids.iter().nth(0).map(|x| x.id).unwrap_or(1)+1)
}
pub async fn get_transactions(&self, id_year: u32) pub async fn get_transactions(&self, id_year: u32)
-> Result<Vec<TransactionSummary>> -> Result<Vec<TransactionSummary>>
{ {
@ -249,7 +266,7 @@ impl AuthentifiedClient {
Ok(serde_json::from_value(val.results)?) Ok(serde_json::from_value(val.results)?)
} }
pub async fn create_user(&self, user: &User) pub async fn create_user(&self, user: &User, next_id: u64)
-> Result<UserSummary> -> Result<UserSummary>
{ {
// single-user import // single-user import
@ -294,11 +311,52 @@ impl AuthentifiedClient {
} }
Ok( Ok(
UserSummary { UserSummary {
id: Id(0), id: Id(next_id),
first_name: u.first_name, first_name: u.first_name,
last_name: u.last_name, last_name: u.last_name,
email: u.email email: u.email
} }
) )
} }
pub async fn register_user_to_service(&self, user: &UserSummary)
-> Result<()>
{
// single-user import
// create virtual file
let u = user.clone();
let mut csv_content: String = String::new();
csv_content.push_str(
r#""Numéro de membre","Activité","Tarif","Date d'inscription","Date d'expiration","Montant à régler","Payé ?""#);
csv_content.push_str("\n");
csv_content.push_str(
format!("{},{:?},{:?},{:?},{:?},{:?},{:?}\n",
u.id,
"Cotisation 2023-2024",
"Physique Individuelle",
"10/10/2023",
"10/10/2025",
"10",
"Oui"
).as_str());
use reqwest::multipart::Form;
use reqwest::multipart::Part;
let part = Part::text(csv_content).file_name("file");
let form = Form::new()
.part("file", part);
let res = self.client
.post(self.base_url.join("services/subscriptions/import")?)
.multipart(form)
.send().await?;
if res.status() != 200 {
return Err(APIClientError::InvalidStatusCode.into());
}
Ok(())
}
} }

View file

@ -1,4 +1,5 @@
use serde::{Serialize, Deserialize, Deserializer}; use serde::{Serialize, Deserialize, Deserializer};
use std::fmt;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use chrono::prelude::{DateTime, Utc}; use chrono::prelude::{DateTime, Utc};
@ -15,6 +16,11 @@ impl Into<String> for Id {
format!("{:x}", self.0) format!("{:x}", self.0)
} }
} }
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
pub fn generate_id() -> Id { pub fn generate_id() -> Id {
Id(thread_rng().gen()) Id(thread_rng().gen())