Add basic employee hour balance report
This commit is contained in:
parent
0eb885216a
commit
d4adcb182f
31 changed files with 2155 additions and 5 deletions
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>;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue