FIX: changed calculation of personnel billing hours; FEA: Added Logging feature

This commit is contained in:
Keanu D?lle 2022-04-21 06:11:12 +02:00
parent 8db5a6f954
commit 187ccde762
15 changed files with 495 additions and 71 deletions

1
Cargo.lock generated
View File

@ -580,6 +580,7 @@ dependencies = [
"iban_validate",
"lettre",
"log",
"num-bigint",
"rand",
"rocket",
"rocket_dyn_templates",

View File

@ -21,6 +21,7 @@ rand = "0.8.5"
iban_validate = "4.0.1"
base64 = "0.13.0"
bigdecimal = "0.1.2"
num-bigint = "0.2.6"
lettre = { version = "0.10.0-rc.4", features = ["tokio1", "tokio1-native-tls"] }
[dependencies.rocket]

View File

@ -0,0 +1,4 @@
-- This file should undo anything in `up.sql`
drop table if exists log_actions;
drop table if exists log;

View File

@ -0,0 +1,33 @@
-- Your SQL goes here
create table log_actions
(
action text
constraint log_actions_pk
primary key,
description text
);
create table log
(
entry_id serial
constraint log_pk
primary key,
timestamp timestamptz default now() not null,
action text not null,
affected_entity uuid
constraint log_entities_entity_id_fk_2
references entities
on update cascade,
causer uuid
constraint log_entities_entity_id_fk
references entities
on update cascade,
details jsonb
);
CREATE INDEX ON log USING gin(details);
INSERT INTO log_actions (action, description)
VALUES ('password_reset', null);
INSERT INTO log_actions (action, description)
VALUES ('billing_state_approved', null);

View File

@ -1,16 +1,17 @@
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, sql_query};
use diesel::{debug_query, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, sql_query};
use diesel::pg::types::sql_types::Uuid;
use rocket::State;
use crate::database::controller::connector::establish_connection;
use crate::logger::entries::LogEntry;
use crate::Settings;
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct BillingState {
entity_id: uuid::Uuid,
name: String,
description: Option<String>,
pub(crate) final_approve: bool,
pub entity_id: uuid::Uuid,
pub name: String,
pub description: Option<String>,
pub final_approve: bool,
}
pub fn get_billing_states(settings: &State<Settings>) -> Result<Vec<BillingState>, diesel::result::Error> {
@ -64,7 +65,7 @@ struct TempQuery {
pub fn get_min_billing_states_for_event(settings: &State<Settings>, event: uuid::Uuid) -> Result<Option<uuid::Uuid>, diesel::result::Error> {
let connection = establish_connection(settings);
let res: Result<Option<TempQuery>, diesel::result::Error> = sql_query("SELECT billing_state_id FROM eu_instances AS eui INNER JOIN billing_states bs on eui.billing_state_id = bs.entity_id WHERE event_id = $1 ORDER BY bs.order ASC LIMIT 1;").bind::<crate::diesel::sql_types::Uuid, _>(event).get_result(&connection);
let res: Result<Option<TempQuery>, diesel::result::Error> = sql_query("SELECT billing_state_id FROM eu_instances AS eui INNER JOIN billing_states bs on eui.billing_state_id = bs.entity_id WHERE event_id = $1 ORDER BY bs.order ASC LIMIT 1;").bind::<crate::diesel::sql_types::Uuid, _>(event).get_result(&connection).optional();
match res {
Ok(res) => {
match res {
@ -77,4 +78,18 @@ pub fn get_min_billing_states_for_event(settings: &State<Settings>, event: uuid:
Err(e)
}
}
}
pub fn get_billing_approve_log_entry_with_state(settings: &State<Settings>, event: uuid::Uuid, state: uuid::Uuid) -> Result<Option<LogEntry>, diesel::result::Error>{
let connection = establish_connection(settings);
let res: Result<Option<LogEntry>, diesel::result::Error> = sql_query("SELECT * FROM log WHERE affected_entity = $1 AND details->>'state' = $2 ORDER BY timestamp DESC LIMIT 1;").bind::<crate::diesel::sql_types::Uuid, _>(event).bind::<crate::diesel::sql_types::Text, _>(state.to_string()).get_result(&connection).optional();
match res{
Err(e) => {
error!("Couldn't get billing approve log entry: {}", e);
Err(e)
},
Ok(res) => Ok(res)
}
}

View File

@ -1,16 +1,20 @@
use crate::helper::settings::Settings;
use rocket::State;
use crate::database::controller::connector::establish_connection;
use crate::schema::password_resets::dsl::{password_resets};
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use diesel::{ExpressionMethods, RunQueryDsl};
use std::iter;
use argon2::Config;
use chrono::{NaiveDateTime, Utc};
use diesel::{ExpressionMethods, RunQueryDsl};
use diesel::query_dsl::filter_dsl::FilterDsl;
use diesel::query_dsl::select_dsl::SelectDsl;
use argon2::Config;
use rand::{Rng, thread_rng};
use rand::distributions::Alphanumeric;
use rocket::State;
use crate::database::controller::connector::establish_connection;
use crate::helper::settings::Settings;
use crate::logger::{add_entry, LogActions};
use crate::logger::entries::{InsertableLogEntry, LogEntry};
use crate::schema::password_resets::dsl::password_resets;
use crate::schema::users::dsl::users;
use std::iter;
/// Adds password reset token to database and returns it
pub fn add_token(settings: &State<Settings>, user_id: uuid::Uuid) -> Result<String, diesel::result::Error>{
@ -96,6 +100,8 @@ pub fn change_password_with_token(settings: &State<Settings>, token2: String, pa
}
};
add_entry(InsertableLogEntry::new(LogActions::PasswordReset, Some(user_id), None, None), settings);
match remove_token(settings, token2){
Ok(_) => Ok(()),
Err(_) => Err(())

View File

@ -0,0 +1,209 @@
use std::borrow::BorrowMut;
use bigdecimal::BigDecimal;
use num_bigint::Sign;
pub fn convert_old(val: BigDecimal, decimal_places: u8, separator: char, thousands_separator: Option<char>) -> String{
let mut res = String::new();
let (base, exponent) = val.as_bigint_and_exponent();
let mut base = base.to_str_radix(10);
let mut base= base.chars();
let mut exp = exponent;
let mut res_base : Vec<char> = Vec::new();
let mut res_decimal_places: Vec<char> = Vec::new();
let mut thousands_counter = 0;
if exponent > 0{
let mut iter = base.rev().peekable();
//-10^exponent
loop{
match iter.next(){
Some(num) => {
println!("num: {}", num);
if exp > 0{
//There are decimal places left
exp = exp-1;
res_decimal_places.push(num);
}else{
if let Some(thsep) = thousands_separator{
if thousands_counter == 3 && iter.peek().is_some(){ //Add thousands separator if there are chars left
res_base.push(thsep);
thousands_counter = 0;
}
thousands_counter = thousands_counter +1;
}
res_base.push(num);
}
},
None => {
if exp > 0{
exp = exp-1;
res_decimal_places.push('0');
}else{
break;
}
}
}
}
//First add all non decimal places:
res.push_str(&res_base.iter().rev().collect::<String>());
if decimal_places > 0{
//There are decimal places, add saperator and add decimal places
res.push(separator);
let mut decimal_places_used = 0;
let res_decimal_places = res_decimal_places.iter().rev();
for place in res_decimal_places{
if decimal_places_used < decimal_places {
res.push(*place)
}else{
break;
}
}
}else{
if res.is_empty(){
res = "0".to_string();
}
}
}else if exponent < 0{
//10^exponent
res = String::from("test123");
}else{
let mut iter = base.rev().peekable();
while let Some(num) = iter.next(){
if let Some(thsep) = thousands_separator{
if thousands_counter == 3 && iter.peek().is_some(){ //Add thousands separator if there are chars left
res_base.push(thsep);
thousands_counter = 0;
}
thousands_counter = thousands_counter +1;
}
res_base.push(num);
}
res.push_str(&res_base.into_iter().rev().collect::<String>());
}
res
}
pub fn convert(val: BigDecimal, decimal_places: u8, seperator: char, thousands_seperator: Option<char>) -> String{
let (base, mut exponent) = val.as_bigint_and_exponent();
let mut base = base.to_str_radix(10);
// Negative power of 10, need to move comma to the left
let mut res_decimal_places:Vec<char> = Vec::new();
let mut rev_base = base.chars().rev();
let mut exp = exponent.clone();
//Add decimal places until exponent = 0
while exp > 0{
match rev_base.next(){
Some(next_base_char) => res_decimal_places.push(next_base_char),
None => res_decimal_places.push('0')
}
exp = exp-1;
}
//Reverse decimal places back into right order
res_decimal_places = res_decimal_places.into_iter().rev().collect::<Vec<char>>();
let mut new_base : Vec<char> = rev_base.rev().collect();
if let Some(ts) = thousands_seperator{
new_base = add_thousands_seperator(new_base, ts)
}
if new_base.is_empty(){
new_base = vec!['0'];
}
if res_decimal_places.len() > 0{
format!("{}{}{}", new_base.iter().collect::<String>(), seperator, res_decimal_places.iter().collect::<String>())
}else{
new_base.iter().collect::<String>()
}
}
pub fn add_thousands_seperator(input: Vec<char>, thousands_seperator: char) -> Vec<char>{
let mut input = input.into_iter().rev().peekable();
let mut output : Vec<char> = Vec::new();
let mut counter = 0;
while let Some(val) = input.next(){
counter = counter+1;
output.push(val);
if counter == 3 && input.peek().is_some(){
output.push(thousands_seperator);
counter = 0;
}
}
output.into_iter().rev().collect()
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[test]
pub fn test_convert_positive() {
let bd = BigDecimal::from_str("54000.16712").unwrap();
println!("Exponent: {}", bd.as_bigint_and_exponent().1.to_string());
assert_eq!(convert(bd, 2, ',', Some('.')), String::from("54.000,16712"));
}
#[test]
pub fn test_convert_negative() {
let bd = BigDecimal::from_str("-54000.16712").unwrap();
assert_eq!(convert(bd, 2, ',', Some('.')), String::from("-54.000,16712"));
}
#[test]
pub fn test_convert_no_decimal_places() {
let bd = BigDecimal::from_str("-54000.16712").unwrap();
assert_eq!(convert(bd, 0, ',', Some('.')), String::from("-54.000"));
}
#[test]
pub fn test_convert_no_thousands_separator() {
let bd = BigDecimal::from_str("-54000.16712").unwrap();
assert_eq!(convert(bd, 0, ',', None), String::from("-54000"));
}
#[test]
pub fn test_convert_positive_no_decimal_places() {
let bd = BigDecimal::from_str("54000").unwrap();
assert_eq!(convert(bd, 2, ',', Some('.')), String::from("54.000"));
}
#[test]
pub fn test_convert_negative_no_decimal_places() {
let bd = BigDecimal::from_str("-54000").unwrap();
assert_eq!(convert(bd, 2, ',', Some('.')), String::from("-54.000"));
}
#[test]
pub fn test_convert2() {
let bd = BigDecimal::from_str("54000000000000").unwrap();
assert_eq!(convert(bd, 2, ',', Some('.')), String::from("54.000.000.000.000"));
}
#[test]
pub fn test_convert3() {
let bd = BigDecimal::from_str("0.000000016712").unwrap();
println!("Exponent: {}", bd.as_bigint_and_exponent().1.to_string());
assert_eq!(convert(bd, 0, ',', None), String::from("0"));
}
#[test]
pub fn test_convert4() {
let bd = BigDecimal::from_str("0.000000016712").unwrap();
println!("Exponent: {}", bd.as_bigint_and_exponent().1.to_string());
assert_eq!(convert(bd, 2, ',', None), String::from("0,00"));
}
}

View File

@ -16,4 +16,5 @@ pub mod mail_templates;
pub mod permission;
pub mod order_enum;
pub mod time;
pub mod serde_patch;
pub mod serde_patch;
pub mod bigdecimal_to_string;

66
src/logger/entries.rs Normal file
View File

@ -0,0 +1,66 @@
use chrono::{DateTime, Utc};
use diesel::sql_types::*;
use serde_json::Value;
use crate::logger::LogActions;
/// LogEntry to create a new log entry with specified action, affected_entity (optional), causer (optional) and optional details
/// Timestamp and log entry id are generated automatically
pub struct InsertableLogEntry {
pub action: String,
pub affected_entity: Option<uuid::Uuid>,
pub causer: Option<uuid::Uuid>,
pub details: Option<Value>
}
impl InsertableLogEntry{
/// Create new LogEntry with given values
pub fn new(action: LogActions, affected_entity: Option<uuid::Uuid>, causer: Option<uuid::Uuid>, details: Option<Value>) -> Self{
InsertableLogEntry{
action: action.value(),
affected_entity,
causer,
details
}
}
}
#[derive(QueryableByName)]
pub struct LogEntry{
#[sql_type = "Integer"]
pub entry_id: i32,
#[sql_type = "Timestamptz"]
pub timestamp: DateTime<Utc>,
#[sql_type = "Text"]
pub action: String,
#[sql_type = "Nullable<Uuid>"]
pub affected_entity: Option<uuid::Uuid>,
#[sql_type = "Nullable<Uuid>"]
pub causer: Option<uuid::Uuid>,
#[sql_type = "Nullable<Jsonb>"]
pub details: Option<Value>
}
#[derive(Serialize)]
pub struct EventBillingStateApproveLogEntry{
pub state: uuid::Uuid
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
pub fn test_conversion() {
let test = EventBillingStateApproveLogEntry{
state: uuid::Uuid::parse_str("00000000-0000-0000-0000-000000001234").unwrap()
};
let test3 : Value = serde_json::to_value(test).unwrap();
let comp = json!({"state": "00000000-0000-0000-0000-000000001234"});
assert_eq!(test3, comp);
}
}

31
src/logger/mod.rs Normal file
View File

@ -0,0 +1,31 @@
use diesel::{ExpressionMethods, RunQueryDsl};
use rocket::State;
use crate::database::controller::connector::establish_connection;
use crate::logger::entries::InsertableLogEntry;
use crate::schema::log::dsl::*;
use crate::Settings;
pub mod entries;
pub fn add_entry(entry: InsertableLogEntry, settings: &State<Settings>){
let connection = establish_connection(settings);
if let Err(err) = diesel::insert_into(crate::schema::log::table).values((action.eq(entry.action), affected_entity.eq(entry.affected_entity), causer.eq(entry.causer), details.eq(entry.details))).execute(&connection){
error!("Couldn't add new application log entry: {}", err);
}
}
pub enum LogActions{
PasswordReset,
BillingStateApproved,
}
impl LogActions{
fn value(&self) -> String {
match *self{
LogActions::PasswordReset => String::from("password_reset"),
LogActions::BillingStateApproved => String::from("billing_state_approved")
}
}
}

View File

@ -3,6 +3,7 @@ extern crate base64;
extern crate chrono;
extern crate chrono_tz;
extern crate config;
extern crate core;
#[macro_use]
extern crate diesel;
extern crate iban;
@ -36,6 +37,7 @@ pub mod helper;
pub mod modules;
pub mod permissions;
pub mod schema;
pub mod logger;
#[launch]
fn rocket() -> _ {

View File

@ -14,6 +14,8 @@ use crate::database::model::events::Event;
use crate::helper::session_cookies::model::SessionCookie;
use crate::helper::settings::Settings;
use crate::helper::translate_diesel_error::translate_diesel;
use crate::logger::{add_entry, LogActions};
use crate::logger::entries::{EventBillingStateApproveLogEntry, InsertableLogEntry, LogEntry};
use crate::MailQueue;
use crate::modules::api::events::create::CreateEventData;
use crate::modules::api::member_management::controller::parser::{parse_member_cookie, parse_option_uuid, parse_uuid_string};
@ -246,6 +248,13 @@ pub async fn approve(
match crate::database::controller::events::approve_stage(settings, event_id, stage, caller.entity_id) {
Ok(_) => {
let details = EventBillingStateApproveLogEntry{
state: stage
};
//Log approval
add_entry(InsertableLogEntry::new(LogActions::BillingStateApproved, Some(event_id), Some(caller.entity_id), Some(serde_json::to_value(details).unwrap())), settings);
if billing_state.final_approve {
if let Err(e) = finish_billing(settings, event_id) {
return Err(translate_diesel(e));

View File

@ -8,14 +8,14 @@ pub fn calculate_hours(real_start_time: chrono::DateTime<Utc>, real_end_time: ch
return Some(0);
}
let hours_since_start = (timestamp_start / 3600.0).floor();
let hours_since_end = (timestamp_end / 3600.0).ceil();
let hours_since_start = (timestamp_start / 3600.0);
let hours_since_end = (timestamp_end / 3600.0);
if hours_since_start > hours_since_end {
error!("real_start_time is after real_end_time");
None
} else {
Some((hours_since_end - hours_since_start) as i32)
Some((hours_since_end - hours_since_start).ceil() as i32)
}
}

View File

@ -6,11 +6,13 @@ use std::path::Path;
use rocket::State;
use crate::database::controller::billing::personnel_billing_rates::get_billing_rate;
use crate::database::controller::billing::states::{get_billing_approve_log_entry_with_state, get_billing_states};
use crate::database::controller::events::{get_event, get_position_instances};
use crate::database::controller::events::instances::instances::get_instances;
use crate::database::controller::groups::get_group;
use crate::database::controller::members::get_member_by_uuid;
use crate::helper::time::utc_to_local_user_time;
use crate::helper::bigdecimal_to_string::convert;
use crate::helper::time::{get_timezone, utc_to_local_user_time};
use crate::Settings;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
@ -108,7 +110,7 @@ pub fn generate_billing_csv(settings: &State<Settings>, event_id: uuid::Uuid) ->
res.push_str(&format!("Abrechnungssatz:,{},{},Pauschale:,{},€/Stunde:,{}\n", sanitize(billing_rate.name), sanitize(billing_rate.description.unwrap_or(String::from(""))), sanitize(format!("{}", billing_rate.lump_sum)), sanitize(format!("{}", billing_rate.payment_per_hour))));
}
res.push_str(&format!("{},{}, {},{},{},{},{},{},{}\n", String::from("Personalnummer"), String::from("Vorname"), String::from("Nachname"), String::from("Von"), String::from("Bis"), String::from("Stunden"), String::from("Pauschale"), String::from("Stundengeld"), String::from("Gesamt")));
res.push_str(&format!("{},{},{},{},{},{},{},{},{}\n", String::from("Personalnummer"), String::from("Vorname"), String::from("Nachname"), String::from("Von"), String::from("Bis"), String::from("Stunden"), String::from("Pauschale"), String::from("Stundengeld"), String::from("Gesamt")));
let position_instances = get_position_instances(settings, instance.instance_id)?;
@ -116,17 +118,17 @@ pub fn generate_billing_csv(settings: &State<Settings>, event_id: uuid::Uuid) ->
if let Some(personnel) = crate::database::controller::billing::personnel_billing::read(settings, position_instance.position_instance_id)? {
match get_member_by_uuid(personnel.member_id, settings) {
Some(member) => {
let tz = get_timezone(settings, None);
let begin = match position_instance.real_start_time {
Some(start) => utc_to_local_user_time(settings, None, start),
Some(start) => start.with_timezone(&tz).format("%d.%m.%Y %H:%M").to_string(),
None => return Err(CSVGeneratorError::Generator(CSVGeneratorErrorKind::MissingTimes))
};
let end = match position_instance.real_end_time {
Some(end) => utc_to_local_user_time(settings, None, end),
Some(end) => end.with_timezone(&tz).format("%d.%m.%Y %H:%M").to_string(),
None => return Err(CSVGeneratorError::Generator(CSVGeneratorErrorKind::MissingTimes))
};
res.push_str(&format!("{},{},{},{},{},{},{},{},{}\n", sanitize(member.personnel_number.unwrap_or(0).to_string()), sanitize(member.firstname), sanitize(member.lastname), sanitize(begin), sanitize(end), sanitize(personnel.fulfilled_time.to_string()), sanitize(personnel.money_from_lump_sum.to_string()), sanitize(personnel.money_for_time.to_string()), sanitize(personnel.total_money.to_string())));
res.push_str(&format!("{},{},{},{},{},{},{},{},{}\n", sanitize(member.personnel_number.unwrap_or(0).to_string()), sanitize(member.firstname), sanitize(member.lastname), sanitize(begin), sanitize(end), sanitize(personnel.fulfilled_time.to_string()), sanitize(personnel.money_from_lump_sum.to_string()), sanitize(convert(personnel.money_for_time.clone(), 2, ',', Some('.'))), sanitize(convert(personnel.money_from_lump_sum+personnel.money_for_time, 2, ',', Some('.')))));
}
None => {
return Err(CSVGeneratorError::Generator(CSVGeneratorErrorKind::MissingMember));
@ -135,6 +137,24 @@ pub fn generate_billing_csv(settings: &State<Settings>, event_id: uuid::Uuid) ->
}
}
res.push('\n');
let billing_states = get_billing_states(settings)?;
res.push_str("Freigaben:\n");
for state in billing_states{
if let Some(entry) = get_billing_approve_log_entry_with_state(settings, event_id, state.entity_id)?{
if let Some(causer) = entry.causer{
if let Some(member) = get_member_by_uuid(causer, settings){
res.push_str(&format!("{},{}\n", sanitize(format!("{} ({})", state.name, state.description.unwrap_or("".to_string()))), sanitize(format!("{} {}", member.firstname, member.lastname))))
}else{
res.push_str(&format!("{},{}\n", sanitize(state.name), "gelöschtes Mitglied"))
}
}else{
warn!("Billing State Approve Log Entry without causer!");
}
}else{
debug!("No entry with event_id {} and state {}", event_id, state.entity_id);
}
}
}
Ok(res)
}

View File

@ -1,6 +1,6 @@
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
addresses (id) {
id -> Uuid,
@ -15,7 +15,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
addresses_entities (address_id, entitiy_id) {
address_id -> Uuid,
@ -25,7 +25,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
appointment_types (type_id) {
type_id -> Uuid,
@ -37,7 +37,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
appointments (id) {
id -> Uuid,
@ -49,7 +49,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
billing_states (entity_id) {
name -> Text,
@ -62,7 +62,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
buildings (entity_id) {
entity_id -> Uuid,
@ -73,7 +73,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
communication_targets (target_id) {
target_id -> Uuid,
@ -86,7 +86,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
communication_types (type_id) {
type_id -> Uuid,
@ -96,7 +96,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
cost_centres (short_id) {
short_id -> Int4,
@ -106,7 +106,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
cost_centres_members (member_entity_id, cost_centre_shortid) {
member_entity_id -> Uuid,
@ -116,7 +116,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
entities (entity_id) {
entity_id -> Uuid,
@ -125,7 +125,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
eu_instances (instance_id) {
instance_id -> Uuid,
@ -144,7 +144,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
eu_position_instances (position_instance_id) {
instance_id -> Uuid,
@ -159,7 +159,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
eu_positions (entity_id) {
entity_id -> Uuid,
@ -171,7 +171,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
eu_positions_templates (position_template_id) {
position_entity_id -> Uuid,
@ -184,7 +184,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
eu_templates (entity_id) {
entity_id -> Uuid,
@ -195,7 +195,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
eu_vehicle_positions (entity_id) {
entity_id -> Uuid,
@ -208,7 +208,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
event_organisers (entity_id) {
entity_id -> Uuid,
@ -223,7 +223,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
event_requests (entity_id) {
entity_id -> Uuid,
@ -246,7 +246,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
event_types (type_id) {
type_id -> Uuid,
@ -258,7 +258,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
events (entity_id) {
entity_id -> Uuid,
@ -281,7 +281,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
group_entity_state (state_id) {
state_id -> Uuid,
@ -293,7 +293,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
groups (entity_id) {
entity_id -> Uuid,
@ -304,7 +304,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
groups_entities (group_id, entity_id) {
group_id -> Uuid,
@ -315,7 +315,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
license_categories (name) {
name -> Text,
@ -325,7 +325,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
licenses_members (member_id, license_name) {
member_id -> Uuid,
@ -336,7 +336,31 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
log (entry_id) {
entry_id -> Int4,
timestamp -> Timestamptz,
action -> Text,
affected_entity -> Nullable<Uuid>,
causer -> Nullable<Uuid>,
details -> Nullable<Jsonb>,
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
log_actions (action) {
action -> Text,
description -> Nullable<Text>,
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
login_attempts (id) {
id -> Uuid,
@ -347,7 +371,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
login_attempts_usernames (id) {
id -> Uuid,
@ -358,7 +382,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
members (entity_id) {
entity_id -> Uuid,
@ -382,7 +406,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
members_roles (member_id, role_id) {
member_id -> Uuid,
@ -392,7 +416,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
notification_types (name) {
name -> Text,
@ -402,7 +426,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
password_resets (token) {
token -> Text,
@ -413,7 +437,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
permissions (permission) {
permission -> Text,
@ -425,7 +449,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
personnel_billing (position_instance_id) {
position_instance_id -> Uuid,
@ -439,7 +463,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
personnel_billing_rates (billing_rate_id) {
billing_rate_id -> Uuid,
@ -453,7 +477,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
qualification_categories (id) {
id -> Uuid,
@ -464,7 +488,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
qualifications (id) {
id -> Uuid,
@ -476,7 +500,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
qualifications_members (member_id, qualification_id) {
member_id -> Uuid,
@ -486,7 +510,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
roles (id) {
id -> Text,
@ -496,7 +520,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
roles_permissions (role_permission_id) {
role_id -> Text,
@ -507,7 +531,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
roles_permissions_context (role_permission_id, entity) {
role_permission_id -> Uuid,
@ -517,7 +541,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
units (unit_id) {
unit_id -> Uuid,
@ -527,7 +551,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
units_members (unit_id, member_id) {
unit_id -> Uuid,
@ -538,7 +562,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
users (id) {
id -> Uuid,
@ -551,7 +575,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
vehicle_categories (id) {
id -> Uuid,
@ -562,7 +586,7 @@ table! {
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
vehicles (entity_id) {
entity_id -> Uuid,
@ -652,6 +676,8 @@ allow_tables_to_appear_in_same_query!(
groups_entities,
license_categories,
licenses_members,
log,
log_actions,
login_attempts,
login_attempts_usernames,
members,