Compare commits

...

10 commits

Author SHA1 Message Date
Simon Goller
724759d1d8 Set version to 0.1.5
Some checks failed
Rust / build (push) Failing after 41s
2024-06-28 15:05:54 +02:00
Simon Goller
e8131196aa Update version to 0.1.5-dev 2024-06-28 08:33:22 +02:00
Simon Goller
9e932e1aca Fix deleting extra hours 2024-06-27 21:05:42 +02:00
Simon Goller
7825a2d172 Set version to 0.1.4 2024-06-27 14:55:32 +02:00
Simon Goller
c9e526492c Add endpoints for working hours
It has an endpoint to show and to delete working hours
2024-06-27 14:55:05 +02:00
Simon Goller
d1f6db2104 Set version to 0.1.3-dev 2024-06-27 07:26:02 +02:00
Simon Goller
c95da03670 Update offline queries 2024-06-26 20:46:50 +02:00
Simon Goller
e477f068dd Set version to 0.1.2 2024-06-26 20:45:42 +02:00
Simon Goller
036551ba39 Fix total amount of extra hours in report 2024-06-26 20:44:20 +02:00
Simon Goller
56fe23f8bc Set version to 0.1.1 2024-06-26 15:26:02 +02:00
11 changed files with 254 additions and 29 deletions

View file

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT id, sales_person_id, amount, category, description, date_time, created, deleted, update_version FROM extra_hours WHERE sales_person_id = ? AND CAST(strftime('%Y', date_time) AS INTEGER) = ? AND CAST(strftime('%m', date_time) AS INTEGER) <= ?", "query": "SELECT id, sales_person_id, amount, category, description, date_time, created, deleted, update_version FROM extra_hours WHERE sales_person_id = ? AND CAST(strftime('%Y', date_time) AS INTEGER) = ? AND deleted IS NULL",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -50,7 +50,7 @@
} }
], ],
"parameters": { "parameters": {
"Right": 3 "Right": 2
}, },
"nullable": [ "nullable": [
false, false,
@ -64,5 +64,5 @@
false false
] ]
}, },
"hash": "053634754e0b3678d61c7dd5f89a894bc5619fa2f94cdbcc6dfd59bcc3aa123f" "hash": "1c8be5c0021c10a1377ac2a03d9b3f8b7e3994e0d4edf0e36275619d11a233f3"
} }

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE extra_hours SET deleted = ?, update_version = ?, update_process = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 4
},
"nullable": []
},
"hash": "77e028e8c559bdb0a725d6af0b75fc7048bb63735b359dc1365abbba68774a93"
}

View file

@ -0,0 +1,68 @@
{
"db_name": "SQLite",
"query": "SELECT id, sales_person_id, amount, category, description, date_time, created, deleted, update_version FROM extra_hours WHERE id = ? AND deleted IS NULL",
"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": "created",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "deleted",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "update_version",
"ordinal": 8,
"type_info": "Blob"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
true,
false,
false,
true,
false
]
},
"hash": "a90b5c6c759c7d5dc7beb05c8e90963c7eec7158004bc002b5fd81cffd0e17e2"
}

2
Cargo.lock generated
View file

@ -59,7 +59,7 @@ checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]] [[package]]
name = "app" name = "app"
version = "0.1.1-dev" version = "0.1.5"
dependencies = [ dependencies = [
"dao", "dao",
"dao_impl", "dao_impl",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "app" name = "app"
version = "0.1.1-dev" version = "0.1.5"
edition = "2021" edition = "2021"
resolver = "2" resolver = "2"

View file

@ -28,11 +28,11 @@ pub struct ExtraHoursEntity {
#[automock] #[automock]
#[async_trait] #[async_trait]
pub trait ExtraHoursDao { pub trait ExtraHoursDao {
async fn find_by_id(&self, id: Uuid) -> Result<Option<ExtraHoursEntity>, crate::DaoError>;
async fn find_by_sales_person_id_and_year( async fn find_by_sales_person_id_and_year(
&self, &self,
sales_person_id: Uuid, sales_person_id: Uuid,
year: u32, year: u32,
until_week: u8,
) -> Result<Arc<[ExtraHoursEntity]>, crate::DaoError>; ) -> Result<Arc<[ExtraHoursEntity]>, crate::DaoError>;
async fn create(&self, entity: &ExtraHoursEntity, process: &str) async fn create(&self, entity: &ExtraHoursEntity, process: &str)
-> Result<(), crate::DaoError>; -> Result<(), crate::DaoError>;

View file

@ -69,19 +69,31 @@ impl ExtraHoursDaoImpl {
#[async_trait] #[async_trait]
impl ExtraHoursDao for ExtraHoursDaoImpl { impl ExtraHoursDao for ExtraHoursDaoImpl {
async fn find_by_id(&self, id: Uuid) -> Result<Option<ExtraHoursEntity>, crate::DaoError> {
let id_vec = id.as_bytes().to_vec();
Ok(query_as!(
ExtraHoursDb,
"SELECT id, sales_person_id, amount, category, description, date_time, created, deleted, update_version FROM extra_hours WHERE id = ? AND deleted IS NULL",
id_vec,
).fetch_optional(self.pool.as_ref())
.await
.map_db_error()?
.as_ref()
.map(ExtraHoursEntity::try_from)
.transpose()?)
}
async fn find_by_sales_person_id_and_year( async fn find_by_sales_person_id_and_year(
&self, &self,
sales_person_id: Uuid, sales_person_id: Uuid,
year: u32, year: u32,
until_week: u8,
) -> Result<Arc<[ExtraHoursEntity]>, crate::DaoError> { ) -> Result<Arc<[ExtraHoursEntity]>, crate::DaoError> {
let id_vec = sales_person_id.as_bytes().to_vec(); let id_vec = sales_person_id.as_bytes().to_vec();
Ok(query_as!( Ok(query_as!(
ExtraHoursDb, ExtraHoursDb,
"SELECT id, sales_person_id, amount, category, description, date_time, created, deleted, update_version FROM extra_hours WHERE sales_person_id = ? AND CAST(strftime('%Y', date_time) AS INTEGER) = ? AND CAST(strftime('%m', date_time) AS INTEGER) <= ?", "SELECT id, sales_person_id, amount, category, description, date_time, created, deleted, update_version FROM extra_hours WHERE sales_person_id = ? AND CAST(strftime('%Y', date_time) AS INTEGER) = ? AND deleted IS NULL",
id_vec, id_vec,
year, year,
until_week,
).fetch_all(self.pool.as_ref()) ).fetch_all(self.pool.as_ref())
.await .await
.map_db_error()? .map_db_error()?
@ -130,10 +142,26 @@ impl ExtraHoursDao for ExtraHoursDaoImpl {
} }
async fn update( async fn update(
&self, &self,
_entity: &ExtraHoursEntity, entity: &ExtraHoursEntity,
_process: &str, process: &str,
) -> Result<(), crate::DaoError> { ) -> Result<(), crate::DaoError> {
unimplemented!() let id_vec = entity.id.as_bytes().to_vec();
let version_vec = entity.version.as_bytes().to_vec();
let delete = entity
.deleted
.map(|date_time| date_time.format(&Iso8601::DATE_TIME))
.transpose()?;
query!(
"UPDATE extra_hours SET deleted = ?, update_version = ?, update_process = ? WHERE id = ?",
delete,
version_vec,
process,
id_vec,
)
.execute(self.pool.as_ref())
.await
.map_db_error()?;
Ok(())
} }
async fn delete(&self, _id: Uuid, _process: &str) -> Result<(), crate::DaoError> { async fn delete(&self, _id: Uuid, _process: &str) -> Result<(), crate::DaoError> {
unimplemented!() unimplemented!()

View file

@ -1,14 +1,63 @@
use std::rc::Rc;
use axum::{ use axum::{
body::Body, extract::State, response::Response, routing::post, Extension, Json, Router, body::Body,
extract::{Path, Query, State},
response::Response,
routing::{delete, get, post},
Extension, Json, Router,
}; };
use rest_types::ExtraHoursTO; use rest_types::ExtraHoursTO;
use serde::Deserialize;
use service::extra_hours::ExtraHoursService; use service::extra_hours::ExtraHoursService;
use uuid::Uuid;
use crate::{error_handler, Context, RestStateDef}; use crate::{error_handler, Context, RestStateDef};
pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> { pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
Router::new().route("/", post(create_extra_hours::<RestState>)) Router::new()
.route("/", post(create_extra_hours::<RestState>))
.route("/:id", delete(delete_extra_hours::<RestState>))
.route(
"/by-sales-person/:id",
get(get_extra_hours_for_sales_person::<RestState>),
)
}
#[derive(Clone, Debug, Deserialize)]
pub struct ExtraHoursForSalesPersonAttributes {
year: u32,
until_week: u8,
}
pub async fn get_extra_hours_for_sales_person<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
query: Query<ExtraHoursForSalesPersonAttributes>,
Path(sales_person_id): Path<Uuid>,
) -> Response {
error_handler(
(async {
let extra_hours: Rc<[ExtraHoursTO]> = rest_state
.extra_hours_service()
.find_by_sales_person_id_and_year(
sales_person_id,
query.year,
query.until_week,
context.into(),
)
.await?
.iter()
.map(ExtraHoursTO::from)
.collect();
Ok(Response::builder()
.status(201)
.body(Body::new(serde_json::to_string(&extra_hours).unwrap()))
.unwrap())
})
.await,
)
} }
pub async fn create_extra_hours<RestState: RestStateDef>( pub async fn create_extra_hours<RestState: RestStateDef>(
@ -25,10 +74,27 @@ pub async fn create_extra_hours<RestState: RestStateDef>(
.await?, .await?,
); );
Ok(Response::builder() Ok(Response::builder()
.status(200) .status(201)
.body(Body::new(serde_json::to_string(&extra_hours).unwrap())) .body(Body::new(serde_json::to_string(&extra_hours).unwrap()))
.unwrap()) .unwrap())
}) })
.await, .await,
) )
} }
pub async fn delete_extra_hours<RestState: RestStateDef>(
rest_state: State<RestState>,
Extension(context): Extension<Context>,
Path(extra_hours_id): Path<Uuid>,
) -> Response {
error_handler(
(async {
rest_state
.extra_hours_service()
.delete(extra_hours_id, context.into())
.await?;
Ok(Response::builder().status(204).body(Body::empty()).unwrap())
})
.await,
)
}

View file

@ -107,5 +107,5 @@ pub trait ExtraHoursService {
&self, &self,
id: Uuid, id: Uuid,
context: Authentication<Self::Context>, context: Authentication<Self::Context>,
) -> Result<ExtraHours, ServiceError>; ) -> Result<(), ServiceError>;
} }

View file

@ -4,7 +4,7 @@ use async_trait::async_trait;
use dao::extra_hours; use dao::extra_hours;
use service::{ use service::{
extra_hours::ExtraHours, extra_hours::ExtraHours,
permission::{Authentication, HR_PRIVILEGE}, permission::{Authentication, HR_PRIVILEGE, SALES_PRIVILEGE},
ServiceError, ServiceError,
}; };
use tokio::join; use tokio::join;
@ -78,12 +78,29 @@ impl<
async fn find_by_sales_person_id_and_year( async fn find_by_sales_person_id_and_year(
&self, &self,
_sales_person_id: Uuid, sales_person_id: Uuid,
_year: u32, year: u32,
_until_week: u8, until_week: u8,
_context: Authentication<Self::Context>, context: Authentication<Self::Context>,
) -> Result<Arc<[ExtraHours]>, ServiceError> { ) -> Result<Arc<[ExtraHours]>, ServiceError> {
unimplemented!() let (hr_permission, sales_person_permission) = join!(
self.permission_service
.check_permission(HR_PRIVILEGE, context.clone()),
self.sales_person_service
.verify_user_is_sales_person(sales_person_id, context),
);
hr_permission.or(sales_person_permission)?;
let extra_hours_entities = self
.extra_hours_dao
.find_by_sales_person_id_and_year(sales_person_id, year)
.await?;
let extra_hours = extra_hours_entities
.iter()
.filter(|extra_hours| extra_hours.date_time.iso_week() <= until_week)
.map(ExtraHours::from)
.collect::<Vec<ExtraHours>>();
Ok(extra_hours.into())
} }
async fn create( async fn create(
@ -127,11 +144,40 @@ impl<
) -> Result<ExtraHours, ServiceError> { ) -> Result<ExtraHours, ServiceError> {
unimplemented!() unimplemented!()
} }
async fn delete( async fn delete(
&self, &self,
_id: Uuid, extra_hours_id: Uuid,
_context: Authentication<Self::Context>, context: Authentication<Self::Context>,
) -> Result<ExtraHours, ServiceError> { ) -> Result<(), ServiceError> {
unimplemented!() let (hr_permission, sales_person_permission) = join!(
self.permission_service
.check_permission(HR_PRIVILEGE, context.clone()),
self.permission_service
.check_permission(SALES_PRIVILEGE, context.clone()),
);
hr_permission.or(sales_person_permission)?;
let mut extra_hours_entity = self
.extra_hours_dao
.find_by_id(extra_hours_id)
.await?
.ok_or(ServiceError::EntityNotFound(extra_hours_id))?;
let (hr_permission, user_permission) = join!(
self.permission_service
.check_permission(HR_PRIVILEGE, context.clone()),
self.sales_person_service
.verify_user_is_sales_person(extra_hours_entity.sales_person_id, context),
);
hr_permission.or(user_permission)?;
extra_hours_entity.deleted = Some(self.clock_service.date_time_now());
self.extra_hours_dao
.update(&extra_hours_entity, "extra_hours_service::delete")
.await?;
Ok(())
} }
} }

View file

@ -178,9 +178,10 @@ where
.sum(); .sum();
let extra_hours = self let extra_hours = self
.extra_hours_dao .extra_hours_dao
.find_by_sales_person_id_and_year(paid_employee.id, year, until_week) .find_by_sales_person_id_and_year(paid_employee.id, year)
.await? .await?
.iter() .iter()
.filter(|eh| eh.date_time.iso_week() <= until_week)
.map(|eh| eh.amount) .map(|eh| eh.amount)
.sum::<f32>(); .sum::<f32>();
let balance_hours = shiftplan_hours + extra_hours - planned_hours; let balance_hours = shiftplan_hours + extra_hours - planned_hours;
@ -221,7 +222,7 @@ where
.await?; .await?;
let extra_hours = self let extra_hours = self
.extra_hours_dao .extra_hours_dao
.find_by_sales_person_id_and_year(*sales_person_id, year, until_week) .find_by_sales_person_id_and_year(*sales_person_id, year)
.await?; .await?;
let planned_hours: f32 = (1..=until_week) let planned_hours: f32 = (1..=until_week)
@ -232,7 +233,11 @@ where
}) })
.sum(); .sum();
let shiftplan_hours = shiftplan_report.iter().map(|r| r.hours).sum::<f32>() as f32; 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 overall_extra_hours = extra_hours
.iter()
.filter(|eh| eh.date_time.iso_week() <= until_week)
.map(|eh| eh.amount)
.sum::<f32>();
let employee_report = EmployeeReport { let employee_report = EmployeeReport {
sales_person: Arc::new(sales_person), sales_person: Arc::new(sales_person),