generated from UK-Export-Finance/nestjs-template
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(APIM-90-91): post and get premium schedules (#45)
### Introduction New endpoint POST /premium/schedule . New endpoint GET /premium/segments/{facilityId} . Get previously generated premium schedules. ### Resolution POST /premium/schedule - uses SP - USP_MDM_INCOME_EXPOSURE - generates and returns premium schedules - returns Location header for GET call GET /premium/segments/{facilityId} - gets data from table using typeOrm Allow referencing third party module "express" in .eslintrc.json Unit test has simple Response mock/fake/stub. It could be improved in future. --------- Co-authored-by: Gabriel Ignat <gabriel.ignat@hotmail.com>
- Loading branch information
Showing
12 changed files
with
575 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,9 @@ | |
}, | ||
"settings": { | ||
"node": { | ||
"allowModules": [ | ||
"express" | ||
], | ||
"tryExtensions": [ | ||
".js", | ||
".json", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
src/modules/premium-schedules/dto/create-premium-schedule.dto.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
import { IsDateString, IsInt, IsNotEmpty, IsNumber, IsOptional, IsString, Length, Matches, Max, Min } from 'class-validator'; | ||
|
||
export class CreatePremiumScheduleDto { | ||
@IsInt() | ||
@IsNotEmpty() | ||
@ApiProperty({ | ||
example: 30000425, | ||
description: 'UKEF id for Facility, but without 00 at beginning. Usually UKEF id is string, but in this endpoint it is number', | ||
}) | ||
readonly facilityURN: number; | ||
|
||
@IsString() | ||
@IsNotEmpty() | ||
@Length(2) | ||
@Matches(/^(EW|BS)$/) | ||
@ApiProperty({ example: 'BS', description: 'Two products are accepted: EW and BS' }) | ||
readonly productGroup: string; | ||
|
||
@IsNumber() | ||
@IsNotEmpty() | ||
@Min(1) | ||
@Max(3) | ||
@ApiProperty({ | ||
example: 1, | ||
description: 'Premium type concerns how we are being paid. It can be: 1 -> In advance, 2 -> In Arrears or 3-> At Maturity.', | ||
}) | ||
readonly premiumTypeId: number; | ||
|
||
@IsNumber() | ||
@IsNotEmpty() | ||
@Min(1) | ||
@Max(3) | ||
@ApiProperty({ | ||
example: 1, | ||
description: 'Payment frequency. It can be: 1 -> Monthly, 2 -> Quarterly, 3-> Semi-annually or 4 -> Annually', | ||
}) | ||
readonly premiumFrequencyId: number; | ||
|
||
@IsDateString() | ||
@IsNotEmpty() | ||
@ApiProperty({ example: '2021-01-19', description: 'Start date' }) | ||
readonly guaranteeCommencementDate: Date; | ||
|
||
@IsDateString() | ||
@IsNotEmpty() | ||
@ApiProperty({ example: '2022-05-17', description: 'End date' }) | ||
readonly guaranteeExpiryDate: Date; | ||
|
||
@IsNumber() | ||
@IsNotEmpty() | ||
@ApiProperty({ example: 80, description: 'Percentage covered, expecting whole number i.e. if 90% expecting the number 90' }) | ||
readonly guaranteePercentage: number; | ||
|
||
@IsNumber() | ||
@IsNotEmpty() | ||
@ApiProperty({ example: 1.35, description: 'UKEF Fee percentage, expecting whole number i.e. if 90% expecting the number 90' }) | ||
readonly guaranteeFeePercentage: number; | ||
|
||
@IsString() | ||
@IsNotEmpty() | ||
@Length(3) | ||
@ApiProperty({ example: '360', description: '360 or 365. UK or US calendar' }) | ||
readonly dayBasis: string; | ||
|
||
@IsNumber() | ||
@IsNotEmpty() | ||
@ApiProperty({ example: 16, description: 'How many periods are we exposed to the risk, This is pre-calculated in the Exposure Period Calc' }) | ||
readonly exposurePeriod: number; | ||
|
||
@IsNumber() | ||
@IsOptional() | ||
@ApiProperty({ example: null, description: 'Optional EWCS Exposure ONLY, this is the cumulative amount drawn on the first disbursement. NULL if not EWCS' }) | ||
readonly cumulativeAmount: number; | ||
|
||
@IsNumber() | ||
@IsNotEmpty() | ||
@ApiProperty({ example: 40000, description: 'Required for BS Exposure' }) | ||
readonly maximumLiability: number; | ||
} |
13 changes: 13 additions & 0 deletions
13
src/modules/premium-schedules/dto/get-premium-schedule-param.dto.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
import { IsNotEmpty, IsString, Matches } from 'class-validator'; | ||
|
||
export class GetPremiumScheduleParamDto { | ||
@IsString() | ||
@IsNotEmpty() | ||
@ApiProperty({ | ||
example: '30000425', | ||
description: 'UKEF id for Facility, but without 00 at beginning', | ||
}) | ||
@Matches(/^\d{8,10}$/) | ||
public facilityId: string; | ||
} |
65 changes: 65 additions & 0 deletions
65
src/modules/premium-schedules/entities/premium-schedule.entity.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { ClassSerializerInterceptor, UseInterceptors } from '@nestjs/common'; | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
import { Column, Entity, PrimaryColumn } from 'typeorm'; | ||
|
||
@Entity({ | ||
name: 'INCOME_EXPOSURE', | ||
schema: 'dbo', | ||
}) | ||
@UseInterceptors(ClassSerializerInterceptor) | ||
export class PremiumScheduleEntity { | ||
@PrimaryColumn({ name: 'D_INCOME_EXPOSURE_ID' }) | ||
id: number; | ||
|
||
@Column({ name: 'FACILITY_ID' }) | ||
@ApiProperty({ | ||
example: '30000425', | ||
description: 'UKEF id for Facility, but without 00 at beginning', | ||
}) | ||
facilityURN: string; | ||
|
||
// Date only. | ||
@Column({ name: 'CALCULATION_DATE', type: 'date' }) | ||
@ApiProperty({ example: '2023-02-27' }) | ||
calculationDate: Date; | ||
|
||
@Column({ name: 'INCOME', type: 'decimal' }) | ||
@ApiProperty({ example: 465.0 }) | ||
income: number; | ||
|
||
@Column({ name: 'INCOME_PER_DAY', type: 'decimal' }) | ||
@ApiProperty({ example: 15.0 }) | ||
incomePerDay: number; | ||
|
||
@Column({ name: 'EXPOSURE', type: 'decimal' }) | ||
@ApiProperty({ example: 400000.0 }) | ||
exposure: number; | ||
|
||
@Column({ name: 'PERIOD' }) | ||
@ApiProperty({ example: 1 }) | ||
period: number; | ||
|
||
@Column({ name: 'DAYS_IN_PERIOD' }) | ||
@ApiProperty({ example: 31 }) | ||
daysInPeriod: number; | ||
|
||
@Column({ name: 'EFFECTIVE_FROM_DATETIME', type: 'timestamp' }) | ||
@ApiProperty({ example: '2023-02-27 00:00:00.000' }) | ||
effectiveFrom: Date; | ||
|
||
@Column({ name: 'EFFECTIVE_TO_DATETIME', type: 'timestamp' }) | ||
@ApiProperty({ example: '2024-02-27 00:00:00.000' }) | ||
effectiveTo: string; | ||
|
||
@Column({ name: 'DATE_CREATED_DATETIME', type: 'timestamp' }) | ||
@ApiProperty({ example: '2023-02-27 00:00:00.000' }) | ||
created: Date; | ||
|
||
@Column({ name: 'DATE_LAST_UPDATED_DATETIME', type: 'timestamp' }) | ||
@ApiProperty({ example: '2023-02-27 00:00:00.000' }) | ||
updated: Date; | ||
|
||
@Column({ name: 'CURRENT_INDICATOR' }) | ||
@ApiProperty({ example: 'Y', description: 'Can be Y or N. Not active records are just for record tracking. Just active records will be returned.' }) | ||
isActive: string; | ||
} |
76 changes: 76 additions & 0 deletions
76
src/modules/premium-schedules/premium-schedules.controller.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import Chance from 'chance'; | ||
import { Response } from 'express'; | ||
|
||
import { CreatePremiumScheduleDto } from './dto/create-premium-schedule.dto'; | ||
import { PremiumSchedulesController } from './premium-schedules.controller'; | ||
import { PremiumSchedulesService } from './premium-schedules.service'; | ||
|
||
const chance = new Chance(); | ||
|
||
// Minimal mock of express Response. "as any" is required to get around Typescript type check. | ||
// TODO: this can be rewritten to use mock library. | ||
const mockResponseObject = { | ||
set: jest.fn().mockReturnValue({}), | ||
} as any as Response; | ||
|
||
describe('PremiumSchedulesController', () => { | ||
let premiumSchedulesController: PremiumSchedulesController; | ||
let premiumSchedulesService: PremiumSchedulesService; | ||
|
||
beforeEach(async () => { | ||
const app: TestingModule = await Test.createTestingModule({ | ||
controllers: [PremiumSchedulesController], | ||
providers: [ | ||
PremiumSchedulesService, | ||
{ | ||
provide: PremiumSchedulesService, | ||
useValue: { | ||
find: jest.fn().mockResolvedValue([ | ||
{ | ||
id: chance.natural(), | ||
facilityURN: chance.natural({ min: 10000000, max: 99999999 }).toString(), | ||
calculationDate: chance.word(), | ||
income: chance.natural(), | ||
incomePerDay: chance.word(), | ||
exposure: chance.currency().code, | ||
period: chance.natural(), | ||
daysInPeriod: chance.word(), | ||
effectiveFrom: chance.date({ string: true }), | ||
effectiveTo: chance.date({ string: true }), | ||
created: chance.date({ string: true }), | ||
updated: chance.date({ string: true }), | ||
isActive: 'Y', | ||
}, | ||
]), | ||
create: jest.fn().mockResolvedValue({}), | ||
}, | ||
}, | ||
], | ||
}).compile(); | ||
|
||
premiumSchedulesController = app.get<PremiumSchedulesController>(PremiumSchedulesController); | ||
premiumSchedulesService = app.get<PremiumSchedulesService>(PremiumSchedulesService); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(PremiumSchedulesController).toBeDefined(); | ||
}); | ||
|
||
describe('create()', () => { | ||
it('should create schedule rates', () => { | ||
const createSchedules = new CreatePremiumScheduleDto(); | ||
premiumSchedulesController.create(mockResponseObject, [createSchedules]); | ||
|
||
expect(premiumSchedulesService.create).toHaveBeenCalled(); | ||
}); | ||
}); | ||
|
||
describe('find()', () => { | ||
it('should find premium schedules for Facility id', () => { | ||
premiumSchedulesController.find({ facilityId: chance.natural({ min: 10000000, max: 99999999 }).toString() }); | ||
|
||
expect(premiumSchedulesService.find).toHaveBeenCalled(); | ||
}); | ||
}); | ||
}); |
46 changes: 46 additions & 0 deletions
46
src/modules/premium-schedules/premium-schedules.controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { BadRequestException, Body, Controller, Get, Param, ParseArrayPipe, Post, Res } from '@nestjs/common'; | ||
import { ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; | ||
import { Response } from 'express'; | ||
|
||
import { CreatePremiumScheduleDto } from './dto/create-premium-schedule.dto'; | ||
import { GetPremiumScheduleParamDto } from './dto/get-premium-schedule-param.dto'; | ||
import { PremiumScheduleEntity } from './entities/premium-schedule.entity'; | ||
import { PremiumSchedulesService } from './premium-schedules.service'; | ||
|
||
@ApiTags('premium-schedules') | ||
@Controller('') | ||
export class PremiumSchedulesController { | ||
constructor(private readonly premiumSchedulesService: PremiumSchedulesService) {} | ||
|
||
@Post('premium/schedule') | ||
@ApiOperation({ summary: 'Create Premium Schedule sequence (aka Income exposure)' }) | ||
@ApiBody({ type: [CreatePremiumScheduleDto] }) | ||
@ApiResponse({ status: 201, description: 'Created.' }) | ||
create( | ||
@Res({ passthrough: true }) res: Response, | ||
@Body(new ParseArrayPipe({ items: CreatePremiumScheduleDto, optional: false })) createPremiumSchedule: CreatePremiumScheduleDto[], | ||
) { | ||
if (!createPremiumSchedule.length) { | ||
throw new BadRequestException('Request payload is empty'); | ||
} | ||
|
||
return this.premiumSchedulesService.create(res, createPremiumSchedule[0]); | ||
} | ||
|
||
@Get('premium/segments/:facilityId') | ||
@ApiOperation({ summary: 'Return previously generated Premium Schedule sequence/segments (aka Income exposures)' }) | ||
@ApiResponse({ | ||
status: 200, | ||
type: [PremiumScheduleEntity], | ||
}) | ||
@ApiParam({ | ||
name: 'facilityId', | ||
required: true, | ||
type: 'string', | ||
description: 'UKEF facility id', | ||
example: '10588388', | ||
}) | ||
find(@Param() param: GetPremiumScheduleParamDto): Promise<PremiumScheduleEntity[]> { | ||
return this.premiumSchedulesService.find(param.facilityId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { TypeOrmModule } from '@nestjs/typeorm'; | ||
import { DATABASE } from '@ukef/constants'; | ||
|
||
import { PremiumScheduleEntity } from './entities/premium-schedule.entity'; | ||
import { PremiumSchedulesController } from './premium-schedules.controller'; | ||
import { PremiumSchedulesService } from './premium-schedules.service'; | ||
|
||
@Module({ | ||
imports: [TypeOrmModule.forFeature([PremiumScheduleEntity], DATABASE.MDM)], | ||
controllers: [PremiumSchedulesController], | ||
providers: [PremiumSchedulesService], | ||
}) | ||
export class PremiumSchedulesModule {} |
Oops, something went wrong.