Add basic employee hour balance report
This commit is contained in:
parent
0eb885216a
commit
d4adcb182f
31 changed files with 2155 additions and 5 deletions
|
|
@ -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