170 lines
5.6 KiB
Rust
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(())
|
|
}
|
|
|
|
|