helloasso-paheko-adapter/src/sync_csv.rs

170 lines
5.6 KiB
Rust

use crate::paheko;
use crate::{
Config, UserCache,
};
use anyhow::Result;
use crate::utils::{normalize_str, parse_date_iso, parse_normalize_phone};
use crate::sync_paheko::{sync_paheko, GeneralizedAnswer, PaymentMode};
use email_address::EmailAddress;
use chrono::prelude::Datelike;
use std::io::BufRead;
use csv::ReaderBuilder;
use std::io;
const CAISSE_ACCOUNT_CODE: &str = "530"; // 530 - Caisse
fn process_csv_value(value: String) -> Option<String> {
let value = normalize_str(value);
if value.is_empty() {
return None
}
Some(value)
}
fn process_price(value: String) -> f64 {
value
.trim()
.chars().filter(|c| c.is_numeric() || *c == '.')
.collect::<String>()
.parse().unwrap_or(0.0)
}
// read csv from stdin
pub async fn sync_csv(
paheko_client: &paheko::AuthentifiedClient,
config: &Config,
_user_cache: &mut UserCache,
dry_run: bool
) -> Result<()> {
// raw row record directly from CSV
#[derive(Debug, serde::Deserialize)]
struct AnswerRecord {
// Ref BP
reference: String,
inception_date: String,
email: String,
first_name: String,
last_name: String,
// Mode / Tarif "Individuel" "Couple"
membership_mode: String,
// CC 1 Prénom conjoint
linked_user_first_name: String,
// CC 2 ADRESSE
address: String,
// CC 3 CODE POSTAL
postal_code: String,
// CC 4 VILLE
city: String,
// CC 5 TÉLÉPHONE
phone: String,
// CC 7 PROFESSION
job: String,
// CC 8 CENTRE D'INTÉRÊTS / COMPÉTENCES
skills: String,
// CC 9 DATE DE NAISSANCE
birth_date: String,
// Cotisation (€)
subscription_amount: String,
// Don (€)
donation_amount: String,
// Mode de paiement (Espèce or Cheque, ESP or CHQ)
payment_mode: String
}
let stdin = io::stdin();
let mut intermediate_inp = "".to_string();
for line_res in stdin.lock().lines() {
let line = line_res.unwrap();
eprintln!("{:?}",&line);
if line.starts_with(',') {
continue;
}
if line.contains("\\FIN_DES_DONNES") {
break;
}
intermediate_inp.push_str(&line);
intermediate_inp.push('\n');
}
let mut rdr = ReaderBuilder::new()
.from_reader(intermediate_inp.as_bytes());
let mut generalized_answers: Vec<GeneralizedAnswer> = vec![];
eprintln!("Reading from stdin");
for parsed_record_res in rdr.deserialize() {
let parsed_record: AnswerRecord = parsed_record_res?;
eprintln!("Parsed_record: {:?}", parsed_record);
let generalized_answer = GeneralizedAnswer {
first_name: Some(normalize_str(parsed_record.first_name)),
last_name: normalize_str(parsed_record.last_name),
email: process_csv_value(parsed_record.email).and_then(|s| EmailAddress::is_valid(&s).then_some(s)),
phone: process_csv_value(parsed_record.phone).and_then(parse_normalize_phone),
skills: process_csv_value(parsed_record.skills),
address: process_csv_value(parsed_record.address)
.expect("Expected answer to have address"),
postal_code: process_csv_value(parsed_record.postal_code)
.expect("Expected answer to have postalcode"),
city: process_csv_value(parsed_record.city)
.expect("Expected answer answer to have city"),
country: "fr".to_string(),
job: process_csv_value(parsed_record.job),
birth_year: process_csv_value(parsed_record.birth_date)
.and_then(|raw_date| parse_date_iso(&raw_date))
.map(|d| d.year() as u32),
inception_time: process_csv_value(parsed_record.inception_date)
.map(|s|
parse_date_iso(&s).expect("Record must have a valid date")
)
.expect("Record must have a date"),
reference: format!("BP/{}", process_csv_value(parsed_record.reference).expect("Row must have reference")), // BP as Bulletin Papier
donation_amount: process_price(parsed_record.donation_amount),
subscription_amount: process_price(parsed_record.subscription_amount), // FIXME: get subscription from mode
membership_mode: serde_json::from_value(serde_json::Value::String(parsed_record.membership_mode.clone()))
.expect("Expected a membership mode to be valid"),
linked_user_first_name: process_csv_value(parsed_record.linked_user_first_name),
payment_mode: match process_csv_value(parsed_record.payment_mode) {
Some(payment_mode_name) => serde_json::from_str(
&format!(
"\"{}\"",
payment_mode_name.to_ascii_uppercase()
)
).expect("Could not parse payment mode"),
None => PaymentMode::Cheque
}
};
generalized_answers.push(generalized_answer);
}
// sort by date, most older first
generalized_answers.sort_by(|a, b| a.inception_time.cmp(&b.inception_time));
eprintln!("Generated GeneralizedAnswers");
if dry_run {
dbg!(generalized_answers);
eprintln!("Stopping here, dry run");
return Ok(());
}
sync_paheko(
paheko_client,
config,
generalized_answers,
CAISSE_ACCOUNT_CODE,
"Papier"
).await?;
eprintln!("CSV sync done.");
Ok(())
}