2023-12-21 16:31:21 +00:00
|
|
|
mod utils;
|
2023-12-20 15:47:13 +00:00
|
|
|
mod paheko;
|
|
|
|
mod helloasso;
|
|
|
|
|
|
|
|
use thiserror::Error;
|
|
|
|
use anyhow::{Context, Result};
|
2023-12-28 01:23:34 +00:00
|
|
|
use chrono::prelude::{NaiveDate, NaiveDateTime, DateTime, Utc, Datelike};
|
2023-11-09 08:14:14 +00:00
|
|
|
use strum::Display;
|
|
|
|
use serde::{Serialize, Deserialize};
|
2023-11-06 08:47:19 +00:00
|
|
|
use std::collections::HashSet;
|
2023-11-09 08:14:14 +00:00
|
|
|
use phonenumber;
|
2023-12-21 16:31:21 +00:00
|
|
|
use utils::generate_id;
|
2023-12-26 15:09:49 +00:00
|
|
|
use paheko::UserSummary;
|
2023-11-03 17:44:11 +00:00
|
|
|
|
|
|
|
/// permanent config to store long-term config
|
|
|
|
/// used to ingest env settings
|
|
|
|
#[derive(Deserialize, Serialize, Debug)]
|
|
|
|
struct Config {
|
|
|
|
helloasso_email: String,
|
2023-12-26 15:09:49 +00:00
|
|
|
helloasso_password: String,
|
|
|
|
paheko_base_url: String,
|
|
|
|
paheko_client_id: String,
|
|
|
|
paheko_client_secret: String,
|
2023-11-03 17:44:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static APP_USER_AGENT: &str = concat!(
|
|
|
|
env!("CARGO_PKG_NAME"),
|
|
|
|
"/",
|
|
|
|
env!("CARGO_PKG_VERSION"),
|
|
|
|
);
|
|
|
|
|
|
|
|
// start user cache management
|
|
|
|
use std::fs;
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct UserCache {
|
2023-12-20 15:47:13 +00:00
|
|
|
helloasso_session: Option<helloasso::WebSession>
|
2023-11-03 17:44:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Display, Debug, Error)]
|
|
|
|
#[strum(serialize_all = "snake_case")]
|
|
|
|
enum LoadError {
|
|
|
|
XDG,
|
|
|
|
Fs,
|
|
|
|
FailedToParse,
|
|
|
|
FailedToEncode,
|
|
|
|
FailedToCreate,
|
|
|
|
FailedToWrite
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn write_user_cache(cache: &UserCache) -> Result<(), LoadError> {
|
|
|
|
let xdg_dirs = xdg::BaseDirectories::with_prefix(env!("CARGO_PKG_NAME"))
|
|
|
|
.map_err(|e| { LoadError::XDG })?;
|
|
|
|
let user_cache_path = xdg_dirs.place_cache_file("session.json").map_err(|e| { LoadError::FailedToCreate })?;
|
|
|
|
let encoded_cache = serde_json::to_string(&cache).map_err(|e| { LoadError::FailedToEncode })?;
|
|
|
|
fs::write(&user_cache_path, encoded_cache.as_str()).map_err(|e| { LoadError::FailedToWrite })?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_user_cache() -> Result<UserCache, LoadError> {
|
|
|
|
let xdg_dirs = xdg::BaseDirectories::with_prefix(env!("CARGO_PKG_NAME"))
|
|
|
|
.map_err(|e| { LoadError::XDG })?;
|
|
|
|
let user_cache_path = xdg_dirs.get_cache_file("session.json");
|
|
|
|
|
|
|
|
if !user_cache_path.exists() {
|
|
|
|
let default_cache = UserCache {
|
|
|
|
helloasso_session: None
|
|
|
|
};
|
|
|
|
write_user_cache(&default_cache)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let session_content = fs::read_to_string(user_cache_path).map_err(|e| { LoadError::Fs })?;
|
|
|
|
let cache: UserCache = serde_json::from_str(&session_content).map_err(|e| { LoadError::FailedToParse })?;
|
|
|
|
|
|
|
|
Ok(cache)
|
|
|
|
}
|
2023-12-20 15:47:13 +00:00
|
|
|
// todo:
|
|
|
|
// - make pagination working
|
|
|
|
// - create paheko client
|
|
|
|
// - get current paheko membership
|
|
|
|
// - function to convert participants to paheko members
|
|
|
|
// - clean up names and things
|
|
|
|
// - map custom fields with the right thing
|
|
|
|
// - handle linked users
|
2023-11-03 17:44:11 +00:00
|
|
|
|
2023-12-20 15:47:13 +00:00
|
|
|
fn get_paheko_membership_from_ha_answers() {
|
|
|
|
|
2023-11-03 17:44:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: find a better way to have the logic implemented
|
2023-12-20 15:47:13 +00:00
|
|
|
async fn get_auth_client_from_cache(
|
|
|
|
user_cache: &mut UserCache,
|
|
|
|
ha_client: &mut helloasso::Client,
|
|
|
|
login_payload: helloasso::LoginPayload
|
|
|
|
) -> Result<helloasso::AuthentifiedClient> {
|
|
|
|
|
|
|
|
async fn login(
|
|
|
|
user_cache: &mut UserCache,
|
|
|
|
ha_client: &mut helloasso::Client,
|
|
|
|
login_payload: helloasso::LoginPayload
|
|
|
|
) -> Result<helloasso::AuthentifiedClient> {
|
2023-11-03 17:44:11 +00:00
|
|
|
let auth_client = ha_client.login(
|
|
|
|
login_payload
|
|
|
|
).await.context("Failed to login")?;
|
|
|
|
user_cache.helloasso_session = Some(auth_client.session.clone());
|
|
|
|
write_user_cache(&user_cache).expect("unable to write user cache");
|
|
|
|
println!("Logged in and wrote token to cache");
|
|
|
|
Ok(auth_client)
|
|
|
|
}
|
|
|
|
|
|
|
|
match &user_cache.helloasso_session {
|
|
|
|
Some(cached_session) => {
|
|
|
|
let auth_client = ha_client.authentified_client(cached_session.clone());
|
|
|
|
|
|
|
|
if !auth_client.verify_auth().await? {
|
|
|
|
println!("Need to relog, token invalid");
|
|
|
|
return Ok(login(user_cache, ha_client, login_payload).await?)
|
|
|
|
}
|
|
|
|
println!("Used anterior token");
|
|
|
|
return Ok(auth_client);
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
println!("First time login");
|
|
|
|
return Ok(login(user_cache, ha_client, login_payload).await?);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// rust how to access inner enum value
|
2023-11-06 08:47:19 +00:00
|
|
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
2023-12-20 15:47:13 +00:00
|
|
|
enum HelloassoCustomFieldType {
|
|
|
|
Email,
|
2023-11-03 17:44:11 +00:00
|
|
|
Address,
|
|
|
|
PostalCode,
|
|
|
|
City,
|
|
|
|
Phone,
|
|
|
|
Job,
|
|
|
|
Skills,
|
2023-11-06 08:47:19 +00:00
|
|
|
Birthday,
|
|
|
|
LinkedUserFirstName
|
|
|
|
}
|
|
|
|
|
2023-12-20 15:47:13 +00:00
|
|
|
impl TryFrom<&str> for HelloassoCustomFieldType {
|
2023-11-06 08:47:19 +00:00
|
|
|
type Error = ();
|
|
|
|
|
|
|
|
fn try_from(subject: &str) -> Result<Self, Self::Error> {
|
|
|
|
match subject {
|
2023-12-20 15:47:13 +00:00
|
|
|
"Prénom conjoint" => Ok(HelloassoCustomFieldType::LinkedUserFirstName),
|
|
|
|
"ADRESSE" => Ok(HelloassoCustomFieldType::Address),
|
|
|
|
"CODE POSTAL" => Ok(HelloassoCustomFieldType::PostalCode),
|
|
|
|
"VILLE" => Ok(HelloassoCustomFieldType::City),
|
|
|
|
"EMAIL" => Ok(HelloassoCustomFieldType::Email),
|
|
|
|
"PROFESSION" => Ok(HelloassoCustomFieldType::Job),
|
|
|
|
"TÉLÉPHONE" => Ok(HelloassoCustomFieldType::Phone),
|
|
|
|
"DATE DE NAISSANCE" => Ok(HelloassoCustomFieldType::Birthday),
|
|
|
|
"CENTRE D'INTÉRÊTS / COMPÉTENCES" => Ok(HelloassoCustomFieldType::Skills),
|
2023-11-06 08:47:19 +00:00
|
|
|
_ => Err(())
|
2023-11-03 17:44:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-20 15:47:13 +00:00
|
|
|
fn read_custom_field(form_answer: &helloasso::FormAnswer, custom_field: HelloassoCustomFieldType) -> Option<String> {
|
2023-11-06 08:47:19 +00:00
|
|
|
// FIXME: compute the type directly at deserialization with serde
|
2023-11-03 17:44:11 +00:00
|
|
|
form_answer.custom_fields.iter()
|
2023-12-20 15:47:13 +00:00
|
|
|
.find(|f| HelloassoCustomFieldType::try_from(f.name.as_str()) == Ok(custom_field))
|
2023-11-03 17:44:11 +00:00
|
|
|
.and_then(|cf| Some(cf.answer.clone()))
|
|
|
|
}
|
|
|
|
|
2023-11-09 08:14:14 +00:00
|
|
|
fn parse_normalize_phone(phone_number_opt: Option<String>) -> Option<String> {
|
|
|
|
let number_raw = phone_number_opt?;
|
|
|
|
|
|
|
|
let parsed = match phonenumber::parse(Some(phonenumber::country::Id::FR), number_raw) {
|
|
|
|
Ok(r) => {
|
|
|
|
r
|
|
|
|
},
|
|
|
|
Err(_e) => {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Some(parsed.to_string())
|
|
|
|
}
|
|
|
|
|
2023-12-20 15:47:13 +00:00
|
|
|
|
2023-12-26 15:09:49 +00:00
|
|
|
fn normalize_str(subject: String) -> String {
|
|
|
|
subject.trim().replace("\n", ";").to_string()
|
|
|
|
}
|
|
|
|
|
2023-12-20 15:47:13 +00:00
|
|
|
/// remove year precision to comply with GDPR eu rules
|
|
|
|
fn parse_and_get_birthday_year(raw_date: String) -> Option<u32> {
|
|
|
|
let d_res = NaiveDate::parse_from_str(&raw_date.trim(), "%d/%m/%Y");
|
|
|
|
let d = d_res.ok()?;
|
|
|
|
Some(d.year().try_into().ok()?)
|
|
|
|
}
|
|
|
|
|
2023-12-28 01:23:34 +00:00
|
|
|
// fn helloasso_to_paheko_membership(helloasso_membership: &helloasso::MembershipMode) -> paheko::MembershipMode {
|
|
|
|
// match helloasso_membership {
|
|
|
|
// helloasso::MembershipMode::Couple => paheko::MembershipMode::Couple,
|
|
|
|
// helloasso::MembershipMode::Individual => paheko::MembershipMode::Individual,
|
|
|
|
// helloasso::MembershipMode::BenefactorCouple => paheko::MembershipMode::BenefactorCouple,
|
|
|
|
// helloasso::MembershipMode::BenefactorIndividual => paheko::MembershipMode::BenefactorIndividual
|
|
|
|
// }
|
|
|
|
// }
|
2023-12-21 16:31:21 +00:00
|
|
|
|
2023-11-03 17:44:11 +00:00
|
|
|
async fn launch_adapter() -> Result<()> {
|
|
|
|
dotenvy::dotenv()?;
|
|
|
|
|
|
|
|
let config: Config = envy::from_env().context("Failed to load env vars")?;
|
|
|
|
|
|
|
|
let mut user_cache = load_user_cache().context("Failed to load user cache")?;
|
2023-12-26 15:09:49 +00:00
|
|
|
|
|
|
|
let mut paheko_client: paheko::Client = paheko::Client::new("http://localhost:8082/api/".to_string());
|
|
|
|
|
|
|
|
let paheko_credentials = paheko::Credentials {
|
|
|
|
client_id: config.paheko_client_id,
|
|
|
|
client_secret: config.paheko_client_secret
|
|
|
|
};
|
|
|
|
let paheko_client: paheko::AuthentifiedClient = paheko_client.login(paheko_credentials).await?;
|
|
|
|
|
|
|
|
|
2023-12-20 15:47:13 +00:00
|
|
|
let mut ha_client: helloasso::Client = Default::default();
|
2023-11-03 17:44:11 +00:00
|
|
|
|
2023-12-20 15:47:13 +00:00
|
|
|
let login_payload = helloasso::LoginPayload {
|
2023-11-03 17:44:11 +00:00
|
|
|
email: config.helloasso_email,
|
|
|
|
password: config.helloasso_password
|
|
|
|
};
|
2023-12-20 15:47:13 +00:00
|
|
|
let auth_client: helloasso::AuthentifiedClient = get_auth_client_from_cache(&mut user_cache, &mut ha_client, login_payload).await?;
|
2023-11-03 17:44:11 +00:00
|
|
|
|
|
|
|
// dbg!(auth_client.get_user_details().await?);
|
|
|
|
|
|
|
|
let slug = "l-etoile-de-bethleem-association-des-amis-de-la-chapelle-de-bethleem-d-aubevoye";
|
|
|
|
|
2023-12-20 15:47:13 +00:00
|
|
|
let org: helloasso::Organization = auth_client.organization(slug);
|
2023-11-03 17:44:11 +00:00
|
|
|
// dbg!(org.get_details().await?);
|
|
|
|
let answers = org.get_form_answers("2023-2024".to_string()).await?;
|
|
|
|
|
|
|
|
// dbg!(&answers);
|
|
|
|
println!("Got {} answers to the membership form. Processing...", &answers.len());
|
|
|
|
|
2023-11-09 08:14:14 +00:00
|
|
|
// first, request the current list of membership in paheko that were created with helloasso
|
|
|
|
// get the list of payments associated
|
|
|
|
|
2023-11-03 17:44:11 +00:00
|
|
|
// first step: output a list of PahekoUser with PahekoMembership
|
2023-12-21 16:31:21 +00:00
|
|
|
let mut pk_memberships: Vec<paheko::Membership> = vec![];
|
2023-11-03 17:44:11 +00:00
|
|
|
|
2023-12-20 15:47:13 +00:00
|
|
|
// read_custom_field(&answer, HelloAssoCustomFieldType::Email).or(Some(answer.payer_user.email.clone())),
|
|
|
|
use email_address::*;
|
|
|
|
fn choose_email(answer: &helloasso::FormAnswer) -> Option<String> {
|
|
|
|
read_custom_field(&answer, HelloassoCustomFieldType::Email)
|
|
|
|
.and_then(|x| {
|
|
|
|
if !EmailAddress::is_valid(&x) {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(x)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.or(Some(answer.payer_user.email.clone()))
|
|
|
|
}
|
|
|
|
|
2023-12-26 15:09:49 +00:00
|
|
|
// get summary of users
|
2023-12-27 00:03:33 +00:00
|
|
|
let mut existing_users = paheko_client.get_users().await.context("Get users")?;
|
2023-12-26 15:09:49 +00:00
|
|
|
// get summary of transactions for that year
|
|
|
|
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
|
|
|
|
// the next auto-incrementing id.
|
2023-12-28 01:23:34 +00:00
|
|
|
let mut pk_next_user_id = paheko_client.get_next_id(&"users")
|
|
|
|
.await.context("Get paheko users next id")?;
|
|
|
|
let mut pk_next_user_service_id = paheko_client.get_next_id(&"services_users")
|
|
|
|
.await.context("Get paheko services_users next id")?;
|
2023-12-26 15:09:49 +00:00
|
|
|
|
2023-11-03 17:44:11 +00:00
|
|
|
for answer in answers {
|
2023-12-28 01:23:34 +00:00
|
|
|
// list of users involved in this answer
|
|
|
|
let mut pk_users_summaries: Vec<paheko::UserSummary> = vec![];
|
|
|
|
let mut pk_user_service_registrations: Vec<paheko::UserServiceRegistration> = vec![];
|
|
|
|
|
2023-12-26 15:09:49 +00:00
|
|
|
eprintln!("Processing answer:");
|
|
|
|
let email = choose_email(&answer);
|
|
|
|
|
|
|
|
eprintln!(" email: {:?}", email);
|
|
|
|
|
2023-12-27 00:03:33 +00:00
|
|
|
let mut pk_user = paheko::User {
|
|
|
|
id: utils::Id(0),
|
2023-12-26 15:09:49 +00:00
|
|
|
first_name: Some(normalize_str(answer.user.first_name.clone())),
|
|
|
|
last_name: normalize_str(answer.user.last_name.clone()),
|
|
|
|
email,
|
2023-12-20 15:47:13 +00:00
|
|
|
phone: parse_normalize_phone(read_custom_field(&answer, HelloassoCustomFieldType::Phone)),
|
2023-12-26 15:09:49 +00:00
|
|
|
skills: read_custom_field(&answer, HelloassoCustomFieldType::Skills).map(normalize_str),
|
|
|
|
address: read_custom_field(&answer, HelloassoCustomFieldType::Address)
|
|
|
|
.map(normalize_str)
|
|
|
|
.expect("to have address"),
|
2023-12-20 15:47:13 +00:00
|
|
|
postal_code: read_custom_field(&answer, HelloassoCustomFieldType::PostalCode).expect("to have postal code"),
|
2023-12-26 15:09:49 +00:00
|
|
|
city: read_custom_field(&answer, HelloassoCustomFieldType::City)
|
|
|
|
.map(normalize_str)
|
|
|
|
.expect("to have city"),
|
|
|
|
country: answer.payer_user.country.clone().trim()[..=1].to_string(), // ISO 3166-1 alpha-2
|
|
|
|
job: read_custom_field(&answer, HelloassoCustomFieldType::Job).map(normalize_str),
|
|
|
|
birth_year: read_custom_field(&answer, HelloassoCustomFieldType::Birthday).and_then(parse_and_get_birthday_year),
|
|
|
|
register_time: answer.order.inception_time,
|
2023-11-09 08:14:14 +00:00
|
|
|
};
|
2023-12-26 15:09:49 +00:00
|
|
|
|
2023-12-27 00:03:33 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2023-12-26 15:09:49 +00:00
|
|
|
// check for existing transactions
|
|
|
|
if let Some(_) = existing_transactions.iter().find(
|
|
|
|
|summary| summary.reference == format!("HA/{}", answer.id)
|
|
|
|
) {
|
|
|
|
eprintln!(" Skipped: existing transaction found");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-12-27 00:03:33 +00:00
|
|
|
let existing_user_opt = existing_users.iter().find(|user| user.email == pk_user.email).cloned();
|
|
|
|
|
2023-12-26 15:09:49 +00:00
|
|
|
// check for existing paheko user, or create paheko user
|
2023-12-27 00:03:33 +00:00
|
|
|
let pk_user_summary = match existing_user_opt.clone() {
|
|
|
|
Some(user) => user,
|
2023-12-26 15:09:49 +00:00
|
|
|
None => {
|
2023-12-27 00:03:33 +00:00
|
|
|
let c = paheko_client.create_user(
|
2023-12-28 01:23:34 +00:00
|
|
|
&pk_user, pk_next_user_id.clone()
|
2023-12-27 00:03:33 +00:00
|
|
|
).await.context("Expected to create paheko user")?;
|
2023-12-26 15:09:49 +00:00
|
|
|
eprintln!(" Created paheko user");
|
2023-12-28 01:23:34 +00:00
|
|
|
pk_next_user_id += 1;
|
2023-12-27 00:03:33 +00:00
|
|
|
existing_users.push(c.clone());
|
|
|
|
c
|
2023-12-26 15:09:49 +00:00
|
|
|
}
|
|
|
|
};
|
2023-12-28 01:23:34 +00:00
|
|
|
pk_users_summaries.push(pk_user_summary);
|
2023-12-26 15:09:49 +00:00
|
|
|
|
2023-12-21 16:31:21 +00:00
|
|
|
let mut pk_membership = paheko::Membership {
|
2023-11-09 08:14:14 +00:00
|
|
|
id: generate_id(),
|
2023-12-28 01:23:34 +00:00
|
|
|
campaign_name: "Cotisation 2023-2024".to_string(),
|
|
|
|
// FIXME: handle errors
|
|
|
|
mode_name: serde_json::to_value(answer.mode.clone())
|
|
|
|
.unwrap().as_str().unwrap().to_string(),
|
|
|
|
start_time: answer.order.inception_time,
|
|
|
|
end_time:
|
|
|
|
DateTime::<Utc>::from_naive_utc_and_offset(
|
|
|
|
NaiveDate::from_ymd_opt(2024, 12, 31).unwrap().and_hms_opt(23, 59, 59).unwrap(),
|
|
|
|
Utc
|
|
|
|
),
|
|
|
|
payed_amount: f64::from(answer.amount)/100.0,
|
2023-12-27 00:03:33 +00:00
|
|
|
users: vec![pk_user.id.clone()],
|
2023-12-21 16:31:21 +00:00
|
|
|
external_references: paheko::ExternalReferences {
|
2023-12-28 01:23:34 +00:00
|
|
|
helloasso_refs: paheko::HelloassoReferences {
|
2023-12-20 15:47:13 +00:00
|
|
|
answer_id: answer.id,
|
|
|
|
order_id: answer.order.id
|
|
|
|
}
|
|
|
|
}
|
2023-11-09 08:14:14 +00:00
|
|
|
};
|
2023-12-27 00:03:33 +00:00
|
|
|
|
2023-12-28 01:23:34 +00:00
|
|
|
// add activity for first member
|
|
|
|
let user_registration = paheko_client.register_user_to_service(
|
|
|
|
pk_users_summaries.iter().nth(0).unwrap(),
|
|
|
|
&pk_membership,
|
|
|
|
pk_next_user_service_id.clone()
|
|
|
|
).await.context("Registering user to paheko server")?;
|
|
|
|
pk_user_service_registrations.push(user_registration);
|
|
|
|
pk_next_user_service_id += 1;
|
|
|
|
|
|
|
|
|
2023-11-06 08:47:19 +00:00
|
|
|
// then create optional linked user
|
2023-12-20 15:47:13 +00:00
|
|
|
if answer.mode == helloasso::MembershipMode::Couple {
|
2023-12-27 00:03:33 +00:00
|
|
|
let mut second_pk_user = pk_user.clone();
|
|
|
|
second_pk_user.id = utils::Id(0);
|
2023-12-20 15:47:13 +00:00
|
|
|
second_pk_user.email = None;
|
|
|
|
second_pk_user.phone = None;
|
|
|
|
second_pk_user.skills = None;
|
|
|
|
second_pk_user.job = None;
|
2023-12-26 15:09:49 +00:00
|
|
|
second_pk_user.birth_year = None;
|
2023-12-20 15:47:13 +00:00
|
|
|
|
|
|
|
// add first_name
|
|
|
|
match read_custom_field(&answer, HelloassoCustomFieldType::LinkedUserFirstName) {
|
2023-11-09 08:14:14 +00:00
|
|
|
Some(name) => {
|
2023-12-26 15:09:49 +00:00
|
|
|
second_pk_user.first_name = Some(name);
|
2023-11-09 08:14:14 +00:00
|
|
|
},
|
|
|
|
None => {
|
2023-12-26 15:09:49 +00:00
|
|
|
second_pk_user.first_name = None;
|
|
|
|
eprintln!("Warn: Got a user with Couple mode but no additional name given!")
|
2023-11-09 08:14:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-27 00:03:33 +00:00
|
|
|
if existing_user_opt.is_none() {
|
2023-12-28 01:23:34 +00:00
|
|
|
let second_pk_user_summary = paheko_client.create_user(&second_pk_user, pk_next_user_id)
|
2023-12-27 00:03:33 +00:00
|
|
|
.await.context("Expected to create second paheko user")?;
|
|
|
|
eprintln!(" Created conjoint paheko user");
|
2023-12-28 01:23:34 +00:00
|
|
|
pk_users_summaries.push(second_pk_user_summary);
|
|
|
|
pk_next_user_id += 1;
|
|
|
|
|
|
|
|
// create activity of second user
|
|
|
|
let user_registration = paheko_client.register_user_to_service(
|
|
|
|
pk_users_summaries.iter().nth(1).unwrap(),
|
|
|
|
&pk_membership,
|
|
|
|
pk_next_user_service_id.clone()
|
|
|
|
).await.context("Registering service to second paheko server")?;
|
|
|
|
pk_user_service_registrations.push(user_registration);
|
|
|
|
pk_next_user_service_id += 1;
|
|
|
|
|
|
|
|
// FIXME: follow the ids of the services registrations, to be able to later
|
|
|
|
// reference that user service
|
2023-12-27 00:03:33 +00:00
|
|
|
}
|
|
|
|
// TODO: get existing linked user from previous year
|
|
|
|
|
2023-11-09 08:14:14 +00:00
|
|
|
pk_membership.users.push(second_pk_user.id.clone());
|
|
|
|
}
|
2023-12-27 00:03:33 +00:00
|
|
|
|
2023-12-28 01:23:34 +00:00
|
|
|
// add transaction
|
|
|
|
let transaction = paheko::SimpleTransaction {
|
|
|
|
label: "Adhésion Helloasso".to_string(),
|
|
|
|
amount: pk_membership.payed_amount,
|
|
|
|
reference: format!("HA/{}", pk_membership.external_references.helloasso_refs.answer_id),
|
|
|
|
credit_account_code: "756".to_string(), // cotisations account
|
|
|
|
debit_account_code: "512HA".to_string(), // helloasso account
|
|
|
|
inception_time: answer.order.inception_time,
|
|
|
|
kind: paheko::TransactionKind::Revenue,
|
|
|
|
linked_users: pk_users_summaries.iter().map(|x| x.id.clone()).collect(),
|
|
|
|
linked_services: pk_user_service_registrations.iter().map(|x| x.id.clone()).collect()
|
|
|
|
};
|
|
|
|
let _ = paheko_client.register_transaction(transaction)
|
|
|
|
.await.context("Expected to create new paheko transaction");
|
2023-12-27 00:03:33 +00:00
|
|
|
|
2023-11-09 08:14:14 +00:00
|
|
|
pk_memberships.push(pk_membership);
|
|
|
|
}
|
|
|
|
dbg!(&pk_memberships.len());
|
|
|
|
|
2023-11-03 17:44:11 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
async fn main() {
|
|
|
|
let res = launch_adapter().await;
|
|
|
|
dbg!(res);
|
|
|
|
}
|