allow adding custom shifts
This commit is contained in:
parent
25af0af6f1
commit
42a4107162
2 changed files with 226 additions and 28 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::grist::{Priority, Shift};
|
use crate::grist::{Priority, Shift};
|
||||||
|
use chrono::{DateTime, FixedOffset, TimeZone};
|
||||||
use clap::{CommandFactory, Parser, Subcommand};
|
use clap::{CommandFactory, Parser, Subcommand};
|
||||||
use matrix_sdk::{Client, Room};
|
use matrix_sdk::{Client, Room};
|
||||||
|
|
||||||
|
|
@ -25,15 +26,38 @@ enum Action {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
/// Date de début, sous ce format : J#-HH-MM (ex: J1-14-30)
|
/// Date de début, sous ce format : J#-HH-MM (ex: J1-14-30)
|
||||||
#[arg(id = "DATE", short = 'd', long = "date")]
|
#[arg(id = "DATE", short = 't', long = "date", value_parser= parse_date)]
|
||||||
starting_time: String,
|
start_time: DateTime<FixedOffset>,
|
||||||
|
|
||||||
/// Priorité
|
/// Priorité
|
||||||
#[arg(id = "PRIORITÉ", short = 'p', long = "priorité", value_enum)]
|
#[arg(id = "PRIORITÉ", short = 'p', long = "priorité", value_enum)]
|
||||||
priority: Priority,
|
priority: Priority,
|
||||||
|
|
||||||
|
/// Nombre de personnes
|
||||||
|
#[arg(id = "PERSONNES", short = 'n', long = "personnes")]
|
||||||
|
persons: u64,
|
||||||
|
|
||||||
|
/// Durée
|
||||||
|
#[arg(id = "DURÉE", short = 'd', long = "durée")]
|
||||||
|
duration: u64,
|
||||||
|
|
||||||
|
/// Guide
|
||||||
|
#[arg(id = "GUIDE", short = 'g', long = "guide")]
|
||||||
|
guide: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_date(input: &str) -> Res<DateTime<FixedOffset>> {
|
||||||
|
let day_index = &input[1..2].parse::<u32>()?;
|
||||||
|
let hour = &input[3..5].parse::<u32>()?;
|
||||||
|
let minute = &input[6..8].parse::<u32>()?;
|
||||||
|
FixedOffset::east_opt(2 * 3600)
|
||||||
|
.ok_or(anyhow!("failed to local time 1"))?
|
||||||
|
.with_ymd_and_hms(2026, 07, 01 + day_index, *hour, *minute, 0)
|
||||||
|
.single()
|
||||||
|
.ok_or(anyhow!("failed to local time 2"))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send_room_msg(_client: Client, room: Room, content: String) -> Res<OwnedEventId> {
|
pub async fn send_room_msg(_client: Client, room: Room, content: String) -> Res<OwnedEventId> {
|
||||||
use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
|
use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
|
||||||
let content = RoomMessageEventContent::text_plain(content);
|
let content = RoomMessageEventContent::text_plain(content);
|
||||||
|
|
@ -84,10 +108,19 @@ pub async fn handle(command: impl AsRef<str>, room: Room, client: Client) -> Res
|
||||||
}
|
}
|
||||||
Action::Add {
|
Action::Add {
|
||||||
name,
|
name,
|
||||||
starting_time,
|
start_time,
|
||||||
priority,
|
priority,
|
||||||
|
persons,
|
||||||
|
duration,
|
||||||
|
guide,
|
||||||
} => {
|
} => {
|
||||||
debug!("user asked for add");
|
debug!("user asked for add");
|
||||||
|
let shift =
|
||||||
|
Shift::create_custom(name, start_time, priority, persons, duration, guide);
|
||||||
|
debug!("{:?}", shift);
|
||||||
|
shift.save_new_custom().await?;
|
||||||
|
send_room_msg_html(client, room, format!("Nouveau shift ajouté : {}", shift))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
215
src/grist.rs
215
src/grist.rs
|
|
@ -1,11 +1,13 @@
|
||||||
prelude! {}
|
prelude! {}
|
||||||
|
|
||||||
use chrono::DateTime;
|
use chrono::{DateTime, FixedOffset};
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use clap::builder::PossibleValue;
|
use clap::builder::PossibleValue;
|
||||||
use grist_client::apis::configuration::Configuration;
|
use grist_client::apis::configuration::Configuration;
|
||||||
use grist_client::apis::records_api::list_records;
|
use grist_client::apis::records_api::{add_records, list_records};
|
||||||
use grist_client::models::{RecordsList, RecordsListRecordsInner};
|
use grist_client::models::{
|
||||||
|
RecordsList, RecordsListRecordsInner, RecordsWithoutId, RecordsWithoutIdRecordsInner,
|
||||||
|
};
|
||||||
|
|
||||||
pub const API_URL: &str = "https://grist.interhacker.space/api";
|
pub const API_URL: &str = "https://grist.interhacker.space/api";
|
||||||
pub const SHIFTS_DOC: &str = "iwSaC82TsQiuD3MYSG9RXX";
|
pub const SHIFTS_DOC: &str = "iwSaC82TsQiuD3MYSG9RXX";
|
||||||
|
|
@ -21,20 +23,36 @@ pub enum Priority {
|
||||||
Urgent,
|
Urgent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Priority {
|
||||||
|
pub fn to_grist(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Prioritaire => "1 - Prioritaire".to_string(),
|
||||||
|
Self::Secondaire => "2 - Secondaire".to_string(),
|
||||||
|
Self::Bonus => "3 - Bonus".to_string(),
|
||||||
|
Self::Urgent => "0 - URGENT".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Priority {
|
impl Display for Priority {
|
||||||
fn fmt(&self, f: &mut Fmt<'_>) -> FmtRes {
|
fn fmt(&self, f: &mut Fmt<'_>) -> FmtRes {
|
||||||
match self {
|
match self {
|
||||||
Self::Prioritaire => write!(f, "<b>Prioritaire</b>"),
|
Self::Prioritaire => write!(f, "<b>Prioritaire</b>"),
|
||||||
Self::Secondaire => write!(f, "Secondaire"),
|
Self::Secondaire => write!(f, "Secondaire"),
|
||||||
Self::Bonus => write!(f, "Bonus"),
|
Self::Bonus => write!(f, "Bonus"),
|
||||||
Self::Urgent => write!(f, "<font color=\"red\">Prioritaire</font>"),
|
Self::Urgent => write!(f, "<font color=\"red\">Urgent</font>"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValueEnum for Priority {
|
impl ValueEnum for Priority {
|
||||||
fn value_variants<'a>() -> &'a [Self] {
|
fn value_variants<'a>() -> &'a [Self] {
|
||||||
&[Self::Prioritaire, Self::Secondaire, Self::Bonus, Self::Urgent]
|
&[
|
||||||
|
Self::Prioritaire,
|
||||||
|
Self::Secondaire,
|
||||||
|
Self::Bonus,
|
||||||
|
Self::Urgent,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_possible_value(&self) -> Option<PossibleValue> {
|
fn to_possible_value(&self) -> Option<PossibleValue> {
|
||||||
|
|
@ -80,43 +98,168 @@ impl Display for ShiftType {
|
||||||
fn fmt(&self, f: &mut Fmt<'_>) -> FmtRes {
|
fn fmt(&self, f: &mut Fmt<'_>) -> FmtRes {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{} ({}): {} x/j, {} personnes, {} h",
|
"<i>{}</i> ({}): {} x/j, {} personnes, {} h",
|
||||||
self.name, self.priority, self.frequency, self.persons, self.duration
|
self.name, self.priority, self.frequency, self.persons, self.duration
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CustomShiftValues {
|
||||||
|
pub name: String,
|
||||||
|
pub priority: Priority,
|
||||||
|
pub persons: u64,
|
||||||
|
pub duration: u64,
|
||||||
|
pub guide: String,
|
||||||
|
}
|
||||||
|
impl CustomShiftValues {
|
||||||
|
pub fn new(
|
||||||
|
name: impl Into<String>,
|
||||||
|
priority: Priority,
|
||||||
|
persons: u64,
|
||||||
|
duration: u64,
|
||||||
|
guide: impl Into<String>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
priority: priority,
|
||||||
|
persons: persons,
|
||||||
|
duration: duration,
|
||||||
|
guide: guide.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum CustomOrDefined {
|
||||||
|
Defined(ShiftType),
|
||||||
|
Custom(CustomShiftValues),
|
||||||
|
}
|
||||||
|
|
||||||
// {"Autres_infos": Null, "Heure_de_debut": Number(1782144000), "Inscriptions": Array [String("L"), Number(1)], "Type_de_shift": Number(23)}
|
// {"Autres_infos": Null, "Heure_de_debut": Number(1782144000), "Inscriptions": Array [String("L"), Number(1)], "Type_de_shift": Number(23)}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Shift {
|
pub struct Shift {
|
||||||
pub shift_type: ShiftType,
|
pub shift_type: CustomOrDefined,
|
||||||
pub start_time: u64,
|
pub start_time: DateTime<FixedOffset>,
|
||||||
pub inscriptions: Vec<String>,
|
pub inscriptions: Vec<String>,
|
||||||
}
|
}
|
||||||
impl Shift {
|
impl Shift {
|
||||||
pub fn new(shift_type: ShiftType, start_time: u64, inscriptions: Vec<String>) -> Self {
|
pub fn new(shift_type: CustomOrDefined, start_time: u64, inscriptions: Vec<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
shift_type: shift_type,
|
shift_type: shift_type,
|
||||||
start_time: start_time.into(),
|
start_time: DateTime::from_timestamp(start_time as i64, 0)
|
||||||
|
.expect("failed to parse timestamp")
|
||||||
|
.with_timezone(
|
||||||
|
&FixedOffset::east_opt(2 * 3600).expect("failed to create timezone"),
|
||||||
|
),
|
||||||
inscriptions: inscriptions,
|
inscriptions: inscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_custom(
|
||||||
|
name: String,
|
||||||
|
start_time: DateTime<FixedOffset>,
|
||||||
|
priority: Priority,
|
||||||
|
persons: u64,
|
||||||
|
duration: u64,
|
||||||
|
guide: impl Into<String>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
shift_type: CustomOrDefined::Custom(CustomShiftValues::new(
|
||||||
|
name, priority, persons, duration, guide,
|
||||||
|
)),
|
||||||
|
start_time: start_time,
|
||||||
|
inscriptions: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
match &self.shift_type {
|
||||||
|
CustomOrDefined::Defined(shift_type) => shift_type.name.clone(),
|
||||||
|
CustomOrDefined::Custom(values) => values.name.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn priority(&self) -> Priority {
|
||||||
|
match &self.shift_type {
|
||||||
|
CustomOrDefined::Defined(shift_type) => shift_type.priority.clone(),
|
||||||
|
CustomOrDefined::Custom(values) => values.priority.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn duration(&self) -> u64 {
|
||||||
|
match &self.shift_type {
|
||||||
|
CustomOrDefined::Defined(shift_type) => shift_type.duration.clone(),
|
||||||
|
CustomOrDefined::Custom(values) => values.duration.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn persons(&self) -> u64 {
|
||||||
|
match &self.shift_type {
|
||||||
|
CustomOrDefined::Defined(shift_type) => shift_type.persons.clone(),
|
||||||
|
CustomOrDefined::Custom(values) => values.persons.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn save_new_custom(&self) -> Res<()> {
|
||||||
|
let conf = conf::get();
|
||||||
|
let api_key = conf.grist_api_key();
|
||||||
|
let grist_conf = Configuration::new(API_URL.into(), Some(api_key.into()));
|
||||||
|
let mut fields = serde_json::Map::new();
|
||||||
|
fields.insert("Type_de_shift".to_string(), serde_json::to_value(27)?); // 17 is custom type id
|
||||||
|
fields.insert(
|
||||||
|
"Heure_de_debut".to_string(),
|
||||||
|
serde_json::to_value(self.start_time)?,
|
||||||
|
);
|
||||||
|
if let CustomOrDefined::Custom(values) = &self.shift_type {
|
||||||
|
fields.insert(
|
||||||
|
"Nom_Custom".to_string(),
|
||||||
|
serde_json::to_value(values.name.as_str())?,
|
||||||
|
);
|
||||||
|
fields.insert(
|
||||||
|
"Priorite_Custom".to_string(),
|
||||||
|
serde_json::to_value(values.priority.to_grist())?,
|
||||||
|
);
|
||||||
|
fields.insert(
|
||||||
|
"Personnes_Custom".to_string(),
|
||||||
|
serde_json::to_value(values.persons)?,
|
||||||
|
);
|
||||||
|
fields.insert(
|
||||||
|
"Duree_Custom".to_string(),
|
||||||
|
serde_json::to_value(values.duration)?,
|
||||||
|
);
|
||||||
|
fields.insert(
|
||||||
|
"Guide_Custom".to_string(),
|
||||||
|
serde_json::to_value(values.guide.as_str())?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let record = RecordsWithoutIdRecordsInner {
|
||||||
|
fields: fields.into(),
|
||||||
|
};
|
||||||
|
let records = RecordsWithoutId {
|
||||||
|
records: vec![record],
|
||||||
|
};
|
||||||
|
|
||||||
|
add_records(&grist_conf, SHIFTS_DOC, SHIFTS_TABLE, records, None)
|
||||||
|
.await
|
||||||
|
.context("Failed to add shift record")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Shift {
|
impl Display for Shift {
|
||||||
fn fmt(&self, f: &mut Fmt<'_>) -> FmtRes {
|
fn fmt(&self, f: &mut Fmt<'_>) -> FmtRes {
|
||||||
let start_time = DateTime::from_timestamp(self.start_time as i64, 0)
|
let start_time = self.start_time.format("le %d à %H:%M");
|
||||||
.expect("failed to parse timestamp")
|
|
||||||
.format("%H:%M");
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{} ({}): à {}, durée {} h, inscrit·es : {}/{}",
|
"<i>{}</i> ({}): {}, durée {} h, inscrit·es : {}/{}",
|
||||||
self.shift_type.name,
|
self.name(),
|
||||||
self.shift_type.priority,
|
self.priority(),
|
||||||
start_time,
|
start_time,
|
||||||
self.shift_type.duration,
|
self.duration(),
|
||||||
self.inscriptions.len(),
|
self.inscriptions.len(),
|
||||||
self.shift_type.persons
|
self.persons()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +289,7 @@ pub fn get_priority(record: &RecordsListRecordsInner, column: &str) -> Res<Prior
|
||||||
s if s.starts_with("2") => Ok(Priority::Secondaire),
|
s if s.starts_with("2") => Ok(Priority::Secondaire),
|
||||||
s if s.starts_with("3") => Ok(Priority::Bonus),
|
s if s.starts_with("3") => Ok(Priority::Bonus),
|
||||||
s if s.starts_with("0") => Ok(Priority::Urgent),
|
s if s.starts_with("0") => Ok(Priority::Urgent),
|
||||||
_ => Err(anyhow!("can't parse priority")),
|
_ => Ok(Priority::Bonus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,17 +315,17 @@ pub fn get_number_array(record: RecordsListRecordsInner, column: &str) -> Res<Ve
|
||||||
Ok(u64_array)
|
Ok(u64_array)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_records(doc: &str, table: &str) -> Res<RecordsList> {
|
pub async fn get_records(doc: &str, table: &str, sort: Option<&str>) -> Res<RecordsList> {
|
||||||
let conf = conf::get();
|
let conf = conf::get();
|
||||||
let api_key = conf.grist_api_key();
|
let api_key = conf.grist_api_key();
|
||||||
let grist_conf = Configuration::new(API_URL.into(), Some(api_key.into()));
|
let grist_conf = Configuration::new(API_URL.into(), Some(api_key.into()));
|
||||||
list_records(&grist_conf, doc, table, None, None, None, None, None, None)
|
list_records(&grist_conf, doc, table, None, sort, None, None, None, None)
|
||||||
.await
|
.await
|
||||||
.context("Failed to list records")
|
.context("Failed to list records")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_shift_type(index: u64) -> Res<ShiftType> {
|
pub async fn get_shift_type(index: u64) -> Res<ShiftType> {
|
||||||
let record_list = get_records(SHIFTS_DOC, SHIFT_TYPES_TABLE).await?;
|
let record_list = get_records(SHIFTS_DOC, SHIFT_TYPES_TABLE, None).await?;
|
||||||
let record = record_list
|
let record = record_list
|
||||||
.records
|
.records
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -199,7 +342,7 @@ pub async fn get_shift_type(index: u64) -> Res<ShiftType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_inscription(index: u64) -> Res<String> {
|
pub async fn get_inscription(index: u64) -> Res<String> {
|
||||||
let record_list = get_records(SHIFTS_DOC, INSCRIPTIONS_TABLE).await?;
|
let record_list = get_records(SHIFTS_DOC, INSCRIPTIONS_TABLE, None).await?;
|
||||||
let record = record_list
|
let record = record_list
|
||||||
.records
|
.records
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -210,14 +353,36 @@ pub async fn get_inscription(index: u64) -> Res<String> {
|
||||||
Ok(name)
|
Ok(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_shift_type_custom(record: &RecordsListRecordsInner) -> Res<CustomShiftValues> {
|
||||||
|
// Ok(CustomShiftValues::new(
|
||||||
|
// get_string(&record, "Nom_Custom")?,
|
||||||
|
// get_priority(&record, "Priority_Custom")?,
|
||||||
|
// get_number(&record, "Persons_Custom")?,
|
||||||
|
// get_number(&record, "Duration_Custom")?,
|
||||||
|
// get_string(&record, "Guide_Custom")?,
|
||||||
|
// ))
|
||||||
|
let name = get_string(&record, "Nom_Custom")?;
|
||||||
|
let priority = get_priority(&record, "Priorite_Custom")?;
|
||||||
|
let persons = get_number(&record, "Personnes_Custom")?;
|
||||||
|
let duration = get_number(&record, "Duree_Custom")?;
|
||||||
|
let guide = get_string(&record, "Guide_Custom")?;
|
||||||
|
Ok(CustomShiftValues::new(
|
||||||
|
name, priority, persons, duration, guide,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_shifts() -> Res<Vec<Shift>> {
|
pub async fn list_shifts() -> Res<Vec<Shift>> {
|
||||||
let record_list = get_records(SHIFTS_DOC, SHIFTS_TABLE).await?;
|
let record_list = get_records(SHIFTS_DOC, SHIFTS_TABLE, Some("Heure_de_debut")).await?;
|
||||||
debug!("{:?}", record_list);
|
debug!("{:?}", record_list);
|
||||||
let mut shifts: Vec<Shift> = Vec::new();
|
let mut shifts: Vec<Shift> = Vec::new();
|
||||||
for record in record_list.records {
|
for record in record_list.records {
|
||||||
let shift_type_ref =
|
let shift_type_ref =
|
||||||
get_number(&record, "Type_de_shift").context("getting shift type ref")?;
|
get_number(&record, "Type_de_shift").context("getting shift type ref")?;
|
||||||
let shift_type = get_shift_type(shift_type_ref).await?;
|
let shift_type = if shift_type_ref == 27 {
|
||||||
|
CustomOrDefined::Custom(get_shift_type_custom(&record)?)
|
||||||
|
} else {
|
||||||
|
CustomOrDefined::Defined(get_shift_type(shift_type_ref).await?)
|
||||||
|
};
|
||||||
let start_time = get_number(&record, "Heure_de_debut")?;
|
let start_time = get_number(&record, "Heure_de_debut")?;
|
||||||
let inscriptions_refs =
|
let inscriptions_refs =
|
||||||
get_number_array(record, "Inscriptions").context("getting inscriptions refs")?;
|
get_number_array(record, "Inscriptions").context("getting inscriptions refs")?;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue