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"
|
argh = "0.1.12"
|
||||||
csv = "1.2.2"
|
csv = "1.2.2"
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
|
mail-send = "0.4.9"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
|
rpassword = "7.3.1"
|
||||||
serde = "1.0.188"
|
serde = "1.0.188"
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
|
tokio = {version="1.42.0", features = ["full"]}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use itertools::Itertools;
|
|||||||
impl Repartition {
|
impl Repartition {
|
||||||
pub fn to_json(&self) -> String {
|
pub fn to_json(&self) -> String {
|
||||||
let participants = self
|
let participants = self
|
||||||
.0
|
.transferts
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|t| vec![t.from.clone(), t.to.clone()])
|
.flat_map(|t| vec![t.from.clone(), t.to.clone()])
|
||||||
.dedup()
|
.dedup()
|
||||||
@@ -25,7 +25,7 @@ impl Repartition {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
let mut str_edges = String::new();
|
let mut str_edges = String::new();
|
||||||
for t in &self.0 {
|
for t in &self.transferts {
|
||||||
str_edges += &format!(
|
str_edges += &format!(
|
||||||
"{{
|
"{{
|
||||||
\"data\": {{
|
\"data\": {{
|
||||||
@@ -42,7 +42,7 @@ impl Repartition {
|
|||||||
t.to.name,
|
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>
|
<body>
|
||||||
<div id=\"graph\" style=\"width: auto;height: 500px;\"></div>
|
<div id=\"graph\" style=\"width: auto;height: 500px;\"></div>
|
||||||
|
|
||||||
<h3>Balance totale (doit être égal à zéro) : TOTAL_BALANCE €</h3>
|
|
||||||
|
|
||||||
<script type=\"text/javascript\">
|
<script type=\"text/javascript\">
|
||||||
var cy = cytoscape({
|
var cy = cytoscape({
|
||||||
|
|
||||||
@@ -98,7 +96,6 @@ use super::repartition::*;
|
|||||||
pub fn plot_repartition(rep: &Repartition) {
|
pub fn plot_repartition(rep: &Repartition) {
|
||||||
let text_html = FORMAT_STRING
|
let text_html = FORMAT_STRING
|
||||||
.to_string()
|
.to_string()
|
||||||
.replace("TOTAL_BALANCE", &rep.total_balance().to_string())
|
|
||||||
.replace("ELEMENTS", &rep.to_json());
|
.replace("ELEMENTS", &rep.to_json());
|
||||||
fs::write("/tmp/repartition.html", text_html).expect("Could not write into /tmp/");
|
fs::write("/tmp/repartition.html", text_html).expect("Could not write into /tmp/");
|
||||||
Command::new("firefox")
|
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::fs;
|
||||||
|
use std::io as stdio;
|
||||||
|
use std::io::prelude::*;
|
||||||
mod io;
|
mod io;
|
||||||
|
mod mail;
|
||||||
pub mod repartition;
|
pub mod repartition;
|
||||||
|
|
||||||
use argh::FromArgs;
|
use argh::FromArgs;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use rand_chacha::ChaCha8Rng;
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
|
||||||
|
extern crate tokio;
|
||||||
|
|
||||||
#[derive(FromArgs)]
|
#[derive(FromArgs)]
|
||||||
/// Computation of repartition
|
/// Computation of repartition
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
@@ -27,7 +31,19 @@ struct Arguments {
|
|||||||
transfert_list_filename: Option<String>,
|
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::*;
|
use repartition::*;
|
||||||
let args: Arguments = argh::from_env();
|
let args: Arguments = argh::from_env();
|
||||||
if let Some(filename) = args.user_list_filename {
|
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"))
|
serde_json::from_reader(fs::File::open(filename).expect("not able to read file"))
|
||||||
.expect("unable to read file");
|
.expect("unable to read file");
|
||||||
io::plot_repartition(&repart);
|
io::plot_repartition(&repart);
|
||||||
|
pause();
|
||||||
|
repart.send_all_mails().await;
|
||||||
} else {
|
} else {
|
||||||
println!("No parameter given, exiting");
|
println!("No parameter given, exiting");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,11 +50,14 @@ impl RandomRepartitionBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_repartition(mut self) -> Repartition {
|
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() {
|
while !self.payers.is_empty() || !self.receivers.is_empty() {
|
||||||
let mut payer = self.get_next_payer();
|
let mut payer = self.get_next_payer();
|
||||||
|
all_payers.insert(payer.name.clone());
|
||||||
let mut receiver = self.get_next_receiver();
|
let mut receiver = self.get_next_receiver();
|
||||||
|
all_receivers.insert(receiver.name.clone());
|
||||||
let amount = min(-payer.balance_cents, receiver.balance_cents);
|
let amount = min(-payer.balance_cents, receiver.balance_cents);
|
||||||
dbg!(amount);
|
dbg!(amount);
|
||||||
let transfert = Transfer {
|
let transfert = Transfer {
|
||||||
@@ -64,7 +67,7 @@ impl RandomRepartitionBuilder {
|
|||||||
};
|
};
|
||||||
payer.balance_cents += amount;
|
payer.balance_cents += amount;
|
||||||
receiver.balance_cents -= amount;
|
receiver.balance_cents -= amount;
|
||||||
rep.0.push(transfert);
|
transfer_vec.push(transfert);
|
||||||
if payer.balance_cents < 0 {
|
if payer.balance_cents < 0 {
|
||||||
self.payers.push(payer);
|
self.payers.push(payer);
|
||||||
}
|
}
|
||||||
@@ -72,8 +75,14 @@ impl RandomRepartitionBuilder {
|
|||||||
self.receivers.push(receiver);
|
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 to: User,
|
||||||
pub amount: u32,
|
pub amount: u32,
|
||||||
}
|
}
|
||||||
|
impl Transfer {
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
pub fn to_string(&self, payer: bool) -> String {
|
||||||
pub struct Repartition(pub Vec<Transfer>);
|
if payer {
|
||||||
|
format!(
|
||||||
type Evaluation = (usize);
|
" - Virement de {:.2}€ à {} ({})",
|
||||||
impl Repartition {
|
self.amount as f32 / 100.,
|
||||||
pub fn total_balance(&self) -> i32 {
|
self.to.name,
|
||||||
self.0.iter().map(|t| t.amount as i32).sum()
|
self.to.email
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
" - Virement de {:.2}€ de {} ({})",
|
||||||
|
self.amount as f32 / 100.,
|
||||||
|
self.from.name,
|
||||||
|
self.from.email
|
||||||
|
)
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
pub fn evaluate(&self) -> Evaluation {
|
}
|
||||||
self.0.len()
|
}
|
||||||
|
|
||||||
|
#[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