Add checks for booking and fix database timestamps
This commit is contained in:
parent
bc8a534353
commit
e3ec694876
7 changed files with 73 additions and 7 deletions
|
|
@ -24,6 +24,7 @@ pub struct BookingEntity {
|
||||||
pub trait BookingDao {
|
pub trait BookingDao {
|
||||||
async fn all(&self) -> Result<Arc<[BookingEntity]>, DaoError>;
|
async fn all(&self) -> Result<Arc<[BookingEntity]>, DaoError>;
|
||||||
async fn find_by_id(&self, id: Uuid) -> Result<Option<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 create(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError>;
|
||||||
async fn update(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError>;
|
async fn update(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use sqlx::{query, query_as};
|
||||||
use time::{format_description::well_known::Iso8601, PrimitiveDateTime};
|
use time::{format_description::well_known::Iso8601, PrimitiveDateTime};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct BookingDb {
|
struct BookingDb {
|
||||||
id: Vec<u8>,
|
id: Vec<u8>,
|
||||||
sales_person_id: Vec<u8>,
|
sales_person_id: Vec<u8>,
|
||||||
|
|
@ -23,6 +24,7 @@ struct BookingDb {
|
||||||
impl TryFrom<&BookingDb> for BookingEntity {
|
impl TryFrom<&BookingDb> for BookingEntity {
|
||||||
type Error = DaoError;
|
type Error = DaoError;
|
||||||
fn try_from(booking: &BookingDb) -> Result<Self, Self::Error> {
|
fn try_from(booking: &BookingDb) -> Result<Self, Self::Error> {
|
||||||
|
dbg!(&booking);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: Uuid::from_slice(booking.id.as_ref()).unwrap(),
|
id: Uuid::from_slice(booking.id.as_ref()).unwrap(),
|
||||||
sales_person_id: Uuid::from_slice(booking.sales_person_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)
|
.map(BookingEntity::try_from)
|
||||||
.transpose()?)
|
.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> {
|
async fn create(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError> {
|
||||||
let id_vec = entity.id.as_bytes().to_vec();
|
let id_vec = entity.id.as_bytes().to_vec();
|
||||||
let sales_person_id_vec = entity.sales_person_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 slot_id_vec = entity.slot_id.as_bytes().to_vec();
|
||||||
let created = entity.created.to_string();
|
let created = entity.created.format(&Iso8601::DATE_TIME).map_db_error()?;
|
||||||
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()?;
|
||||||
let version_vec = entity.version.as_bytes().to_vec();
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
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
|
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> {
|
async fn update(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError> {
|
||||||
let id_vec = entity.id.as_bytes().to_vec();
|
let id_vec = entity.id.as_bytes().to_vec();
|
||||||
let version_vec = entity.version.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!(
|
query!(
|
||||||
"UPDATE booking SET deleted = ?, update_version = ?, update_process = ? WHERE id = ?",
|
"UPDATE booking SET deleted = ?, update_version = ?, update_process = ? WHERE id = ?",
|
||||||
deleted,
|
deleted,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ impl TryFrom<&SalesPersonDb> for SalesPersonEntity {
|
||||||
deleted: sales_person
|
deleted: sales_person
|
||||||
.deleted
|
.deleted
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|deleted| PrimitiveDateTime::parse(deleted, &Iso8601::DATE))
|
.map(|deleted| PrimitiveDateTime::parse(deleted, &Iso8601::DATE_TIME))
|
||||||
.transpose()?,
|
.transpose()?,
|
||||||
version: Uuid::from_slice(&sales_person.update_version).unwrap(),
|
version: Uuid::from_slice(&sales_person.update_version).unwrap(),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ impl dao::slot::SlotDao for SlotDaoImpl {
|
||||||
let to = slot.to.to_string();
|
let to = slot.to.to_string();
|
||||||
let valid_from = slot.valid_from.to_string();
|
let valid_from = slot.valid_from.to_string();
|
||||||
let valid_to = slot.valid_to.map(|valid_to| valid_to.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 (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
query!("INSERT INTO slot (id, day_of_week, time_from, time_to, valid_from, valid_to, deleted, update_version, update_process) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
id_vec,
|
id_vec,
|
||||||
day_of_week,
|
day_of_week,
|
||||||
|
|
@ -112,7 +112,7 @@ impl dao::slot::SlotDao for SlotDaoImpl {
|
||||||
let id_vec = slot.id.as_bytes().to_vec();
|
let id_vec = slot.id.as_bytes().to_vec();
|
||||||
let version_vec = slot.version.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 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 = ?",
|
query!("UPDATE slot SET valid_to = ?, deleted = ?, update_version = ?, update_process = ? WHERE id = ?",
|
||||||
valid_to,
|
valid_to,
|
||||||
deleted,
|
deleted,
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,9 @@ pub enum RestError {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn error_handler(result: Result<Response, RestError>) -> Response {
|
fn error_handler(result: Result<Response, RestError>) -> Response {
|
||||||
|
if result.is_err() {
|
||||||
|
println!("REST error mapping: {:?}", result);
|
||||||
|
}
|
||||||
match result {
|
match result {
|
||||||
Ok(response) => response,
|
Ok(response) => response,
|
||||||
Err(err @ RestError::InconsistentId(_, _)) => Response::builder()
|
Err(err @ RestError::InconsistentId(_, _)) => Response::builder()
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,16 @@ where
|
||||||
booking.slot_id,
|
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() {
|
if !validation.is_empty() {
|
||||||
return Err(ServiceError::ValidationError(validation.into()));
|
return Err(ServiceError::ValidationError(validation.into()));
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,8 @@ impl BookingServiceDependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_dependencies(permission: bool, role: &'static str) -> 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();
|
let mut permission_service = MockPermissionService::new();
|
||||||
permission_service
|
permission_service
|
||||||
.expect_check_permission()
|
.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]
|
#[tokio::test]
|
||||||
async fn test_create_slot_does_not_exist() {
|
async fn test_create_slot_does_not_exist() {
|
||||||
let mut deps = build_dependencies(true, "hr");
|
let mut deps = build_dependencies(true, "hr");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue