Skip to content

Commit

Permalink
Affiche le nb de congés payés restants
Browse files Browse the repository at this point in the history
  • Loading branch information
florimondmanca committed Jul 5, 2024
1 parent d6088a8 commit 94b16b3
Show file tree
Hide file tree
Showing 19 changed files with 280 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { IQuery } from 'src/Application/IQuery';

export class GetLeaveRequestsOverviewQuery implements IQuery {
constructor(public readonly date: Date, public readonly userId: string) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Inject } from '@nestjs/common';
import { QueryHandler } from '@nestjs/cqrs';
import { GetLeaveRequestsOverviewQuery } from './GetLeaveRequestsOverviewQuery';
import { ILeaveRequestsOverview } from 'src/Domain/HumanResource/Leave/ILeaveRequestsOverview';
import { IDateUtils } from 'src/Application/IDateUtils';
import { ILeaveRepository } from 'src/Domain/HumanResource/Leave/Repository/ILeaveRepository';

@QueryHandler(GetLeaveRequestsOverviewQuery)
export class GetLeaveRequestsOverviewQueryHandler {
constructor(
@Inject('IDateUtils')
private readonly dateUtils: IDateUtils,
@Inject('ILeaveRepository')
private readonly leaveRepository: ILeaveRepository
) {}

public async execute(
query: GetLeaveRequestsOverviewQuery
): Promise<ILeaveRequestsOverview> {
const [startDate, endDate] = this.dateUtils.getLeaveReferencePeriodDays(
query.date
);

const numDaysPerWeek = this.dateUtils.getWorkedDaysPerWeek();
const numWeeks = this.dateUtils.getNumberOfPaidLeaveWeeks();
const daysPerYear = numWeeks * numDaysPerWeek;
const totalDurationTaken = await this.leaveRepository.getSumOfDurationsBetween(
startDate,
endDate,
query.userId
);
const daysTaken = this.dateUtils.getLeaveDurationAsDays(totalDurationTaken);
const daysRemaining = daysPerYear - daysTaken;

return {
daysPerYear,
daysTaken,
daysRemaining
};
}
}
4 changes: 4 additions & 0 deletions src/Application/IDateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ export interface IDateUtils {
getWorkedDaysDuringAPeriod(start: Date, end: Date): Date[];
isAWorkingDay(date: Date): boolean;
getWorkedFreeDays(year: number): Date[];
getWorkedDaysPerWeek(): number;
getNumberOfPaidLeaveWeeks(): number;
getEasterDate(year: number): Date;
getLeaveDuration(
startDate: string,
isStartsAllDay: boolean,
endDate: string,
isEndsAllDay: boolean
): number;
getLeaveDurationAsDays(duration: number);
getLeaveReferencePeriodDays(date: Date): [Date, Date];
addDaysToDate(date: Date, days: number): Date;
getYear(date: Date): number;
getMonth(date: Date): MonthDate;
Expand Down
5 changes: 5 additions & 0 deletions src/Domain/HumanResource/Leave/ILeaveRequestsOverview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface ILeaveRequestsOverview {
daysPerYear: number;
daysTaken: number;
daysRemaining: number;
}
5 changes: 5 additions & 0 deletions src/Domain/HumanResource/Leave/Repository/ILeaveRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@ export interface ILeaveRepository {
endDate: string
): Promise<number>;
sumOfDurationLeaveByUserAndDate(user: User, date: string): Promise<number>;
getSumOfDurationsBetween(
startDate: Date,
endDate: Date,
userId: string
): Promise<number>;
}
34 changes: 34 additions & 0 deletions src/Infrastructure/Adapter/DateUtilsAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,38 @@ export class DateUtilsAdapter implements IDateUtils {

return duration;
}

/**
* @param duration Duration in minutes
* @returns Duration in days
*/
public getLeaveDurationAsDays(duration: number) {
return duration / 420;

Check warning on line 216 in src/Infrastructure/Adapter/DateUtilsAdapter.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/Adapter/DateUtilsAdapter.ts#L215-L216

Added lines #L215 - L216 were not covered by tests
}

getLeaveReferencePeriodDays(date: Date): [Date, Date] {

Check warning on line 219 in src/Infrastructure/Adapter/DateUtilsAdapter.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/Adapter/DateUtilsAdapter.ts#L219

Added line #L219 was not covered by tests
// Reference period is between June 1st and May 31st.

let startDate = this.makeDate(this.getYear(date), 6, 1);

Check warning on line 222 in src/Infrastructure/Adapter/DateUtilsAdapter.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/Adapter/DateUtilsAdapter.ts#L222

Added line #L222 was not covered by tests

if (startDate > date) {
startDate = this.makeDate(this.getYear(date) - 1, 6, 1);

Check warning on line 225 in src/Infrastructure/Adapter/DateUtilsAdapter.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/Adapter/DateUtilsAdapter.ts#L225

Added line #L225 was not covered by tests
}

let endDate = this.makeDate(this.getYear(date), 5, 31);

Check warning on line 228 in src/Infrastructure/Adapter/DateUtilsAdapter.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/Adapter/DateUtilsAdapter.ts#L228

Added line #L228 was not covered by tests

if (endDate < date) {
endDate = this.makeDate(this.getYear(date) + 1, 5, 31);

Check warning on line 231 in src/Infrastructure/Adapter/DateUtilsAdapter.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/Adapter/DateUtilsAdapter.ts#L231

Added line #L231 was not covered by tests
}

return [startDate, endDate];

Check warning on line 234 in src/Infrastructure/Adapter/DateUtilsAdapter.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/Adapter/DateUtilsAdapter.ts#L234

Added line #L234 was not covered by tests
}

getWorkedDaysPerWeek(): number {
return 5;

Check warning on line 238 in src/Infrastructure/Adapter/DateUtilsAdapter.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/Adapter/DateUtilsAdapter.ts#L237-L238

Added lines #L237 - L238 were not covered by tests
}

getNumberOfPaidLeaveWeeks(): number {
return 7;

Check warning on line 242 in src/Infrastructure/Adapter/DateUtilsAdapter.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/Adapter/DateUtilsAdapter.ts#L241-L242

Added lines #L241 - L242 were not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,57 @@ import { User } from 'src/Domain/HumanResource/User/User.entity';
import { GetLeaveRequestsQuery } from 'src/Application/HumanResource/Leave/Query/GetLeaveRequestsQuery';
import { IsAuthenticatedGuard } from 'src/Infrastructure/HumanResource/User/Security/IsAuthenticatedGuard';
import { WithName } from 'src/Infrastructure/Common/ExtendedRouting/WithName';
import { PaginationDTO } from 'src/Infrastructure/Common/DTO/PaginationDTO';
import { Pagination } from 'src/Application/Common/Pagination';
import { LeaveRequestView } from 'src/Application/HumanResource/Leave/View/LeaveRequestView';
import { LoggedUser } from '../../User/Decorator/LoggedUser';
import { LeaveRequestTableFactory } from '../Table/LeaveRequestTableFactory';
import { LeaveRequestsOverviewTableFactory } from '../Table/LeaveRequestOverviewTableFactory';
import { GetLeaveRequestsOverviewQuery } from 'src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQuery';
import { UserView } from 'src/Application/HumanResource/User/View/UserView';
import { GetUsersQuery } from 'src/Application/HumanResource/User/Query/GetUsersQuery';
import { ListLeaveRequestsControllerDTO } from '../DTO/ListLeaveRequestsControllerDTO';

@Controller('app/people/leave_requests')
@UseGuards(IsAuthenticatedGuard)
export class ListLeaveRequestsController {
constructor(
@Inject('IQueryBus')
private readonly queryBus: IQueryBus,
private readonly tableFactory: LeaveRequestTableFactory
private readonly tableFactory: LeaveRequestTableFactory,
private readonly overviewTableFactory: LeaveRequestsOverviewTableFactory
) {}

@Get()
@WithName('people_leave_requests_list')
@Render('pages/leave_requests/list.njk')
public async get(
@Query() paginationDto: PaginationDTO,
@Query() dto: ListLeaveRequestsControllerDTO,
@LoggedUser() user: User
) {
const pagination: Pagination<LeaveRequestView> = await this.queryBus.execute(
new GetLeaveRequestsQuery(user.getId(), paginationDto.page)
new GetLeaveRequestsQuery(user.getId(), dto.page)
);

const users: UserView[] = await this.queryBus.execute(
new GetUsersQuery(false, true)
);

const table = this.tableFactory.create(pagination.items, user.getId());

return { table, pagination, currentPage: paginationDto.page };
const userId = dto.userId ? dto.userId : user['id'];

const overview = await this.queryBus.execute(
new GetLeaveRequestsOverviewQuery(new Date(), userId)
);
const overviewTable = this.overviewTableFactory.create(overview);

return {
table,
overviewTable,
users,
userId,
pagination,
currentPage: dto.page
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IsUUID, IsOptional } from 'class-validator';
import { PaginationDTO } from 'src/Infrastructure/Common/DTO/PaginationDTO';

export class ListLeaveRequestsControllerDTO extends PaginationDTO {
@IsUUID()
@IsOptional()
public userId?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ILeaveRepository } from 'src/Domain/HumanResource/Leave/Repository/ILea
import { Leave } from 'src/Domain/HumanResource/Leave/Leave.entity';
import { User, UserRole } from 'src/Domain/HumanResource/User/User.entity';
import { IDateUtils } from 'src/Application/IDateUtils';
import { Type } from 'src/Domain/HumanResource/Leave/LeaveRequest.entity';

export class LeaveRepository implements ILeaveRepository {
constructor(
Expand Down Expand Up @@ -68,4 +69,23 @@ export class LeaveRepository implements ILeaveRepository {

return Number(result.time) || 0;
}

public async getSumOfDurationsBetween(
startDate: Date,
endDate: Date,
userId: string

Check warning on line 76 in src/Infrastructure/HumanResource/Leave/Repository/LeaveRepository.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/HumanResource/Leave/Repository/LeaveRepository.ts#L76

Added line #L76 was not covered by tests
): Promise<number> {
const result = await this.repository

Check warning on line 78 in src/Infrastructure/HumanResource/Leave/Repository/LeaveRepository.ts

View check run for this annotation

Codecov / codecov/patch

src/Infrastructure/HumanResource/Leave/Repository/LeaveRepository.ts#L78

Added line #L78 was not covered by tests
.createQueryBuilder('leave')
.select('SUM(leave.time) as time')
.innerJoin('leave.leaveRequest', 'leaveRequest')
.innerJoin('leaveRequest.user', 'user')
.where('leave.date >= :startDate', { startDate })
.andWhere('leave.date < :endDate', { endDate })
.andWhere('leaveRequest.type = :type', { type: Type.PAID })
.andWhere('user.id = :userId', { userId: userId })
.getRawOne();

return Number(result.time) || 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Injectable } from '@nestjs/common';
import { ILeaveRequestsOverview } from 'src/Domain/HumanResource/Leave/ILeaveRequestsOverview';
import { Table } from 'src/Infrastructure/Tables';
import { RowFactory } from 'src/Infrastructure/Tables/RowFactory';

@Injectable()
export class LeaveRequestsOverviewTableFactory {
constructor(private rowFactory: RowFactory) {}

public create(overview: ILeaveRequestsOverview): Table {
const columns = ['leaves-overview-daysRemaining'];

const row = this.rowFactory
.createBuilder()
.template('pages/leave_requests/_overview_badge.njk', {
overview
})
.build();

return new Table(columns, [row]);
}
}
4 changes: 4 additions & 0 deletions src/Infrastructure/HumanResource/humanResource.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ import { ListMealTicketsController } from './MealTicket/Controller/ListMealTicke
import { MealTicketTableFactory } from './MealTicket/Table/MealTicketTableFactory';
import { AddMealTicketRemovalController } from './MealTicket/Controller/AddMealTicketRemovalController';
import { ExportLeavesCalendarController } from './Leave/Controller/ExportLeavesCalendarController';
import { LeaveRequestsOverviewTableFactory } from './Leave/Table/LeaveRequestOverviewTableFactory';
import { GetLeaveRequestsOverviewQueryHandler } from 'src/Application/HumanResource/Leave/Query/GetLeaveRequestsOverviewQueryHandler';

@Module({
imports: [
Expand Down Expand Up @@ -183,6 +185,8 @@ import { ExportLeavesCalendarController } from './Leave/Controller/ExportLeavesC
IncreaseUserSavingsRecordCommandHandler,
UserTableFactory,
LeaveRequestTableFactory,
GetLeaveRequestsOverviewQueryHandler,
LeaveRequestsOverviewTableFactory,
PayrollElementsTableFactory,
MealTicketTableFactory
]
Expand Down
2 changes: 1 addition & 1 deletion src/assets/styles/components/button.css
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
padding: 0;
width: var(--button-size, calc(4 * var(--w)));
height: var(--button-size, calc(4 * var(--w)));
display: grid;
display: inline-grid;
place-items: center;
}

Expand Down
7 changes: 7 additions & 0 deletions src/assets/styles/defaults.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,10 @@ fieldset {
dialog {
margin: auto;
}

:popover-open {
position: absolute;
inset: unset;
margin: var(--w);
white-space: wrap;
}
12 changes: 12 additions & 0 deletions src/assets/styles/utilities/placement.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@
display: grid;
place-items: center;
}

.pc-relative {
position: relative;
}

.pc-absolute {
position: absolute;
top: var(--top, unset);
left: var(--left, unset);
right: var(--right, unset);
bottom: var(--bottom, unset);
}
9 changes: 9 additions & 0 deletions src/assets/styles/variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@
--event-other-border: rgb(244, 245, 247);
--event-other-text: rgb(36, 38, 45);
--event-today: #fcf8e3;
--leave-daysPerYear-background: rgb(244, 245, 247);
--leave-daysPerYear-border: rgb(244, 245, 247);
--leave-daysPerYear-text: rgb(36, 38, 45);
--leave-daysTaken-background: rgb(253, 246, 178);
--leave-daysTaken-border: rgb(253, 246, 178);
--leave-daysTaken-text: rgb(142, 75, 16);
--leave-daysRemaining-background: rgb(222, 247, 236);
--leave-daysRemaining-border: rgb(222, 247, 236);
--leave-daysRemaining-text: rgb(4, 108, 78);
--header-height-desktop: 56px;
}

Expand Down
24 changes: 24 additions & 0 deletions src/templates/pages/leave_requests/_filters_form.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{% macro filters_form() %}
<pc-auto-form>
<form
method="GET"
action=""
class="pc-cluster pc-gap"
style="--cluster-align: flex-end"
>
<div class="pc-select-group pc-m" style="--m: 0">
<label class="pc-label" for="userId">{{ 'leaves-user'|trans }}</label>
<select name="userId" id="userId">
{% for user in users %}
<option value="{{ user.id }}" {% if user.id == userId %}selected{% endif %}>{{ user|fullName }}</option>
{% endfor %}
</select>
</div>
<noscript>
<button type="submit" class="pc-btn pc-btn--secondary">
{{ 'common-form-update'|trans }}
</button>
</noscript>
</form>
</pc-auto-form>
{% endmacro %}
16 changes: 16 additions & 0 deletions src/templates/pages/leave_requests/_overview_badge.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<span class="pc-badge pc-bold" style="--badge-color: var(--leave-daysRemaining-text); --badge-background: var(--leave-daysRemaining-background)">
{{ 'leaves-overview-daysRemaining-value'|trans({ daysRemaining: overview.daysRemaining }) }}
</span>

<span class="pc-relative">
<button
popovertarget="leaves-explanation"
class="pc-btn pc-btn--secondary pc-btn--circle"
title="{{ 'leaves-overview-daysRemaining-showExplanation'|trans }}"
aria-label="{{ 'leaves-overview-daysRemaining-showExplanation'|trans }}"
>i</button>

<div id="leaves-explanation" popover>
{{ 'leaves-overview-daysRemaining-explanation-value'|trans({ daysTaken: overview.daysTaken, daysPerYear: overview.daysPerYear }) }}
</div>
</span>
12 changes: 12 additions & 0 deletions src/templates/pages/leave_requests/list.njk
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{% extends 'layouts/app.njk' %}
{% import 'macros/links.njk' as links %}
{% from 'macros/breadcrumb.njk' import breadcrumb %}
{% from './_filters_form.njk' import filters_form with context %}

{% set title = 'leaves-title'|trans %}

Expand All @@ -15,6 +16,17 @@
{{ links.add(path('people_leave_requests_add'), text='leave-requests-add-title'|trans) }}
</div>

<h2>{{ 'leaves-summary-title'|trans }}</h2>

<div class="pc-stack">
<div class="pc-cluster" style="--cluster-gap: calc(2 * var(--w)); --cluster-align: flex-end">
{{ filters_form() }}
{% table overviewTable, { attr: {class: 'pc-table--center pc-table--no-shadow'} } %}
</div>
</div>

<h2>{{ 'leaves-list-title'|trans }}</h2>

{% table table %}

{% set paginationUrl = path('people_leave_requests_list') %}
Expand Down
Loading

0 comments on commit 94b16b3

Please sign in to comment.