297 lines
12 KiB
Rust
297 lines
12 KiB
Rust
use std::sync::Arc;
|
|
|
|
use chrono::NaiveDateTime;
|
|
use lettre::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;
|
|
use crate::database::controller::events::{change_event, finish_billing, get_event};
|
|
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::time::get_timezone;
|
|
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};
|
|
use crate::modules::api::model::api_outcome::{ApiError, ApiErrorWrapper};
|
|
use crate::modules::event_billing::generate_billing_csv::generate_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(_) => {
|
|
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));
|
|
}
|
|
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 mut msg = Message::builder()
|
|
.from(settings.mail.from.clone().parse().unwrap())
|
|
.reply_to(settings.mail.reply_to.clone().parse().unwrap());
|
|
for receiver in settings.billing.personnel_billing_emails.split(","){
|
|
match receiver.parse(){
|
|
Ok(receiver) => {
|
|
msg = msg.to(receiver)
|
|
},
|
|
Err(e) => {
|
|
error!("Couldn't parse settings.billing.personnel_billing_emails email {}: {}", receiver, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
let event = get_event(settings, event_id).unwrap();
|
|
let msg = msg.subject(format!("Einsatzabrechnung: {} - {}", event.name, event.start.format("%d.%m.%Y").to_string()))
|
|
.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))
|
|
}
|
|
} |