Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(FN-3664): add risk data to created customers #1163

Merged
merged 7 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/constants/customers.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ export const CUSTOMERS = {
COMPANYREG: '06012345',
PARTYURN: '00302069',
NAME: 'Testing Systems Ltd',
CREDIT_RISK_RATING: 'B+',
LOSS_GIVEN_DEFAULT: 50,
},
};
22 changes: 22 additions & 0 deletions src/helpers/date-formatter.helper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { salesforceFormattedCurrentDate } from './date-formatter.helper';

describe('salesforceFormattedCurrentDate function', () => {
afterEach(() => {
jest.restoreAllMocks();
});

const testCases = [
{ mockDate: new Date('2007-04-27T00:00:00Z'), expected: '2007-04-27' },
{ mockDate: new Date('2007-04-27'), expected: '2007-04-27' },
{ mockDate: new Date(2007, 3, 27), expected: '2007-04-27' },
{ mockDate: new Date('1970-01-01T12:34:56Z'), expected: '1970-01-01' },
{ mockDate: new Date('9999-12-31'), expected: '9999-12-31' },
{ mockDate: new Date('2020-02-29T00:00:00Z'), expected: '2020-02-29' }, // Leap year
];

test.each(testCases)('should format the date $input as $expected', ({ mockDate, expected }) => {
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);

expect(salesforceFormattedCurrentDate()).toBe(expected);
});
});
14 changes: 14 additions & 0 deletions src/helpers/date-formatter.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Returns the current date in the correct format for ingestion by the Salesforce sObject API.
abhi-markan marked this conversation as resolved.
Show resolved Hide resolved
*
* @returns {string} the current date in yyyy-mm-dd format
*/

export const salesforceFormattedCurrentDate = () => {
const today = new Date();
const dd = String(today.getDate()).padStart(2, '0');
const mm = String(today.getMonth() + 1).padStart(2, '0');
const yyyy = String(today.getFullYear());

return `${yyyy}-${mm}-${dd}`;
abhi-markan marked this conversation as resolved.
Show resolved Hide resolved
};
2 changes: 1 addition & 1 deletion src/modules/customers/customers.controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('CustomersController', () => {
});

describe('getOrCreateCustomer', () => {
const DTFSCustomerDto: DTFSCustomerDto = { companyRegistrationNumber: CUSTOMERS.EXAMPLES.COMPANYREG, companyName: 'TEST NAME' };
const DTFSCustomerDto: DTFSCustomerDto = { companyRegistrationNumber: CUSTOMERS.EXAMPLES.COMPANYREG, companyName: 'TEST NAME', probabilityOfDefault: 3 };

const getOrCreateCustomerResponse: GetCustomersResponse = [
{
Expand Down
2 changes: 1 addition & 1 deletion src/modules/customers/customers.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe('CustomerService', () => {
});

describe('getOrCreateCustomer', () => {
const DTFSCustomerDto: DTFSCustomerDto = { companyRegistrationNumber: '12345678', companyName: 'TEST NAME' };
const DTFSCustomerDto: DTFSCustomerDto = { companyRegistrationNumber: '12345678', companyName: 'TEST NAME', probabilityOfDefault: 3 };
const salesforceCreateCustomerResponse: CreateCustomerSalesforceResponseDto = {
id: 'customer-id',
errors: null,
Expand Down
8 changes: 7 additions & 1 deletion src/modules/customers/customers.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
import { CUSTOMERS } from '@ukef/constants';
import { DunAndBradstreetService } from '@ukef/helper-modules/dun-and-bradstreet/dun-and-bradstreet.service';
import { salesforceFormattedCurrentDate } from '@ukef/helpers/date-formatter.helper';
import { GetCustomersInformaticaQueryDto } from '@ukef/modules/informatica/dto/get-customers-informatica-query.dto';
import { InformaticaService } from '@ukef/modules/informatica/informatica.service';
import { SalesforceService } from '@ukef/modules/salesforce/salesforce.service';
Expand Down Expand Up @@ -70,7 +72,7 @@ export class CustomersService {

try {
const existingCustomersInInformatica = await this.informaticaService.getCustomers(backendQuery);
// If the customer exist in Informatica
// If the customer does exist in Informatica
if (existingCustomersInInformatica?.[0]) {
return await this.handleInformaticaResponse(res, DTFSCustomerDto, existingCustomersInInformatica);
} else {
Expand Down Expand Up @@ -205,6 +207,10 @@ export class CustomersService {
Party_URN__c: partyUrn,
D_B_Number__c: dunsNumber,
Company_Registration_Number__c: DTFSCustomerDto.companyRegistrationNumber,
CCM_Credit_Risk_Rating__c: CUSTOMERS.EXAMPLES.CREDIT_RISK_RATING,
CCM_Credit_Risk_Rating_Date__c: salesforceFormattedCurrentDate(),
CCM_Loss_Given_Default__c: CUSTOMERS.EXAMPLES.LOSS_GIVEN_DEFAULT,
CCM_Probability_of_Default__c: DTFSCustomerDto.probabilityOfDefault,
};

const salesforceCreateCustomerResponse: CreateCustomerSalesforceResponseDto = await this.salesforceService.createCustomer(createCustomerDto);
Expand Down
27 changes: 26 additions & 1 deletion src/modules/customers/dto/create-customer.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, Length, MaxLength, MinLength } from 'class-validator';
import { IsNotEmpty, IsNumber, IsString, Length, Max, MaxLength, Min, MinLength } from 'class-validator';

export class CreateCustomerDto {
@ApiProperty({ description: 'Account Name' })
Expand All @@ -25,4 +25,29 @@ export class CreateCustomerDto {
@MinLength(8)
@MaxLength(10)
Company_Registration_Number__c: string;

@ApiProperty({ description: 'Credit Risk Rating' })
@IsString()
@IsNotEmpty()
CCM_Credit_Risk_Rating__c: string;

@ApiProperty({ description: 'Credit Risk Rating Date (YYYY-MM-DD)' })
@IsString()
abhi-markan marked this conversation as resolved.
Show resolved Hide resolved
@Length(10)
@IsNotEmpty()
CCM_Credit_Risk_Rating_Date__c: string;

@ApiProperty({ description: 'Loss Given Default' })
@IsNumber()
@IsNotEmpty()
@Min(0)
@Max(100)
abhi-markan marked this conversation as resolved.
Show resolved Hide resolved
CCM_Loss_Given_Default__c: number;

@ApiProperty({ description: 'Probability of Default' })
@IsNumber()
@IsNotEmpty()
@Min(0)
@Max(100)
abhi-markan marked this conversation as resolved.
Show resolved Hide resolved
CCM_Probability_of_Default__c: number;
}
9 changes: 8 additions & 1 deletion src/modules/customers/dto/dtfs-customer.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, MaxLength, MinLength } from 'class-validator';
import { IsNotEmpty, IsNumber, IsString, Max, MaxLength, Min, MinLength } from 'class-validator';

export class DTFSCustomerDto {
@ApiProperty({ description: 'Company Registration Number', minLength: 8, maxLength: 10 })
Expand All @@ -13,4 +13,11 @@ export class DTFSCustomerDto {
@IsString()
@IsNotEmpty()
companyName: string;

@ApiProperty({ description: 'Probability of Default' })
@IsNumber()
@IsNotEmpty()
abhi-markan marked this conversation as resolved.
Show resolved Hide resolved
@Min(0)
@Max(100)
probabilityOfDefault: number;
}
89 changes: 77 additions & 12 deletions src/modules/salesforce/salesforce.service.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { CUSTOMERS } from '@ukef/constants';
import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator';
import { AxiosError, HttpStatusCode } from 'axios';
import { when } from 'jest-when';
Expand Down Expand Up @@ -54,21 +55,61 @@ describe('SalesforceService', () => {
errors: null,
success: true,
};

const query: CreateCustomerDto = {
const baseQuery = {
Name: companyRegNo,
Party_URN__c: '00312345',
D_B_Number__c: '12341234',
Company_Registration_Number__c: companyRegNo,
CCM_Credit_Risk_Rating_Date__c: '2007-03-27',
CCM_Credit_Risk_Rating__c: CUSTOMERS.EXAMPLES.CREDIT_RISK_RATING,
CCM_Loss_Given_Default__c: CUSTOMERS.EXAMPLES.LOSS_GIVEN_DEFAULT,
CCM_Probability_of_Default__c: 3,
};

const expectedHttpServicePostArgs: [string, body: CreateCustomerDto, object] = [
const baseExpectedHttpServicePostArgs: [string, body: CreateCustomerDto, object] = [
customerBasePath,
query,
baseQuery,
{ headers: { Authorization: 'Bearer ' + expectedAccessToken } },
];

it('sends a POST to the Salesforce /sobjects/Account endpoint with the specified request', async () => {
it.each([
baseQuery,
{
Name: companyRegNo,
Party_URN__c: '00312345',
D_B_Number__c: '12341234',
Company_Registration_Number__c: companyRegNo,
CCM_Credit_Risk_Rating_Date__c: '2007-03-27',
CCM_Credit_Risk_Rating__c: CUSTOMERS.EXAMPLES.CREDIT_RISK_RATING,
CCM_Loss_Given_Default__c: CUSTOMERS.EXAMPLES.LOSS_GIVEN_DEFAULT,
CCM_Probability_of_Default__c: 0.0,
},
{
Name: companyRegNo,
Party_URN__c: '00312345',
D_B_Number__c: '12341234',
Company_Registration_Number__c: companyRegNo,
CCM_Credit_Risk_Rating_Date__c: '2007-03-27',
CCM_Credit_Risk_Rating__c: CUSTOMERS.EXAMPLES.CREDIT_RISK_RATING,
CCM_Loss_Given_Default__c: CUSTOMERS.EXAMPLES.LOSS_GIVEN_DEFAULT,
CCM_Probability_of_Default__c: 100.0,
},
{
Name: companyRegNo,
Party_URN__c: '00312345',
D_B_Number__c: '12341234',
Company_Registration_Number__c: companyRegNo,
CCM_Credit_Risk_Rating_Date__c: '2007-03-27',
CCM_Credit_Risk_Rating__c: 'A+++',
CCM_Loss_Given_Default__c: 100,
CCM_Probability_of_Default__c: 14.1,
},
])('sends a POST to the Salesforce /sobjects/Account endpoint with the specified request', async (query) => {
const expectedHttpServicePostArgs: [string, body: CreateCustomerDto, object] = [
customerBasePath,
query,
{ headers: { Authorization: 'Bearer ' + expectedAccessToken } },
];

when(httpServicePost)
.calledWith(...expectedHttpServicePostArgs)
.mockReturnValueOnce(
Expand All @@ -90,23 +131,47 @@ describe('SalesforceService', () => {
it('throws a SalesforceException if the request to Salesforce fails', async () => {
const axiosRequestError = new AxiosError();
when(httpServicePost)
.calledWith(...expectedHttpServicePostArgs)
.calledWith(...baseExpectedHttpServicePostArgs)
.mockReturnValueOnce(throwError(() => axiosRequestError));
const getCustomersPromise = service.createCustomer(query);
const getCustomersPromise = service.createCustomer(baseQuery);

await expect(getCustomersPromise).rejects.toBeInstanceOf(SalesforceException);
await expect(getCustomersPromise).rejects.toThrow('Failed to create customer in Salesforce');
await expect(getCustomersPromise).rejects.toHaveProperty('innerError', axiosRequestError);
});

it('throws a TypeError if the request is malformed', async () => {
const malformedQuery: CreateCustomerDto = {
it.each([
{
Name: companyRegNo,
Party_URN__c: null,
D_B_Number__c: null,
Company_Registration_Number__c: '12341234',
};

CCM_Credit_Risk_Rating_Date__c: '2007-03-27',
CCM_Credit_Risk_Rating__c: null,
CCM_Loss_Given_Default__c: CUSTOMERS.EXAMPLES.LOSS_GIVEN_DEFAULT,
CCM_Probability_of_Default__c: null,
abhi-markan marked this conversation as resolved.
Show resolved Hide resolved
},
{
Name: companyRegNo,
Party_URN__c: null,
D_B_Number__c: null,
Company_Registration_Number__c: '12341234',
CCM_Credit_Risk_Rating_Date__c: '2007-03-27',
CCM_Credit_Risk_Rating__c: CUSTOMERS.EXAMPLES.CREDIT_RISK_RATING,
CCM_Loss_Given_Default__c: CUSTOMERS.EXAMPLES.LOSS_GIVEN_DEFAULT,
CCM_Probability_of_Default__c: 101,
},
{
Name: companyRegNo,
Party_URN__c: null,
D_B_Number__c: null,
Company_Registration_Number__c: '12341234',
CCM_Credit_Risk_Rating_Date__c: '2007-03-27',
CCM_Credit_Risk_Rating__c: CUSTOMERS.EXAMPLES.CREDIT_RISK_RATING,
CCM_Loss_Given_Default__c: CUSTOMERS.EXAMPLES.LOSS_GIVEN_DEFAULT,
CCM_Probability_of_Default__c: -1,
},
])('throws a TypeError if the request is malformed', async (malformedQuery) => {
const typeError = new TypeError("Cannot read properties of undefined (reading 'pipe')");
const getCustomersPromise = service.createCustomer(malformedQuery);

Expand Down
4 changes: 4 additions & 0 deletions test/docs/__snapshots__/get-docs-yaml.api-test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -808,9 +808,13 @@ components:
companyName:
type: string
description: Company Name
probabilityOfDefault:
type: number
description: Probability of Default
abhi-markan marked this conversation as resolved.
Show resolved Hide resolved
required:
- companyRegistrationNumber
- companyName
- probabilityOfDefault
CreateUkefIdDto:
type: object
properties:
Expand Down
Loading