Finalement fini ce script de ses morts
This commit is contained in:
1850
Cargo.lock
generated
1850
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,10 @@ edition = "2021"
|
||||
argh = "0.1.12"
|
||||
csv = "1.2.2"
|
||||
itertools = "0.13.0"
|
||||
mail-send = "0.4.9"
|
||||
rand = "0.8.5"
|
||||
rand_chacha = "0.3.1"
|
||||
rpassword = "7.3.1"
|
||||
serde = "1.0.188"
|
||||
serde_json = "1.0.107"
|
||||
tokio = {version="1.42.0", features = ["full"]}
|
||||
|
||||
@@ -7,7 +7,7 @@ use itertools::Itertools;
|
||||
impl Repartition {
|
||||
pub fn to_json(&self) -> String {
|
||||
let participants = self
|
||||
.0
|
||||
.transferts
|
||||
.iter()
|
||||
.flat_map(|t| vec![t.from.clone(), t.to.clone()])
|
||||
.dedup()
|
||||
@@ -25,7 +25,7 @@ impl Repartition {
|
||||
);
|
||||
}
|
||||
let mut str_edges = String::new();
|
||||
for t in &self.0 {
|
||||
for t in &self.transferts {
|
||||
str_edges += &format!(
|
||||
"{{
|
||||
\"data\": {{
|
||||
@@ -42,7 +42,7 @@ impl Repartition {
|
||||
t.to.name,
|
||||
)
|
||||
}
|
||||
"[".to_owned() + &str_nodes + "," + &str_edges + "]"
|
||||
"{".to_owned() + "nodes:[" + &str_nodes + "], edges: [" + &str_edges + "]}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,6 @@ const FORMAT_STRING: &'static str = "<html>
|
||||
<body>
|
||||
<div id=\"graph\" style=\"width: auto;height: 500px;\"></div>
|
||||
|
||||
<h3>Balance totale (doit être égal à zéro) : TOTAL_BALANCE €</h3>
|
||||
|
||||
<script type=\"text/javascript\">
|
||||
var cy = cytoscape({
|
||||
|
||||
@@ -98,7 +96,6 @@ use super::repartition::*;
|
||||
pub fn plot_repartition(rep: &Repartition) {
|
||||
let text_html = FORMAT_STRING
|
||||
.to_string()
|
||||
.replace("TOTAL_BALANCE", &rep.total_balance().to_string())
|
||||
.replace("ELEMENTS", &rep.to_json());
|
||||
fs::write("/tmp/repartition.html", text_html).expect("Could not write into /tmp/");
|
||||
Command::new("firefox")
|
||||
|
||||
111
src/mail.rs
Normal file
111
src/mail.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use itertools::Itertools;
|
||||
use std::process::Command;
|
||||
|
||||
use crate::repartition::Repartition;
|
||||
|
||||
const FORMAT_MAIL_RECEIVER: &'static str = "
|
||||
Bonjour $NOM,
|
||||
Voilà venu le temps de la répartition des dépenses des Alpes 2024 (toutes mes excuses pour le retard).
|
||||
|
||||
Tu es dans le groupe des personnes qui **à qui on doit de l'argent**.
|
||||
|
||||
On te dois en tout $ARGENT€.
|
||||
|
||||
Voici les virements que tu dois recevoir:
|
||||
$VIREMENTS
|
||||
|
||||
Il faudra contacter les gens directement !
|
||||
|
||||
Merci d'avance et des gros bisous.
|
||||
|
||||
Joël de la compta
|
||||
";
|
||||
|
||||
const FORMAT_MAIL_PAYER: &'static str = "
|
||||
Bonjour $NOM,
|
||||
Voilà venu le temps de la répartition des dépenses des Alpes 2024 (toutes mes excuses pour le retard).
|
||||
|
||||
Tu es dans le groupe des personnes qui **qui doivent de l'argent**.
|
||||
|
||||
Tu dois en tout $ARGENT€.
|
||||
|
||||
Voici les virements que tu dois faire:
|
||||
$VIREMENTS
|
||||
|
||||
Il faudra contacter les gens directement !
|
||||
|
||||
Merci d'avance et des gros bisous.
|
||||
|
||||
Joël de la compta
|
||||
";
|
||||
|
||||
impl Repartition {
|
||||
fn prepare_mail(&self, name: String, payer: bool) -> (String, String) {
|
||||
let format_text = if payer {
|
||||
FORMAT_MAIL_PAYER
|
||||
} else {
|
||||
FORMAT_MAIL_RECEIVER
|
||||
};
|
||||
let mut total_amount = 0;
|
||||
let mut current_mail = String::new();
|
||||
let total_virements = self
|
||||
.transferts
|
||||
.iter()
|
||||
.filter(|t| (payer && t.from.name == name) || t.to.name == name)
|
||||
.map(|t| {
|
||||
if payer {
|
||||
current_mail = t.from.email.clone();
|
||||
} else {
|
||||
current_mail = t.to.email.clone();
|
||||
}
|
||||
total_amount += t.amount;
|
||||
t.to_string(payer)
|
||||
})
|
||||
.join("\n");
|
||||
(
|
||||
current_mail,
|
||||
format_text
|
||||
.replace("$NOM", &name)
|
||||
.replace("$ARGENT", &format!("{:.2}", total_amount as f32 / 100.))
|
||||
.replace("$VIREMENTS", &total_virements),
|
||||
)
|
||||
}
|
||||
pub async fn send_all_mails(&self) {
|
||||
use mail_send::*;
|
||||
|
||||
let sending_mail = "commu.alpes.site@gmail.com";
|
||||
let password_mail = rpassword::prompt_password("Password?").unwrap();
|
||||
|
||||
let mut client = SmtpClientBuilder::new("smtp.gmail.com", 587)
|
||||
.implicit_tls(false)
|
||||
.credentials((sending_mail.as_ref(), password_mail.as_ref()))
|
||||
.connect()
|
||||
.await
|
||||
.unwrap();
|
||||
let mut mails = vec![];
|
||||
|
||||
for name in &self.payers {
|
||||
let (to_mail, mail) = self.prepare_mail(name.clone(), true);
|
||||
mails.push((name, to_mail, mail.clone()));
|
||||
println!("{}", mail);
|
||||
}
|
||||
for name in &self.receivers {
|
||||
let (to_mail, mail) = self.prepare_mail(name.clone(), false);
|
||||
mails.push((name, to_mail, mail.clone()));
|
||||
println!("{}", mail);
|
||||
}
|
||||
for (name, to_mail, mail) in mails {
|
||||
let message = mail_builder::MessageBuilder::new()
|
||||
.from(("Compta Alpes", "commu.alpes.site@gmail.com"))
|
||||
.to(vec![(name.clone(), to_mail.clone())])
|
||||
.cc(vec![("Joël de la compta")])
|
||||
.subject("Compta Alpes 2024")
|
||||
.text_body(mail.clone());
|
||||
println!("Sending mail to {}", name);
|
||||
println!("{}", mail);
|
||||
client.send(message).await.unwrap();
|
||||
let mut child = Command::new("sleep").arg("3").spawn().unwrap();
|
||||
let _result = child.wait().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/main.rs
22
src/main.rs
@@ -1,12 +1,16 @@
|
||||
use std::fs;
|
||||
|
||||
use std::io as stdio;
|
||||
use std::io::prelude::*;
|
||||
mod io;
|
||||
mod mail;
|
||||
pub mod repartition;
|
||||
|
||||
use argh::FromArgs;
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
||||
extern crate tokio;
|
||||
|
||||
#[derive(FromArgs)]
|
||||
/// Computation of repartition
|
||||
struct Arguments {
|
||||
@@ -27,7 +31,19 @@ struct Arguments {
|
||||
transfert_list_filename: Option<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn pause() {
|
||||
let mut stdin = stdio::stdin();
|
||||
let mut stdout = stdio::stdout();
|
||||
|
||||
// We want the cursor to stay at the end of the line, so we print without a newline and flush manually.
|
||||
write!(stdout, "Press any key to continue...").unwrap();
|
||||
stdout.flush().unwrap();
|
||||
|
||||
// Read a single byte and discard
|
||||
let _ = stdin.read(&mut [0u8]).unwrap();
|
||||
}
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use repartition::*;
|
||||
let args: Arguments = argh::from_env();
|
||||
if let Some(filename) = args.user_list_filename {
|
||||
@@ -76,6 +92,8 @@ fn main() {
|
||||
serde_json::from_reader(fs::File::open(filename).expect("not able to read file"))
|
||||
.expect("unable to read file");
|
||||
io::plot_repartition(&repart);
|
||||
pause();
|
||||
repart.send_all_mails().await;
|
||||
} else {
|
||||
println!("No parameter given, exiting");
|
||||
}
|
||||
|
||||
@@ -50,11 +50,14 @@ impl RandomRepartitionBuilder {
|
||||
}
|
||||
|
||||
pub fn build_repartition(mut self) -> Repartition {
|
||||
let mut rep = Repartition(Vec::new());
|
||||
|
||||
let mut transfer_vec = Vec::new();
|
||||
let mut all_payers = HashSet::new();
|
||||
let mut all_receivers = HashSet::new();
|
||||
while !self.payers.is_empty() || !self.receivers.is_empty() {
|
||||
let mut payer = self.get_next_payer();
|
||||
all_payers.insert(payer.name.clone());
|
||||
let mut receiver = self.get_next_receiver();
|
||||
all_receivers.insert(receiver.name.clone());
|
||||
let amount = min(-payer.balance_cents, receiver.balance_cents);
|
||||
dbg!(amount);
|
||||
let transfert = Transfer {
|
||||
@@ -64,7 +67,7 @@ impl RandomRepartitionBuilder {
|
||||
};
|
||||
payer.balance_cents += amount;
|
||||
receiver.balance_cents -= amount;
|
||||
rep.0.push(transfert);
|
||||
transfer_vec.push(transfert);
|
||||
if payer.balance_cents < 0 {
|
||||
self.payers.push(payer);
|
||||
}
|
||||
@@ -72,8 +75,14 @@ impl RandomRepartitionBuilder {
|
||||
self.receivers.push(receiver);
|
||||
}
|
||||
}
|
||||
assert_eq!(self.payers.len(), 0);
|
||||
assert_eq!(self.receivers.len(), 0);
|
||||
|
||||
rep
|
||||
Repartition {
|
||||
transferts: transfer_vec,
|
||||
payers: all_payers,
|
||||
receivers: all_receivers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,16 +92,51 @@ pub struct Transfer {
|
||||
pub to: User,
|
||||
pub amount: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Repartition(pub Vec<Transfer>);
|
||||
|
||||
type Evaluation = (usize);
|
||||
impl Repartition {
|
||||
pub fn total_balance(&self) -> i32 {
|
||||
self.0.iter().map(|t| t.amount as i32).sum()
|
||||
}
|
||||
pub fn evaluate(&self) -> Evaluation {
|
||||
self.0.len()
|
||||
impl Transfer {
|
||||
pub fn to_string(&self, payer: bool) -> String {
|
||||
if payer {
|
||||
format!(
|
||||
" - Virement de {:.2}€ à {} ({})",
|
||||
self.amount as f32 / 100.,
|
||||
self.to.name,
|
||||
self.to.email
|
||||
)
|
||||
.to_string()
|
||||
} else {
|
||||
format!(
|
||||
" - Virement de {:.2}€ de {} ({})",
|
||||
self.amount as f32 / 100.,
|
||||
self.from.name,
|
||||
self.from.email
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Repartition {
|
||||
pub transferts: Vec<Transfer>,
|
||||
pub payers: HashSet<String>,
|
||||
pub receivers: HashSet<String>,
|
||||
}
|
||||
|
||||
type Evaluation = (i64);
|
||||
impl Repartition {
|
||||
pub fn total_balance(&self) -> i32 {
|
||||
self.transferts
|
||||
.iter()
|
||||
.map(|t| {
|
||||
if self.payers.contains(&t.from.name) {
|
||||
t.amount as i32
|
||||
} else {
|
||||
-(t.amount as i32)
|
||||
}
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
pub fn evaluate(&self) -> Evaluation {
|
||||
(self.transferts.len() as i64) * 20
|
||||
- (100 / (self.transferts.iter().map(|t| t.amount).min().unwrap() as f32) as i64)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user