Add checks for booking and fix database timestamps

This commit is contained in:
Simon Goller 2024-05-09 09:45:53 +02:00
parent bc8a534353
commit e3ec694876
7 changed files with 73 additions and 7 deletions

View file

@ -24,6 +24,7 @@ pub struct BookingEntity {
pub trait BookingDao {
async fn all(&self) -> Result<Arc<[BookingEntity]>, DaoError>;
async fn find_by_id(&self, id: Uuid) -> Result<Option<BookingEntity>, DaoError>;
async fn find_by_booking_data(&self, sales_person_id: Uuid, slot_id: Uuid, calendar_week: i32, year: u32) -> Result<Option<BookingEntity>, DaoError>;
async fn create(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError>;
async fn update(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError>;
}

View file

@ -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<u8>,
sales_person_id: Vec<u8>,
@ -23,6 +24,7 @@ struct BookingDb {
impl TryFrom<&BookingDb> for BookingEntity {
type Error = DaoError;
fn try_from(booking: &BookingDb) -> Result<Self, Self::Error> {
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<Option<BookingEntity>, 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,

View file

@ -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(),
})

View file

@ -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,

View file

@ -57,6 +57,9 @@ pub enum RestError {
}
fn error_handler(result: Result<Response, RestError>) -> Response {
if result.is_err() {
println!("REST error mapping: {:?}", result);
}
match result {
Ok(response) => response,
Err(err @ RestError::InconsistentId(_, _)) => Response::builder()

View file

@ -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()));

View file

@ -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");