mirror of
https://github.com/LastExceed/spacermake.git
synced 2025-04-19 09:46:26 +02:00
add user config (closes #3)
This commit is contained in:
parent
9a2318432a
commit
6df991078f
837
Cargo.lock
generated
837
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ csv = "1.3.0"
|
||||
chrono = "0.4.33"
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
tap = "1.0.1"
|
||||
lazy_static = "1.4.0"
|
||||
toml = "0.8.10"
|
||||
futures = "0.3.30"
|
||||
colour = "0.7.0"
|
||||
config = { version = "0.15.11", features = ["toml"] }
|
||||
|
11
spacermake.toml
Normal file
11
spacermake.toml
Normal file
@ -0,0 +1,11 @@
|
||||
SLAVES_BY_MASTER = "master-slave_relations.toml"
|
||||
SLAVE_PROPERTIES = "slave_properties.toml"
|
||||
MACHINE_IDS = "/root/fabfire/config.toml"
|
||||
BILLING_LOG = "billinglog.csv"
|
||||
MACHINE_LOG = "machinelog.csv"
|
||||
DEBUG_LOG = "machinelog_debug.csv"
|
||||
DATA_USER = "DataUser.csv"
|
||||
DATA_MACHINES = "DataMachines.csv"
|
||||
MQTT_HOST = "mqtt.makerspace-bocholt.local"
|
||||
# MQTT_USERNAME = ""
|
||||
# MQTT_PASSWORD = ""
|
52
src/main.rs
52
src/main.rs
@ -1,65 +1,39 @@
|
||||
use std::time::Duration;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use colour::{dark_grey_ln, magenta_ln};
|
||||
use lazy_static::*;
|
||||
use rumqttc::{AsyncClient, EventLoop, MqttOptions, QoS};
|
||||
use state::{Announcer, Listener, State};
|
||||
|
||||
use utils::parse_toml_file;
|
||||
|
||||
use crate::utils::logs::log_start;
|
||||
use self::my_config::MyConfig;
|
||||
|
||||
pub mod my_config;
|
||||
mod state;
|
||||
mod utils;
|
||||
|
||||
pub const BOOKING_TOPIC: &str = "fabaccess/log";
|
||||
|
||||
lazy_static! {
|
||||
static ref SLAVES_BY_MASTER: HashMap<String, HashSet<String>> = parse_toml_file("master-slave_relations.toml");
|
||||
static ref SLAVE_PROPERTIES: HashMap<String, [bool; 3]> = parse_toml_file("slave_properties.toml");
|
||||
static ref MACHINE_IDS: HashMap<String, String> = parse_toml_file::<toml::Table>("/root/fabfire/config.toml")
|
||||
["readers"]
|
||||
.as_table()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(_key, value)| {
|
||||
let entry = value.as_table().unwrap();
|
||||
(
|
||||
entry["machine"].as_str().unwrap().replace("urn:fabaccess:resource:", ""),
|
||||
entry["id"].as_str().unwrap().into()
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
magenta_ln!("===== spacermake =====");
|
||||
log_start().expect("startup log failed");
|
||||
print_config();
|
||||
let (client, event_loop) = create_client().await;
|
||||
|
||||
let my_config = MyConfig::load();
|
||||
dark_grey_ln!("{my_config:#?}");
|
||||
|
||||
let (client, event_loop) = create_client(&my_config).await;
|
||||
magenta_ln!("start");
|
||||
let listener = State::new(Listener, client);
|
||||
let listener = State::new(Listener, client, my_config);
|
||||
let announcer = listener.duplicate_as(Announcer);
|
||||
|
||||
tokio::spawn(announcer.run());
|
||||
listener.run(event_loop).await;
|
||||
}
|
||||
|
||||
fn print_config() {
|
||||
let slaves_by_master: &HashMap<_, _> = &SLAVES_BY_MASTER;
|
||||
let slave_properties: &HashMap<_, _> = &SLAVE_PROPERTIES;
|
||||
let machine_ids: &HashMap<_, _> = &MACHINE_IDS;
|
||||
|
||||
let data_machine: &HashMap<_, _> = &utils::logs::billing::DATA_MACHINES;
|
||||
let data_user: &HashMap<_, _> = &utils::logs::billing::DATA_USER;
|
||||
dark_grey_ln!("{slaves_by_master:#?}{slave_properties:#?}{machine_ids:#?}{data_machine:#?}{data_user:#?}");
|
||||
}
|
||||
|
||||
async fn create_client() -> (AsyncClient, EventLoop) {
|
||||
let mut mqttoptions = MqttOptions::new("spacermake", "mqtt.makerspace-bocholt.local", 1883);
|
||||
async fn create_client(my_config: &MyConfig) -> (AsyncClient, EventLoop) {
|
||||
let mut mqttoptions = MqttOptions::new("spacermake", &my_config.mqtt_host, 1883);
|
||||
mqttoptions.set_keep_alive(Duration::from_secs(5));
|
||||
if let (Some(username), Some(password)) = (&my_config.mqtt_username, &my_config.mqtt_password) {
|
||||
mqttoptions.set_credentials(username, password);
|
||||
}
|
||||
|
||||
let (client, event_loop) = AsyncClient::new(mqttoptions, 10);
|
||||
client.subscribe("tele/+/MARGINS", QoS::AtMostOnce).await.expect("failed to subscribe");
|
||||
|
134
src/my_config.rs
Normal file
134
src/my_config.rs
Normal file
@ -0,0 +1,134 @@
|
||||
use std::io::Read;
|
||||
use std::fs::File;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use config::Config;
|
||||
use tap::Pipe;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MyConfig {
|
||||
pub slaves_by_master: HashMap<String, HashSet<String>>,
|
||||
pub slave_properties: HashMap<String, [bool; 3]>,
|
||||
pub machine_ids : HashMap<String, String>,
|
||||
pub data_user : HashMap<String, UserData>,
|
||||
pub data_machines : HashMap<String, MachineData>,
|
||||
pub billing_log : String,
|
||||
pub machine_log : String,
|
||||
pub debug_log : String,
|
||||
pub mqtt_host : String,
|
||||
pub mqtt_username : Option<String>,
|
||||
pub mqtt_password : Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UserData {
|
||||
pub id: Option<i32>,
|
||||
pub to_be_used: bool
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MachineData {
|
||||
pub id: Option<i32>,
|
||||
pub to_be_used: bool,
|
||||
pub power_sense: bool, //1 = runtime, 0 = booked time
|
||||
pub divider: i32
|
||||
}
|
||||
|
||||
impl MyConfig {
|
||||
pub fn load() -> Self {
|
||||
let config = Config::builder()
|
||||
.add_source(config::File::with_name("spacermake"))
|
||||
.add_source(config::Environment::default())
|
||||
.build()
|
||||
.expect("failed to load paths");
|
||||
|
||||
let slaves_by_master = open_or_create_file(&config, "SLAVES_BY_MASTER") // master-slave_relations.toml
|
||||
.pipe_as_ref(toml::from_str)
|
||||
.expect("failed to load SLAVES_BY_MASTER");
|
||||
|
||||
let slave_properties = open_or_create_file(&config, "SLAVE_PROPERTIES") // slave_properties.toml
|
||||
.pipe_as_ref(toml::from_str)
|
||||
.expect("failed to load SLAVE_PROPERTIES");
|
||||
|
||||
let machine_ids = open_or_create_file(&config, "MACHINE_IDS") // /root/fabfire/config.toml
|
||||
.pipe_as_ref(toml::from_str::<toml::Table>)
|
||||
.expect("failed to load MACHINE_IDS")
|
||||
["readers"]
|
||||
.as_table()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|(_key, value)| {
|
||||
let entry = value.as_table().unwrap();
|
||||
(
|
||||
entry["machine"].as_str().unwrap().replace("urn:fabaccess:resource:", ""),
|
||||
entry["id"].as_str().unwrap().into()
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let data_user = open_or_create_file(&config, "DATA_USER") // DataUser.csv
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let mut splits = line.split(',');
|
||||
|
||||
let name = splits.next().unwrap().to_string();
|
||||
|
||||
let ud = UserData {
|
||||
id : splits.next().unwrap().parse ().ok(),
|
||||
to_be_used: splits.next().unwrap().parse::<i32>().unwrap_or(1) == 1,
|
||||
};
|
||||
|
||||
(name, ud)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let data_machines = open_or_create_file(&config, "DATA_MACHINES") // DataMachines.csv
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let mut splits = line.split(',');
|
||||
|
||||
let name = splits.next().unwrap().to_string();
|
||||
let md = MachineData {
|
||||
id : splits.next().unwrap().parse ().ok(),
|
||||
to_be_used : splits.next().unwrap().parse::<i32>().unwrap_or(1) == 1,
|
||||
power_sense: splits.next().unwrap().parse::<i32>().unwrap() == 1,
|
||||
divider : splits.next().unwrap().parse ().unwrap()
|
||||
};
|
||||
|
||||
(name, md)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
slaves_by_master,
|
||||
slave_properties,
|
||||
machine_ids,
|
||||
data_user,
|
||||
data_machines,
|
||||
billing_log : config.get("BILLING_LOG").unwrap(),
|
||||
machine_log : config.get("MACHINE_LOG").unwrap(),
|
||||
debug_log : config.get("DEBUG_LOG").unwrap(),
|
||||
mqtt_host : config.get("MQTT_HOST").unwrap(),
|
||||
mqtt_username: config.get("MQTT_USERNAME").ok(),
|
||||
mqtt_password: config.get("MQTT_PASSWORD").ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_or_create_file(config: &Config, key: &str) -> String {
|
||||
let path = config
|
||||
.get_string(key)
|
||||
.unwrap();
|
||||
|
||||
let mut out = String::new();
|
||||
|
||||
File::options()
|
||||
.read(true)
|
||||
// .create(true)
|
||||
.open(path)
|
||||
.unwrap()
|
||||
.read_to_string(&mut out)
|
||||
.unwrap();
|
||||
|
||||
out
|
||||
}
|
@ -6,9 +6,9 @@ use colour::dark_grey_ln;
|
||||
use rumqttc::{AsyncClient, QoS};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::my_config::MyConfig;
|
||||
use crate::utils::index;
|
||||
use crate::utils::booking::Booking;
|
||||
use crate::SLAVE_PROPERTIES;
|
||||
|
||||
mod announcer;
|
||||
mod listener;
|
||||
@ -20,15 +20,17 @@ pub struct Announcer;
|
||||
pub struct State<Kind> {
|
||||
#[expect(dead_code, reason = "like PhantomData")]
|
||||
pub kind: Kind,
|
||||
pub config: Arc<MyConfig>,
|
||||
pub client: Arc<RwLock<AsyncClient>>,
|
||||
pub bookings: Arc<RwLock<HashMap<String, Booking>>>,
|
||||
pub scheduled_shutdowns: Arc<RwLock<VecDeque<(Instant, String)>>>
|
||||
}
|
||||
|
||||
impl<Kind> State<Kind> {
|
||||
pub fn new(kind: Kind, client: AsyncClient) -> Self {
|
||||
pub fn new(kind: Kind, client: AsyncClient, my_config: MyConfig) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
config: Arc::new(my_config),
|
||||
client: Arc::new(RwLock::new(client)),
|
||||
bookings: Default::default(),
|
||||
scheduled_shutdowns: Default::default()
|
||||
@ -38,6 +40,7 @@ impl<Kind> State<Kind> {
|
||||
pub fn duplicate_as<NewKind>(&self, kind: NewKind) -> State<NewKind> {
|
||||
State {
|
||||
kind,
|
||||
config: Arc::clone(&self.config),
|
||||
client: Arc::clone(&self.client),
|
||||
bookings: Arc::clone(&self.bookings),
|
||||
scheduled_shutdowns: Arc::clone(&self.scheduled_shutdowns)
|
||||
@ -47,7 +50,7 @@ impl<Kind> State<Kind> {
|
||||
//probably doesn't belong here, dunno where else to put it
|
||||
async fn set_power_state(&self, machine: &str, new_state: bool) {
|
||||
dark_grey_ln!("set power state - {machine} {new_state}");
|
||||
let is_tasmota = SLAVE_PROPERTIES[machine][index::IS_TASMOTA];
|
||||
let is_tasmota = self.config.slave_properties[machine][index::IS_TASMOTA];
|
||||
let topic =
|
||||
if is_tasmota {
|
||||
format!("cmnd/{machine}/Power")
|
||||
|
@ -7,7 +7,6 @@ use rumqttc::QoS;
|
||||
use tap::Pipe;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::MACHINE_IDS;
|
||||
use crate::utils::{create_display_time_string, minute_mark};
|
||||
use crate::{Announcer, State};
|
||||
|
||||
@ -35,7 +34,7 @@ impl State<Announcer> {
|
||||
|
||||
blue_ln!("updating display of {machine}");
|
||||
|
||||
let Some(id) = MACHINE_IDS.get(machine) else {
|
||||
let Some(id) = self.config.machine_ids.get(machine) else {
|
||||
red_ln!("error: no ID found for {machine}");
|
||||
return None;
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ use rumqttc::EventLoop;
|
||||
use rumqttc::Event::Incoming;
|
||||
use rumqttc::Packet::Publish;
|
||||
|
||||
use crate::{State, Listener, BOOKING_TOPIC, SLAVES_BY_MASTER, SLAVE_PROPERTIES};
|
||||
use crate::{State, Listener, BOOKING_TOPIC};
|
||||
use crate::utils::index;
|
||||
use crate::utils::get_power_state;
|
||||
use crate::utils::logs::{log_debug, machinelog};
|
||||
@ -35,12 +35,12 @@ impl State<Listener> {
|
||||
dark_grey_ln!("payload: {payload}");
|
||||
|
||||
let result = self.handle_payload(&publish.topic, &payload).await;
|
||||
|
||||
log_debug(&publish.topic, &payload, result)
|
||||
.expect("debug log failed")
|
||||
|
||||
log_debug(&publish.topic, &payload, result, &self.config)
|
||||
.expect("debug log failed");
|
||||
}
|
||||
|
||||
async fn handle_payload(&mut self, topic: &str, payload: &str) -> Result<(), &str> {
|
||||
async fn handle_payload(&mut self, topic: &str, payload: &str) -> Result<(), &'static str> {
|
||||
let splits: Result<[_; 3], _> = topic
|
||||
.split('/')
|
||||
.collect::<Vec<_>>()
|
||||
@ -97,7 +97,7 @@ impl State<Listener> {
|
||||
.remove(machine)
|
||||
.ok_or("released unbooked machine")?;
|
||||
|
||||
machinelog(machine, &booking)
|
||||
machinelog(machine, &booking, &self.config)
|
||||
.expect("machine log failed");
|
||||
|
||||
let was_running = booking.track(false);
|
||||
@ -143,24 +143,27 @@ impl State<Listener> {
|
||||
.iter()
|
||||
.filter(|(other, _booking)| *other != master)
|
||||
.flat_map(|(machine, booking)|
|
||||
SLAVES_BY_MASTER
|
||||
self.config
|
||||
.slaves_by_master
|
||||
.get(machine)
|
||||
.unwrap_or(&fallback) // machine being unknown already got logged when it got turned on, so we can ignore it here
|
||||
.iter()
|
||||
.filter(|slave| booking.is_running() || SLAVE_PROPERTIES[*slave][index::RUNS_CONTINUOUSLY])
|
||||
.filter(|slave| booking.is_running() || self.config.slave_properties[*slave][index::RUNS_CONTINUOUSLY])
|
||||
)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let slaves_to_update = SLAVES_BY_MASTER
|
||||
let slaves_to_update = self
|
||||
.config
|
||||
.slaves_by_master
|
||||
.get(master)
|
||||
.ok_or("unknown master")?
|
||||
.sub(&slaves_used_by_others)
|
||||
.into_iter()
|
||||
.filter(|slave| if SLAVE_PROPERTIES[slave][index::RUNS_CONTINUOUSLY] { long_slaves } else { short_slaves });
|
||||
.filter(|slave| if self.config.slave_properties[slave][index::RUNS_CONTINUOUSLY] { long_slaves } else { short_slaves });
|
||||
|
||||
for slave in slaves_to_update {
|
||||
if SLAVE_PROPERTIES[&slave][index::NEEDS_TRAILING_TIME] {
|
||||
if self.config.slave_properties[&slave][index::NEEDS_TRAILING_TIME] {
|
||||
if power {
|
||||
self.cancel_scheduled_shutdown(&slave).await;
|
||||
} else {
|
||||
|
19
src/utils.rs
19
src/utils.rs
@ -1,28 +1,9 @@
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::ErrorKind;
|
||||
use std::time::Duration;
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use tap::Pipe;
|
||||
|
||||
pub mod logs;
|
||||
pub mod booking;
|
||||
pub mod index;
|
||||
|
||||
pub fn parse_toml_file<T: DeserializeOwned>(path: &str) -> T {
|
||||
match fs::read_to_string(path) {
|
||||
Ok(s) => s,
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||
File::create(path).expect("failed to create file");
|
||||
String::new()
|
||||
},
|
||||
_ => panic!("error reading {path}")
|
||||
}
|
||||
.pipe_as_ref(toml::from_str)
|
||||
.expect("failed to parse toml")
|
||||
}
|
||||
|
||||
pub fn get_power_state(payload: &str) -> Result<String, &'static str> {
|
||||
//todo: there gotta be an easier way to do this
|
||||
json::parse(payload)
|
||||
|
@ -1,5 +1,5 @@
|
||||
use std::ops::Div;
|
||||
use std::io::{self, Write};
|
||||
use std::{io::Write, ops::Div};
|
||||
use std::io;
|
||||
use std::fs::File;
|
||||
|
||||
use chrono::Local;
|
||||
@ -7,7 +7,7 @@ use colour::red_ln;
|
||||
use csv::WriterBuilder;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::utils::booking::Booking;
|
||||
use crate::{my_config::MyConfig, utils::booking::Booking};
|
||||
|
||||
use self::billing::billinglog;
|
||||
|
||||
@ -24,8 +24,8 @@ struct Record<'s> {
|
||||
user: &'s str
|
||||
}
|
||||
|
||||
pub fn machinelog(machine: &str, booking: &Booking) -> io::Result<()> {
|
||||
billinglog(machine, booking)?;
|
||||
pub fn machinelog(machine: &str, booking: &Booking, config: &MyConfig) -> io::Result<()> {
|
||||
billinglog(machine, booking, config)?;
|
||||
|
||||
let record = Record {
|
||||
machine,
|
||||
@ -40,7 +40,7 @@ pub fn machinelog(machine: &str, booking: &Booking) -> io::Result<()> {
|
||||
let file_writer = File::options()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("machinelog.csv")?;
|
||||
.open(&config.machine_log)?;
|
||||
|
||||
WriterBuilder::new()
|
||||
.has_headers(false)
|
||||
@ -52,15 +52,7 @@ pub fn machinelog(machine: &str, booking: &Booking) -> io::Result<()> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn log_start() -> io::Result<()> {
|
||||
File::options()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("machinelog_debug.csv")?
|
||||
.write_all(format!("\n\n===== startup {} =====\n\n", Local::now()).as_bytes())
|
||||
}
|
||||
|
||||
pub fn log_debug(topic: &str, payload: &str, result: Result<(), &str>) -> io::Result<()> {
|
||||
pub fn log_debug(topic: &str, payload: &str, result: Result<(), &str>, config: &MyConfig) -> io::Result<()> {
|
||||
if let Err(error) = result {
|
||||
red_ln!("error: {error}");
|
||||
red_ln!(" topic: {topic}");
|
||||
@ -80,6 +72,6 @@ result: {result}",
|
||||
|
||||
File::options()
|
||||
.append(true)
|
||||
.open("machinelog_debug.csv")?
|
||||
.open(&config.debug_log)?
|
||||
.write_all(record.as_bytes())
|
||||
}
|
@ -1,80 +1,28 @@
|
||||
use std::{io, ops::Div};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
|
||||
use chrono::Local;
|
||||
use colour::red_ln;
|
||||
use csv::WriterBuilder;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::utils::booking::Booking;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserData {
|
||||
id: Option<i32>,
|
||||
to_be_used: bool
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MachineData {
|
||||
id: Option<i32>,
|
||||
to_be_used: bool,
|
||||
power_sense: bool, //1 = runtime, 0 = booked time
|
||||
divider: i32
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref DATA_USER: HashMap<String, UserData> = fs::read_to_string("DataUser.csv")
|
||||
.expect("failed to open DataUser.csv")
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let mut splits = line.split(',');
|
||||
|
||||
let name = splits.next().unwrap().to_string();
|
||||
|
||||
let ud = UserData {
|
||||
id : splits.next().unwrap().parse ().ok(),
|
||||
to_be_used: splits.next().unwrap().parse::<i32>().unwrap_or(1) == 1,
|
||||
};
|
||||
|
||||
(name, ud)
|
||||
})
|
||||
.collect();
|
||||
pub static ref DATA_MACHINES: HashMap<String, MachineData> = fs::read_to_string("DataMachines.csv")
|
||||
.expect("failed to open DataMachines.csv")
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let mut splits = line.split(',');
|
||||
|
||||
let name = splits.next().unwrap().to_string();
|
||||
let md = MachineData {
|
||||
id : splits.next().unwrap().parse ().ok(),
|
||||
to_be_used : splits.next().unwrap().parse::<i32>().unwrap_or(1) == 1,
|
||||
power_sense: splits.next().unwrap().parse::<i32>().unwrap() == 1,
|
||||
divider : splits.next().unwrap().parse ().unwrap()
|
||||
};
|
||||
|
||||
(name, md)
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
use crate::utils::booking::Booking;
|
||||
use crate::my_config::{MachineData, MyConfig};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct BillingRecord {
|
||||
user_id: String, // id nachschlagen in DataUser.csv. Wenn Spalte 3 ("toBeUsed") == 0 dann skip. Wenn nicht vorhanden dann fallback zum Namen
|
||||
quelle: &'static str, // "allgemeiner Beleg"
|
||||
brutto_netto: i32, // 2
|
||||
artikel_id: String, // DataMachine.csv#2
|
||||
artikel_id: String, // DataMachine.csv#2
|
||||
positionsdetails: String, // Date
|
||||
anzahl: i32, // minutes divided by DataMachine.csv#5 (ceil)
|
||||
rechnungstyp: i32 // 0
|
||||
}
|
||||
|
||||
pub fn billinglog(machine: &str, booking: &Booking) -> io::Result<()> {
|
||||
pub fn billinglog(machine: &str, booking: &Booking, config: &MyConfig) -> io::Result<()> {
|
||||
let user_id =
|
||||
if let Some(user_data) = &DATA_USER.get(&booking.user.to_string()) {
|
||||
if let Some(user_data) = &config.data_user.get(&booking.user.to_string()) {
|
||||
if !user_data.to_be_used { return Ok(()); }
|
||||
user_data
|
||||
.id
|
||||
@ -84,8 +32,9 @@ pub fn billinglog(machine: &str, booking: &Booking) -> io::Result<()> {
|
||||
booking.user.to_string()
|
||||
};
|
||||
|
||||
let machine_data = &DATA_MACHINES
|
||||
.get(&machine.to_string())
|
||||
let machine_data = &config
|
||||
.data_machines
|
||||
.get(machine)
|
||||
.unwrap_or(&MachineData {
|
||||
id: None,
|
||||
to_be_used: true,
|
||||
@ -127,7 +76,7 @@ pub fn billinglog(machine: &str, booking: &Booking) -> io::Result<()> {
|
||||
let file_writer = File::options()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("billinglog.csv")?;
|
||||
.open(&config.billing_log)?;
|
||||
|
||||
WriterBuilder::new()
|
||||
.has_headers(false)
|
||||
|
Loading…
x
Reference in New Issue
Block a user