449 lines
18 KiB
Rust
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))
|
|
}
|
|
} |