EinsatzOnline/src/modules/api/events/update.rs

279 lines
12 KiB
Rust

use std::sync::Arc;
use chrono::NaiveDateTime;
use diesel::expression::ops::Mul;
use lettre::{AsyncTransport, Message};
use lettre::message::{Attachment, MultiPart, SinglePart};
use lettre::message::header::ContentType;
use rocket::serde::json::Json;
use rocket::State;
use crate::database::controller::billing::states::{get_billing_state, get_billing_states};
use crate::database::controller::events::{change_event, finish_billing, get_event};
use crate::database::controller::events::instances::instance_positions::RawPositionInstanceChangeset;
use crate::database::controller::events::instances::instances::get_instances;
use crate::database::controller::members::check_access_to_resource;
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::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};
use crate::modules::api::model::api_outcome::{ApiError, ApiErrorWrapper};
use crate::modules::event_billing::generate_billing_csv::{generate_billing_csv, save_billing_csv};
#[put("/api/events/<entity_id>", format = "json", data = "<update_event_data>")]
pub fn update_event(
settings: &State<Settings>,
cookie: SessionCookie,
entity_id: String,
update_event_data: Json<CreateEventData>,
) -> Result<Json<Event>, Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
//TODO: Check if caller is member_responsible -> if so skip this check
if !caller.has_permission(crate::permissions::modules::event_management::events::EDIT.to_string()) {
return Err(Json(
ApiError::new(403, "Keine Berechtigung Einsatz zu bearbeiten!".to_string()).to_wrapper(),
));
}
let ecd = update_event_data.into_inner();
let start = match NaiveDateTime::parse_from_str(&ecd.start, "%Y-%m-%dT%H:%M") {
Ok(start) => start,
Err(e) => {
error!("Couldn't parse start datetime: {}", e);
return Err(Json(ApiError::new(400, "Das eingegebene Datum konnte nicht verarbeitet werden.".to_string()).to_wrapper()))
}
};
let end = match NaiveDateTime::parse_from_str(&ecd.end, "%Y-%m-%dT%H:%M") {
Ok(start) => start,
Err(e) => {
error!("Couldn't parse end datetime: {}", e);
return Err(Json(ApiError::new(400, "Das eingegebene Datum konnte nicht verarbeitet werden.".to_string()).to_wrapper()))
}
};
let state = match ecd.state {
Some(state) => state,
None => 0,
};
//TODO: Remove this parse_option_uuid crap. Just use rocket + serde deserialisation by replacing string to proper type in CreateEventData
//TODO: related_request
let input: Event = Event {
entity_id: parse_uuid_string(entity_id)?,
name: ecd.name,
start,
end,
site: ecd.site,
organiser_id: parse_option_uuid(ecd.organiser_id)?,
etype: parse_option_uuid(ecd.etype)?,
contact_on_site_name: ecd.contact_on_site_name,
contact_on_site_phone: ecd.contact_on_site_phone,
member_responsible: parse_option_uuid(ecd.member_responsible)?,
related_group: parse_option_uuid(ecd.related_group)?,
other: ecd.other,
other_intern: ecd.other_intern,
related_request: None,
state,
};
match change_event(settings, input) {
Ok(event) => Ok(Json(event)),
Err(e) => Err(translate_diesel(e))
}
}
#[put("/api/events/<entity_id>/close", format = "json")]
pub fn close_event(
settings: &State<Settings>,
cookie: SessionCookie,
entity_id: String,
) -> Result<(), Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
let event_id = parse_uuid_string(entity_id)?;
let event = match get_event(settings, event_id) {
Ok(event) => event,
Err(e) => {
warn!("{} tried to close event with invalid event_id {}: {}", caller.entity_id, event_id, e);
return Err(Json(ApiError::new(404, "No event with this entity_id found.".to_string()).to_wrapper()));
}
};
if !caller.has_permission(crate::permissions::modules::event_management::events::EDIT.to_string()) {
match event.member_responsible {
Some(resp) => {
if caller.entity_id != resp {
warn!("No permissions to close event for member {}", caller.entity_id);
return Err(Json(ApiError::new(403, "No permissions to edit event.".to_string()).to_wrapper()));
}
}
None => {
warn!("No permissions to close event for member {}", caller.entity_id);
return Err(Json(ApiError::new(403, "No permissions to edit event.".to_string()).to_wrapper()));
}
}
}
match crate::database::controller::events::close_event(settings, event_id) {
Ok(_) => Ok(()),
Err(e) => Err(translate_diesel(e))
}
}
#[put("/api/events/<entity_id>/approve_times", format = "json")]
pub fn approve_times(
settings: &State<Settings>,
cookie: SessionCookie,
entity_id: String,
) -> Result<(), Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
let event_id = parse_uuid_string(entity_id)?;
let event = match get_event(settings, event_id) {
Ok(event) => event,
Err(e) => {
warn!("{} tried to close event with invalid event_id {}: {}", caller.entity_id, event_id, e);
return Err(Json(ApiError::new(404, "No event with this entity_id found.".to_string()).to_wrapper()));
}
};
if !caller.has_permission(crate::permissions::modules::event_billing::start_end_times::EDIT.to_string()) {
match event.member_responsible {
Some(resp) => {
if caller.entity_id != resp {
warn!("No permissions to approve times for member {}", caller.entity_id);
return Err(Json(ApiError::new(403, "No permissions to edit event.".to_string()).to_wrapper()));
}
}
None => {
warn!("No permissions to approve times for member {}", caller.entity_id);
return Err(Json(ApiError::new(403, "No permissions to edit event.".to_string()).to_wrapper()));
}
}
}
//Set missing real_start_time and/or real_end_time to planned_start_time/planned_end_time for all instances with event_id
match crate::database::controller::events::instances::instances::set_missing_real_times_to_planned_times(settings, event_id) {
Ok(_) => (),
Err(_) => {
return Err(Json(ApiError::new(500, "Couldn't set missing real times to planned times for instances.".to_string()).to_wrapper()));
}
}
//Set missing real_start_time and/or real_end_time for all position_instances to the real_start_time/real_end_time of their related instance
match crate::database::controller::events::instances::instance_positions::set_missing_real_times_to_instance_times(settings, event_id) {
Ok(_) => (),
Err(_) => {
return Err(Json(ApiError::new(500, "Couldn't set missing real times to planned times for position_instances.".to_string()).to_wrapper()));
}
}
//TODO: remove empty position instances
match crate::database::controller::events::approve_event_times(settings, event_id) {
Ok(_) => Ok(()),
Err(e) => Err(translate_diesel(e))
}
}
#[put("/api/events/<entity_id>/finish_personnel_billing", format = "json")]
pub fn finish_personnel_billing(
settings: &State<Settings>,
cookie: SessionCookie,
entity_id: String,
) -> Result<(), Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
let event_id = parse_uuid_string(entity_id)?;
let event = match get_event(settings, event_id) {
Ok(event) => event,
Err(e) => {
warn!("{} tried to close event with invalid event_id {}: {}", caller.entity_id, event_id, e);
return Err(Json(ApiError::new(404, "No event with this entity_id found.".to_string()).to_wrapper()));
}
};
if !caller.has_permission(crate::permissions::modules::event_billing::personnel::EDIT.to_string()) {
match event.member_responsible {
Some(resp) => {
if caller.entity_id != resp {
warn!("No permissions to edit event billing for member {}", caller.entity_id);
return Err(Json(ApiError::new(403, "No permissions to edit event billing.".to_string()).to_wrapper()));
}
}
None => {
warn!("No permissions to edit event billing for member {}", caller.entity_id);
return Err(Json(ApiError::new(403, "No permissions to edit event billing.".to_string()).to_wrapper()));
}
}
}
match crate::database::controller::events::finish_personnel_billing(settings, event_id) {
Ok(_) => Ok(()),
Err(e) => Err(translate_diesel(e))
}
}
#[put("/api/events/<entity_id>/approve?<stage>", format = "json")]
pub async fn approve(
settings: &State<Settings>,
cookie: SessionCookie,
entity_id: String,
stage: String,
mq: &State<Arc<MailQueue>>,
) -> Result<(), Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
let event_id = parse_uuid_string(entity_id)?;
let stage = parse_uuid_string(stage)?;
let billing_state = match get_billing_state(settings, stage) {
Ok(state) => state,
Err(e) => {
warn!("Couldn't find stage: {}", e);
return Err(Json(ApiError::new(404, "Stage not found.".to_string()).to_wrapper()));
}
};
if !check_access_to_resource(settings, caller.entity_id, stage, crate::permissions::modules::event_billing::APPROVE) {
return Err(Json(ApiError::new(403, "No permission to approve this billing state.".to_string()).to_wrapper()));
}
match crate::database::controller::events::approve_stage(settings, event_id, stage, caller.entity_id) {
Ok(_) => {
if billing_state.final_approve {
if let Err(e) = finish_billing(settings, event_id) {
return Err(translate_diesel(e));
}
let csv = match generate_billing_csv(settings, event_id) {
Ok(csv) => csv,
Err(e) => {
error!("Error generating CSV: {}", e); //TODO: report failure to user
return Ok(())
}
};
debug!("Generated CSV: {}", csv);
if settings.billing.send_personnel_billing_to_email {
let attachement = Attachment::new(String::from("Einsatzabrechnung.txt")).body(csv, ContentType::parse("text/csv").unwrap());
let msg = Message::builder()
.from(settings.mail.from.clone().parse().unwrap())
.reply_to(settings.mail.reply_to.clone().parse().unwrap())
.to(settings.billing.personnel_billing_email.parse().unwrap())
.subject("Einsatzabrechnung")
.multipart(MultiPart::mixed().singlepart(attachement).singlepart(SinglePart::plain(String::from("Es wurde eine Einsatzabrechnung freigegeben. Sie befindet sich im Anhang dieser E-Mail.")))).unwrap();
mq.add_mail(msg);
}
}
Ok(())
},
Err(e) => Err(translate_diesel(e))
}
}