Add basic employee hour balance report

This commit is contained in:
Simon Goller 2024-06-23 18:12:54 +02:00
parent 0eb885216a
commit d4adcb182f
31 changed files with 2155 additions and 5 deletions

View 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()
}

View 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>;
}

View file

@ -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
View 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>;
}

View file

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

View 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>;
}