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 12, 2024
1 parent 42b3d96 commit b55d424
Show file tree
Hide file tree
Showing 27 changed files with 426 additions and 14 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,55 @@
import { mock, instance, when, verify } from 'ts-mockito';
import { DateUtilsAdapter } from 'src/Infrastructure/Adapter/DateUtilsAdapter';
import { LeaveRepository } from 'src/Infrastructure/HumanResource/Leave/Repository/LeaveRepository';
import { GetLeaveRequestsOverviewQueryHandler } from './GetLeaveRequestsOverviewQueryHandler';
import { ILeaveRequestsOverview } from 'src/Domain/HumanResource/Leave/ILeaveRequestsOverview';
import { GetLeaveRequestsOverviewQuery } from './GetLeaveRequestsOverviewQuery';

describe('GetLeaveRequestsOverviewQueryHandler', () => {
let leaveRepository: LeaveRepository;
let dateUtils: DateUtilsAdapter;
let queryHandler: GetLeaveRequestsOverviewQueryHandler;

beforeEach(() => {
leaveRepository = mock(LeaveRepository);
dateUtils = mock(DateUtilsAdapter);
queryHandler = new GetLeaveRequestsOverviewQueryHandler(
instance(dateUtils),
instance(leaveRepository)
);
});

it('testGetLeaveRequestsOverview', async () => {
const expectedResult: ILeaveRequestsOverview = {
daysPerYear: 35,
daysTaken: 6,
daysRemaining: 29
};

const now = new Date('2024-07-05');
const startDate = new Date('2024-06-01');
const endDate = new Date('2025-05-31');
const userId = '06691061-b62f-7499-8000-a65d631224f1';

when(dateUtils.getLeaveReferencePeriodDays(now)).thenReturn([
startDate,
endDate
]);
when(dateUtils.getWorkedDaysPerWeek()).thenReturn(5);
when(dateUtils.getNumberOfPaidLeaveWeeks()).thenReturn(7);

when(
leaveRepository.getSumOfPaidLeaveDurationsBetween(
startDate,
endDate,
userId
)
).thenReturn(new Promise(resolve => resolve(6 * 420)));

when(dateUtils.getLeaveDurationAsDays(6 * 420)).thenReturn(6);

expect(
await queryHandler.execute(new GetLeaveRequestsOverviewQuery(now, userId))
).toMatchObject(expectedResult);
});
});
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.getSumOfPaidLeaveDurationsBetween(
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>;
getSumOfPaidLeaveDurationsBetween(
startDate: Date,
endDate: Date,
userId: string
): Promise<number>;
}
30 changes: 30 additions & 0 deletions src/Infrastructure/Adapter/DateUtilsAdapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,34 @@ describe('DateUtilsAdapter', () => {
const result = dateUtils.getMonth(date);
expect(result).toStrictEqual(expectedResult);
});

it('getLeaveDurationsAsDays', () => {
const dateUtils = new DateUtilsAdapter();
expect(dateUtils.getLeaveDurationAsDays(0)).toBe(0);
expect(dateUtils.getLeaveDurationAsDays(420)).toBe(1);
expect(dateUtils.getLeaveDurationAsDays(420 / 2)).toBe(0.5);
});

it('getLeaveReferencePeriodDays', () => {
const dateUtils = new DateUtilsAdapter();
expect(
dateUtils.getLeaveReferencePeriodDays(new Date('2024-01-01'))
).toEqual([new Date('2023-06-01'), new Date('2024-05-31')]);
expect(
dateUtils.getLeaveReferencePeriodDays(new Date('2024-05-31'))
).toEqual([new Date('2023-06-01'), new Date('2024-05-31')]);
expect(
dateUtils.getLeaveReferencePeriodDays(new Date('2024-06-01'))
).toEqual([new Date('2024-06-01'), new Date('2025-05-31')]);
});

it('getWorkedDaysPerWeek', () => {
const dateUtils = new DateUtilsAdapter();
expect(dateUtils.getWorkedDaysPerWeek()).toBe(5);
});

it('getNumberOfPaidLeaveWeeks', () => {
const dateUtils = new DateUtilsAdapter();
expect(dateUtils.getNumberOfPaidLeaveWeeks()).toBe(7);
});
});
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;
}

getLeaveReferencePeriodDays(date: Date): [Date, Date] {
// Reference period is between June 1st and May 31st.

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

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

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

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

return [startDate, endDate];
}

getWorkedDaysPerWeek(): number {
return 5;
}

getNumberOfPaidLeaveWeeks(): number {
return 7;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,54 @@ 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 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 = await this.overviewTableFactory.create(
overview,
userId
);

return {
table,
overviewTable,
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 getSumOfPaidLeaveDurationsBetween(
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,55 @@
import { Inject, Injectable } from '@nestjs/common';
import { IQueryBus } from '@nestjs/cqrs';
import { GetUsersQuery } from 'src/Application/HumanResource/User/Query/GetUsersQuery';
import { UserView } from 'src/Application/HumanResource/User/View/UserView';
import { ILeaveRequestsOverview } from 'src/Domain/HumanResource/Leave/ILeaveRequestsOverview';
import { Table } from 'src/Infrastructure/Tables';
import { HtmlColumn } from 'src/Infrastructure/Tables/HtmlColumn';
import { RowFactory } from 'src/Infrastructure/Tables/RowFactory';
import { ITemplates } from 'src/Infrastructure/Templates/ITemplates';

@Injectable()
export class LeaveRequestsOverviewTableFactory {
constructor(
@Inject('IQueryBus')
private queryBus: IQueryBus,
private rowFactory: RowFactory,
@Inject('ITemplates')
private templates: ITemplates
) {}

public async create(
overview: ILeaveRequestsOverview,
userId: string
): Promise<Table> {
const users: UserView[] = await this.queryBus.execute(
new GetUsersQuery(false, true)
);

const columns = [
'leaves-user',
new HtmlColumn(
'leaves-overview-daysRemaining',
this.templates.render(
'pages/leave_requests/overview/days_remaining_column_header.njk',
{
daysPerYear: overview.daysPerYear
}
)
)
];

const row = this.rowFactory
.createBuilder()
.template('pages/leave_requests/overview/user_cell.njk', {
users,
userId
})
.template('pages/leave_requests/overview/days_remaining_cell.njk', {
daysRemaining: overview.daysRemaining
})
.build();

return new Table(columns, [row]);
}
}
6 changes: 6 additions & 0 deletions src/Infrastructure/HumanResource/humanResource.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ 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';
import { TemplatesModule } from '../Templates/templates.module';

@Module({
imports: [
Expand All @@ -100,6 +103,7 @@ import { ExportLeavesCalendarController } from './Leave/Controller/ExportLeavesC
UserSavingsRecord,
InterestRate
]),
TemplatesModule,
ExtendedRoutingModule,
TablesModule
],
Expand Down Expand Up @@ -183,6 +187,8 @@ import { ExportLeavesCalendarController } from './Leave/Controller/ExportLeavesC
IncreaseUserSavingsRecordCommandHandler,
UserTableFactory,
LeaveRequestTableFactory,
GetLeaveRequestsOverviewQueryHandler,
LeaveRequestsOverviewTableFactory,
PayrollElementsTableFactory,
MealTicketTableFactory
]
Expand Down
Loading

0 comments on commit b55d424

Please sign in to comment.