Skip to content

Commit

Permalink
Merge pull request #1839 from bcgov/feature/ALCS-2048
Browse files Browse the repository at this point in the history
Add GIS Subtask Days to Home Page Subtask Table
  • Loading branch information
Abradat authored Sep 10, 2024
2 parents 8c28adc + a7ae096 commit 56609b2
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
<ng-container matColumnDef="type">
<th class="type-cell" mat-header-cell *matHeaderCellDef>Type</th>
<td mat-cell class="type-cell" *matCellDef="let element">
<ng-container *ngIf="!element.appType" class="center">
-
</ng-container>
<ng-container *ngIf="!element.appType" class="center"> - </ng-container>
<app-application-type-pill [useShortLabel]="true" *ngIf="element.appType" [type]="element.appType">
</app-application-type-pill>
<app-application-type-pill
Expand Down Expand Up @@ -53,6 +51,11 @@
<td mat-cell *matCellDef="let element" [innerHTML]="element.card.status.label"></td>
</ng-container>

<ng-container matColumnDef="subtaskDays">
<th class="subtask-days-cell" mat-header-cell *matHeaderCellDef>Subtask Days</th>
<td mat-cell *matCellDef="let element" [innerHTML]="element.subtaskDays"></td>
</ng-container>

<ng-container matColumnDef="assignee">
<th class="assignee-header" mat-header-cell *matHeaderCellDef>Assignee</th>
<td class="assignee-column" mat-cell *matCellDef="let element">
Expand Down Expand Up @@ -91,7 +94,7 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
[ngClass]="{
paused: row.paused
paused: row.paused,
}"
mat-row
*matRowDef="let row; columns: displayedColumns"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
width: 25%;
}

.subtask-days-cell {
width: 10%;
}

.assignee-header {
padding: 0px 26px !important;
}
Expand Down Expand Up @@ -101,4 +105,4 @@
.ng-dropdown-panel-items .ng-option {
padding: 4px 16px !important;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, Input } from '@angular/core';
import { Router } from '@angular/router';
import { NgSelectComponent } from '@ng-select/ng-select';
import { HomepageSubtaskDto } from '../../../../services/card/card-subtask/card-subtask.dto';
import { CARD_SUBTASK_TYPE, HomepageSubtaskDto } from '../../../../services/card/card-subtask/card-subtask.dto';
import { CardSubtaskService } from '../../../../services/card/card-subtask/card-subtask.service';
import { AssigneeDto, UserDto } from '../../../../services/user/user.dto';
import {
Expand Down Expand Up @@ -45,6 +45,11 @@ export class SubtaskTableComponent {
}
}

const isGIS = this.subtasks.some((task) => task.type.code === CARD_SUBTASK_TYPE.GIS);
if (isGIS) {
const index = columns.indexOf('stage');
columns.splice(index + 1, 0, 'subtaskDays');
}
return columns;
}

Expand Down
1 change: 1 addition & 0 deletions services/apps/alcs/src/alcs/admin/admin.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,6 @@ import { UnarchiveCardService } from './unarchive-card/unarchive-card.service';
ApplicationDecisionConditionTypesService,
ConfigurationService,
],
exports: [HolidayService],
})
export class AdminModule {}
21 changes: 21 additions & 0 deletions services/apps/alcs/src/alcs/admin/holiday/holiday.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,25 @@ describe('HolidayService', () => {
expect(result[0]).toEqual('2020');
expect(mockRepository.query).toBeCalledTimes(1);
});

it('should calculate business days of a given date without holidays', () => {
const res = service.calculateBusinessDays(
new Date('2024-09-09'),
new Date('2024-09-16'),
[],
);
expect(res).toEqual(6);
});

it('should calculate business days of a given date with holidays', () => {
const holiday: HolidayEntity = new HolidayEntity({
day: new Date('2024-09-09'),
});
const res = service.calculateBusinessDays(
new Date('2024-09-09'),
new Date('2024-09-16'),
[holiday],
);
expect(res).toEqual(5);
});
});
55 changes: 55 additions & 0 deletions services/apps/alcs/src/alcs/admin/holiday/holiday.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,59 @@ export class HolidayService {
)) as { year: string }[];
return res.map((res) => res.year);
}

async fetchAllHolidays() {
return await this.holidayRepository
.createQueryBuilder('holiday')
.select('holiday.day')
.getMany();
}

calculateBusinessDays(
fromDate: Date,
toDate: Date,
holidays: HolidayEntity[],
): number {
const formatDate = (date: Date): string => {
return date.toISOString().split('T')[0];
};

const holidaysSet = new Set<string>(
holidays.map((holiday) => formatDate(new Date(holiday.day))),
);

const isWeekend = (date: Date): boolean => {
const day = date.getDay();
return day === 0 || day === 6;
};

const isBusinessDay = (date: Date): boolean => {
return isWeekend(date) || holidaysSet.has(formatDate(date))
? false
: true;
};

const addDays = (date: Date, days: number): Date => {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
};

const differenceInDays = (startDate: Date, endDate: Date): number => {
const timeDiff = endDate.getTime() - startDate.getTime();
return Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
};

const totalDays = differenceInDays(fromDate, toDate) + 1;
let businessDaysCount = 0;

for (let i = 0; i < totalDays; i++) {
const currentDate = addDays(fromDate, i);
if (isBusinessDay(currentDate)) {
businessDaysCount++;
}
}

return businessDaysCount;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export class HomepageSubtaskDTO extends CardSubtaskDto {
parentType: PARENT_TYPE;
activeDays?: number;
paused: boolean;
subtaskDays?: number;
}

export enum CARD_SUBTASK_TYPE {
Expand Down
65 changes: 65 additions & 0 deletions services/apps/alcs/src/alcs/home/home.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
initApplicationMockEntity,
initApplicationModificationMockEntity,
initApplicationReconsiderationMockEntity,
initCardGISSubtaskMockEntity,
initCardMockEntity,
} from '../../../test/mocks/mockEntities';
import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes';
Expand Down Expand Up @@ -35,6 +36,7 @@ import { Notification } from '../notification/notification.entity';
import { NotificationService } from '../notification/notification.service';
import { PlanningReferralService } from '../planning-review/planning-referral/planning-referral.service';
import { HomeController } from './home.controller';
import { HolidayService } from '../admin/holiday/holiday.service';

describe('HomeController', () => {
let controller: HomeController;
Expand All @@ -48,6 +50,7 @@ describe('HomeController', () => {
let mockNotificationService: DeepMocked<NotificationService>;
let mockPlanningReferralService: DeepMocked<PlanningReferralService>;
let mockInquiryService: DeepMocked<InquiryService>;
let mockHolidayService: DeepMocked<HolidayService>;

beforeEach(async () => {
mockApplicationService = createMock();
Expand All @@ -60,6 +63,7 @@ describe('HomeController', () => {
mockNotificationService = createMock();
mockPlanningReferralService = createMock();
mockInquiryService = createMock();
mockHolidayService = createMock();

const module: TestingModule = await Test.createTestingModule({
imports: [
Expand Down Expand Up @@ -117,6 +121,10 @@ describe('HomeController', () => {
provide: InquiryService,
useValue: mockInquiryService,
},
{
provide: HolidayService,
useValue: mockHolidayService,
},
ApplicationProfile,
ApplicationSubtaskProfile,
UserProfile,
Expand Down Expand Up @@ -253,6 +261,7 @@ describe('HomeController', () => {
]),
);

mockHolidayService.fetchAllHolidays.mockResolvedValue([]);
const res = await controller.getIncompleteSubtasksByType(
CARD_SUBTASK_TYPE.GIS,
);
Expand All @@ -265,6 +274,49 @@ describe('HomeController', () => {
expect(res[0].title).toContain(mockApplication.applicant);
expect(res[0].activeDays).toBe(activeDays);
expect(res[0].paused).toBeTruthy();
expect(mockHolidayService.fetchAllHolidays).toHaveBeenCalled();
});

it('should call ApplicationService and map an Application and calculate GIS subtask days', async () => {
const mockApplication = initApplicationMockEntity();
mockApplication.card!.subtasks = [
initCardGISSubtaskMockEntity(mockApplication.card!),
];
const activeDays = 5;
mockApplicationService.getWithIncompleteSubtaskByType.mockResolvedValue([
mockApplication,
]);
mockApplicationTimeTrackingService.getPausedStatus.mockResolvedValue(
new Map([[mockApplication.uuid, true]]),
);
mockApplicationTimeTrackingService.fetchActiveTimes.mockResolvedValue(
new Map([
[
mockApplication.uuid,
{
activeDays,
pausedDays: 0,
},
],
]),
);

mockHolidayService.fetchAllHolidays.mockResolvedValue([]);
mockHolidayService.calculateBusinessDays.mockReturnValue(0);
const res = await controller.getIncompleteSubtasksByType(
CARD_SUBTASK_TYPE.GIS,
);

expect(res.length).toEqual(1);
expect(
mockApplicationService.getWithIncompleteSubtaskByType,
).toBeCalledTimes(1);
expect(res[0].title).toContain(mockApplication.fileNumber);
expect(res[0].title).toContain(mockApplication.applicant);
expect(res[0].activeDays).toBe(activeDays);
expect(res[0].paused).toBeTruthy();
expect(mockHolidayService.fetchAllHolidays).toHaveBeenCalled();
expect(mockHolidayService.calculateBusinessDays).toHaveBeenCalled();
});

it('should call Reconsideration Service and map it', async () => {
Expand All @@ -273,6 +325,7 @@ describe('HomeController', () => {
[mockReconsideration],
);

mockHolidayService.fetchAllHolidays.mockResolvedValue([]);
const res = await controller.getIncompleteSubtasksByType(
CARD_SUBTASK_TYPE.GIS,
);
Expand All @@ -287,6 +340,7 @@ describe('HomeController', () => {
expect(res[0].title).toContain(mockReconsideration.application.applicant);
expect(res[0].activeDays).toBeUndefined();
expect(res[0].paused).toBeFalsy();
expect(mockHolidayService.fetchAllHolidays).toHaveBeenCalled();
});

// TODO: Fix when finishing planning reviews
Expand Down Expand Up @@ -320,6 +374,7 @@ describe('HomeController', () => {
mockApplicationModificationService.getWithIncompleteSubtaskByType.mockResolvedValue(
[mockModification],
);
mockHolidayService.fetchAllHolidays.mockResolvedValue([]);

const res = await controller.getIncompleteSubtasksByType(
CARD_SUBTASK_TYPE.GIS,
Expand All @@ -334,6 +389,7 @@ describe('HomeController', () => {
expect(res[0].title).toContain(mockModification.application.applicant);
expect(res[0].activeDays).toBeUndefined();
expect(res[0].paused).toBeFalsy();
expect(mockHolidayService.fetchAllHolidays).toHaveBeenCalled();
});

it('should call NOI Service and map it', async () => {
Expand All @@ -357,6 +413,7 @@ describe('HomeController', () => {
],
]),
);
mockHolidayService.fetchAllHolidays.mockResolvedValue([]);

const res = await controller.getIncompleteSubtasksByType(
CARD_SUBTASK_TYPE.PEER_REVIEW,
Expand All @@ -370,6 +427,7 @@ describe('HomeController', () => {
expect(res[0].title).toContain(mockNoi.fileNumber);
expect(res[0].title).toContain(mockNoi.applicant);
expect(res[0].activeDays).toBe(activeDays);
expect(mockHolidayService.fetchAllHolidays).toHaveBeenCalled();
});

it('should call NOI Modification Service and map it', async () => {
Expand All @@ -383,6 +441,7 @@ describe('HomeController', () => {
mockNoticeOfIntentModificationService.getWithIncompleteSubtaskByType.mockResolvedValue(
[mockNoiModification],
);
mockHolidayService.fetchAllHolidays.mockResolvedValue([]);

const res = await controller.getIncompleteSubtasksByType(
CARD_SUBTASK_TYPE.PEER_REVIEW,
Expand All @@ -399,6 +458,7 @@ describe('HomeController', () => {
expect(res[0].title).toContain(
mockNoiModification.noticeOfIntent.applicant,
);
expect(mockHolidayService.fetchAllHolidays).toHaveBeenCalled();
});

it('should call Notification Service and map it', async () => {
Expand All @@ -410,6 +470,7 @@ describe('HomeController', () => {
mockNotificationService.getWithIncompleteSubtaskByType.mockResolvedValue([
mockNotification,
]);
mockHolidayService.fetchAllHolidays.mockResolvedValue([]);

const res = await controller.getIncompleteSubtasksByType(
CARD_SUBTASK_TYPE.PEER_REVIEW,
Expand All @@ -422,6 +483,7 @@ describe('HomeController', () => {

expect(res[0].title).toContain(mockNotification.fileNumber);
expect(res[0].title).toContain(mockNotification.applicant);
expect(mockHolidayService.fetchAllHolidays).toHaveBeenCalled();
});

it('should call Inquiry Service and map it', async () => {
Expand All @@ -434,6 +496,8 @@ describe('HomeController', () => {
mockInquiry,
]);

mockHolidayService.fetchAllHolidays.mockResolvedValue([]);

const res = await controller.getIncompleteSubtasksByType(
CARD_SUBTASK_TYPE.PEER_REVIEW,
);
Expand All @@ -445,6 +509,7 @@ describe('HomeController', () => {

expect(res[0].title).toContain(mockInquiry.fileNumber);
expect(res[0].title).toContain(mockInquiry.inquirerLastName);
expect(mockHolidayService.fetchAllHolidays).toHaveBeenCalled();
});
});
});
Loading

0 comments on commit 56609b2

Please sign in to comment.