Skip to content

Commit

Permalink
[M1_TR-207] Backend for M1_TR-5
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoleamber committed Sep 14, 2023
1 parent 57d63ec commit dc90b61
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 6 deletions.
26 changes: 24 additions & 2 deletions server/src/api/jobs/dto/job-query.dto.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
import { ApiProperty } from '@nestjs/swagger';
import { Status, Tag } from '@prisma/client';
import { Transform } from 'class-transformer';
import { IsNumber, IsOptional } from 'class-validator';
import { IsEnum, IsNumber, IsOptional } from 'class-validator';
import { IsDateRange } from '../../../utils/validators/dateRange.validator';

export class JobQueryDto {
@ApiProperty()
@IsNumber()
@Transform(({ value }) => parseInt(value))
page: number;

@ApiProperty({ required: false})
@ApiProperty({ required: false })
@IsOptional()
@IsNumber()
@Transform(({ value }) => parseInt(value))
perPage?: number;

@ApiProperty({ required: false })
@IsOptional()
@IsEnum(Tag)
tag: Tag;

@ApiProperty({ required: false })
@IsOptional()
@IsEnum(Status)
status: Status;

@ApiProperty({ required: false })
@IsOptional()
@IsDateRange()
startDate: Date;

@ApiProperty({ required: false })
@IsOptional()
@IsDateRange()
endDate: Date;
}
56 changes: 55 additions & 1 deletion server/src/api/jobs/jobs.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Test, TestingModule } from '@nestjs/testing';
import { $Enums } from '@prisma/client';
import { JobsController } from './jobs.controller';
import { JobsService } from './jobs.service';

Expand All @@ -25,7 +26,7 @@ describe('JobsController', () => {

describe('findAll', () => {
it('should return a list of jobs with the total number of jobs', async () => {
const jobQuery = { page: 1, perPage: 2 };
const jobQuery = { page: 1, perPage: 2, tag: null, status: null, startDate: null, endDate: null };
const jobList = {
jobs: [
{
Expand Down Expand Up @@ -66,4 +67,57 @@ describe('JobsController', () => {
expect(jobs).toEqual(jobList);
});
});

describe('filter', () => {
it('should return a filtered list of jobs with total number of filtered jobs', async () => {
const mockTags = [$Enums.Tag.TAG_A];
const mockStatus = $Enums.Status.APPROVED;
const jobQuery = { page: 1, perPage: 2, tag: mockTags[0], status: mockStatus, startDate: null, endDate: null };

const jobList = {
jobs: [
{
id: 1,
title: "Job A",
type: "Type A",
tags: mockTags,
remarks: null,
customerId: 1,
paymentMethod: null,
userId: 1,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
estimation: {
status: mockStatus,
totalCost: 1000.00
}
},
{
id: 2,
title: "Job B",
type: "Type B",
tags: [],
remarks: null,
customerId: 2,
paymentMethod: null,
userId: 2,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
}
],
count: 2
};

const filteredJobList = { jobs: jobList.jobs.slice(0, 1), count: 1}

jest.spyOn(mockJobService, 'findAll').mockResolvedValue(filteredJobList);

const jobs = await jobController.findAll(jobQuery);

expect(mockJobService.findAll).toHaveBeenCalledWith(jobQuery);
expect(jobs).toEqual(filteredJobList);
});
});
});
50 changes: 49 additions & 1 deletion server/src/api/jobs/jobs.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Test, TestingModule } from '@nestjs/testing';
import { $Enums } from '@prisma/client';
import { PrismaService } from '../../database/connection.service';
import { JobsService } from './jobs.service';

Expand Down Expand Up @@ -29,7 +30,7 @@ describe('JobsService', () => {

describe('findAll', () => {
it('should be return a list of jobs with the total number of jobs', async () => {
const jobQuery = { page: 1, perPage: 2 };
const jobQuery = { page: 1, perPage: 2, tag: null, status: null, startDate: null, endDate: null };
const mockJobList = [
{
id: 1,
Expand Down Expand Up @@ -67,4 +68,51 @@ describe('JobsService', () => {
expect(jobs).toEqual({ jobs: mockJobList, count: mockJobCount });
});
});

describe('filter', () => {
it('should be return a filtered list of jobs with the total number of filtered jobs', async () => {
const mockTags = [$Enums.Tag.TAG_A];
const mockStatus = $Enums.Status.APPROVED;
const jobQuery = { page: 1, perPage: 2, tag: mockTags[0], status: mockStatus, startDate: null, endDate: null }
const mockJobList = [
{
id: 1,
title: "Job A",
type: "Type A",
tags: mockTags,
remarks: null,
customerId: 1,
paymentMethod: null,
userId: 1,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
estimation: {
status: mockStatus,
totalCost: 1000.00
}
},
{
id: 2,
title: "Job B",
type: "Type B",
tags: [],
remarks: null,
customerId: 2,
paymentMethod: null,
userId: 2,
pipelinePhase: null,
createdAt: new Date("2023-09-07T09:38:42.296Z"),
updatedAt: new Date("2023-09-07T09:38:42.296Z"),
}
];
const mockJobCount = 1;

mockPrismaService.$transaction.mockResolvedValue([ mockJobList.slice(0, 1), mockJobCount ]);

const jobs = await jobService.findAll(jobQuery);

expect(jobs).toEqual({ jobs: mockJobList.slice(0, 1), count: mockJobCount });
});
});
});
40 changes: 38 additions & 2 deletions server/src/api/jobs/jobs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,47 @@ export class JobsService {

async findAll(query: JobQueryDto): Promise<JobListDto> {
try {
const { page, perPage = 12 } = query;
const { page, tag, status, startDate, endDate, perPage = 12 } = query;
const skip = (page - 1) * perPage;

const tagCondition = tag ? {
tags: {
has: tag
}
} : {};

const statusCondition = status ? {
estimation: {
status: {
equals: status
}
}
} : {};

let dateRangeCondition = {}
if (startDate && endDate) {
const newEndDate = new Date(endDate);
newEndDate.setDate(newEndDate.getDate() + 1);

dateRangeCondition = {
createdAt: {
lt: newEndDate,
gte: new Date(startDate),
}
};
}

const whereCondition = {
AND: [
tagCondition,
statusCondition,
dateRangeCondition
]
}

const [ jobs, count ] = await this.prisma.$transaction([
this.prisma.job.findMany({
where: whereCondition,
take: perPage,
skip,
include: {
Expand Down Expand Up @@ -45,7 +81,7 @@ export class JobsService {
}
}
}),
this.prisma.job.count()
this.prisma.job.count({ where: whereCondition })
]);

return { jobs, count };
Expand Down
64 changes: 64 additions & 0 deletions server/src/utils/validators/dateRange.validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {
registerDecorator,
ValidationArguments,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';


@ValidatorConstraint({ async: false })
export class DateRangeValidator implements ValidatorConstraintInterface {
validate(value: number | Date, args: ValidationArguments) {
const { object } = args;

const startDateStr = object['startDate'];
const endDateStr = object['endDate'];

if (startDateStr && !endDateStr) {
args.constraints.push('endDateMissing');
return false;
}

if (endDateStr && !startDateStr) {
args.constraints.push('startDateMissing');
return false;
}

const startDate = new Date(startDateStr);
const endDate = new Date(endDateStr);

if (endDate < startDate) {
args.constraints.push('invalidDateRange');
return false;
}

return true;
}

defaultMessage(args: ValidationArguments) {
if (args.constraints.includes('startDateMissing')) {
return 'startDate is missing.';
}

if (args.constraints.includes('endDateMissing')) {
return 'endDate is missing.';
}

if (args.constraints.includes('invalidDateRange')) {
return 'endDate should be greater than startDate.';
}
}
}

export function IsDateRange(validationOptions?: ValidationOptions) {
return function (object: object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [],
validator: DateRangeValidator,
});
};
}

0 comments on commit dc90b61

Please sign in to comment.