From e3ec694876cb21baebf7b46f88c368bb33193d92 Mon Sep 17 00:00:00 2001 From: Simon Goller Date: Thu, 9 May 2024 09:45:53 +0200 Subject: [PATCH] Add checks for booking and fix database timestamps --- dao/src/booking.rs | 1 + dao_impl/src/booking.rs | 29 ++++++++++++++++++++++++++--- dao_impl/src/sales_person.rs | 2 +- dao_impl/src/slot.rs | 4 ++-- rest/src/lib.rs | 3 +++ service_impl/src/booking.rs | 10 ++++++++++ service_impl/src/test/booking.rs | 31 ++++++++++++++++++++++++++++++- 7 files changed, 73 insertions(+), 7 deletions(-) diff --git a/dao/src/booking.rs b/dao/src/booking.rs index e2f40ad..30a3946 100644 --- a/dao/src/booking.rs +++ b/dao/src/booking.rs @@ -24,6 +24,7 @@ pub struct BookingEntity { pub trait BookingDao { async fn all(&self) -> Result, DaoError>; async fn find_by_id(&self, id: Uuid) -> Result, DaoError>; + async fn find_by_booking_data(&self, sales_person_id: Uuid, slot_id: Uuid, calendar_week: i32, year: u32) -> Result, DaoError>; async fn create(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError>; async fn update(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError>; } diff --git a/dao_impl/src/booking.rs b/dao_impl/src/booking.rs index 000091f..6f88177 100644 --- a/dao_impl/src/booking.rs +++ b/dao_impl/src/booking.rs @@ -10,6 +10,7 @@ use sqlx::{query, query_as}; use time::{format_description::well_known::Iso8601, PrimitiveDateTime}; use uuid::Uuid; +#[derive(Debug)] struct BookingDb { id: Vec, sales_person_id: Vec, @@ -23,6 +24,7 @@ struct BookingDb { impl TryFrom<&BookingDb> for BookingEntity { type Error = DaoError; fn try_from(booking: &BookingDb) -> Result { + dbg!(&booking); Ok(Self { id: Uuid::from_slice(booking.id.as_ref()).unwrap(), sales_person_id: Uuid::from_slice(booking.sales_person_id.as_ref()).unwrap(), @@ -78,12 +80,33 @@ impl BookingDao for BookingDaoImpl { .map(BookingEntity::try_from) .transpose()?) } + + async fn find_by_booking_data(&self, sales_person_id: Uuid, slot_id: Uuid, calendar_week: i32, year: u32) -> Result, DaoError> { + let sales_person_id_vec = sales_person_id.as_bytes().to_vec(); + let slot_id_vec = slot_id.as_bytes().to_vec(); + Ok(query_as!( + BookingDb, + "SELECT id, sales_person_id, slot_id, calendar_week, year, created, deleted, update_version FROM booking WHERE sales_person_id = ? AND slot_id = ? AND calendar_week = ? AND year = ? AND deleted IS NULL", + sales_person_id_vec, + slot_id_vec, + calendar_week, + year, + ) + .fetch_optional(self.pool.as_ref()) + .await + .map_db_error()? + .as_ref() + .map(BookingEntity::try_from) + .transpose()?) + } + + async fn create(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError> { let id_vec = entity.id.as_bytes().to_vec(); let sales_person_id_vec = entity.sales_person_id.as_bytes().to_vec(); let slot_id_vec = entity.slot_id.as_bytes().to_vec(); - let created = entity.created.to_string(); - let deleted = entity.deleted.as_ref().map(|deleted| deleted.to_string()); + let created = entity.created.format(&Iso8601::DATE_TIME).map_db_error()?; + let deleted = entity.deleted.as_ref().map(|deleted| deleted.format(&Iso8601::DATE_TIME)).transpose().map_db_error()?; let version_vec = entity.version.as_bytes().to_vec(); query!("INSERT INTO booking (id, sales_person_id, slot_id, calendar_week, year, created, deleted, update_version, update_process) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", id_vec, sales_person_id_vec, slot_id_vec, entity.calendar_week, entity.year, created, deleted, version_vec, process @@ -93,7 +116,7 @@ impl BookingDao for BookingDaoImpl { async fn update(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError> { let id_vec = entity.id.as_bytes().to_vec(); let version_vec = entity.version.as_bytes().to_vec(); - let deleted = entity.deleted.as_ref().map(|deleted| deleted.to_string()); + let deleted = entity.deleted.as_ref().map(|deleted| deleted.format(&Iso8601::DATE_TIME)).transpose().map_db_error()?; query!( "UPDATE booking SET deleted = ?, update_version = ?, update_process = ? WHERE id = ?", deleted, diff --git a/dao_impl/src/sales_person.rs b/dao_impl/src/sales_person.rs index ad05482..f230b03 100644 --- a/dao_impl/src/sales_person.rs +++ b/dao_impl/src/sales_person.rs @@ -36,7 +36,7 @@ impl TryFrom<&SalesPersonDb> for SalesPersonEntity { deleted: sales_person .deleted .as_ref() - .map(|deleted| PrimitiveDateTime::parse(deleted, &Iso8601::DATE)) + .map(|deleted| PrimitiveDateTime::parse(deleted, &Iso8601::DATE_TIME)) .transpose()?, version: Uuid::from_slice(&sales_person.update_version).unwrap(), }) diff --git a/dao_impl/src/slot.rs b/dao_impl/src/slot.rs index 526022c..a7fdc95 100644 --- a/dao_impl/src/slot.rs +++ b/dao_impl/src/slot.rs @@ -90,7 +90,7 @@ impl dao::slot::SlotDao for SlotDaoImpl { let to = slot.to.to_string(); let valid_from = slot.valid_from.to_string(); let valid_to = slot.valid_to.map(|valid_to| valid_to.to_string()); - let deleted = slot.deleted.map(|deleted| deleted.to_string()); + let deleted = slot.deleted.as_ref().map(|deleted| deleted.to_string()); query!("INSERT INTO slot (id, day_of_week, time_from, time_to, valid_from, valid_to, deleted, update_version, update_process) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", id_vec, day_of_week, @@ -112,7 +112,7 @@ impl dao::slot::SlotDao for SlotDaoImpl { let id_vec = slot.id.as_bytes().to_vec(); let version_vec = slot.version.as_bytes().to_vec(); let valid_to = slot.valid_to.map(|valid_to| valid_to.to_string()); - let deleted = slot.deleted.map(|deleted| deleted.to_string()); + let deleted = slot.deleted.as_ref().map(|deleted| deleted.to_string()); query!("UPDATE slot SET valid_to = ?, deleted = ?, update_version = ?, update_process = ? WHERE id = ?", valid_to, deleted, diff --git a/rest/src/lib.rs b/rest/src/lib.rs index 9abafd9..d4dd155 100644 --- a/rest/src/lib.rs +++ b/rest/src/lib.rs @@ -57,6 +57,9 @@ pub enum RestError { } fn error_handler(result: Result) -> Response { + if result.is_err() { + println!("REST error mapping: {:?}", result); + } match result { Ok(response) => response, Err(err @ RestError::InconsistentId(_, _)) => Response::builder() diff --git a/service_impl/src/booking.rs b/service_impl/src/booking.rs index ceaf112..c77f661 100644 --- a/service_impl/src/booking.rs +++ b/service_impl/src/booking.rs @@ -170,6 +170,16 @@ where booking.slot_id, )); } + if self + .booking_dao + .find_by_booking_data( + booking.sales_person_id, + booking.slot_id, + booking.calendar_week, + booking.year, + ).await?.is_some() { + validation.push(ValidationFailureItem::Duplicate); + } if !validation.is_empty() { return Err(ServiceError::ValidationError(validation.into())); diff --git a/service_impl/src/test/booking.rs b/service_impl/src/test/booking.rs index 4d7884a..6a477f8 100644 --- a/service_impl/src/test/booking.rs +++ b/service_impl/src/test/booking.rs @@ -94,7 +94,8 @@ impl BookingServiceDependencies { } pub fn build_dependencies(permission: bool, role: &'static str) -> BookingServiceDependencies { - let booking_dao = MockBookingDao::new(); + let mut booking_dao = MockBookingDao::new(); + booking_dao.expect_find_by_booking_data().returning(|_, _, _, _| Ok(None)); let mut permission_service = MockPermissionService::new(); permission_service .expect_check_permission() @@ -351,6 +352,34 @@ async fn test_create_sales_person_does_not_exist() { ); } +#[tokio::test] +async fn test_create_booking_data_already_exists() { + let mut deps = build_dependencies(true, "hr"); + deps.booking_dao.checkpoint(); + deps.booking_dao + .expect_find_by_booking_data() + .with(eq(default_sales_person_id()), eq(default_slot_id()), eq(3), eq(2024)) + .returning(|_, _, _, _| Ok(Some(default_booking_entity()))); + let service = deps.build_service(); + let result = service + .create( + &Booking { + id: Uuid::nil(), + version: Uuid::nil(), + created: None, + ..default_booking() + }, + (), + ) + .await; + test_validation_error( + &result, + &ValidationFailureItem::Duplicate, + 1, + ); + +} + #[tokio::test] async fn test_create_slot_does_not_exist() { let mut deps = build_dependencies(true, "hr");