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-3819): save bankTeamName, bankTeamEmails in fee record correction table #4187

Merged
merged 11 commits into from
Feb 3, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ describe('handleFeeRecordCorrectionRequestedEvent', () => {
lastName: 'User',
};

const bankTeamName = 'Payment Officer Team';
const bankTeamEmails = ['test@ukexportfinance.gov.uk'];

const aCorrectionRequestedEventPayload = (): FeeRecordCorrectionRequestedEvent['payload'] => ({
transactionEntityManager: mockEntityManager,
reasons: [RECORD_CORRECTION_REASON.REPORTED_CURRENCY_INCORRECT],
Expand All @@ -31,6 +34,8 @@ describe('handleFeeRecordCorrectionRequestedEvent', () => {
...requestedByUser,
},
requestSource: aDbRequestSource(),
bankTeamName,
bankTeamEmails,
});

afterEach(() => {
Expand Down Expand Up @@ -104,6 +109,8 @@ describe('handleFeeRecordCorrectionRequestedEvent', () => {
additionalInfo,
requestedByUser,
requestSource,
bankTeamName,
bankTeamEmails,
});

// Assert
Expand All @@ -113,6 +120,8 @@ describe('handleFeeRecordCorrectionRequestedEvent', () => {
reasons,
additionalInfo,
requestSource,
bankTeamName,
bankTeamEmails,
});
expect(mockSave).toHaveBeenCalledWith(FeeRecordCorrectionEntity, newCorrection);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ type CorrectionRequestedEventPayload = {
requestedByUser: RequestedByUser;
reasons: RecordCorrectionReason[];
additionalInfo: string;
bankTeamName: string;
bankTeamEmails: string[];
};

export type FeeRecordCorrectionRequestedEvent = BaseFeeRecordEvent<'CORRECTION_REQUESTED', CorrectionRequestedEventPayload>;
Expand All @@ -25,14 +27,16 @@ export type FeeRecordCorrectionRequestedEvent = BaseFeeRecordEvent<'CORRECTION_R
*/
export const handleFeeRecordCorrectionRequestedEvent = async (
feeRecord: FeeRecordEntity,
{ transactionEntityManager, requestSource, requestedByUser, reasons, additionalInfo }: CorrectionRequestedEventPayload,
{ transactionEntityManager, requestSource, requestedByUser, reasons, additionalInfo, bankTeamName, bankTeamEmails }: CorrectionRequestedEventPayload,
): Promise<FeeRecordEntity> => {
const correction = FeeRecordCorrectionEntity.createRequestedCorrection({
feeRecord,
requestedByUser,
reasons,
additionalInfo,
requestSource,
bankTeamName,
bankTeamEmails,
});

await transactionEntityManager.save(FeeRecordCorrectionEntity, correction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ describe('FeeRecordStateMachine', () => {
reasons: [RECORD_CORRECTION_REASON.FACILITY_ID_INCORRECT],
additionalInfo: 'some additional information',
requestSource: { platform: REQUEST_PLATFORM_TYPE.TFM, userId: 'abc123' },
bankTeamName: 'Payment Officer Team',
bankTeamEmails: ['test@ukexportfinance.gov.uk'],
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { getFormattedReportPeriodWithLongMonth, mapReasonToDisplayValue, RECORD_CORRECTION_REASON, RecordCorrectionReason } from '@ukef/dtfs2-common';
import { formatReasonsAsBulletedListForEmail, generateFeeRecordCorrectionRequestEmailParameters, sendFeeRecordCorrectionRequestEmails } from './helpers';
import { aBank, aReportPeriod } from '../../../../../../test-helpers';
import { getBankById } from '../../../../../repositories/banks-repo';
import { NotFoundError } from '../../../../../errors';
import { aReportPeriod } from '../../../../../../test-helpers';
import externalApi from '../../../../../external-api/api';
import EMAIL_TEMPLATE_IDS from '../../../../../constants/email-template-ids';

Expand All @@ -19,14 +17,7 @@ describe('post-fee-record-correction.controller helpers', () => {
const firstPaymentOfficerEmail = 'officer-1@example.com';
const secondPaymentOfficerEmail = 'officer-2@example.com';
const teamName = 'Payment Officer Team';

const bank = {
...aBank(),
paymentOfficerTeam: {
teamName,
emails: [firstPaymentOfficerEmail, secondPaymentOfficerEmail],
},
};
const teamEmails = [firstPaymentOfficerEmail, secondPaymentOfficerEmail];

describe('formatReasonsAsBulletedListForEmail', () => {
it('should format reasons as a bulleted list when there is a single reasons', () => {
Expand Down Expand Up @@ -61,15 +52,11 @@ describe('post-fee-record-correction.controller helpers', () => {
const reasons: RecordCorrectionReason[] = [RECORD_CORRECTION_REASON.UTILISATION_INCORRECT, RECORD_CORRECTION_REASON.REPORTED_CURRENCY_INCORRECT];
const reportPeriod = aReportPeriod();
const exporter = 'Test Exporter';
const bankId = '123';
const requestedByUserEmail = 'user@example.com';

it('should generate email parameters', async () => {
// Arrange
jest.mocked(getBankById).mockResolvedValue(bank);

it('should generate email parameters', () => {
// Act
const result = await generateFeeRecordCorrectionRequestEmailParameters(reasons, reportPeriod, exporter, bankId, requestedByUserEmail);
const result = generateFeeRecordCorrectionRequestEmailParameters(reasons, reportPeriod, exporter, requestedByUserEmail, teamName, teamEmails);

// Assert
expect(result).toEqual({
Expand All @@ -82,17 +69,6 @@ describe('post-fee-record-correction.controller helpers', () => {
},
});
});

it('should throw a NotFoundError if the bank is not found', async () => {
// Arrange
jest.mocked(getBankById).mockResolvedValue(null);

// Act & Assert
await expect(generateFeeRecordCorrectionRequestEmailParameters(reasons, reportPeriod, exporter, bankId, requestedByUserEmail)).rejects.toThrow(
new NotFoundError(`Bank not found: ${bankId}`),
);
expect(console.error).toHaveBeenCalledWith('Bank not found: %s', bankId);
});
});

describe('sendFeeRecordCorrectionRequestEmails', () => {
Expand All @@ -101,16 +77,13 @@ describe('post-fee-record-correction.controller helpers', () => {
const reasons: RecordCorrectionReason[] = [RECORD_CORRECTION_REASON.UTILISATION_INCORRECT, RECORD_CORRECTION_REASON.REPORTED_CURRENCY_INCORRECT];
const reportPeriod = aReportPeriod();
const exporter = 'Potato exporter';
const bankId = '567';
const requestedByUserEmail = 'tfm-user@email.com';

jest.mocked(getBankById).mockResolvedValue(bank);

// Act
await sendFeeRecordCorrectionRequestEmails(reasons, reportPeriod, exporter, bankId, requestedByUserEmail);
await sendFeeRecordCorrectionRequestEmails(reasons, reportPeriod, exporter, requestedByUserEmail, teamName, teamEmails);

// Assert
const { variables } = await generateFeeRecordCorrectionRequestEmailParameters(reasons, reportPeriod, exporter, bankId, requestedByUserEmail);
const { variables } = generateFeeRecordCorrectionRequestEmailParameters(reasons, reportPeriod, exporter, requestedByUserEmail, teamName, teamEmails);
expect(externalApi.sendEmail).toHaveBeenCalledTimes(3);
expect(externalApi.sendEmail).toHaveBeenCalledWith(EMAIL_TEMPLATE_IDS.FEE_RECORD_CORRECTION_REQUEST, firstPaymentOfficerEmail, variables);
expect(externalApi.sendEmail).toHaveBeenCalledWith(EMAIL_TEMPLATE_IDS.FEE_RECORD_CORRECTION_REQUEST, secondPaymentOfficerEmail, variables);
Expand All @@ -122,49 +95,23 @@ describe('post-fee-record-correction.controller helpers', () => {
const reasons: RecordCorrectionReason[] = [RECORD_CORRECTION_REASON.UTILISATION_INCORRECT, RECORD_CORRECTION_REASON.REPORTED_CURRENCY_INCORRECT];
const reportPeriod = aReportPeriod();
const exporter = 'Potato exporter';
const bankId = '567';
const requestedByUserEmail = 'tfm-user@email.com';

jest.mocked(getBankById).mockResolvedValue(bank);

// Act
const response = await sendFeeRecordCorrectionRequestEmails(reasons, reportPeriod, exporter, bankId, requestedByUserEmail);
const response = await sendFeeRecordCorrectionRequestEmails(reasons, reportPeriod, exporter, requestedByUserEmail, teamName, teamEmails);

// Assert
const { emails } = await generateFeeRecordCorrectionRequestEmailParameters(reasons, reportPeriod, exporter, bankId, requestedByUserEmail);
const { emails } = generateFeeRecordCorrectionRequestEmailParameters(reasons, reportPeriod, exporter, requestedByUserEmail, teamName, teamEmails);

expect(response).toEqual({ emails });
});

it('should throw NotFoundError error if the bank cannot be found', async () => {
// Arrange
const bankId = '123';
jest.mocked(getBankById).mockResolvedValue(null);

const expectedError = new NotFoundError(`Bank not found: ${bankId}`);

// Act + Assert
await expect(sendFeeRecordCorrectionRequestEmails([], aReportPeriod(), 'test exporter', bankId, 'test@test.com')).rejects.toThrow(expectedError);
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith('Bank not found: %s', bankId);
});

it('should log and rethrow error if sending an email fails', async () => {
// Arrange
const bankId = '123';
jest.mocked(getBankById).mockResolvedValue({
...aBank(),
paymentOfficerTeam: {
teamName,
emails: ['test1@test.com'],
},
});

const error = new Error('Failed to send second email');
jest.mocked(externalApi.sendEmail).mockResolvedValueOnce().mockRejectedValueOnce(error);

// Act + Assert
await expect(sendFeeRecordCorrectionRequestEmails([], aReportPeriod(), 'test exporter', bankId, 'test2@test.com')).rejects.toThrow(error);
await expect(sendFeeRecordCorrectionRequestEmails([], aReportPeriod(), 'test exporter', 'test2@test.com', teamName, teamEmails)).rejects.toThrow(error);
expect(console.error).toHaveBeenCalledWith('Error sending fee record correction request email: %o', error);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { getFormattedReportPeriodWithLongMonth, mapReasonToDisplayValue, RecordC
import externalApi from '../../../../../external-api/api';
import EMAIL_TEMPLATE_IDS from '../../../../../constants/email-template-ids';
import { FeeRecordCorrectionRequestEmails, FeeRecordCorrectionRequestEmailAddresses } from '../../../../../types/utilisation-reports';
import { getBankById } from '../../../../../repositories/banks-repo';
import { NotFoundError } from '../../../../../errors';

/**
* Formats the reasons for record correction into a bulleted list.
Expand All @@ -27,28 +25,20 @@ export const formatReasonsAsBulletedListForEmail = (reasons: RecordCorrectionRea
* @param report
* @returns emails and variables for the email
*/
export const generateFeeRecordCorrectionRequestEmailParameters = async (
export const generateFeeRecordCorrectionRequestEmailParameters = (
reasons: RecordCorrectionReason[],
reportPeriod: ReportPeriod,
exporter: string,
bankId: string,
requestedByUserEmail: string,
): Promise<FeeRecordCorrectionRequestEmails> => {
const bank = await getBankById(bankId);

if (!bank) {
console.error('Bank not found: %s', bankId);
throw new NotFoundError(`Bank not found: ${bankId}`);
}

const { teamName, emails } = bank.paymentOfficerTeam;

recipient: string,
paymentOfficerTeamEmails: string[],
): FeeRecordCorrectionRequestEmails => {
const reportPeriodString = getFormattedReportPeriodWithLongMonth(reportPeriod);

return {
emails: [...emails, requestedByUserEmail],
emails: [...paymentOfficerTeamEmails, requestedByUserEmail],
variables: {
recipient: teamName,
recipient,
reportPeriod: reportPeriodString,
exporterName: exporter,
reasonsList: formatReasonsAsBulletedListForEmail(reasons),
Expand All @@ -73,10 +63,18 @@ export const sendFeeRecordCorrectionRequestEmails = async (
reasons: RecordCorrectionReason[],
reportPeriod: ReportPeriod,
exporter: string,
bankId: string,
requestedByUserEmail: string,
recipient: string,
RLavender98 marked this conversation as resolved.
Show resolved Hide resolved
paymentOfficerTeamEmails: string[],
): Promise<FeeRecordCorrectionRequestEmailAddresses> => {
const { emails, variables } = await generateFeeRecordCorrectionRequestEmailParameters(reasons, reportPeriod, exporter, bankId, requestedByUserEmail);
const { emails, variables } = generateFeeRecordCorrectionRequestEmailParameters(
reasons,
reportPeriod,
exporter,
requestedByUserEmail,
recipient,
paymentOfficerTeamEmails,
);

try {
await Promise.all(emails.map((email) => externalApi.sendEmail(EMAIL_TEMPLATE_IDS.FEE_RECORD_CORRECTION_REQUEST, email, variables)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ import { FEE_RECORD_EVENT_TYPE } from '../../../../../services/state-machines/fe
import { FeeRecordRepo } from '../../../../../repositories/fee-record-repo';
import { sendFeeRecordCorrectionRequestEmails } from './helpers';
import { FeeRecordCorrectionRequestEmailAddresses } from '../../../../../types/utilisation-reports';
import { getBankPaymentOfficerTeamDetails } from '../../helpers/get-bank-payment-officer-team-details';

jest.mock('../../../../../helpers');
jest.mock('../../../../../services/state-machines/fee-record/fee-record.state-machine');
jest.mock('../../../../../repositories/fee-record-correction-request-transient-form-data-repo');
jest.mock('../../../../../repositories/fee-record-repo');
jest.mock('./helpers');
jest.mock('../../helpers/get-bank-payment-officer-team-details');

console.error = jest.fn();

Expand All @@ -40,6 +42,14 @@ describe('post-fee-record-correction.controller', () => {
const mockHandleEvent = jest.fn();
const mockForFeeRecordStateMachineConstructor = jest.fn();

const mockTeamName = 'Mock team name';
const mockEmails = ['test1@ukexportfinance.gov.uk', 'test2@ukexportfinance.gov.uk'];

const getBankPaymentOfficerTeamDetailsResponse = {
teamName: mockTeamName,
emails: mockEmails,
};

beforeEach(() => {
jest.mocked(executeWithSqlTransaction).mockImplementation(async (functionToExecute) => {
return await functionToExecute(mockEntityManager);
Expand All @@ -54,6 +64,7 @@ describe('post-fee-record-correction.controller', () => {
deleteByUserIdAndFeeRecordId: mockDeleteTransientFormData,
});
jest.spyOn(FeeRecordRepo, 'withTransaction').mockReturnValue({ findOneByIdAndReportIdWithReport: mockFindFeeRecordWithReport });
jest.mocked(getBankPaymentOfficerTeamDetails).mockResolvedValue(getBankPaymentOfficerTeamDetailsResponse);
});

afterEach(() => {
Expand Down Expand Up @@ -149,8 +160,9 @@ describe('post-fee-record-correction.controller', () => {
reasons,
mockReport.reportPeriod,
mockFeeRecord.exporter,
mockReport.bankId,
user.email,
mockTeamName,
mockEmails,
);
});

Expand All @@ -171,6 +183,8 @@ describe('post-fee-record-correction.controller', () => {
platform: REQUEST_PLATFORM_TYPE.TFM,
userId,
},
bankTeamName: mockTeamName,
bankTeamEmails: mockEmails,
};

// Act
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PostFeeRecordCorrectionPayload } from '../../../../routes/middleware/pa
import { FeeRecordRepo } from '../../../../../repositories/fee-record-repo';
import { sendFeeRecordCorrectionRequestEmails } from './helpers';
import { FeeRecordCorrectionRequestEmailAddresses } from '../../../../../types/utilisation-reports';
import { getBankPaymentOfficerTeamDetails } from '../../helpers/get-bank-payment-officer-team-details';

export type PostFeeRecordCorrectionRequest = CustomExpressRequest<{
params: {
Expand Down Expand Up @@ -64,6 +65,8 @@ export const postFeeRecordCorrection = async (req: PostFeeRecordCorrectionReques
throw new NotFoundError(`Failed to find a fee record with id ${feeRecordId} and report id ${reportId}`);
}

const { teamName, emails } = await getBankPaymentOfficerTeamDetails(feeRecord.report.bankId);
RLavender98 marked this conversation as resolved.
Show resolved Hide resolved

const stateMachine = FeeRecordStateMachine.forFeeRecord(feeRecord);

await stateMachine.handleEvent({
Expand All @@ -81,6 +84,8 @@ export const postFeeRecordCorrection = async (req: PostFeeRecordCorrectionReques
platform: REQUEST_PLATFORM_TYPE.TFM,
userId,
},
bankTeamName: teamName,
bankTeamEmails: emails,
},
});

Expand All @@ -90,8 +95,9 @@ export const postFeeRecordCorrection = async (req: PostFeeRecordCorrectionReques
reasons,
feeRecord.report.reportPeriod,
feeRecord.exporter,
feeRecord.report.bankId,
user.email,
teamName,
emails,
);

return notifiedEmails;
Expand Down
Loading
Loading