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

449 lines
18 KiB
Rust

use std::convert::TryInto;
use rocket::serde::json::Json;
use rocket::State;
use crate::database::controller::events::{change_position_instances, get_event, get_position_instance};
use crate::database::controller::events::instances::instance_positions::RawPositionInstanceChangeset;
use crate::database::controller::events::instances::instances::{get_instance, get_instances, RawEventUnitInstanceChangeset, update_instance};
use crate::helper::serde_patch::Patch;
use crate::helper::session_cookies::model::SessionCookie;
use crate::helper::settings::Settings;
use crate::helper::time::datetime_str_to_utc;
use crate::helper::translate_diesel_error::translate_diesel;
use crate::modules::api::events::instances::read::{EventUnitInstance, PositionInstance};
use crate::modules::api::member_management::controller::parser::{parse_member_cookie, parse_uuid_string};
use crate::modules::api::model::api_outcome::{ApiError, ApiErrorWrapper};
use crate::modules::event_management::check_position_requirements::{check_position_requirements, RequirementParserError};
#[put("/api/entities/<entity_id>/position_instances/<position_instance>", format = "json", rank = 1)]
pub fn put_entity_in_position(
settings: &State<Settings>,
cookie: SessionCookie,
position_instance: String,
entity_id: String,
) -> Result<Json<usize>, Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
let position_instance = parse_uuid_string(position_instance)?;
let entity_id = parse_uuid_string(entity_id)?;
let position_instance_data = match get_position_instance(settings, position_instance){
Ok(res) => res,
Err(e) => {
error!("Couldn't retrieve position instance: {}", e);
return Err(Json(ApiError::new(404, "position instance not found!".to_string()).to_wrapper()));
}
};
let instance = match get_instance(settings, position_instance_data.instance_id) {
Ok(instance) => instance,
Err(e) =>
{
warn!("Instance not found: {}", e);
return Err(Json(ApiError::new(404, "instance not found!".to_string()).to_wrapper()));
}
};
let event = match get_event(settings, instance.event_id) {
Ok(event) => event,
Err(e) => {
warn!("Event not found: {}", e);
return Err(Json(
ApiError::new(404, "event for instance not found!".to_string()).to_wrapper()))
}
};
if event.state >= 4 {
return Err(Json(
ApiError::new(400, "Dieser Einsatz wurde bereits geschlossen!".to_string()).to_wrapper()))
}
if caller.entity_id == entity_id {
match check_position_requirements(settings, position_instance_data.position_id, entity_id) {
Ok(res) => {
if !res { //if member tries to add himself to a position, but don't fulfill the position requirements AND don't have the event edit permission (overwrite), abort
if !caller.has_permission(crate::permissions::modules::event_management::events::EDIT.to_string()) {
return Err(Json(
ApiError::new(403, "Keine Berechtigung Einsätze zu bearbeiten!".to_string()).to_wrapper(),
));
}
}
}
Err(e) => {
match e{
RequirementParserError::Database(e) => {
return Err(translate_diesel(e))
}
RequirementParserError::Parser(e) => {
error!("Couldn't parse requirements: {}", e.as_str());
return Err(Json(ApiError::new(400, "invalid json for requirements".to_string()).to_wrapper()))
}
}
}
}
} else if !caller.has_permission(crate::permissions::modules::event_management::events::EDIT.to_string()) {
return Err(Json(
ApiError::new(403, "Keine Berechtigung Einsätze zu bearbeiten!".to_string()).to_wrapper(),
));
}
match change_position_instances(settings, position_instance, Some(entity_id)) {
Ok(pos) => Ok(Json(pos)),
Err(e) => return Err(translate_diesel(e))
}
}
#[delete("/api/position_instances/<position_instance>/taken_by", format = "json", rank = 1)]
pub fn remove_entity_from_position(
settings: &State<Settings>,
cookie: SessionCookie,
position_instance: String,
) -> Result<Json<usize>, Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
if !caller.has_permission(crate::permissions::modules::event_management::events::EDIT.to_string()) {
return Err(Json(
ApiError::new(403, "Keine Berechtigung Einsätze zu bearbeiten!".to_string()).to_wrapper(),
));
}
let position_instance = parse_uuid_string(position_instance)?;
let position_instance_data = match get_position_instance(settings, position_instance){
Ok(res) => res,
Err(e) => {
error!("Couldn't retrieve position instance: {}", e);
return Err(Json(ApiError::new(404, "position instance not found!".to_string()).to_wrapper()));
}
};
let instance = match get_instance(settings, position_instance_data.instance_id) {
Ok(instance) => instance,
Err(e) =>
{
warn!("Instance not found: {}", e);
return Err(Json(ApiError::new(404, "instance not found!".to_string()).to_wrapper()));
}
};
let event = match get_event(settings, instance.event_id) {
Ok(event) => event,
Err(e) => {
warn!("Event not found: {}", e);
return Err(Json(
ApiError::new(404, "event for instance not found!".to_string()).to_wrapper()))
}
};
if event.state >= 4 {
return Err(Json(
ApiError::new(400, "Dieser Einsatz wurde bereits geschlossen!".to_string()).to_wrapper()))
}
match change_position_instances(settings, position_instance, None) {
Ok(pos) => Ok(Json(pos)),
Err(e) => return Err(translate_diesel(e))
}
}
#[derive(Queryable, Deserialize)]
pub struct PatchInstanceData {
pub instance_id: uuid::Uuid,
#[serde(default)]
pub name: Patch<String>,
#[serde(default)]
pub planned_start_time: Patch<String>,
#[serde(default)]
pub planned_end_time: Patch<String>,
#[serde(default)]
pub real_start_time: Patch<String>,
#[serde(default)]
pub real_end_time: Patch<String>,
#[serde(default)]
pub billing_rate_id: Patch<uuid::Uuid>,
#[serde(default)]
pub billing_state_id: Patch<uuid::Uuid>,
}
#[patch("/api/events/<event_id>/instances/<instance_id>", format = "json", data = "<patch_instance_data>")]
pub fn patch_instance(
settings: &State<Settings>,
cookie: SessionCookie,
patch_instance_data: Json<PatchInstanceData>,
event_id: String,
instance_id: String,
) -> Result<Json<EventUnitInstance>, Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member.clone())?;
if !caller.has_permission(crate::permissions::modules::event_management::events::EDIT.to_string()) {
return Err(Json(
ApiError::new(403, "Keine Berechtigung Einsätze zu bearbeiten!".to_string()).to_wrapper(),
));
}
let pid = patch_instance_data.into_inner();
let event_id = parse_uuid_string(event_id)?;
let instance_id = parse_uuid_string(instance_id)?;
if instance_id != pid.instance_id {
return Err(Json(ApiError::new(400, "instance_id in URI doesn't match instance_id in body data.".to_string()).to_wrapper()))
}
let name = match pid.name.try_into() {
Ok(name) => name,
Err(_) => return Err(Json(ApiError::new(400, "Cannot set name to null!".to_string()).to_wrapper()))
};
let event = match get_event(settings, event_id) {
Ok(event) => event,
Err(e) => return Err(translate_diesel(e))
};
//Check if real_start_time or real_end_time is changed
if matches!(pid.real_start_time, Patch::Value(_)) || matches!(pid.real_start_time, Patch::Null) || matches!(pid.real_end_time, Patch::Value(_)) || matches!(pid.real_end_time, Patch::Null) {
//If so, check for additional permissions
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 {
return Err(Json(
ApiError::new(403, "Keine Berechtigung Einsatzzeiten zu verändern!".to_string()).to_wrapper(),
));
}
}
None => {
return Err(Json(
ApiError::new(403, "Keine Berechtigung Einsatzzeiten zu verändern!".to_string()).to_wrapper(),
));
}
}
}
}
if matches!(pid.billing_rate_id, Patch::Value(_)) || matches!(pid.billing_rate_id, Patch::Null) || matches!(pid.billing_state_id, Patch::Value(_)) || matches!(pid.billing_state_id, Patch::Null) {
if !caller.has_permission(crate::permissions::modules::event_billing::personnel::EDIT.to_string()) {
match event.member_responsible {
Some(resp) => {
if caller.entity_id != resp {
return Err(Json(
ApiError::new(403, "Keine Berechtigung Abrechnung zu verändern!".to_string()).to_wrapper(),
));
}
}
None => {
return Err(Json(
ApiError::new(403, "Keine Berechtigung Abrechnung zu verändern!".to_string()).to_wrapper(),
));
}
}
}
}
let planned_start_time = match pid.planned_start_time {
Patch::Missing => None,
Patch::Null => Some(None),
Patch::Value(v) => {
match datetime_str_to_utc(settings, Some(&cookie), &v) {
Ok(v) => Some(Some(v)),
Err(e) => {
warn!("Couldn't parse planned start time as DateTime: {}",e);
return Err(Json(
ApiError::new(400, "Couldn't parse planned start time!".to_string()).to_wrapper(),
))
}
}
}
};
let planned_end_time = match pid.planned_end_time {
Patch::Missing => None,
Patch::Null => Some(None),
Patch::Value(v) => {
match datetime_str_to_utc(settings, Some(&cookie), &v) {
Ok(v) => Some(Some(v)),
Err(e) => {
warn!("Couldn't parse planned end time as DateTime: {}",e);
return Err(Json(
ApiError::new(400, "Couldn't parse planned end time!".to_string()).to_wrapper(),
))
}
}
}
};
let real_start_time = match pid.real_start_time {
Patch::Missing => None,
Patch::Null => Some(None),
Patch::Value(v) => {
match datetime_str_to_utc(settings, Some(&cookie), &v) {
Ok(v) => Some(Some(v)),
Err(e) => {
warn!("Couldn't parse real start time as DateTime: {}",e);
return Err(Json(
ApiError::new(400, "Couldn't parse real start time!".to_string()).to_wrapper(),
))
}
}
}
};
let real_end_time = match pid.real_end_time {
Patch::Missing => None,
Patch::Null => Some(None),
Patch::Value(v) => {
match datetime_str_to_utc(settings, Some(&cookie), &v) {
Ok(v) => Some(Some(v)),
Err(e) => {
warn!("Couldn't parse real end time as DateTime: {}",e);
return Err(Json(
ApiError::new(400, "Couldn't parse real end time!".to_string()).to_wrapper(),
))
}
}
}
};
let data = RawEventUnitInstanceChangeset {
instance_id,
event_id,
name,
planned_start_time,
planned_end_time,
real_start_time,
real_end_time,
billing_rate_id: pid.billing_rate_id.into(),
billing_state_id: pid.billing_state_id.into(),
};
match update_instance(settings, data) {
Ok(res) => Ok(Json(EventUnitInstance::from_raw(res, settings, &cookie))),
Err(e) => return Err(translate_diesel(e))
}
}
#[derive(Queryable, Deserialize)]
pub struct PatchPositionInstanceData {
#[serde(default)]
pub(crate) instance_id: Patch<uuid::Uuid>,
#[serde(default)]
pub(crate) position_id: Patch<uuid::Uuid>,
#[serde(default)]
pub(crate) taken_by: Patch<uuid::Uuid>,
#[serde(default)]
pub(crate) position_instance_id: uuid::Uuid,
#[serde(default)]
pub(crate) real_start_time: Patch<String>,
#[serde(default)]
pub(crate) real_end_time: Patch<String>,
#[serde(default)]
pub(crate) billable: Patch<bool>,
}
#[patch("/api/events/position_instances/<position_instance_id>", format = "json", data = "<patch_position_instance_data>")]
pub fn patch_position_instance(
settings: &State<Settings>,
cookie: SessionCookie,
patch_position_instance_data: Json<PatchPositionInstanceData>,
position_instance_id: String,
) -> Result<Json<PositionInstance>, Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member.clone())?;
if !caller.has_permission(crate::permissions::modules::event_management::events::EDIT.to_string()) {
return Err(Json(
ApiError::new(403, "Keine Berechtigung Einsätze zu bearbeiten!".to_string()).to_wrapper(),
));
}
let pid = patch_position_instance_data.into_inner();
let position_instance_id = parse_uuid_string(position_instance_id)?;
if position_instance_id != pid.position_instance_id {
return Err(Json(ApiError::new(400, "position_instance_id in URI doesn't match position_instance_id in body data.".to_string()).to_wrapper()))
}
let instance_id = match pid.instance_id.try_into() {
Ok(res) => res,
Err(_) => {
return Err(Json(ApiError::new(400, "Cannot set instance_id to null!".to_string()).to_wrapper()))
}
};
let position_id = match pid.position_id.try_into() {
Ok(res) => res,
Err(_) => return Err(Json(ApiError::new(400, "Cannot set position_id to null!".to_string()).to_wrapper()))
};
let real_start_time = match pid.real_start_time {
Patch::Missing => None,
Patch::Null => Some(None),
Patch::Value(v) => {
match datetime_str_to_utc(settings, Some(&cookie), &v) {
Ok(v) => Some(Some(v)),
Err(e) => {
warn!("Couldn't parse real start time as DateTime: {}",e);
return Err(Json(
ApiError::new(400, "Couldn't parse real start time!".to_string()).to_wrapper(),
))
}
}
}
};
let real_end_time = match pid.real_end_time {
Patch::Missing => None,
Patch::Null => Some(None),
Patch::Value(v) => {
match datetime_str_to_utc(settings, Some(&cookie), &v) {
Ok(end) => {
match real_start_time {
Some(start) => match start {
Some(start) => {
//start and end time are set, check if start is after end
if start.ge(&end) {
return Err(Json(ApiError::new(400, "Start time can't be later than the end time!".to_string()).to_wrapper()))
}
},
None => {}
},
None => {}
}
Some(Some(end))
},
Err(e) => {
warn!("Couldn't parse real end time as DateTime: {}",e);
return Err(Json(
ApiError::new(400, "Couldn't parse real end time!".to_string()).to_wrapper(),
))
}
}
}
};
let billable = pid.billable.into();
if let Some(is_bill) = billable {
if let Some(is_bill2) = is_bill {
if !is_bill2 {
match crate::database::controller::billing::personnel_billing::remove(settings, position_instance_id) {
Ok(_) => {},
Err(e) => {
error!("Couldn't delete personnel_billing entries left after billable was set to false: {}", e);
return Err(translate_diesel(e))
}
}
}
}
}
let data = RawPositionInstanceChangeset {
instance_id,
position_id,
taken_by: pid.taken_by.into(),
position_instance_id,
real_start_time,
real_end_time,
billable,
};
match crate::database::controller::events::instances::instance_positions::update(settings, data) {
Ok(res) => {
match PositionInstance::from_raw(&res, settings, &cookie) {
Ok(res) => Ok(Json(res)),
Err(_) => return Err(Json(ApiError::new(500, "Couldn't retrieve changed PositionInstance!".to_string()).to_wrapper())),
}
},
Err(e) => return Err(translate_diesel(e))
}
}