From c8f28e1f7bf0c0b5d0ef5915e260cdcd9d650aa4 Mon Sep 17 00:00:00 2001 From: Simon Goller Date: Mon, 24 Jun 2024 08:31:47 +0200 Subject: [PATCH] Add endpoint to add extra_hours --- app/src/main.rs | 24 ++++++- dao/src/extra_hours.rs | 2 + dao_impl/src/extra_hours.rs | 55 ++++++++++++--- rest-types/src/lib.rs | 82 ++++++++++++++++++++++- rest/src/extra_hours.rs | 34 ++++++++++ rest/src/lib.rs | 7 ++ service/src/extra_hours.rs | 47 ++++++++++--- service_impl/src/extra_hours.rs | 111 +++++++++++++++++++++++++++++++ service_impl/src/lib.rs | 1 + service_impl/src/reporting.rs | 1 - service_impl/src/sales_person.rs | 9 --- 11 files changed, 335 insertions(+), 38 deletions(-) create mode 100644 rest/src/extra_hours.rs create mode 100644 service_impl/src/extra_hours.rs diff --git a/app/src/main.rs b/app/src/main.rs index 1c60d58..5d43308 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -49,6 +49,12 @@ type WorkingHoursService = service_impl::working_hours::WorkingHoursServiceImpl< ClockService, UuidService, >; +type ExtraHoursService = service_impl::extra_hours::ExtraHoursServiceImpl< + dao_impl::extra_hours::ExtraHoursDaoImpl, + PermissionService, + ClockService, + UuidService, +>; #[derive(Clone)] pub struct RestStateImpl { @@ -59,6 +65,7 @@ pub struct RestStateImpl { booking_service: Arc, reporting_service: Arc, working_hours_service: Arc, + extra_hours_service: Arc, } impl rest::RestStateDef for RestStateImpl { type UserService = UserService; @@ -68,6 +75,7 @@ impl rest::RestStateDef for RestStateImpl { type BookingService = BookingService; type ReportingService = ReportingService; type WorkingHoursService = WorkingHoursService; + type ExtraHoursService = ExtraHoursService; fn user_service(&self) -> Arc { self.user_service.clone() @@ -90,6 +98,9 @@ impl rest::RestStateDef for RestStateImpl { fn working_hours_service(&self) -> Arc { self.working_hours_service.clone() } + fn extra_hours_service(&self) -> Arc { + self.extra_hours_service.clone() + } } impl RestStateImpl { pub fn new(pool: Arc>) -> Self { @@ -140,7 +151,7 @@ impl RestStateImpl { slot_service.clone(), )); let reporting_service = Arc::new(service_impl::reporting::ReportingServiceImpl::new( - extra_hours_dao, + extra_hours_dao.clone(), shiftplan_report_dao, working_hours_dao.clone(), sales_person_service.clone(), @@ -152,9 +163,15 @@ impl RestStateImpl { Arc::new(service_impl::working_hours::WorkingHoursServiceImpl::new( working_hours_dao, permission_service.clone(), - clock_service, - uuid_service, + clock_service.clone(), + uuid_service.clone(), )); + let extra_hours_service = Arc::new(service_impl::extra_hours::ExtraHoursServiceImpl::new( + extra_hours_dao, + permission_service.clone(), + clock_service, + uuid_service, + )); Self { user_service, permission_service, @@ -163,6 +180,7 @@ impl RestStateImpl { booking_service, reporting_service, working_hours_service, + extra_hours_service, } } } diff --git a/dao/src/extra_hours.rs b/dao/src/extra_hours.rs index 448242c..38a262f 100644 --- a/dao/src/extra_hours.rs +++ b/dao/src/extra_hours.rs @@ -20,7 +20,9 @@ pub struct ExtraHoursEntity { pub category: ExtraHoursCategoryEntity, pub description: Arc, pub date_time: time::PrimitiveDateTime, + pub created: time::PrimitiveDateTime, pub deleted: Option, + pub version: Uuid, } #[automock] diff --git a/dao_impl/src/extra_hours.rs b/dao_impl/src/extra_hours.rs index 063d9e3..6fe22e9 100644 --- a/dao_impl/src/extra_hours.rs +++ b/dao_impl/src/extra_hours.rs @@ -6,7 +6,7 @@ use dao::{ extra_hours::{ExtraHoursCategoryEntity, ExtraHoursDao, ExtraHoursEntity}, DaoError, }; -use sqlx::query_as; +use sqlx::{query, query_as}; use time::{format_description::well_known::Iso8601, PrimitiveDateTime}; use uuid::Uuid; @@ -18,15 +18,17 @@ struct ExtraHoursDb { category: String, description: Option, date_time: String, + created: String, deleted: Option, + update_version: Vec, } impl TryFrom<&ExtraHoursDb> for ExtraHoursEntity { type Error = DaoError; fn try_from(extra_hours: &ExtraHoursDb) -> Result { Ok(Self { - id: Uuid::from_slice(extra_hours.id.as_ref()).unwrap(), - sales_person_id: Uuid::from_slice(extra_hours.sales_person_id.as_ref()).unwrap(), + id: Uuid::from_slice(extra_hours.id.as_ref())?, + sales_person_id: Uuid::from_slice(extra_hours.sales_person_id.as_ref())?, amount: extra_hours.amount as f32, category: match extra_hours.category.as_str() { "ExtraWork" => ExtraHoursCategoryEntity::ExtraWork, @@ -44,14 +46,14 @@ impl TryFrom<&ExtraHoursDb> for ExtraHoursEntity { date_time: PrimitiveDateTime::parse( extra_hours.date_time.as_str(), &Iso8601::DATE_TIME, - ) - .unwrap(), + )?, + created: PrimitiveDateTime::parse(extra_hours.created.as_str(), &Iso8601::DATE_TIME)?, deleted: extra_hours .deleted .as_ref() .map(|deleted| PrimitiveDateTime::parse(deleted, &Iso8601::DATE_TIME)) - .transpose() - .unwrap(), + .transpose()?, + version: Uuid::from_slice(&extra_hours.update_version)?, }) } } @@ -76,7 +78,7 @@ impl ExtraHoursDao for ExtraHoursDaoImpl { let id_vec = sales_person_id.as_bytes().to_vec(); Ok(query_as!( ExtraHoursDb, - "SELECT id, sales_person_id, amount, category, description, date_time, deleted FROM extra_hours WHERE sales_person_id = ? AND strftime('%Y', date_time) = ? AND strftime('%m', date_time) <= ?", + "SELECT id, sales_person_id, amount, category, description, date_time, created, deleted, update_version FROM extra_hours WHERE sales_person_id = ? AND CAST(strftime('%Y', date_time) AS INTEGER) = ? AND CAST(strftime('%m', date_time) AS INTEGER) <= ?", id_vec, year, until_week, @@ -90,10 +92,41 @@ impl ExtraHoursDao for ExtraHoursDaoImpl { } async fn create( &self, - _entity: &ExtraHoursEntity, - _process: &str, + entity: &ExtraHoursEntity, + process: &str, ) -> Result<(), crate::DaoError> { - unimplemented!() + let id_vec = entity.id.as_bytes().to_vec(); + let sales_person_id_vec = entity.sales_person_id.as_bytes().to_vec(); + let category = match entity.category { + ExtraHoursCategoryEntity::ExtraWork => "ExtraWork", + ExtraHoursCategoryEntity::Vacation => "Vacation", + ExtraHoursCategoryEntity::SickLeave => "SickLeave", + ExtraHoursCategoryEntity::Holiday => "Holiday", + }; + let description = entity.description.as_ref(); + let date_time = entity.date_time.format(&Iso8601::DATE_TIME)?; + let created = entity.created.format(&Iso8601::DATE_TIME)?; + let deleted = entity + .deleted + .map(|deleted| deleted.format(&Iso8601::DATE_TIME)) + .transpose()?; + let version_vec = entity.version.as_bytes().to_vec(); + query!( + "INSERT INTO extra_hours (id, sales_person_id, amount, category, description, date_time, created, deleted, update_process, update_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + id_vec, + sales_person_id_vec, + entity.amount, + category, + description, + date_time, + created, + deleted, + process, + version_vec, + ).execute(self.pool.as_ref()) + .await + .map_db_error()?; + Ok(()) } async fn update( &self, diff --git a/rest-types/src/lib.rs b/rest-types/src/lib.rs index b8895e9..018a9be 100644 --- a/rest-types/src/lib.rs +++ b/rest-types/src/lib.rs @@ -237,14 +237,14 @@ impl From<&service::reporting::ShortEmployeeReport> for ShortEmployeeReportTO { } } #[derive(Debug, Serialize, Deserialize)] -pub enum ExtraHoursCategoryTO { +pub enum ExtraHoursReportCategoryTO { Shiftplan, ExtraWork, Vacation, SickLeave, Holiday, } -impl From<&service::reporting::ExtraHoursCategory> for ExtraHoursCategoryTO { +impl From<&service::reporting::ExtraHoursCategory> for ExtraHoursReportCategoryTO { fn from(category: &service::reporting::ExtraHoursCategory) -> Self { match category { service::reporting::ExtraHoursCategory::Shiftplan => Self::Shiftplan, @@ -260,7 +260,7 @@ impl From<&service::reporting::ExtraHoursCategory> for ExtraHoursCategoryTO { pub struct WorkingHoursDayTO { pub date: time::Date, pub hours: f32, - pub category: ExtraHoursCategoryTO, + pub category: ExtraHoursReportCategoryTO, } impl From<&service::reporting::WorkingHoursDay> for WorkingHoursDayTO { fn from(day: &service::reporting::WorkingHoursDay) -> Self { @@ -409,3 +409,79 @@ impl From<&WorkingHoursTO> for service::working_hours::WorkingHours { } } } + +#[derive(Debug, Serialize, Deserialize)] +pub enum ExtraHoursCategoryTO { + ExtraWork, + Vacation, + SickLeave, + Holiday, +} +impl From<&service::extra_hours::ExtraHoursCategory> for ExtraHoursCategoryTO { + fn from(category: &service::extra_hours::ExtraHoursCategory) -> Self { + match category { + service::extra_hours::ExtraHoursCategory::ExtraWork => Self::ExtraWork, + service::extra_hours::ExtraHoursCategory::Vacation => Self::Vacation, + service::extra_hours::ExtraHoursCategory::SickLeave => Self::SickLeave, + service::extra_hours::ExtraHoursCategory::Holiday => Self::Holiday, + } + } +} +impl From<&ExtraHoursCategoryTO> for service::extra_hours::ExtraHoursCategory { + fn from(category: &ExtraHoursCategoryTO) -> Self { + match category { + ExtraHoursCategoryTO::ExtraWork => Self::ExtraWork, + ExtraHoursCategoryTO::Vacation => Self::Vacation, + ExtraHoursCategoryTO::SickLeave => Self::SickLeave, + ExtraHoursCategoryTO::Holiday => Self::Holiday, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ExtraHoursTO { + #[serde(default)] + pub id: Uuid, + pub sales_person_id: Uuid, + pub amount: f32, + pub category: ExtraHoursCategoryTO, + pub description: Arc, + pub date_time: time::PrimitiveDateTime, + #[serde(default)] + pub created: Option, + #[serde(default)] + pub deleted: Option, + #[serde(rename = "$version")] + #[serde(default)] + pub version: Uuid, +} +impl From<&service::extra_hours::ExtraHours> for ExtraHoursTO { + fn from(extra_hours: &service::extra_hours::ExtraHours) -> Self { + Self { + id: extra_hours.id, + sales_person_id: extra_hours.sales_person_id, + amount: extra_hours.amount, + category: (&extra_hours.category).into(), + description: extra_hours.description.clone(), + date_time: extra_hours.date_time, + created: extra_hours.created, + deleted: extra_hours.deleted, + version: extra_hours.version, + } + } +} +impl From<&ExtraHoursTO> for service::extra_hours::ExtraHours { + fn from(extra_hours: &ExtraHoursTO) -> Self { + Self { + id: extra_hours.id, + sales_person_id: extra_hours.sales_person_id, + amount: extra_hours.amount, + category: (&extra_hours.category).into(), + description: extra_hours.description.clone(), + date_time: extra_hours.date_time, + created: extra_hours.created, + deleted: extra_hours.deleted, + version: extra_hours.version, + } + } +} diff --git a/rest/src/extra_hours.rs b/rest/src/extra_hours.rs new file mode 100644 index 0000000..82d06cb --- /dev/null +++ b/rest/src/extra_hours.rs @@ -0,0 +1,34 @@ +use axum::{ + body::Body, extract::State, response::Response, routing::post, Extension, Json, Router, +}; +use rest_types::ExtraHoursTO; + +use service::extra_hours::ExtraHoursService; + +use crate::{error_handler, Context, RestStateDef}; + +pub fn generate_route() -> Router { + Router::new().route("/", post(create_extra_hours::)) +} + +pub async fn create_extra_hours( + rest_state: State, + Extension(context): Extension, + Json(sales_person): Json, +) -> Response { + error_handler( + (async { + let extra_hours = ExtraHoursTO::from( + &rest_state + .extra_hours_service() + .create(&(&sales_person).into(), context.into()) + .await?, + ); + Ok(Response::builder() + .status(200) + .body(Body::new(serde_json::to_string(&extra_hours).unwrap())) + .unwrap()) + }) + .await, + ) +} diff --git a/rest/src/lib.rs b/rest/src/lib.rs index 76427b3..c9d6628 100644 --- a/rest/src/lib.rs +++ b/rest/src/lib.rs @@ -1,6 +1,7 @@ use std::{convert::Infallible, sync::Arc}; mod booking; +mod extra_hours; mod permission; mod report; mod sales_person; @@ -231,6 +232,10 @@ pub trait RestStateDef: Clone + Send + Sync + 'static { + Send + Sync + 'static; + type ExtraHoursService: service::extra_hours::ExtraHoursService + + Send + + Sync + + 'static; fn user_service(&self) -> Arc; fn permission_service(&self) -> Arc; @@ -239,6 +244,7 @@ pub trait RestStateDef: Clone + Send + Sync + 'static { fn booking_service(&self) -> Arc; fn reporting_service(&self) -> Arc; fn working_hours_service(&self) -> Arc; + fn extra_hours_service(&self) -> Arc; } pub struct OidcConfig { @@ -344,6 +350,7 @@ pub async fn start_server(rest_state: RestState) { .nest("/booking", booking::generate_route()) .nest("/report", report::generate_route()) .nest("/working-hours", working_hours::generate_route()) + .nest("/extra-hours", extra_hours::generate_route()) .with_state(rest_state) .layer(middleware::from_fn(context_extractor)); diff --git a/service/src/extra_hours.rs b/service/src/extra_hours.rs index d431d6f..9ab6484 100644 --- a/service/src/extra_hours.rs +++ b/service/src/extra_hours.rs @@ -1,10 +1,11 @@ +use std::fmt::Debug; use std::sync::Arc; use async_trait::async_trait; use mockall::automock; use uuid::Uuid; -use dao::DaoError; +use crate::{permission::Authentication, ServiceError}; #[derive(Clone, Debug, PartialEq)] pub enum ExtraHoursCategory { @@ -42,7 +43,9 @@ pub struct ExtraHours { pub category: ExtraHoursCategory, pub description: Arc, pub date_time: time::PrimitiveDateTime, + pub created: Option, pub deleted: Option, + pub version: Uuid, } impl From<&dao::extra_hours::ExtraHoursEntity> for ExtraHours { fn from(extra_hours: &dao::extra_hours::ExtraHoursEntity) -> Self { @@ -53,34 +56,56 @@ impl From<&dao::extra_hours::ExtraHoursEntity> for ExtraHours { category: (&extra_hours.category).into(), description: extra_hours.description.clone(), date_time: extra_hours.date_time, + created: Some(extra_hours.created), deleted: extra_hours.deleted, + version: extra_hours.version, } } } -impl From<&ExtraHours> for dao::extra_hours::ExtraHoursEntity { - fn from(extra_hours: &ExtraHours) -> Self { - Self { +impl TryFrom<&ExtraHours> for dao::extra_hours::ExtraHoursEntity { + type Error = ServiceError; + fn try_from(extra_hours: &ExtraHours) -> Result { + Ok(Self { id: extra_hours.id, sales_person_id: extra_hours.sales_person_id, amount: extra_hours.amount, category: (&extra_hours.category).into(), description: extra_hours.description.clone(), date_time: extra_hours.date_time, + created: extra_hours + .created + .ok_or_else(|| ServiceError::InternalError)?, deleted: extra_hours.deleted, - } + version: extra_hours.version, + }) } } -#[automock] +#[automock(type Context=();)] #[async_trait] pub trait ExtraHoursService { - fn find_by_sales_person_id_and_year( + type Context: Clone + Debug + PartialEq + Eq + Send + Sync + 'static; + + async fn find_by_sales_person_id_and_year( &self, sales_person_id: Uuid, year: u32, until_week: u8, - ) -> Result, DaoError>; - fn create(&self, entity: &ExtraHours, process: &str) -> Result<(), DaoError>; - fn update(&self, entity: &ExtraHours, process: &str) -> Result<(), DaoError>; - fn delete(&self, id: Uuid, process: &str) -> Result<(), DaoError>; + context: Authentication, + ) -> Result, ServiceError>; + async fn create( + &self, + entity: &ExtraHours, + context: Authentication, + ) -> Result; + async fn update( + &self, + entity: &ExtraHours, + context: Authentication, + ) -> Result; + async fn delete( + &self, + id: Uuid, + context: Authentication, + ) -> Result; } diff --git a/service_impl/src/extra_hours.rs b/service_impl/src/extra_hours.rs new file mode 100644 index 0000000..82e6d1c --- /dev/null +++ b/service_impl/src/extra_hours.rs @@ -0,0 +1,111 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use dao::extra_hours; +use service::{ + extra_hours::ExtraHours, + permission::{Authentication, HR_PRIVILEGE}, + ServiceError, +}; +use uuid::Uuid; + +pub struct ExtraHoursServiceImpl< + ExtraHoursDao: dao::extra_hours::ExtraHoursDao, + PermissionService: service::PermissionService, + ClockService: service::clock::ClockService, + UuidService: service::uuid_service::UuidService, +> { + extra_hours_dao: Arc, + permission_service: Arc, + clock_service: Arc, + uuid_service: Arc, +} + +impl + ExtraHoursServiceImpl +where + ExtraHoursDao: dao::extra_hours::ExtraHoursDao + Sync + Send, + PermissionService: service::PermissionService + Sync + Send, + ClockService: service::clock::ClockService + Sync + Send, + UuidService: service::uuid_service::UuidService + Sync + Send, +{ + pub fn new( + extra_hours_dao: Arc, + permission_service: Arc, + clock_service: Arc, + uuid_service: Arc, + ) -> Self { + Self { + extra_hours_dao, + permission_service, + clock_service, + uuid_service, + } + } +} + +#[async_trait] +impl< + ExtraHoursDao: dao::extra_hours::ExtraHoursDao + Sync + Send, + PermissionService: service::PermissionService + Sync + Send, + ClockService: service::clock::ClockService + Sync + Send, + UuidService: service::uuid_service::UuidService + Sync + Send, + > service::extra_hours::ExtraHoursService + for ExtraHoursServiceImpl +{ + type Context = PermissionService::Context; + + async fn find_by_sales_person_id_and_year( + &self, + _sales_person_id: Uuid, + _year: u32, + _until_week: u8, + _context: Authentication, + ) -> Result, ServiceError> { + unimplemented!() + } + async fn create( + &self, + extra_hours: &ExtraHours, + context: Authentication, + ) -> Result { + self.permission_service + .check_permission(HR_PRIVILEGE, context) + .await?; + + let mut extra_hours = extra_hours.to_owned(); + if !extra_hours.id.is_nil() { + return Err(ServiceError::IdSetOnCreate); + } + if !extra_hours.version.is_nil() { + return Err(ServiceError::VersionSetOnCreate); + } + + extra_hours.id = self.uuid_service.new_uuid("extra_hours_service::create id"); + extra_hours.version = self + .uuid_service + .new_uuid("extra_hours_service::create version"); + extra_hours.created = Some(self.clock_service.date_time_now()); + + let extra_hours_entity = extra_hours::ExtraHoursEntity::try_from(&extra_hours)?; + self.extra_hours_dao + .create(&extra_hours_entity, "extra_hours_service::create") + .await?; + + Ok(extra_hours.into()) + } + async fn update( + &self, + _entity: &ExtraHours, + _context: Authentication, + ) -> Result { + unimplemented!() + } + async fn delete( + &self, + _id: Uuid, + _context: Authentication, + ) -> Result { + unimplemented!() + } +} diff --git a/service_impl/src/lib.rs b/service_impl/src/lib.rs index e7fd793..7e38ed7 100644 --- a/service_impl/src/lib.rs +++ b/service_impl/src/lib.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; pub mod booking; pub mod clock; +pub mod extra_hours; pub mod permission; pub mod reporting; pub mod sales_person; diff --git a/service_impl/src/reporting.rs b/service_impl/src/reporting.rs index 776f4f8..4400324 100644 --- a/service_impl/src/reporting.rs +++ b/service_impl/src/reporting.rs @@ -124,7 +124,6 @@ pub fn find_working_hours_for_calendar_week( year: u32, week: u8, ) -> Option<&WorkingHoursEntity> { - dbg!((year, week)); working_hours.iter().find(|wh| { (year, week) >= (wh.from_year, wh.from_calendar_week) && (year, week) <= (wh.to_year, wh.to_calendar_week) diff --git a/service_impl/src/sales_person.rs b/service_impl/src/sales_person.rs index 2b3b8d2..f61be41 100644 --- a/service_impl/src/sales_person.rs +++ b/service_impl/src/sales_person.rs @@ -87,14 +87,10 @@ where .await .is_err() { - println!("No HR Role - remove sensitive data"); sales_persons.iter_mut().for_each(|sales_person| { sales_person.is_paid = None; }); - } else { - println!("HR ROLE - no sensitive data removal"); } - Ok(sales_persons.into()) } @@ -128,7 +124,6 @@ where .check_permission(HR_PRIVILEGE, context.clone()) ); shiftplanner.or(sales).or(hr)?; - println!("Has roles"); let mut sales_person = self .sales_person_dao .find_by_id(id) @@ -143,21 +138,17 @@ where .await .is_err() { - println!("No HR Role - futher checks required"); if let (Some(current_user_id), Some(assigned_user)) = ( self.permission_service .current_user_id(context.clone()) .await?, self.get_assigned_user(id, Authentication::Full).await?, ) { - println!("Check if user ID matches"); current_user_id != assigned_user } else { - println!("UserID or assigned user is missing - must remove sensitive data"); true } } else { - println!("HR Role - no sensitive data removal"); false };