Add basic employee hour balance report
This commit is contained in:
parent
0eb885216a
commit
d4adcb182f
31 changed files with 2155 additions and 5 deletions
56
.sqlx/query-0bae9db9d14012e1b614e2f63b3704cac2c8c030fd05637d5d877060f7f59912.json
generated
Normal file
56
.sqlx/query-0bae9db9d14012e1b614e2f63b3704cac2c8c030fd05637d5d877060f7f59912.json
generated
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "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) <= ?",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "sales_person_id",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "amount",
|
||||
"ordinal": 2,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "category",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "date_time",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "deleted",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "0bae9db9d14012e1b614e2f63b3704cac2c8c030fd05637d5d877060f7f59912"
|
||||
}
|
||||
12
.sqlx/query-0e0d06ab641ad595b8626e89d876c92bff41c677b050393eb210fe3c8eb47b7d.json
generated
Normal file
12
.sqlx/query-0e0d06ab641ad595b8626e89d876c92bff41c677b050393eb210fe3c8eb47b7d.json
generated
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n INSERT INTO working_hours (\n id,\n sales_person_id,\n expected_hours,\n from_calendar_week,\n from_year,\n to_calendar_week,\n to_year,\n created,\n update_process,\n update_version\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 10
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0e0d06ab641ad595b8626e89d876c92bff41c677b050393eb210fe3c8eb47b7d"
|
||||
}
|
||||
32
.sqlx/query-3834c9f6fc72c8d6ab28f01f0607e830240201908048364d8c85246f22929b9b.json
generated
Normal file
32
.sqlx/query-3834c9f6fc72c8d6ab28f01f0607e830240201908048364d8c85246f22929b9b.json
generated
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n sales_person.id as sales_person_id,\n sum((STRFTIME('%H', slot.time_to) + STRFTIME('%M', slot.time_to) / 60.0) - (STRFTIME('%H', slot.time_from) + STRFTIME('%M', slot.time_from))) as hours,\n booking.year\n FROM slot\n INNER JOIN booking ON (booking.slot_id = slot.id AND booking.deleted IS NULL)\n INNER JOIN sales_person ON booking.sales_person_id = sales_person.id\n WHERE booking.year = ?\n AND booking.calendar_week <= ?\n GROUP BY sales_person_id, year\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "sales_person_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "hours",
|
||||
"ordinal": 1,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "year",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3834c9f6fc72c8d6ab28f01f0607e830240201908048364d8c85246f22929b9b"
|
||||
}
|
||||
74
.sqlx/query-53ab5252dae2a4bbbb5f098bcdb9ee7d55b61ef54b82b80310bea94276dfb7b2.json
generated
Normal file
74
.sqlx/query-53ab5252dae2a4bbbb5f098bcdb9ee7d55b61ef54b82b80310bea94276dfb7b2.json
generated
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id,\n sales_person_id,\n expected_hours,\n from_calendar_week,\n from_year,\n to_calendar_week,\n to_year,\n created,\n deleted,\n update_version\n FROM\n working_hours\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "sales_person_id",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "expected_hours",
|
||||
"ordinal": 2,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "from_calendar_week",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "from_year",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "to_calendar_week",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "to_year",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "deleted",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "update_version",
|
||||
"ordinal": 9,
|
||||
"type_info": "Blob"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "53ab5252dae2a4bbbb5f098bcdb9ee7d55b61ef54b82b80310bea94276dfb7b2"
|
||||
}
|
||||
12
.sqlx/query-a49a3abf9187ba8d16e2e6cc0d6898a2acb26fe5639f8b2351854eb21ffa85d5.json
generated
Normal file
12
.sqlx/query-a49a3abf9187ba8d16e2e6cc0d6898a2acb26fe5639f8b2351854eb21ffa85d5.json
generated
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE working_hours SET\n deleted = ?,\n update_process = ?\n WHERE\n id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a49a3abf9187ba8d16e2e6cc0d6898a2acb26fe5639f8b2351854eb21ffa85d5"
|
||||
}
|
||||
74
.sqlx/query-a85db22630c8d55b5eb6be027cca9016d77615573d141348015f7c69021f1581.json
generated
Normal file
74
.sqlx/query-a85db22630c8d55b5eb6be027cca9016d77615573d141348015f7c69021f1581.json
generated
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id,\n sales_person_id,\n expected_hours,\n from_calendar_week,\n from_year,\n to_calendar_week,\n to_year,\n created,\n deleted,\n update_version\n FROM\n working_hours\n WHERE\n sales_person_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "sales_person_id",
|
||||
"ordinal": 1,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "expected_hours",
|
||||
"ordinal": 2,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "from_calendar_week",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "from_year",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "to_calendar_week",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "to_year",
|
||||
"ordinal": 6,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "deleted",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "update_version",
|
||||
"ordinal": 9,
|
||||
"type_info": "Blob"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "a85db22630c8d55b5eb6be027cca9016d77615573d141348015f7c69021f1581"
|
||||
}
|
||||
44
.sqlx/query-c4cb7c4ac52ee81fb6f61e2399b308d2ac6e11103dd2cfb0f8db60d562ff03df.json
generated
Normal file
44
.sqlx/query-c4cb7c4ac52ee81fb6f61e2399b308d2ac6e11103dd2cfb0f8db60d562ff03df.json
generated
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n sales_person.id as sales_person_id,\n sum((STRFTIME('%H', slot.time_to) + STRFTIME('%M', slot.time_to) / 60.0) - (STRFTIME('%H', slot.time_from) + STRFTIME('%M', slot.time_from))) as hours,\n booking.calendar_week, booking.year, slot.day_of_week\n FROM slot\n INNER JOIN booking ON (booking.slot_id = slot.id AND booking.deleted IS NULL)\n INNER JOIN sales_person ON booking.sales_person_id = sales_person.id\n WHERE sales_person.id = ?\n AND booking.year = ?\n AND booking.calendar_week <= ?\n GROUP BY year, calendar_week, day_of_week\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "sales_person_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Blob"
|
||||
},
|
||||
{
|
||||
"name": "hours",
|
||||
"ordinal": 1,
|
||||
"type_info": "Float"
|
||||
},
|
||||
{
|
||||
"name": "calendar_week",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "year",
|
||||
"ordinal": 3,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "day_of_week",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c4cb7c4ac52ee81fb6f61e2399b308d2ac6e11103dd2cfb0f8db60d562ff03df"
|
||||
}
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use dao_impl::{
|
||||
extra_hours::ExtraHoursDaoImpl, shiftplan_report::ShiftplanReportDaoImpl,
|
||||
working_hours::WorkingHoursDaoImpl,
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
#[cfg(feature = "mock_auth")]
|
||||
|
|
@ -30,6 +34,21 @@ type BookingService = service_impl::booking::BookingServiceImpl<
|
|||
SalesPersonService,
|
||||
SlotService,
|
||||
>;
|
||||
type ReportingService = service_impl::reporting::ReportingServiceImpl<
|
||||
dao_impl::extra_hours::ExtraHoursDaoImpl,
|
||||
dao_impl::shiftplan_report::ShiftplanReportDaoImpl,
|
||||
dao_impl::working_hours::WorkingHoursDaoImpl,
|
||||
SalesPersonService,
|
||||
PermissionService,
|
||||
ClockService,
|
||||
UuidService,
|
||||
>;
|
||||
type WorkingHoursService = service_impl::working_hours::WorkingHoursServiceImpl<
|
||||
dao_impl::working_hours::WorkingHoursDaoImpl,
|
||||
PermissionService,
|
||||
ClockService,
|
||||
UuidService,
|
||||
>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RestStateImpl {
|
||||
|
|
@ -38,6 +57,8 @@ pub struct RestStateImpl {
|
|||
slot_service: Arc<SlotService>,
|
||||
sales_person_service: Arc<SalesPersonService>,
|
||||
booking_service: Arc<BookingService>,
|
||||
reporting_service: Arc<ReportingService>,
|
||||
working_hours_service: Arc<WorkingHoursService>,
|
||||
}
|
||||
impl rest::RestStateDef for RestStateImpl {
|
||||
type UserService = UserService;
|
||||
|
|
@ -45,6 +66,8 @@ impl rest::RestStateDef for RestStateImpl {
|
|||
type SlotService = SlotService;
|
||||
type SalesPersonService = SalesPersonService;
|
||||
type BookingService = BookingService;
|
||||
type ReportingService = ReportingService;
|
||||
type WorkingHoursService = WorkingHoursService;
|
||||
|
||||
fn user_service(&self) -> Arc<Self::UserService> {
|
||||
self.user_service.clone()
|
||||
|
|
@ -61,13 +84,22 @@ impl rest::RestStateDef for RestStateImpl {
|
|||
fn booking_service(&self) -> Arc<Self::BookingService> {
|
||||
self.booking_service.clone()
|
||||
}
|
||||
fn reporting_service(&self) -> Arc<Self::ReportingService> {
|
||||
self.reporting_service.clone()
|
||||
}
|
||||
fn working_hours_service(&self) -> Arc<Self::WorkingHoursService> {
|
||||
self.working_hours_service.clone()
|
||||
}
|
||||
}
|
||||
impl RestStateImpl {
|
||||
pub fn new(pool: Arc<sqlx::Pool<sqlx::Sqlite>>) -> Self {
|
||||
let permission_dao = dao_impl::PermissionDaoImpl::new(pool.clone());
|
||||
let slot_dao = dao_impl::slot::SlotDaoImpl::new(pool.clone());
|
||||
let sales_person_dao = dao_impl::sales_person::SalesPersonDaoImpl::new(pool.clone());
|
||||
let booking_dao = dao_impl::booking::BookingDaoImpl::new(pool);
|
||||
let booking_dao = dao_impl::booking::BookingDaoImpl::new(pool.clone());
|
||||
let extra_hours_dao = Arc::new(ExtraHoursDaoImpl::new(pool.clone()));
|
||||
let shiftplan_report_dao = Arc::new(ShiftplanReportDaoImpl::new(pool.clone()));
|
||||
let working_hours_dao = Arc::new(WorkingHoursDaoImpl::new(pool.clone()));
|
||||
|
||||
// Always authenticate with DEVUSER during development.
|
||||
// This is used to test the permission service locally without a login service.
|
||||
|
|
@ -102,17 +134,35 @@ impl RestStateImpl {
|
|||
let booking_service = Arc::new(service_impl::booking::BookingServiceImpl::new(
|
||||
booking_dao.into(),
|
||||
permission_service.clone(),
|
||||
clock_service,
|
||||
uuid_service,
|
||||
clock_service.clone(),
|
||||
uuid_service.clone(),
|
||||
sales_person_service.clone(),
|
||||
slot_service.clone(),
|
||||
));
|
||||
let reporting_service = Arc::new(service_impl::reporting::ReportingServiceImpl::new(
|
||||
extra_hours_dao,
|
||||
shiftplan_report_dao,
|
||||
working_hours_dao.clone(),
|
||||
sales_person_service.clone(),
|
||||
permission_service.clone(),
|
||||
clock_service.clone(),
|
||||
uuid_service.clone(),
|
||||
));
|
||||
let working_hours_service =
|
||||
Arc::new(service_impl::working_hours::WorkingHoursServiceImpl::new(
|
||||
working_hours_dao,
|
||||
permission_service.clone(),
|
||||
clock_service,
|
||||
uuid_service,
|
||||
));
|
||||
Self {
|
||||
user_service,
|
||||
permission_service,
|
||||
slot_service,
|
||||
sales_person_service,
|
||||
booking_service,
|
||||
reporting_service,
|
||||
working_hours_service,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
40
dao/src/extra_hours.rs
Normal file
40
dao/src/extra_hours.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mockall::automock;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ExtraHoursCategoryEntity {
|
||||
ExtraWork,
|
||||
Vacation,
|
||||
SickLeave,
|
||||
Holiday,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ExtraHoursEntity {
|
||||
pub id: Uuid,
|
||||
pub sales_person_id: Uuid,
|
||||
pub amount: f32,
|
||||
pub category: ExtraHoursCategoryEntity,
|
||||
pub description: Arc<str>,
|
||||
pub date_time: time::PrimitiveDateTime,
|
||||
pub deleted: Option<time::PrimitiveDateTime>,
|
||||
}
|
||||
|
||||
#[automock]
|
||||
#[async_trait]
|
||||
pub trait ExtraHoursDao {
|
||||
async fn find_by_sales_person_id_and_year(
|
||||
&self,
|
||||
sales_person_id: Uuid,
|
||||
year: u32,
|
||||
until_week: u8,
|
||||
) -> Result<Arc<[ExtraHoursEntity]>, crate::DaoError>;
|
||||
async fn create(&self, entity: &ExtraHoursEntity, process: &str)
|
||||
-> Result<(), crate::DaoError>;
|
||||
async fn update(&self, entity: &ExtraHoursEntity, process: &str)
|
||||
-> Result<(), crate::DaoError>;
|
||||
async fn delete(&self, id: Uuid, process: &str) -> Result<(), crate::DaoError>;
|
||||
}
|
||||
|
|
@ -5,9 +5,12 @@ use mockall::automock;
|
|||
use thiserror::Error;
|
||||
|
||||
pub mod booking;
|
||||
pub mod extra_hours;
|
||||
pub mod permission;
|
||||
pub mod sales_person;
|
||||
pub mod shiftplan_report;
|
||||
pub mod slot;
|
||||
pub mod working_hours;
|
||||
|
||||
pub use permission::MockPermissionDao;
|
||||
pub use permission::PermissionDao;
|
||||
|
|
@ -28,6 +31,12 @@ pub enum DaoError {
|
|||
|
||||
#[error("Date/Time parse error: {0}")]
|
||||
DateTimeParseError(#[from] time::error::Parse),
|
||||
|
||||
#[error("Date/Time format error: {0}")]
|
||||
DateTimeFormatError(#[from] time::error::Format),
|
||||
|
||||
#[error("Enum value not found: {0}")]
|
||||
EnumValueNotFound(Arc<str>),
|
||||
}
|
||||
|
||||
#[automock]
|
||||
|
|
|
|||
41
dao/src/shiftplan_report.rs
Normal file
41
dao/src/shiftplan_report.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use async_trait::async_trait;
|
||||
use mockall::automock;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::slot::DayOfWeek;
|
||||
use crate::DaoError;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ShiftplanReportEntity {
|
||||
pub sales_person_id: Uuid,
|
||||
pub hours: f32,
|
||||
pub year: u32,
|
||||
pub calendar_week: u8,
|
||||
pub day_of_week: DayOfWeek,
|
||||
}
|
||||
|
||||
pub struct ShiftplanQuickOverviewEntity {
|
||||
pub sales_person_id: Uuid,
|
||||
pub hours: f32,
|
||||
pub year: u32,
|
||||
}
|
||||
|
||||
#[automock]
|
||||
#[async_trait]
|
||||
pub trait ShiftplanReportDao {
|
||||
/// A report which contains the worked hours of a sales person for each day.
|
||||
async fn extract_shiftplan_report(
|
||||
&self,
|
||||
sales_person_id: Uuid,
|
||||
year: u32,
|
||||
until_week: u8,
|
||||
) -> Result<Arc<[ShiftplanReportEntity]>, DaoError>;
|
||||
|
||||
/// A report which shows the summed up yearly work hours of all sales persons.
|
||||
async fn extract_quick_shiftplan_report(
|
||||
&self,
|
||||
year: u32,
|
||||
until_week: u8,
|
||||
) -> Result<Arc<[ShiftplanQuickOverviewEntity]>, DaoError>;
|
||||
}
|
||||
32
dao/src/working_hours.rs
Normal file
32
dao/src/working_hours.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use crate::DaoError;
|
||||
use async_trait::async_trait;
|
||||
use mockall::automock;
|
||||
use std::sync::Arc;
|
||||
use time::PrimitiveDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct WorkingHoursEntity {
|
||||
pub id: Uuid,
|
||||
pub sales_person_id: Uuid,
|
||||
pub expected_hours: f32,
|
||||
pub from_calendar_week: u8,
|
||||
pub from_year: u32,
|
||||
pub to_calendar_week: u8,
|
||||
pub to_year: u32,
|
||||
pub created: PrimitiveDateTime,
|
||||
pub deleted: Option<PrimitiveDateTime>,
|
||||
pub version: Uuid,
|
||||
}
|
||||
|
||||
#[automock]
|
||||
#[async_trait]
|
||||
pub trait WorkingHoursDao {
|
||||
async fn all(&self) -> Result<Arc<[WorkingHoursEntity]>, DaoError>;
|
||||
async fn find_by_sales_person_id(
|
||||
&self,
|
||||
sales_person_id: Uuid,
|
||||
) -> Result<Arc<[WorkingHoursEntity]>, DaoError>;
|
||||
async fn create(&self, entity: &WorkingHoursEntity, process: &str) -> Result<(), DaoError>;
|
||||
async fn update(&self, entity: &WorkingHoursEntity, process: &str) -> Result<(), DaoError>;
|
||||
}
|
||||
108
dao_impl/src/extra_hours.rs
Normal file
108
dao_impl/src/extra_hours.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::ResultDbErrorExt;
|
||||
use async_trait::async_trait;
|
||||
use dao::{
|
||||
extra_hours::{ExtraHoursCategoryEntity, ExtraHoursDao, ExtraHoursEntity},
|
||||
DaoError,
|
||||
};
|
||||
use sqlx::query_as;
|
||||
use time::{format_description::well_known::Iso8601, PrimitiveDateTime};
|
||||
use uuid::Uuid;
|
||||
|
||||
struct ExtraHoursDb {
|
||||
id: Vec<u8>,
|
||||
sales_person_id: Vec<u8>,
|
||||
amount: f64,
|
||||
|
||||
category: String,
|
||||
description: Option<String>,
|
||||
date_time: String,
|
||||
deleted: Option<String>,
|
||||
}
|
||||
impl TryFrom<&ExtraHoursDb> for ExtraHoursEntity {
|
||||
type Error = DaoError;
|
||||
|
||||
fn try_from(extra_hours: &ExtraHoursDb) -> Result<Self, DaoError> {
|
||||
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(),
|
||||
amount: extra_hours.amount as f32,
|
||||
category: match extra_hours.category.as_str() {
|
||||
"ExtraWork" => ExtraHoursCategoryEntity::ExtraWork,
|
||||
"Vacation" => ExtraHoursCategoryEntity::Vacation,
|
||||
"SickLeave" => ExtraHoursCategoryEntity::SickLeave,
|
||||
"Holiday" => ExtraHoursCategoryEntity::Holiday,
|
||||
value @ _ => return Err(DaoError::EnumValueNotFound(value.into())),
|
||||
},
|
||||
description: extra_hours
|
||||
.description
|
||||
.clone()
|
||||
.unwrap_or_else(|| String::new())
|
||||
.as_str()
|
||||
.into(),
|
||||
date_time: PrimitiveDateTime::parse(
|
||||
extra_hours.date_time.as_str(),
|
||||
&Iso8601::DATE_TIME,
|
||||
)
|
||||
.unwrap(),
|
||||
deleted: extra_hours
|
||||
.deleted
|
||||
.as_ref()
|
||||
.map(|deleted| PrimitiveDateTime::parse(deleted, &Iso8601::DATE_TIME))
|
||||
.transpose()
|
||||
.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtraHoursDaoImpl {
|
||||
pub pool: Arc<sqlx::SqlitePool>,
|
||||
}
|
||||
impl ExtraHoursDaoImpl {
|
||||
pub fn new(pool: Arc<sqlx::SqlitePool>) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ExtraHoursDao for ExtraHoursDaoImpl {
|
||||
async fn find_by_sales_person_id_and_year(
|
||||
&self,
|
||||
sales_person_id: Uuid,
|
||||
year: u32,
|
||||
until_week: u8,
|
||||
) -> Result<Arc<[ExtraHoursEntity]>, crate::DaoError> {
|
||||
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) <= ?",
|
||||
id_vec,
|
||||
year,
|
||||
until_week,
|
||||
).fetch_all(self.pool.as_ref())
|
||||
.await
|
||||
.map_db_error()?
|
||||
.iter()
|
||||
.map(ExtraHoursEntity::try_from)
|
||||
.collect::<Result<Arc<[_]>, _>>()?
|
||||
.into())
|
||||
}
|
||||
async fn create(
|
||||
&self,
|
||||
_entity: &ExtraHoursEntity,
|
||||
_process: &str,
|
||||
) -> Result<(), crate::DaoError> {
|
||||
unimplemented!()
|
||||
}
|
||||
async fn update(
|
||||
&self,
|
||||
_entity: &ExtraHoursEntity,
|
||||
_process: &str,
|
||||
) -> Result<(), crate::DaoError> {
|
||||
unimplemented!()
|
||||
}
|
||||
async fn delete(&self, _id: Uuid, _process: &str) -> Result<(), crate::DaoError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,11 @@ use dao::{DaoError, PrivilegeEntity};
|
|||
use sqlx::{query, query_as, SqlitePool};
|
||||
|
||||
pub mod booking;
|
||||
pub mod extra_hours;
|
||||
pub mod sales_person;
|
||||
pub mod shiftplan_report;
|
||||
pub mod slot;
|
||||
pub mod working_hours;
|
||||
|
||||
pub trait ResultDbErrorExt<T, E> {
|
||||
fn map_db_error(self) -> Result<T, DaoError>;
|
||||
|
|
|
|||
123
dao_impl/src/shiftplan_report.rs
Normal file
123
dao_impl/src/shiftplan_report.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::ResultDbErrorExt;
|
||||
use async_trait::async_trait;
|
||||
use dao::{
|
||||
shiftplan_report::{ShiftplanQuickOverviewEntity, ShiftplanReportDao, ShiftplanReportEntity},
|
||||
slot::DayOfWeek,
|
||||
DaoError,
|
||||
};
|
||||
use sqlx::query_as;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct ShiftplanReportDb {
|
||||
pub sales_person_id: Vec<u8>,
|
||||
pub hours: Option<f64>,
|
||||
pub year: i64,
|
||||
pub calendar_week: i64,
|
||||
pub day_of_week: i64,
|
||||
}
|
||||
impl TryFrom<&ShiftplanReportDb> for ShiftplanReportEntity {
|
||||
type Error = DaoError;
|
||||
fn try_from(entity: &ShiftplanReportDb) -> Result<Self, DaoError> {
|
||||
Ok(Self {
|
||||
sales_person_id: Uuid::from_slice(entity.sales_person_id.as_ref())?,
|
||||
hours: entity.hours.unwrap_or(0.0) as f32,
|
||||
year: entity.year as u32,
|
||||
calendar_week: entity.calendar_week as u8,
|
||||
day_of_week: DayOfWeek::from_number(entity.day_of_week as u8)
|
||||
.ok_or_else(|| DaoError::InvalidDayOfWeek(entity.day_of_week as u8))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShiftplanQuickOverviewDb {
|
||||
pub sales_person_id: Vec<u8>,
|
||||
pub hours: Option<f64>,
|
||||
pub year: i64,
|
||||
}
|
||||
impl From<&ShiftplanQuickOverviewDb> for ShiftplanQuickOverviewEntity {
|
||||
fn from(entity: &ShiftplanQuickOverviewDb) -> Self {
|
||||
Self {
|
||||
sales_person_id: Uuid::from_slice(entity.sales_person_id.as_ref()).unwrap(),
|
||||
hours: entity.hours.unwrap_or(0.0) as f32,
|
||||
year: entity.year as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShiftplanReportDaoImpl {
|
||||
pub pool: Arc<sqlx::SqlitePool>,
|
||||
}
|
||||
impl ShiftplanReportDaoImpl {
|
||||
pub fn new(pool: Arc<sqlx::SqlitePool>) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ShiftplanReportDao for ShiftplanReportDaoImpl {
|
||||
async fn extract_shiftplan_report(
|
||||
&self,
|
||||
sales_person_id: Uuid,
|
||||
year: u32,
|
||||
until_week: u8,
|
||||
) -> Result<Arc<[ShiftplanReportEntity]>, DaoError> {
|
||||
let sales_person_id_vec = sales_person_id.as_bytes().to_vec();
|
||||
Ok(query_as!(
|
||||
ShiftplanReportDb,
|
||||
r#"
|
||||
SELECT
|
||||
sales_person.id as sales_person_id,
|
||||
sum((STRFTIME('%H', slot.time_to) + STRFTIME('%M', slot.time_to) / 60.0) - (STRFTIME('%H', slot.time_from) + STRFTIME('%M', slot.time_from))) as hours,
|
||||
booking.calendar_week, booking.year, slot.day_of_week
|
||||
FROM slot
|
||||
INNER JOIN booking ON (booking.slot_id = slot.id AND booking.deleted IS NULL)
|
||||
INNER JOIN sales_person ON booking.sales_person_id = sales_person.id
|
||||
WHERE sales_person.id = ?
|
||||
AND booking.year = ?
|
||||
AND booking.calendar_week <= ?
|
||||
GROUP BY year, calendar_week, day_of_week
|
||||
"#,
|
||||
sales_person_id_vec,
|
||||
year,
|
||||
until_week
|
||||
).fetch_all(self.pool.as_ref())
|
||||
.await
|
||||
.map_db_error()?
|
||||
.iter()
|
||||
.map(ShiftplanReportEntity::try_from)
|
||||
.collect::<Result<Arc<[_]>, _>>()?
|
||||
)
|
||||
}
|
||||
|
||||
async fn extract_quick_shiftplan_report(
|
||||
&self,
|
||||
year: u32,
|
||||
until_week: u8,
|
||||
) -> Result<Arc<[ShiftplanQuickOverviewEntity]>, DaoError> {
|
||||
Ok(query_as!(
|
||||
ShiftplanQuickOverviewDb,
|
||||
r#"
|
||||
SELECT
|
||||
sales_person.id as sales_person_id,
|
||||
sum((STRFTIME('%H', slot.time_to) + STRFTIME('%M', slot.time_to) / 60.0) - (STRFTIME('%H', slot.time_from) + STRFTIME('%M', slot.time_from))) as hours,
|
||||
booking.year
|
||||
FROM slot
|
||||
INNER JOIN booking ON (booking.slot_id = slot.id AND booking.deleted IS NULL)
|
||||
INNER JOIN sales_person ON booking.sales_person_id = sales_person.id
|
||||
WHERE booking.year = ?
|
||||
AND booking.calendar_week <= ?
|
||||
GROUP BY sales_person_id, year
|
||||
"#,
|
||||
year,
|
||||
until_week
|
||||
).fetch_all(self.pool.as_ref())
|
||||
.await
|
||||
.map_db_error()?
|
||||
.iter()
|
||||
.map(ShiftplanQuickOverviewEntity::from)
|
||||
.collect::<Arc<[_]>>()
|
||||
)
|
||||
}
|
||||
}
|
||||
183
dao_impl/src/working_hours.rs
Normal file
183
dao_impl/src/working_hours.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::ResultDbErrorExt;
|
||||
use async_trait::async_trait;
|
||||
use dao::{
|
||||
working_hours::{WorkingHoursDao, WorkingHoursEntity},
|
||||
DaoError,
|
||||
};
|
||||
use sqlx::{query, query_as};
|
||||
use time::{format_description::well_known::Iso8601, PrimitiveDateTime};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct WorkingHoursDb {
|
||||
pub id: Vec<u8>,
|
||||
pub sales_person_id: Vec<u8>,
|
||||
pub expected_hours: f64,
|
||||
pub from_calendar_week: i64,
|
||||
pub from_year: i64,
|
||||
pub to_calendar_week: i64,
|
||||
pub to_year: i64,
|
||||
pub created: String,
|
||||
pub deleted: Option<String>,
|
||||
update_version: Vec<u8>,
|
||||
}
|
||||
|
||||
impl TryFrom<&WorkingHoursDb> for WorkingHoursEntity {
|
||||
type Error = DaoError;
|
||||
|
||||
fn try_from(working_hours: &WorkingHoursDb) -> Result<Self, DaoError> {
|
||||
Ok(Self {
|
||||
id: Uuid::from_slice(working_hours.id.as_ref())?,
|
||||
sales_person_id: Uuid::from_slice(working_hours.sales_person_id.as_ref()).unwrap(),
|
||||
expected_hours: working_hours.expected_hours as f32,
|
||||
from_calendar_week: working_hours.from_calendar_week as u8,
|
||||
from_year: working_hours.from_year as u32,
|
||||
to_calendar_week: working_hours.to_calendar_week as u8,
|
||||
to_year: working_hours.to_year as u32,
|
||||
created: PrimitiveDateTime::parse(working_hours.created.as_str(), &Iso8601::DATE_TIME)?,
|
||||
deleted: working_hours
|
||||
.deleted
|
||||
.as_ref()
|
||||
.map(|deleted| PrimitiveDateTime::parse(deleted, &Iso8601::DATE_TIME))
|
||||
.transpose()?,
|
||||
version: Uuid::from_slice(&working_hours.update_version)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorkingHoursDaoImpl {
|
||||
pub pool: Arc<sqlx::SqlitePool>,
|
||||
}
|
||||
|
||||
impl WorkingHoursDaoImpl {
|
||||
pub fn new(pool: Arc<sqlx::SqlitePool>) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WorkingHoursDao for WorkingHoursDaoImpl {
|
||||
async fn all(&self) -> Result<Arc<[WorkingHoursEntity]>, DaoError> {
|
||||
query_as!(
|
||||
WorkingHoursDb,
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
sales_person_id,
|
||||
expected_hours,
|
||||
from_calendar_week,
|
||||
from_year,
|
||||
to_calendar_week,
|
||||
to_year,
|
||||
created,
|
||||
deleted,
|
||||
update_version
|
||||
FROM
|
||||
working_hours
|
||||
"#
|
||||
)
|
||||
.fetch_all(self.pool.as_ref())
|
||||
.await
|
||||
.map_db_error()?
|
||||
.iter()
|
||||
.map(WorkingHoursEntity::try_from)
|
||||
.collect::<Result<_, _>>()
|
||||
}
|
||||
|
||||
async fn find_by_sales_person_id(
|
||||
&self,
|
||||
sales_person_id: Uuid,
|
||||
) -> Result<Arc<[WorkingHoursEntity]>, DaoError> {
|
||||
let id_vec = sales_person_id.as_bytes().to_vec();
|
||||
query_as!(
|
||||
WorkingHoursDb,
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
sales_person_id,
|
||||
expected_hours,
|
||||
from_calendar_week,
|
||||
from_year,
|
||||
to_calendar_week,
|
||||
to_year,
|
||||
created,
|
||||
deleted,
|
||||
update_version
|
||||
FROM
|
||||
working_hours
|
||||
WHERE
|
||||
sales_person_id = ?
|
||||
"#,
|
||||
id_vec
|
||||
)
|
||||
.fetch_all(self.pool.as_ref())
|
||||
.await
|
||||
.map_db_error()?
|
||||
.iter()
|
||||
.map(WorkingHoursEntity::try_from)
|
||||
.collect::<Result<_, _>>()
|
||||
}
|
||||
async fn create(&self, entity: &WorkingHoursEntity, process: &str) -> Result<(), DaoError> {
|
||||
let id = entity.id.as_bytes().to_vec();
|
||||
let sales_person_id = entity.sales_person_id.as_bytes().to_vec();
|
||||
let expected_hours = entity.expected_hours as f64;
|
||||
let from_calendar_week = entity.from_calendar_week as i64;
|
||||
let from_year = entity.from_year as i64;
|
||||
let to_calendar_week = entity.to_calendar_week as i64;
|
||||
let to_year = entity.to_year as i64;
|
||||
let created = entity.created.format(&Iso8601::DATE_TIME)?;
|
||||
let version = entity.id.as_bytes().to_vec();
|
||||
query!(
|
||||
r#"
|
||||
INSERT INTO working_hours (
|
||||
id,
|
||||
sales_person_id,
|
||||
expected_hours,
|
||||
from_calendar_week,
|
||||
from_year,
|
||||
to_calendar_week,
|
||||
to_year,
|
||||
created,
|
||||
update_process,
|
||||
update_version
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
id,
|
||||
sales_person_id,
|
||||
expected_hours,
|
||||
from_calendar_week,
|
||||
from_year,
|
||||
to_calendar_week,
|
||||
to_year,
|
||||
created,
|
||||
process,
|
||||
version,
|
||||
)
|
||||
.execute(self.pool.as_ref())
|
||||
.await
|
||||
.map_db_error()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update(&self, entity: &WorkingHoursEntity, process: &str) -> Result<(), DaoError> {
|
||||
let id = entity.id.as_bytes().to_vec();
|
||||
let deleted = entity.deleted.as_ref().map(|deleted| deleted.to_string());
|
||||
query!(
|
||||
r#"
|
||||
UPDATE working_hours SET
|
||||
deleted = ?,
|
||||
update_process = ?
|
||||
WHERE
|
||||
id = ?
|
||||
"#,
|
||||
deleted,
|
||||
process,
|
||||
id
|
||||
)
|
||||
.execute(self.pool.as_ref())
|
||||
.await
|
||||
.map_db_error()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ ADD COLUMN is_paid BOOLEAN DEFAULT 0 NOT NULL;
|
|||
CREATE TABLE working_hours (
|
||||
id blob(16) NOT NULL PRIMARY KEY,
|
||||
sales_person_id blob(16) NOT NULL,
|
||||
expected_hours FLOAT NOT NULL,
|
||||
from_calendar_week INTEGER NOT NULL,
|
||||
from_year INTEGER NOT NULL,
|
||||
to_calendar_week INTEGER NOT NULL,
|
||||
|
|
@ -16,15 +17,16 @@ CREATE TABLE working_hours (
|
|||
update_timestamp TEXT,
|
||||
update_process TEXT NOT NULL,
|
||||
update_version blob(16) NOT NULL,
|
||||
|
||||
|
||||
FOREIGN KEY (sales_person_id) REFERENCES sales_person(id)
|
||||
);
|
||||
|
||||
CREATE TABLE extra_hours (
|
||||
id blob(16) NOT NULL PRIMARY KEY,
|
||||
sales_person_id blob(16) NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
amount FLOAT NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
description TEXT,
|
||||
date_time TEXT NOT NULL,
|
||||
created TEXT NOT NULL,
|
||||
deleted TEXT,
|
||||
|
|
|
|||
64
migrations/20240619085745_default-slots.sql
Normal file
64
migrations/20240619085745_default-slots.sql
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
-- Add migration script here
|
||||
|
||||
INSERT INTO slot VALUES(X'08ab25881f9e4d03b70fd6ecd9747e53',1,'09:00:00.0','10:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'5a4be30de0f743789a2da07d11b4f14d');
|
||||
INSERT INTO slot VALUES(X'46fe075a4e9b49d28d138a3ce368ba3e',1,'10:00:00.0','11:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'52163187643344519943bac153a5d33f');
|
||||
INSERT INTO slot VALUES(X'c34ad85a7bfb4a35b98ecd858eb84ea9',1,'11:00:00.0','12:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'9b3ee57e71eb46eea33eb57ae6abd212');
|
||||
INSERT INTO slot VALUES(X'4b6808fd1955436aaee6afaad87832f8',1,'12:00:00.0','13:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'3f719589b0474c5bb575af52290ac24f');
|
||||
INSERT INTO slot VALUES(X'58ee28a028b44debae3cb909e14bbc82',1,'13:00:00.0','14:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'3e7351fdfc9049649b69c43dfb51d860');
|
||||
INSERT INTO slot VALUES(X'8ef857dea2a7429aad765a898e01948b',1,'14:00:00.0','15:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'b3dc7f132a354ad2b719366b507691ac');
|
||||
INSERT INTO slot VALUES(X'0824b95ae1a54ef98150cc7c18172c9f',1,'15:00:00.0','16:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'023a5ad0fe0b4e55bca61bb6634ac1db');
|
||||
INSERT INTO slot VALUES(X'ae4fa09ca6b94234b2fad2daa993fee0',1,'16:00:00.0','17:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'2109af124c5742e6b1076559a252e6fe');
|
||||
INSERT INTO slot VALUES(X'70695390f5ec4f7b83ca1b4a68bb8a14',1,'17:00:00.0','18:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'1644962cc6fe455f8e6df37126ce67c1');
|
||||
INSERT INTO slot VALUES(X'8b538e961f214935ac4c4c045c989473',1,'18:00:00.0','19:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'726b7c1c741246efa7e36114af3c8db5');
|
||||
INSERT INTO slot VALUES(X'f0037047d29e4579ae0d1d743818bddc',1,'19:00:00.0','19:30:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'ea91542af18a4a75bc5d1c04ab53e596');
|
||||
INSERT INTO slot VALUES(X'96465be6f1664ba68c3630858a7e4207',2,'09:00:00.0','10:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'6a6a7e107738466aacdb7ca4e4f1ab8a');
|
||||
INSERT INTO slot VALUES(X'6851abd6b9134ee7b311fbb9502a1d05',3,'09:00:00.0','10:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'0d379974b71d42029ad4f174461d70c4');
|
||||
INSERT INTO slot VALUES(X'54d84cb4622b424ba1f6cd3b09910db3',4,'09:00:00.0','10:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'64eb8bcbd88648dc888132e3e5486967');
|
||||
INSERT INTO slot VALUES(X'012f7b1eb86a479497ad6d12bf502e1a',5,'09:00:00.0','10:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'72a49fcb3ce9496f85a6be39e067a5ab');
|
||||
INSERT INTO slot VALUES(X'a847e67a1a9a45339710987a022dac4b',6,'09:00:00.0','10:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'a3bf1b1fecd04294991ef3f432425991');
|
||||
INSERT INTO slot VALUES(X'7cf59fc6d6fe4aba83a6a70044583eeb',6,'11:00:00.0','12:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'db82bcb229c74cec828a2f9c2fef761b');
|
||||
INSERT INTO slot VALUES(X'd53ec0272aef4b41823bf500932d6283',6,'12:00:00.0','13:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'76dc52e48cd4429c8c8f48ad9e3643bb');
|
||||
INSERT INTO slot VALUES(X'70fb58cf2f674f099ac3dddb8ee5cbc2',6,'13:00:00.0','14:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'197021b8f291497282d9c1f597b5ffeb');
|
||||
INSERT INTO slot VALUES(X'c9ff96df42464a18beac02ae7790fb37',6,'14:00:00.0','15:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'd5f2bc5d014a4387afc79085a6bd3b01');
|
||||
INSERT INTO slot VALUES(X'6a2537e618f94658abea65504da38325',6,'15:00:00.0','16:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'60b24f0a3dc64634bfaf0e86912abe6b');
|
||||
INSERT INTO slot VALUES(X'c4cfc6f8f2c14b7596392bd878c3fe2a',5,'10:00:00.0','11:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'a58e50f7996a47dbbb52d345ce77b02a');
|
||||
INSERT INTO slot VALUES(X'194f4a66275f4529a65db8995f225afb',5,'11:00:00.0','12:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'321cb7911dab4d189d371317d853e5ab');
|
||||
INSERT INTO slot VALUES(X'd59417fe72244e189a21f28c4e7d52cc',5,'12:00:00.0','13:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'b531a3180b8948f8b2d134b2ceacf6ac');
|
||||
INSERT INTO slot VALUES(X'08d74382d32c404ca5f796c109f00787',5,'13:00:00.0','14:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'578a705492c04327afbde317de6624a0');
|
||||
INSERT INTO slot VALUES(X'bbea533beaab463bafe80e40a69c8393',5,'14:00:00.0','15:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'c38361f42db9491ca62858ea0beddb50');
|
||||
INSERT INTO slot VALUES(X'35f629542e3942b081bd0b1b214d5b59',5,'15:00:00.0','16:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'76edd2f0214d4ffeb0e0d32aa668f248');
|
||||
INSERT INTO slot VALUES(X'8720cc55e3954470be7b977c1e31719f',5,'16:00:00.0','17:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'cc5f682b46b84c839bd5b62269422637');
|
||||
INSERT INTO slot VALUES(X'345968cebe6c477f9650d81bd28e226a',5,'17:00:00.0','18:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'95feede9d4ab4d0ca5c14a564b4ee4f4');
|
||||
INSERT INTO slot VALUES(X'3afe8ab64c034c71bfe8b95ea1872d2b',5,'18:00:00.0','19:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'60bf3c7046334ce7b6ccde20f2768c25');
|
||||
INSERT INTO slot VALUES(X'68f62933dead423d8e11c19911a3bc57',5,'19:00:00.0','19:30:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'c9f4bf0f0c07416391f7893da531009b');
|
||||
INSERT INTO slot VALUES(X'e38a96be82fc4e8890609bf1326ed377',4,'10:00:00.0','11:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'ed81821119be4dffb1266e29f5dcf5d6');
|
||||
INSERT INTO slot VALUES(X'96d0aad4dc334ceb87c4ac233e0c7c88',6,'10:00:00.0','11:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'7b5c1d9ec707472abf25a9f224ee41d1');
|
||||
INSERT INTO slot VALUES(X'b07a606213204cee89109c3140f676b8',4,'11:00:00.0','12:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'bded643784b444118ea427821be72f0e');
|
||||
INSERT INTO slot VALUES(X'de904c71fc3f4ec2a5e8104e99754fdc',4,'12:00:00.0','13:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'3a0375b7f97c47198db697de983de9fc');
|
||||
INSERT INTO slot VALUES(X'87f3e28c0da1443593835f3aed078c1c',4,'13:00:00.0','14:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'bac09f2e9e174c4284585f72fd09f343');
|
||||
INSERT INTO slot VALUES(X'81df50ac841e46f18fc7b929c4a83d58',4,'14:00:00.0','15:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'53a09581911446a5b2a75a10db9e34e2');
|
||||
INSERT INTO slot VALUES(X'c90300ba0f3446099eba97244267cc2c',4,'15:00:00.0','16:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'0e554ef38f3a4306b2318bd8237ef595');
|
||||
INSERT INTO slot VALUES(X'96aad0153a794bc4a90195b7ac8b67d5',4,'16:00:00.0','17:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'bb314bf0d8b64960b8a91f72717de972');
|
||||
INSERT INTO slot VALUES(X'844d9ca424c0410798a67518540b1bb7',4,'17:00:00.0','18:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'6040220400a644ef8fbe867e92363a86');
|
||||
INSERT INTO slot VALUES(X'62d008388f274da2a6ba0cbf9b2da9f1',4,'18:00:00.0','19:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'46bc35a8fa45423cb8f5dc44d639f95e');
|
||||
INSERT INTO slot VALUES(X'06525073a2e1474faf43d013654f1219',4,'19:00:00.0','19:30:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'63affcfa76f8496ba3e93441d085f607');
|
||||
INSERT INTO slot VALUES(X'0141437ecfed44dab8e921fd24b3f960',3,'10:00:00.0','11:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'c5cee643e99e421493eda930b33a9e12');
|
||||
INSERT INTO slot VALUES(X'fd190d07a228449d96778454c4b7d4e7',3,'11:00:00.0','12:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'60b766ff2229400d95eebc8c101b509d');
|
||||
INSERT INTO slot VALUES(X'c254812651734677b7fedca933405920',3,'12:00:00.0','13:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'32a5c06431a046b09fc3a6b6f701dc7f');
|
||||
INSERT INTO slot VALUES(X'68efe36f94084de0b409655239a6488e',3,'13:00:00.0','14:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'2781e182e09e49bd88fa1531fbe5ff3a');
|
||||
INSERT INTO slot VALUES(X'4ea95b27fe1c402aa5a6e45141fc4a9b',3,'14:00:00.0','15:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'ac7b30f3379d4d7696731f55da78df11');
|
||||
INSERT INTO slot VALUES(X'a001bb86efab4f2fbe817cbb065be1da',3,'15:00:00.0','16:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'b912efd3b92c4535962b99d95ab6fee2');
|
||||
INSERT INTO slot VALUES(X'af02ed2b3fee48928db7b58c11df0d75',3,'16:00:00.0','17:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'5d42c8a871c54cd79cc56172d7c5deb2');
|
||||
INSERT INTO slot VALUES(X'69f19afd18e044d0b75776f2f8704df1',3,'17:00:00.0','18:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'5bc60267cc5149ae9cbda0f89cd43b51');
|
||||
INSERT INTO slot VALUES(X'af973478b5c54362863385b39967c8e0',3,'18:00:00.0','19:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'5c4d18709a9b4f8097e16511f96f7415');
|
||||
INSERT INTO slot VALUES(X'72b3086cb7db4a7cbe21c0a2b87be249',3,'19:00:00.0','19:30:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'b8e08ba8c6a047db8954c6e63ef33d35');
|
||||
INSERT INTO slot VALUES(X'16b777bec4434e69a255665adcdd2977',2,'10:00:00.0','11:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'1d8947cf33f246159fe61c2472ecf20a');
|
||||
INSERT INTO slot VALUES(X'47fd87b94aab45848ca8fe3ace7e1469',2,'11:00:00.0','12:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'4c675d9d0ea642f6b75d102a2dfccf2b');
|
||||
INSERT INTO slot VALUES(X'81d4f730bc6941338788a078d4f3fc33',2,'12:00:00.0','13:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'446fd4ecfd354f498037ad7a014245ca');
|
||||
INSERT INTO slot VALUES(X'ea2ec8d628184d50b820de050f291efc',2,'13:00:00.0','14:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'55ce7330b09545f3b640a163552cd421');
|
||||
INSERT INTO slot VALUES(X'1cabdbf2c66a413895fa24c157e68cff',2,'14:00:00.0','15:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'f330de0dcb2e4fccb57919aaa5b2e8af');
|
||||
INSERT INTO slot VALUES(X'197f64b7ff294b3ea9fcea6aa3c2335d',2,'15:00:00.0','16:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'484f6a08b70042debab12ae44cca3bc5');
|
||||
INSERT INTO slot VALUES(X'959a15a2e77f4f8085f84016befdf588',2,'16:00:00.0','17:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'0e35c7310b734d508cfee3774f71716e');
|
||||
INSERT INTO slot VALUES(X'4a9b6cdccdd94e14a221af4f07149e03',2,'17:00:00.0','18:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'0f36127f96e94dd0918edc11d3aa3fe2');
|
||||
INSERT INTO slot VALUES(X'a90a970739b94f439a0a46f9134b3aa3',2,'18:00:00.0','19:00:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'bc9fe5894e3b4327afd2831e0842c9c7');
|
||||
INSERT INTO slot VALUES(X'bf5245e03bef43b580c1fe245de4a8a9',2,'19:00:00.0','19:30:00.0','2020-01-01',NULL,NULL,NULL,'slot-service',X'b08db53d35844c9ea36f7e63389dd727');
|
||||
|
|
@ -221,3 +221,191 @@ impl From<&SlotTO> for service::slot::Slot {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ShortEmployeeReportTO {
|
||||
pub sales_person: SalesPersonTO,
|
||||
pub balance_hours: f32,
|
||||
}
|
||||
|
||||
impl From<&service::reporting::ShortEmployeeReport> for ShortEmployeeReportTO {
|
||||
fn from(report: &service::reporting::ShortEmployeeReport) -> Self {
|
||||
Self {
|
||||
sales_person: SalesPersonTO::from(report.sales_person.as_ref()),
|
||||
balance_hours: report.balance_hours,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum ExtraHoursCategoryTO {
|
||||
Shiftplan,
|
||||
ExtraWork,
|
||||
Vacation,
|
||||
SickLeave,
|
||||
Holiday,
|
||||
}
|
||||
impl From<&service::reporting::ExtraHoursCategory> for ExtraHoursCategoryTO {
|
||||
fn from(category: &service::reporting::ExtraHoursCategory) -> Self {
|
||||
match category {
|
||||
service::reporting::ExtraHoursCategory::Shiftplan => Self::Shiftplan,
|
||||
service::reporting::ExtraHoursCategory::ExtraWork => Self::ExtraWork,
|
||||
service::reporting::ExtraHoursCategory::Vacation => Self::Vacation,
|
||||
service::reporting::ExtraHoursCategory::SickLeave => Self::SickLeave,
|
||||
service::reporting::ExtraHoursCategory::Holiday => Self::Holiday,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WorkingHoursDayTO {
|
||||
pub date: time::Date,
|
||||
pub hours: f32,
|
||||
pub category: ExtraHoursCategoryTO,
|
||||
}
|
||||
impl From<&service::reporting::WorkingHoursDay> for WorkingHoursDayTO {
|
||||
fn from(day: &service::reporting::WorkingHoursDay) -> Self {
|
||||
Self {
|
||||
date: day.date,
|
||||
hours: day.hours,
|
||||
category: (&day.category).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WorkingHoursReportTO {
|
||||
pub from: time::Date,
|
||||
pub to: time::Date,
|
||||
pub expected_hours: f32,
|
||||
pub overall_hours: f32,
|
||||
pub balance: f32,
|
||||
|
||||
pub shiftplan_hours: f32,
|
||||
pub extra_work_hours: f32,
|
||||
pub vacation_hours: f32,
|
||||
pub sick_leave_hours: f32,
|
||||
pub holiday_hours: f32,
|
||||
|
||||
pub days: Arc<[WorkingHoursDayTO]>,
|
||||
}
|
||||
|
||||
impl From<&service::reporting::WorkingHours> for WorkingHoursReportTO {
|
||||
fn from(hours: &service::reporting::WorkingHours) -> Self {
|
||||
Self {
|
||||
from: hours.from,
|
||||
to: hours.to,
|
||||
expected_hours: hours.expected_hours,
|
||||
overall_hours: hours.overall_hours,
|
||||
balance: hours.balance,
|
||||
shiftplan_hours: hours.shiftplan_hours,
|
||||
extra_work_hours: hours.extra_work_hours,
|
||||
vacation_hours: hours.vacation_hours,
|
||||
sick_leave_hours: hours.sick_leave_hours,
|
||||
holiday_hours: hours.holiday_hours,
|
||||
days: hours
|
||||
.days
|
||||
.iter()
|
||||
.map(|day| WorkingHoursDayTO::from(day))
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct EmployeeReportTO {
|
||||
pub sales_person: Arc<SalesPersonTO>,
|
||||
|
||||
pub balance_hours: f32,
|
||||
pub overall_hours: f32,
|
||||
pub expected_hours: f32,
|
||||
|
||||
pub shiftplan_hours: f32,
|
||||
pub extra_work_hours: f32,
|
||||
pub vacation_hours: f32,
|
||||
pub sick_leave_hours: f32,
|
||||
pub holiday_hours: f32,
|
||||
|
||||
pub by_week: Arc<[WorkingHoursReportTO]>,
|
||||
pub by_month: Arc<[WorkingHoursReportTO]>,
|
||||
}
|
||||
|
||||
impl From<&service::reporting::EmployeeReport> for EmployeeReportTO {
|
||||
fn from(report: &service::reporting::EmployeeReport) -> Self {
|
||||
Self {
|
||||
sales_person: Arc::new(SalesPersonTO::from(report.sales_person.as_ref())),
|
||||
balance_hours: report.balance_hours,
|
||||
overall_hours: report.overall_hours,
|
||||
expected_hours: report.expected_hours,
|
||||
shiftplan_hours: report.shiftplan_hours,
|
||||
extra_work_hours: report.extra_work_hours,
|
||||
vacation_hours: report.vacation_hours,
|
||||
sick_leave_hours: report.sick_leave_hours,
|
||||
holiday_hours: report.holiday_hours,
|
||||
by_week: report
|
||||
.by_week
|
||||
.iter()
|
||||
.map(|hours| WorkingHoursReportTO::from(hours))
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
by_month: report
|
||||
.by_month
|
||||
.iter()
|
||||
.map(|hours| WorkingHoursReportTO::from(hours))
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct WorkingHoursTO {
|
||||
#[serde(default)]
|
||||
pub id: Uuid,
|
||||
pub sales_person_id: Uuid,
|
||||
pub expected_hours: f32,
|
||||
pub from_calendar_week: u8,
|
||||
pub from_year: u32,
|
||||
pub to_calendar_week: u8,
|
||||
pub to_year: u32,
|
||||
#[serde(default)]
|
||||
pub created: Option<time::PrimitiveDateTime>,
|
||||
#[serde(default)]
|
||||
pub deleted: Option<time::PrimitiveDateTime>,
|
||||
#[serde(rename = "$version")]
|
||||
#[serde(default)]
|
||||
pub version: Uuid,
|
||||
}
|
||||
impl From<&service::working_hours::WorkingHours> for WorkingHoursTO {
|
||||
fn from(working_hours: &service::working_hours::WorkingHours) -> Self {
|
||||
Self {
|
||||
id: working_hours.id,
|
||||
sales_person_id: working_hours.sales_person_id,
|
||||
expected_hours: working_hours.expected_hours,
|
||||
from_calendar_week: working_hours.from_calendar_week,
|
||||
from_year: working_hours.from_year,
|
||||
to_calendar_week: working_hours.to_calendar_week,
|
||||
to_year: working_hours.to_year,
|
||||
created: working_hours.created,
|
||||
deleted: working_hours.deleted,
|
||||
version: working_hours.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&WorkingHoursTO> for service::working_hours::WorkingHours {
|
||||
fn from(working_hours: &WorkingHoursTO) -> Self {
|
||||
Self {
|
||||
id: working_hours.id,
|
||||
sales_person_id: working_hours.sales_person_id,
|
||||
expected_hours: working_hours.expected_hours,
|
||||
from_calendar_week: working_hours.from_calendar_week,
|
||||
from_year: working_hours.from_year,
|
||||
to_calendar_week: working_hours.to_calendar_week,
|
||||
to_year: working_hours.to_year,
|
||||
created: working_hours.created,
|
||||
deleted: working_hours.deleted,
|
||||
version: working_hours.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ use std::{convert::Infallible, sync::Arc};
|
|||
|
||||
mod booking;
|
||||
mod permission;
|
||||
mod report;
|
||||
mod sales_person;
|
||||
mod slot;
|
||||
mod working_hours;
|
||||
|
||||
#[cfg(feature = "oidc")]
|
||||
use axum::error_handling::HandleErrorLayer;
|
||||
|
|
@ -199,6 +201,12 @@ fn error_handler(result: Result<Response, RestError>) -> Response {
|
|||
.body(Body::new(err.to_string()))
|
||||
.unwrap()
|
||||
}
|
||||
Err(RestError::ServiceError(err @ service::ServiceError::TimeComponentRangeError(_))) => {
|
||||
Response::builder()
|
||||
.status(500)
|
||||
.body(Body::new(err.to_string()))
|
||||
.unwrap()
|
||||
}
|
||||
Err(RestError::ServiceError(ServiceError::InternalError)) => Response::builder()
|
||||
.status(500)
|
||||
.body(Body::new("Internal server error".to_string()))
|
||||
|
|
@ -215,12 +223,22 @@ pub trait RestStateDef: Clone + Send + Sync + 'static {
|
|||
+ Sync
|
||||
+ 'static;
|
||||
type BookingService: service::booking::BookingService<Context = Context> + Send + Sync + 'static;
|
||||
type ReportingService: service::reporting::ReportingService<Context = Context>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static;
|
||||
type WorkingHoursService: service::working_hours::WorkingHoursService<Context = Context>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static;
|
||||
|
||||
fn user_service(&self) -> Arc<Self::UserService>;
|
||||
fn permission_service(&self) -> Arc<Self::PermissionService>;
|
||||
fn slot_service(&self) -> Arc<Self::SlotService>;
|
||||
fn sales_person_service(&self) -> Arc<Self::SalesPersonService>;
|
||||
fn booking_service(&self) -> Arc<Self::BookingService>;
|
||||
fn reporting_service(&self) -> Arc<Self::ReportingService>;
|
||||
fn working_hours_service(&self) -> Arc<Self::WorkingHoursService>;
|
||||
}
|
||||
|
||||
pub struct OidcConfig {
|
||||
|
|
@ -324,6 +342,8 @@ pub async fn start_server<RestState: RestStateDef>(rest_state: RestState) {
|
|||
.nest("/slot", slot::generate_route())
|
||||
.nest("/sales-person", sales_person::generate_route())
|
||||
.nest("/booking", booking::generate_route())
|
||||
.nest("/report", report::generate_route())
|
||||
.nest("/working-hours", working_hours::generate_route())
|
||||
.with_state(rest_state)
|
||||
.layer(middleware::from_fn(context_extractor));
|
||||
|
||||
|
|
|
|||
77
rest/src/report.rs
Normal file
77
rest/src/report.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{Path, Query, State},
|
||||
response::Response,
|
||||
routing::get,
|
||||
Extension, Router,
|
||||
};
|
||||
use rest_types::{EmployeeReportTO, ShortEmployeeReportTO};
|
||||
use serde::Deserialize;
|
||||
use service::reporting::ReportingService;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{error_handler, Context, RestStateDef};
|
||||
|
||||
pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
|
||||
Router::new()
|
||||
.route("/", get(get_short_report_for_all::<RestState>))
|
||||
.route("/:id", get(get_report::<RestState>))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct ReportRequest {
|
||||
year: u32,
|
||||
until_week: u8,
|
||||
}
|
||||
|
||||
pub async fn get_short_report_for_all<RestState: RestStateDef>(
|
||||
rest_state: State<RestState>,
|
||||
query: Query<ReportRequest>,
|
||||
Extension(context): Extension<Context>,
|
||||
) -> Response {
|
||||
error_handler(
|
||||
(async {
|
||||
let short_report: Arc<[ShortEmployeeReportTO]> = rest_state
|
||||
.reporting_service()
|
||||
.get_reports_for_all_employees(query.year, query.until_week, context.into())
|
||||
.await?
|
||||
.iter()
|
||||
.map(ShortEmployeeReportTO::from)
|
||||
.collect();
|
||||
Ok(Response::builder()
|
||||
.status(200)
|
||||
.body(Body::new(serde_json::to_string(&short_report).unwrap()))
|
||||
.unwrap())
|
||||
})
|
||||
.await,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get_report<RestState: RestStateDef>(
|
||||
rest_state: State<RestState>,
|
||||
query: Query<ReportRequest>,
|
||||
Path(sales_person_id): Path<Uuid>,
|
||||
Extension(context): Extension<Context>,
|
||||
) -> Response {
|
||||
error_handler(
|
||||
(async {
|
||||
let report: EmployeeReportTO = (&rest_state
|
||||
.reporting_service()
|
||||
.get_report_for_employee(
|
||||
&sales_person_id,
|
||||
query.year,
|
||||
query.until_week,
|
||||
context.into(),
|
||||
)
|
||||
.await?)
|
||||
.into();
|
||||
Ok(Response::builder()
|
||||
.status(200)
|
||||
.body(Body::new(serde_json::to_string(&report).unwrap()))
|
||||
.unwrap())
|
||||
})
|
||||
.await,
|
||||
)
|
||||
}
|
||||
34
rest/src/working_hours.rs
Normal file
34
rest/src/working_hours.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use axum::{
|
||||
body::Body, extract::State, response::Response, routing::post, Extension, Json, Router,
|
||||
};
|
||||
use rest_types::WorkingHoursTO;
|
||||
|
||||
use service::working_hours::WorkingHoursService;
|
||||
|
||||
use crate::{error_handler, Context, RestStateDef};
|
||||
|
||||
pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
|
||||
Router::new().route("/", post(create_working_hours::<RestState>))
|
||||
}
|
||||
|
||||
pub async fn create_working_hours<RestState: RestStateDef>(
|
||||
rest_state: State<RestState>,
|
||||
Extension(context): Extension<Context>,
|
||||
Json(working_hours): Json<WorkingHoursTO>,
|
||||
) -> Response {
|
||||
error_handler(
|
||||
(async {
|
||||
let working_hours = WorkingHoursTO::from(
|
||||
&rest_state
|
||||
.working_hours_service()
|
||||
.create(&(&working_hours).into(), context.into())
|
||||
.await?,
|
||||
);
|
||||
Ok(Response::builder()
|
||||
.status(200)
|
||||
.body(Body::new(serde_json::to_string(&working_hours).unwrap()))
|
||||
.unwrap())
|
||||
})
|
||||
.await,
|
||||
)
|
||||
}
|
||||
68
service/src/datetime_utils.rs
Normal file
68
service/src/datetime_utils.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use time::{error::ComponentRange, Date};
|
||||
|
||||
use crate::slot::DayOfWeek;
|
||||
|
||||
pub fn calenar_week_to_date(
|
||||
year: i32,
|
||||
week: u8,
|
||||
day_of_week: DayOfWeek,
|
||||
) -> Result<Date, ComponentRange> {
|
||||
Date::from_iso_week_date(year, week, day_of_week.into())
|
||||
}
|
||||
|
||||
pub fn date_to_calendar_week(date: Date) -> (i32, u8, DayOfWeek) {
|
||||
let year = date.year();
|
||||
let week = date.iso_week();
|
||||
let day_of_week = DayOfWeek::from(date.weekday());
|
||||
|
||||
(year, week, day_of_week)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
|
||||
pub struct CalendarWeek {
|
||||
pub year: i32,
|
||||
pub week: u8,
|
||||
}
|
||||
|
||||
pub trait AsCalendarWeek {
|
||||
fn as_date(&self) -> CalendarWeek;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, PartialOrd, Ord)]
|
||||
pub struct Month {
|
||||
pub year: i32,
|
||||
pub month: u8,
|
||||
}
|
||||
pub trait AsMonth {
|
||||
fn as_date(&self) -> Month;
|
||||
}
|
||||
|
||||
pub fn group_by_calendar_week<T: AsCalendarWeek + Clone>(
|
||||
items: &[T],
|
||||
) -> BTreeMap<CalendarWeek, Arc<[T]>> {
|
||||
let mut map = BTreeMap::new();
|
||||
for item in items {
|
||||
let calendar_week = item.as_date();
|
||||
map.entry(calendar_week)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(item.to_owned());
|
||||
}
|
||||
map.into_iter()
|
||||
.map(|(calendar_week, items)| (calendar_week, items.into()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn group_by_month<T: AsMonth + Clone>(items: &[T]) -> BTreeMap<Month, Arc<[T]>> {
|
||||
let mut map = BTreeMap::new();
|
||||
for item in items {
|
||||
let month = item.as_date();
|
||||
map.entry(month)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(item.to_owned());
|
||||
}
|
||||
map.into_iter()
|
||||
.map(|(month, items)| (month, items.into()))
|
||||
.collect()
|
||||
}
|
||||
86
service/src/extra_hours.rs
Normal file
86
service/src/extra_hours.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mockall::automock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use dao::DaoError;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ExtraHoursCategory {
|
||||
ExtraWork,
|
||||
Vacation,
|
||||
SickLeave,
|
||||
Holiday,
|
||||
}
|
||||
impl From<&dao::extra_hours::ExtraHoursCategoryEntity> for ExtraHoursCategory {
|
||||
fn from(category: &dao::extra_hours::ExtraHoursCategoryEntity) -> Self {
|
||||
match category {
|
||||
dao::extra_hours::ExtraHoursCategoryEntity::ExtraWork => Self::ExtraWork,
|
||||
dao::extra_hours::ExtraHoursCategoryEntity::Vacation => Self::Vacation,
|
||||
dao::extra_hours::ExtraHoursCategoryEntity::SickLeave => Self::SickLeave,
|
||||
dao::extra_hours::ExtraHoursCategoryEntity::Holiday => Self::Holiday,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&ExtraHoursCategory> for dao::extra_hours::ExtraHoursCategoryEntity {
|
||||
fn from(category: &ExtraHoursCategory) -> Self {
|
||||
match category {
|
||||
ExtraHoursCategory::ExtraWork => Self::ExtraWork,
|
||||
ExtraHoursCategory::Vacation => Self::Vacation,
|
||||
ExtraHoursCategory::SickLeave => Self::SickLeave,
|
||||
ExtraHoursCategory::Holiday => Self::Holiday,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ExtraHours {
|
||||
pub id: Uuid,
|
||||
pub sales_person_id: Uuid,
|
||||
pub amount: f32,
|
||||
pub category: ExtraHoursCategory,
|
||||
pub description: Arc<str>,
|
||||
pub date_time: time::PrimitiveDateTime,
|
||||
pub deleted: Option<time::PrimitiveDateTime>,
|
||||
}
|
||||
impl From<&dao::extra_hours::ExtraHoursEntity> for ExtraHours {
|
||||
fn from(extra_hours: &dao::extra_hours::ExtraHoursEntity) -> 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,
|
||||
deleted: extra_hours.deleted,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&ExtraHours> for dao::extra_hours::ExtraHoursEntity {
|
||||
fn from(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,
|
||||
deleted: extra_hours.deleted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[automock]
|
||||
#[async_trait]
|
||||
pub trait ExtraHoursService {
|
||||
fn find_by_sales_person_id_and_year(
|
||||
&self,
|
||||
sales_person_id: Uuid,
|
||||
year: u32,
|
||||
until_week: u8,
|
||||
) -> Result<Arc<[ExtraHours]>, 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>;
|
||||
}
|
||||
|
|
@ -6,11 +6,15 @@ use uuid::Uuid;
|
|||
|
||||
pub mod booking;
|
||||
pub mod clock;
|
||||
pub mod datetime_utils;
|
||||
pub mod extra_hours;
|
||||
pub mod permission;
|
||||
pub mod reporting;
|
||||
pub mod sales_person;
|
||||
pub mod slot;
|
||||
pub mod user_service;
|
||||
pub mod uuid_service;
|
||||
pub mod working_hours;
|
||||
|
||||
pub use permission::MockPermissionService;
|
||||
pub use permission::PermissionService;
|
||||
|
|
@ -64,6 +68,9 @@ pub enum ServiceError {
|
|||
#[error("Date order wrong. {0} must is not smaller or equal to {1}")]
|
||||
DateOrderWrong(Date, Date),
|
||||
|
||||
#[error("Time component range error: {0}")]
|
||||
TimeComponentRangeError(#[from] time::error::ComponentRange),
|
||||
|
||||
#[error("Internal error")]
|
||||
InternalError,
|
||||
}
|
||||
|
|
|
|||
99
service/src/reporting.rs
Normal file
99
service/src/reporting.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mockall::automock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::permission::Authentication;
|
||||
use crate::sales_person::SalesPerson;
|
||||
use crate::ServiceError;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ExtraHoursCategory {
|
||||
Shiftplan,
|
||||
ExtraWork,
|
||||
Vacation,
|
||||
SickLeave,
|
||||
Holiday,
|
||||
}
|
||||
|
||||
impl From<&dao::extra_hours::ExtraHoursCategoryEntity> for ExtraHoursCategory {
|
||||
fn from(category: &dao::extra_hours::ExtraHoursCategoryEntity) -> Self {
|
||||
match category {
|
||||
dao::extra_hours::ExtraHoursCategoryEntity::ExtraWork => Self::ExtraWork,
|
||||
dao::extra_hours::ExtraHoursCategoryEntity::Vacation => Self::Vacation,
|
||||
dao::extra_hours::ExtraHoursCategoryEntity::SickLeave => Self::SickLeave,
|
||||
dao::extra_hours::ExtraHoursCategoryEntity::Holiday => Self::Holiday,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct WorkingHoursDay {
|
||||
pub date: time::Date,
|
||||
pub hours: f32,
|
||||
pub category: ExtraHoursCategory,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct WorkingHours {
|
||||
pub from: time::Date,
|
||||
pub to: time::Date,
|
||||
pub expected_hours: f32,
|
||||
pub overall_hours: f32,
|
||||
pub balance: f32,
|
||||
|
||||
pub shiftplan_hours: f32,
|
||||
pub extra_work_hours: f32,
|
||||
pub vacation_hours: f32,
|
||||
pub sick_leave_hours: f32,
|
||||
pub holiday_hours: f32,
|
||||
|
||||
pub days: Arc<[WorkingHoursDay]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ShortEmployeeReport {
|
||||
pub sales_person: Arc<SalesPerson>,
|
||||
pub balance_hours: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct EmployeeReport {
|
||||
pub sales_person: Arc<SalesPerson>,
|
||||
|
||||
pub balance_hours: f32,
|
||||
pub overall_hours: f32,
|
||||
pub expected_hours: f32,
|
||||
|
||||
pub shiftplan_hours: f32,
|
||||
pub extra_work_hours: f32,
|
||||
pub vacation_hours: f32,
|
||||
pub sick_leave_hours: f32,
|
||||
pub holiday_hours: f32,
|
||||
|
||||
pub by_week: Arc<[WorkingHours]>,
|
||||
pub by_month: Arc<[WorkingHours]>,
|
||||
}
|
||||
|
||||
#[automock(type Context=();)]
|
||||
#[async_trait]
|
||||
pub trait ReportingService {
|
||||
type Context: Clone + Debug + PartialEq + Eq + Send + Sync + 'static;
|
||||
|
||||
async fn get_reports_for_all_employees(
|
||||
&self,
|
||||
years: u32,
|
||||
until_week: u8,
|
||||
context: Authentication<Self::Context>,
|
||||
) -> Result<Arc<[ShortEmployeeReport]>, ServiceError>;
|
||||
|
||||
async fn get_report_for_employee(
|
||||
&self,
|
||||
sales_person_id: &Uuid,
|
||||
years: u32,
|
||||
until_week: u8,
|
||||
context: Authentication<Self::Context>,
|
||||
) -> Result<EmployeeReport, ServiceError>;
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ use async_trait::async_trait;
|
|||
use mockall::automock;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use time::Weekday;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::permission::Authentication;
|
||||
|
|
@ -43,6 +44,32 @@ impl From<DayOfWeek> for dao::slot::DayOfWeek {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl From<Weekday> for DayOfWeek {
|
||||
fn from(weekday: Weekday) -> Self {
|
||||
match weekday {
|
||||
Weekday::Monday => Self::Monday,
|
||||
Weekday::Tuesday => Self::Tuesday,
|
||||
Weekday::Wednesday => Self::Wednesday,
|
||||
Weekday::Thursday => Self::Thursday,
|
||||
Weekday::Friday => Self::Friday,
|
||||
Weekday::Saturday => Self::Saturday,
|
||||
Weekday::Sunday => Self::Sunday,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<DayOfWeek> for Weekday {
|
||||
fn from(day_of_week: DayOfWeek) -> Self {
|
||||
match day_of_week {
|
||||
DayOfWeek::Monday => Self::Monday,
|
||||
DayOfWeek::Tuesday => Self::Tuesday,
|
||||
DayOfWeek::Wednesday => Self::Wednesday,
|
||||
DayOfWeek::Thursday => Self::Thursday,
|
||||
DayOfWeek::Friday => Self::Friday,
|
||||
DayOfWeek::Saturday => Self::Saturday,
|
||||
DayOfWeek::Sunday => Self::Sunday,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Slot {
|
||||
|
|
|
|||
85
service/src/working_hours.rs
Normal file
85
service/src/working_hours.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mockall::automock;
|
||||
use time::PrimitiveDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::permission::Authentication;
|
||||
use crate::ServiceError;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct WorkingHours {
|
||||
pub id: Uuid,
|
||||
pub sales_person_id: Uuid,
|
||||
pub expected_hours: f32,
|
||||
pub from_calendar_week: u8,
|
||||
pub from_year: u32,
|
||||
pub to_calendar_week: u8,
|
||||
pub to_year: u32,
|
||||
pub created: Option<PrimitiveDateTime>,
|
||||
pub deleted: Option<PrimitiveDateTime>,
|
||||
pub version: Uuid,
|
||||
}
|
||||
impl From<&dao::working_hours::WorkingHoursEntity> for WorkingHours {
|
||||
fn from(working_hours: &dao::working_hours::WorkingHoursEntity) -> Self {
|
||||
Self {
|
||||
id: working_hours.id,
|
||||
sales_person_id: working_hours.sales_person_id,
|
||||
expected_hours: working_hours.expected_hours,
|
||||
from_calendar_week: working_hours.from_calendar_week,
|
||||
from_year: working_hours.from_year,
|
||||
to_calendar_week: working_hours.to_calendar_week,
|
||||
to_year: working_hours.to_year,
|
||||
created: Some(working_hours.created),
|
||||
deleted: working_hours.deleted,
|
||||
version: working_hours.version,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<&WorkingHours> for dao::working_hours::WorkingHoursEntity {
|
||||
type Error = ServiceError;
|
||||
fn try_from(working_hours: &WorkingHours) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
id: working_hours.id,
|
||||
sales_person_id: working_hours.sales_person_id,
|
||||
expected_hours: working_hours.expected_hours,
|
||||
from_calendar_week: working_hours.from_calendar_week,
|
||||
from_year: working_hours.from_year,
|
||||
to_calendar_week: working_hours.to_calendar_week,
|
||||
to_year: working_hours.to_year,
|
||||
created: working_hours
|
||||
.created
|
||||
.ok_or_else(|| ServiceError::InternalError)?,
|
||||
deleted: working_hours.deleted,
|
||||
version: working_hours.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[automock(type Context=();)]
|
||||
#[async_trait]
|
||||
pub trait WorkingHoursService {
|
||||
type Context: Clone + Debug + PartialEq + Eq + Send + Sync + 'static;
|
||||
|
||||
async fn all(
|
||||
&self,
|
||||
context: Authentication<Self::Context>,
|
||||
) -> Result<Arc<[WorkingHours]>, ServiceError>;
|
||||
async fn find_by_sales_person_id(
|
||||
&self,
|
||||
sales_person_id: Uuid,
|
||||
context: Authentication<Self::Context>,
|
||||
) -> Result<Arc<[WorkingHours]>, ServiceError>;
|
||||
async fn create(
|
||||
&self,
|
||||
entity: &WorkingHours,
|
||||
context: Authentication<Self::Context>,
|
||||
) -> Result<WorkingHours, ServiceError>;
|
||||
async fn update(
|
||||
&self,
|
||||
entity: &WorkingHours,
|
||||
context: Authentication<Self::Context>,
|
||||
) -> Result<WorkingHours, ServiceError>;
|
||||
}
|
||||
|
|
@ -5,10 +5,12 @@ use async_trait::async_trait;
|
|||
pub mod booking;
|
||||
pub mod clock;
|
||||
pub mod permission;
|
||||
pub mod reporting;
|
||||
pub mod sales_person;
|
||||
pub mod slot;
|
||||
mod test;
|
||||
pub mod uuid_service;
|
||||
pub mod working_hours;
|
||||
|
||||
pub use permission::PermissionServiceImpl;
|
||||
use service::permission::MockContext;
|
||||
|
|
|
|||
389
service_impl/src/reporting.rs
Normal file
389
service_impl/src/reporting.rs
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use dao::{
|
||||
extra_hours::{ExtraHoursCategoryEntity, ExtraHoursEntity},
|
||||
shiftplan_report::ShiftplanReportEntity,
|
||||
working_hours::WorkingHoursEntity,
|
||||
};
|
||||
use service::{
|
||||
permission::{Authentication, HR_PRIVILEGE},
|
||||
reporting::{
|
||||
EmployeeReport, ExtraHoursCategory, ShortEmployeeReport, WorkingHours, WorkingHoursDay,
|
||||
},
|
||||
ServiceError,
|
||||
};
|
||||
use tokio::join;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct ReportingServiceImpl<
|
||||
ExtraHoursDao,
|
||||
ShiftplanReportDao,
|
||||
WorkingHoursDao,
|
||||
SalesPersonService,
|
||||
PermissionService,
|
||||
ClockService,
|
||||
UuidService,
|
||||
> where
|
||||
ExtraHoursDao: dao::extra_hours::ExtraHoursDao + Send + Sync,
|
||||
ShiftplanReportDao: dao::shiftplan_report::ShiftplanReportDao + Send + Sync,
|
||||
WorkingHoursDao: dao::working_hours::WorkingHoursDao + Send + Sync,
|
||||
SalesPersonService: service::sales_person::SalesPersonService + Send + Sync,
|
||||
PermissionService: service::permission::PermissionService + Send + Sync,
|
||||
ClockService: service::clock::ClockService + Send + Sync,
|
||||
UuidService: service::uuid_service::UuidService + Send + Sync,
|
||||
{
|
||||
pub extra_hours_dao: Arc<ExtraHoursDao>,
|
||||
pub shiftplan_report_dao: Arc<ShiftplanReportDao>,
|
||||
pub working_hours_dao: Arc<WorkingHoursDao>,
|
||||
pub sales_person_service: Arc<SalesPersonService>,
|
||||
pub permission_service: Arc<PermissionService>,
|
||||
pub clock_service: Arc<ClockService>,
|
||||
pub uuid_service: Arc<UuidService>,
|
||||
}
|
||||
|
||||
impl<
|
||||
ExtraHoursDao,
|
||||
ShiftplanReportDao,
|
||||
WorkingHoursDao,
|
||||
SalesPersonService,
|
||||
PermissionService,
|
||||
ClockService,
|
||||
UuidService,
|
||||
>
|
||||
ReportingServiceImpl<
|
||||
ExtraHoursDao,
|
||||
ShiftplanReportDao,
|
||||
WorkingHoursDao,
|
||||
SalesPersonService,
|
||||
PermissionService,
|
||||
ClockService,
|
||||
UuidService,
|
||||
>
|
||||
where
|
||||
ExtraHoursDao: dao::extra_hours::ExtraHoursDao + Send + Sync,
|
||||
ShiftplanReportDao: dao::shiftplan_report::ShiftplanReportDao + Send + Sync,
|
||||
WorkingHoursDao: dao::working_hours::WorkingHoursDao + Send + Sync,
|
||||
SalesPersonService: service::sales_person::SalesPersonService + Send + Sync,
|
||||
PermissionService: service::permission::PermissionService + Send + Sync,
|
||||
ClockService: service::clock::ClockService + Send + Sync,
|
||||
UuidService: service::uuid_service::UuidService + Send + Sync,
|
||||
{
|
||||
pub fn new(
|
||||
extra_hours_dao: Arc<ExtraHoursDao>,
|
||||
shiftplan_report_dao: Arc<ShiftplanReportDao>,
|
||||
working_hours_dao: Arc<WorkingHoursDao>,
|
||||
sales_person_service: Arc<SalesPersonService>,
|
||||
permission_service: Arc<PermissionService>,
|
||||
clock_service: Arc<ClockService>,
|
||||
uuid_service: Arc<UuidService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
extra_hours_dao,
|
||||
shiftplan_report_dao,
|
||||
working_hours_dao,
|
||||
sales_person_service,
|
||||
permission_service,
|
||||
clock_service,
|
||||
uuid_service,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn check_user_is_sales_person<AuthContext>(
|
||||
&self,
|
||||
sales_person_id: Uuid,
|
||||
context: AuthContext,
|
||||
) -> Result<(), ServiceError>
|
||||
where
|
||||
AuthContext: Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ std::fmt::Debug
|
||||
+ Eq
|
||||
+ 'static
|
||||
+ Into<Authentication<SalesPersonService::Context>>,
|
||||
{
|
||||
if let Some(sales_person) = self
|
||||
.sales_person_service
|
||||
.get_sales_person_current_user(context.into())
|
||||
.await?
|
||||
{
|
||||
if sales_person.id == sales_person_id {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ServiceError::Forbidden)
|
||||
}
|
||||
} else {
|
||||
Err(ServiceError::Forbidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_working_hours_for_calendar_week(
|
||||
working_hours: &[WorkingHoursEntity],
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
ExtraHoursDao,
|
||||
ShiftplanReportDao,
|
||||
WorkingHoursDao,
|
||||
SalesPersonService,
|
||||
PermissionService,
|
||||
ClockService,
|
||||
UuidService,
|
||||
> service::reporting::ReportingService
|
||||
for ReportingServiceImpl<
|
||||
ExtraHoursDao,
|
||||
ShiftplanReportDao,
|
||||
WorkingHoursDao,
|
||||
SalesPersonService,
|
||||
PermissionService,
|
||||
ClockService,
|
||||
UuidService,
|
||||
>
|
||||
where
|
||||
ExtraHoursDao: dao::extra_hours::ExtraHoursDao + Send + Sync,
|
||||
ShiftplanReportDao: dao::shiftplan_report::ShiftplanReportDao + Send + Sync,
|
||||
WorkingHoursDao: dao::working_hours::WorkingHoursDao + Send + Sync,
|
||||
SalesPersonService: service::sales_person::SalesPersonService<Context = PermissionService::Context>
|
||||
+ Send
|
||||
+ Sync,
|
||||
PermissionService: service::permission::PermissionService + Send + Sync,
|
||||
ClockService: service::clock::ClockService + Send + Sync,
|
||||
UuidService: service::uuid_service::UuidService + Send + Sync,
|
||||
{
|
||||
type Context = PermissionService::Context;
|
||||
|
||||
async fn get_reports_for_all_employees(
|
||||
&self,
|
||||
year: u32,
|
||||
until_week: u8,
|
||||
context: Authentication<Self::Context>,
|
||||
) -> Result<Arc<[ShortEmployeeReport]>, ServiceError> {
|
||||
self.permission_service
|
||||
.check_permission(HR_PRIVILEGE, context)
|
||||
.await?;
|
||||
|
||||
let shiftplan_report = self
|
||||
.shiftplan_report_dao
|
||||
.extract_quick_shiftplan_report(year, until_week)
|
||||
.await?;
|
||||
|
||||
let working_hours = self.working_hours_dao.all().await?;
|
||||
|
||||
let employees = self
|
||||
.sales_person_service
|
||||
.get_all(Authentication::Full)
|
||||
.await?;
|
||||
let mut short_employee_report: Vec<ShortEmployeeReport> = Vec::new();
|
||||
for paid_employee in employees
|
||||
.iter()
|
||||
.filter(|employee| employee.is_paid.unwrap_or(false))
|
||||
{
|
||||
let shiftplan_hours = shiftplan_report
|
||||
.iter()
|
||||
.filter(|r| r.sales_person_id == paid_employee.id)
|
||||
.map(|r| r.hours)
|
||||
.sum::<f32>();
|
||||
let working_hours: Arc<[WorkingHoursEntity]> = working_hours
|
||||
.iter()
|
||||
.filter(|wh| wh.sales_person_id == paid_employee.id)
|
||||
.cloned()
|
||||
.collect();
|
||||
let planned_hours: f32 = (1..=until_week)
|
||||
.map(|week| {
|
||||
find_working_hours_for_calendar_week(&working_hours, year, week)
|
||||
.map(|wh| wh.expected_hours)
|
||||
.unwrap_or(0.0)
|
||||
})
|
||||
.sum();
|
||||
let extra_hours = self
|
||||
.extra_hours_dao
|
||||
.find_by_sales_person_id_and_year(paid_employee.id, year, until_week)
|
||||
.await?
|
||||
.iter()
|
||||
.map(|eh| eh.amount)
|
||||
.sum::<f32>();
|
||||
let balance_hours = shiftplan_hours + extra_hours - planned_hours;
|
||||
short_employee_report.push(ShortEmployeeReport {
|
||||
sales_person: Arc::new(paid_employee.clone()),
|
||||
balance_hours,
|
||||
});
|
||||
}
|
||||
Ok(short_employee_report.into())
|
||||
}
|
||||
|
||||
async fn get_report_for_employee(
|
||||
&self,
|
||||
sales_person_id: &Uuid,
|
||||
year: u32,
|
||||
until_week: u8,
|
||||
context: Authentication<Self::Context>,
|
||||
) -> Result<EmployeeReport, ServiceError> {
|
||||
let (hr_permission, user_permission) = join!(
|
||||
self.permission_service
|
||||
.check_permission(HR_PRIVILEGE, context.clone()),
|
||||
self.check_user_is_sales_person(*sales_person_id, context.clone())
|
||||
);
|
||||
hr_permission.or(user_permission)?;
|
||||
|
||||
let sales_person = self
|
||||
.sales_person_service
|
||||
.get(*sales_person_id, context)
|
||||
.await?;
|
||||
let working_hours = self
|
||||
.working_hours_dao
|
||||
.find_by_sales_person_id(*sales_person_id)
|
||||
.await?;
|
||||
let shiftplan_report = self
|
||||
.shiftplan_report_dao
|
||||
.extract_shiftplan_report(*sales_person_id, year, until_week)
|
||||
.await?;
|
||||
let extra_hours = self
|
||||
.extra_hours_dao
|
||||
.find_by_sales_person_id_and_year(*sales_person_id, year, until_week)
|
||||
.await?;
|
||||
|
||||
let planned_hours: f32 = (1..=until_week)
|
||||
.map(|week| {
|
||||
find_working_hours_for_calendar_week(&working_hours, year, week)
|
||||
.map(|wh| wh.expected_hours)
|
||||
.unwrap_or(0.0)
|
||||
})
|
||||
.sum();
|
||||
let shiftplan_hours = shiftplan_report.iter().map(|r| r.hours).sum::<f32>() as f32;
|
||||
let overall_extra_hours = extra_hours.iter().map(|eh| eh.amount).sum::<f32>();
|
||||
|
||||
let employee_report = EmployeeReport {
|
||||
sales_person: Arc::new(sales_person),
|
||||
balance_hours: shiftplan_hours + overall_extra_hours - planned_hours,
|
||||
overall_hours: shiftplan_hours + overall_extra_hours,
|
||||
expected_hours: planned_hours,
|
||||
shiftplan_hours,
|
||||
extra_work_hours: extra_hours
|
||||
.iter()
|
||||
.filter(|extra_hours| extra_hours.category == ExtraHoursCategoryEntity::ExtraWork)
|
||||
.map(|extra_hours| extra_hours.amount)
|
||||
.sum(),
|
||||
vacation_hours: extra_hours
|
||||
.iter()
|
||||
.filter(|extra_hours| extra_hours.category == ExtraHoursCategoryEntity::Vacation)
|
||||
.map(|extra_hours| extra_hours.amount)
|
||||
.sum(),
|
||||
sick_leave_hours: extra_hours
|
||||
.iter()
|
||||
.filter(|extra_hours| extra_hours.category == ExtraHoursCategoryEntity::SickLeave)
|
||||
.map(|extra_hours| extra_hours.amount)
|
||||
.sum(),
|
||||
holiday_hours: extra_hours
|
||||
.iter()
|
||||
.filter(|extra_hours| extra_hours.category == ExtraHoursCategoryEntity::Holiday)
|
||||
.map(|extra_hours| extra_hours.amount)
|
||||
.sum(),
|
||||
by_week: hours_per_week(
|
||||
&shiftplan_report,
|
||||
&extra_hours,
|
||||
&working_hours,
|
||||
year,
|
||||
until_week,
|
||||
)?,
|
||||
by_month: Arc::new([]),
|
||||
};
|
||||
|
||||
Ok(employee_report)
|
||||
}
|
||||
}
|
||||
|
||||
fn hours_per_week(
|
||||
shiftplan_hours_list: &Arc<[ShiftplanReportEntity]>,
|
||||
extra_hours_list: &Arc<[ExtraHoursEntity]>,
|
||||
working_hours: &[WorkingHoursEntity],
|
||||
year: u32,
|
||||
week_until: u8,
|
||||
) -> Result<Arc<[WorkingHours]>, ServiceError> {
|
||||
let mut weeks: Vec<WorkingHours> = Vec::new();
|
||||
for week in 1..=week_until {
|
||||
let shiftplan_hours = shiftplan_hours_list
|
||||
.iter()
|
||||
.filter(|r| r.calendar_week == week)
|
||||
.map(|r| r.hours)
|
||||
.sum::<f32>();
|
||||
let working_hours = working_hours
|
||||
.iter()
|
||||
.filter(|wh| wh.from_calendar_week <= week && wh.to_calendar_week >= week)
|
||||
.map(|wh| wh.expected_hours)
|
||||
.sum::<f32>();
|
||||
let extra_hours = extra_hours_list
|
||||
.iter()
|
||||
.filter(|eh| eh.date_time.iso_week() == week)
|
||||
.map(|eh| eh.amount)
|
||||
.sum::<f32>();
|
||||
|
||||
let mut day_list = extra_hours_list
|
||||
.iter()
|
||||
.map(|eh| {
|
||||
Ok(WorkingHoursDay {
|
||||
date: eh.date_time.date(),
|
||||
hours: eh.amount,
|
||||
category: (&eh.category).into(),
|
||||
})
|
||||
})
|
||||
.chain(shiftplan_hours_list.iter().map(|working_hours_day| {
|
||||
Ok::<WorkingHoursDay, ServiceError>(WorkingHoursDay {
|
||||
date: time::Date::from_iso_week_date(
|
||||
year as i32,
|
||||
working_hours_day.calendar_week,
|
||||
time::Weekday::Sunday.nth_next(working_hours_day.day_of_week.to_number()),
|
||||
)?,
|
||||
hours: working_hours_day.hours,
|
||||
category: ExtraHoursCategory::Shiftplan,
|
||||
})
|
||||
}))
|
||||
.collect::<Result<Vec<WorkingHoursDay>, ServiceError>>()?;
|
||||
day_list.sort_by_key(|day| day.date);
|
||||
|
||||
weeks.push(WorkingHours {
|
||||
from: time::Date::from_iso_week_date(year as i32, week, time::Weekday::Monday).unwrap(),
|
||||
to: time::Date::from_iso_week_date(year as i32, week, time::Weekday::Sunday).unwrap(),
|
||||
expected_hours: working_hours,
|
||||
overall_hours: shiftplan_hours + extra_hours,
|
||||
balance: shiftplan_hours + extra_hours - working_hours,
|
||||
shiftplan_hours,
|
||||
extra_work_hours: extra_hours_list
|
||||
.iter()
|
||||
.filter(|eh| eh.category == ExtraHoursCategoryEntity::ExtraWork)
|
||||
.map(|eh| eh.amount)
|
||||
.sum(),
|
||||
vacation_hours: extra_hours_list
|
||||
.iter()
|
||||
.filter(|eh| eh.category == ExtraHoursCategoryEntity::Vacation)
|
||||
.map(|eh| eh.amount)
|
||||
.sum(),
|
||||
sick_leave_hours: extra_hours_list
|
||||
.iter()
|
||||
.filter(|eh| eh.category == ExtraHoursCategoryEntity::SickLeave)
|
||||
.map(|eh| eh.amount)
|
||||
.sum(),
|
||||
holiday_hours: extra_hours_list
|
||||
.iter()
|
||||
.filter(|eh| eh.category == ExtraHoursCategoryEntity::Holiday)
|
||||
.map(|eh| eh.amount)
|
||||
.sum(),
|
||||
days: day_list
|
||||
.iter()
|
||||
.filter(|day| day.date.iso_week() == week && day.date.year() == year as i32)
|
||||
.cloned()
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
Ok(weeks.into())
|
||||
}
|
||||
109
service_impl/src/working_hours.rs
Normal file
109
service_impl/src/working_hours.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use dao::working_hours::WorkingHoursEntity;
|
||||
use service::{
|
||||
permission::{Authentication, HR_PRIVILEGE},
|
||||
working_hours::WorkingHours,
|
||||
ServiceError,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct WorkingHoursServiceImpl<
|
||||
WorkingHoursDao: dao::working_hours::WorkingHoursDao,
|
||||
PermissionService: service::PermissionService,
|
||||
ClockService: service::clock::ClockService,
|
||||
UuidService: service::uuid_service::UuidService,
|
||||
> {
|
||||
working_hours_dao: Arc<WorkingHoursDao>,
|
||||
permission_service: Arc<PermissionService>,
|
||||
clock_service: Arc<ClockService>,
|
||||
uuid_service: Arc<UuidService>,
|
||||
}
|
||||
|
||||
impl<WorkingHoursDao, PermissionService, ClockService, UuidService>
|
||||
WorkingHoursServiceImpl<WorkingHoursDao, PermissionService, ClockService, UuidService>
|
||||
where
|
||||
WorkingHoursDao: dao::working_hours::WorkingHoursDao + Sync + Send,
|
||||
PermissionService: service::PermissionService + Sync + Send,
|
||||
ClockService: service::clock::ClockService + Sync + Send,
|
||||
UuidService: service::uuid_service::UuidService + Sync + Send,
|
||||
{
|
||||
pub fn new(
|
||||
working_hours_dao: Arc<WorkingHoursDao>,
|
||||
permission_service: Arc<PermissionService>,
|
||||
clock_service: Arc<ClockService>,
|
||||
uuid_service: Arc<UuidService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
working_hours_dao,
|
||||
permission_service,
|
||||
clock_service,
|
||||
uuid_service,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<
|
||||
WorkingHoursDao: dao::working_hours::WorkingHoursDao + Sync + Send,
|
||||
PermissionService: service::PermissionService + Sync + Send,
|
||||
ClockService: service::clock::ClockService + Sync + Send,
|
||||
UuidService: service::uuid_service::UuidService + Sync + Send,
|
||||
> service::working_hours::WorkingHoursService
|
||||
for WorkingHoursServiceImpl<WorkingHoursDao, PermissionService, ClockService, UuidService>
|
||||
{
|
||||
type Context = PermissionService::Context;
|
||||
|
||||
async fn all(
|
||||
&self,
|
||||
_context: Authentication<Self::Context>,
|
||||
) -> Result<Arc<[WorkingHours]>, ServiceError> {
|
||||
unimplemented!()
|
||||
}
|
||||
async fn find_by_sales_person_id(
|
||||
&self,
|
||||
_sales_person_id: Uuid,
|
||||
_context: Authentication<Self::Context>,
|
||||
) -> Result<Arc<[WorkingHours]>, ServiceError> {
|
||||
unimplemented!()
|
||||
}
|
||||
async fn create(
|
||||
&self,
|
||||
working_hours: &WorkingHours,
|
||||
context: Authentication<Self::Context>,
|
||||
) -> Result<WorkingHours, ServiceError> {
|
||||
let mut working_hours = working_hours.to_owned();
|
||||
self.permission_service
|
||||
.check_permission(HR_PRIVILEGE, context)
|
||||
.await?;
|
||||
|
||||
working_hours.created = Some(self.clock_service.date_time_now());
|
||||
let mut entity: WorkingHoursEntity = (&working_hours).try_into()?;
|
||||
|
||||
if !entity.id.is_nil() {
|
||||
return Err(ServiceError::IdSetOnCreate);
|
||||
}
|
||||
if !entity.version.is_nil() {
|
||||
return Err(ServiceError::VersionSetOnCreate);
|
||||
}
|
||||
entity.id = self
|
||||
.uuid_service
|
||||
.new_uuid("working-hours-service::create id");
|
||||
entity.version = self
|
||||
.uuid_service
|
||||
.new_uuid("working-hours-service::create version");
|
||||
self.working_hours_dao
|
||||
.create(&entity, "working-hours-service::create")
|
||||
.await?;
|
||||
|
||||
Ok(WorkingHours::from(&entity))
|
||||
}
|
||||
async fn update(
|
||||
&self,
|
||||
_entity: &WorkingHours,
|
||||
_context: Authentication<Self::Context>,
|
||||
) -> Result<WorkingHours, ServiceError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue