From b648d3fba994e31c4913fe277f2e622323527459 Mon Sep 17 00:00:00 2001 From: Aleksandra Frost Date: Tue, 30 Apr 2024 11:16:55 +0200 Subject: [PATCH 01/15] [ADD] controller to revert the uac approval --- docker-compose.yaml | 1 + .../proposal-contracting.controller.spec.ts | 18 ++ .../proposal-contracting.controller.ts | 19 +- .../dto/revert-location-decision.dto.ts | 10 + .../proposal/enums/history-event.enum.ts | 3 + .../proposal-contracting.service.spec.ts | 50 ++++- .../services/proposal-contracting.service.ts | 20 +- ...c.ts => handle-location-vote.util.spec.ts} | 183 +++++++++++++++++- .../__tests__/proposal-history.util.spec.ts | 15 ++ ...e.util.ts => handle-location-vote.util.ts} | 50 +++++ .../proposal/utils/proposal-history.util.ts | 10 + .../proposal/utils/validate-vote.util.ts | 6 + 12 files changed, 377 insertions(+), 8 deletions(-) create mode 100644 src/modules/proposal/dto/revert-location-decision.dto.ts rename src/modules/proposal/utils/__tests__/{add-location-vote.util.spec.ts => handle-location-vote.util.spec.ts} (53%) rename src/modules/proposal/utils/{add-location-vote.util.ts => handle-location-vote.util.ts} (75%) diff --git a/docker-compose.yaml b/docker-compose.yaml index fbb32f3..5821daf 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -28,3 +28,4 @@ services: - 8081:3000 environment: ENABLE_TELEMETRY: false + PRINT_FRONTEND_HOST: http://localhost:3000 diff --git a/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts b/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts index c0c8604..d3dd48f 100644 --- a/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts +++ b/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts @@ -5,6 +5,7 @@ import { Role } from 'src/shared/enums/role.enum'; import { FdpgRequest } from 'src/shared/types/request-user.interface'; import { SetDizApprovalDto } from '../../dto/set-diz-approval.dto'; import { SetUacApprovalDto } from '../../dto/set-uac-approval.dto'; +import { RevertLocationDecisionDto } from '../../dto/revert-location-decision.dto'; import { SignContractDto } from '../../dto/sign-contract.dto'; import { ProposalContractingController } from '../proposal-contracting.controller'; import { ProposalContractingService } from '../../services/proposal-contracting.service'; @@ -75,6 +76,23 @@ describe('ProposalContractingController', () => { }); }); + describe('revertLocationDecision', () => { + it('should revert the location decision', async () => { + const params = { + id: 'mongoId', + }; + const input = new RevertLocationDecisionDto(); + jest.spyOn(proposalContractingService, 'revertLocationDecision'); + + await proposalContractingController.revertLocationDecision(params, input, request); + expect(proposalContractingService.revertLocationDecision).toHaveBeenCalledWith( + params.id, + input.location, + request.user, + ); + }); + }); + describe('signContract', () => { it('should set the contract sign', async () => { const params = { diff --git a/src/modules/proposal/controller/proposal-contracting.controller.ts b/src/modules/proposal/controller/proposal-contracting.controller.ts index 4040f57..d8c1a08 100644 --- a/src/modules/proposal/controller/proposal-contracting.controller.ts +++ b/src/modules/proposal/controller/proposal-contracting.controller.ts @@ -1,3 +1,4 @@ +import { ProposalContractingService } from './../services/proposal-contracting.service'; import { Body, HttpCode, @@ -24,8 +25,8 @@ import { ContractingUploadDto } from '../dto/contracting-upload.dto'; import { ProposalMarkConditionAcceptedReturnDto } from '../dto/proposal/proposal.dto'; import { SetDizApprovalDto } from '../dto/set-diz-approval.dto'; import { SetUacApprovalDto, SetUacApprovalWithFileDto } from '../dto/set-uac-approval.dto'; +import { RevertLocationDecisionDto } from '../dto/revert-location-decision.dto'; import { SignContractDto, SignContractWithFileDto } from '../dto/sign-contract.dto'; -import { ProposalContractingService } from '../services/proposal-contracting.service'; import { InitContractingDto } from '../dto/proposal/init-contracting.dto'; @ApiController('proposals', undefined, 'contracting') @@ -66,6 +67,22 @@ export class ProposalContractingController { return await this.proposalContractingService.setUacApproval(id, vote, file, user); } + @Auth(Role.FdpgMember) + @Put(':id/revertLocationDecision') + @UsePipes(ValidationPipe) + @ApiNotFoundResponse({ description: 'Proposal could not be found' }) + @ApiOperation({ summary: 'Reverts the Location Decision' }) + @ApiNoContentResponse({ description: 'Location Decision reverted. No content returns.' }) + @HttpCode(204) + @ApiBody({ type: RevertLocationDecisionDto }) + async revertLocationDecision( + @Param() { id }: MongoIdParamDto, + @Body() { location }: RevertLocationDecisionDto, + @Request() { user }: FdpgRequest, + ): Promise { + return await this.proposalContractingService.revertLocationDecision(id, location, user); + } + @Auth(Role.FdpgMember) @Put(':id/init-contracting') @UsePipes(ValidationPipe) diff --git a/src/modules/proposal/dto/revert-location-decision.dto.ts b/src/modules/proposal/dto/revert-location-decision.dto.ts new file mode 100644 index 0000000..e113541 --- /dev/null +++ b/src/modules/proposal/dto/revert-location-decision.dto.ts @@ -0,0 +1,10 @@ +import { Exclude, Expose } from 'class-transformer'; +import { IsEnum } from 'class-validator'; +import { MiiLocation } from 'src/shared/constants/mii-locations'; + +@Exclude() +export class RevertLocationDecisionDto { + @Expose() + @IsEnum(MiiLocation) + location: MiiLocation; +} diff --git a/src/modules/proposal/enums/history-event.enum.ts b/src/modules/proposal/enums/history-event.enum.ts index a5c79f0..f1d2763 100644 --- a/src/modules/proposal/enums/history-event.enum.ts +++ b/src/modules/proposal/enums/history-event.enum.ts @@ -29,6 +29,9 @@ export enum HistoryEventType { /** FDPG Veto */ FdpgApprovedLocationRemoved = 'FDPG_APPROVED_LOCATION_REMOVED', + /** FDPG reverts DIZ and UAC decision */ + FdpgRevertedLocationDecision = 'FDPG_REVERTED_LOCATION_DECISION', + /** Data Delivery */ /** Contracting */ diff --git a/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts b/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts index e78f3f9..0bba9bf 100644 --- a/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts +++ b/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts @@ -1,3 +1,4 @@ +import { handleLocationDecision } from './../../utils/handle-location-vote.util'; import { Test, TestingModule } from '@nestjs/testing'; import { StorageService } from 'src/modules/storage/storage.service'; import { EventEngineService } from 'src/modules/event-engine/event-engine.service'; @@ -10,23 +11,29 @@ import { SignContractDto } from '../../dto/sign-contract.dto'; import { ProposalStatus } from '../../enums/proposal-status.enum'; import { ProposalDocument } from '../../schema/proposal.schema'; import { addContractSign } from '../../utils/add-contract-sign.util'; -import { addDizApproval, addUacApproval, addUacApprovalWithCondition } from '../../utils/add-location-vote.util'; +import { addDizApproval, addUacApproval, addUacApprovalWithCondition } from '../../utils/handle-location-vote.util'; import { addHistoryItemForContractSign, addHistoryItemForDizApproval, addHistoryItemForStatus, addHistoryItemForUacApproval, + addHistoryItemForRevertLocationDecision, } from '../../utils/proposal-history.util'; import { addUpload } from '../../utils/proposal.utils'; import { validateContractSign } from '../../utils/validate-contract-sign.util'; import { validateStatusChange } from '../../utils/validate-status-change.util'; -import { validateDizApproval, validateUacApproval } from '../../utils/validate-vote.util'; +import { + validateDizApproval, + validateRevertLocationDecision, + validateUacApproval, +} from '../../utils/validate-vote.util'; import { ProposalContractingService } from '../proposal-contracting.service'; import { ProposalCrudService } from '../proposal-crud.service'; import { StatusChangeService } from '../status-change.service'; import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { NoErrorThrownError, getError } from 'test/get-error'; import { InitContractingDto } from '../../dto/proposal/init-contracting.dto'; +import { ProposalUploadService } from '../proposal-upload.service'; jest.mock('class-transformer', () => { const original = jest.requireActual('class-transformer'); @@ -39,13 +46,15 @@ jest.mock('class-transformer', () => { jest.mock('../../utils/validate-vote.util', () => ({ validateDizApproval: jest.fn(), validateUacApproval: jest.fn(), + validateRevertLocationDecision: jest.fn(), })); -jest.mock('../../utils/add-location-vote.util', () => ({ +jest.mock('../../utils/handle-location-vote.util', () => ({ addDizApproval: jest.fn(), addUacApproval: jest.fn(), addUacApprovalWithCondition: jest.fn(), addUacConditionReview: jest.fn(), + handleLocationDecision: jest.fn(), })); jest.mock('../../utils/proposal-history.util', () => ({ @@ -54,6 +63,7 @@ jest.mock('../../utils/proposal-history.util', () => ({ addHistoryItemForStatus: jest.fn(), addHistoryItemForUacApproval: jest.fn(), addHistoryItemForUacCondition: jest.fn(), + addHistoryItemForRevertLocationDecision: jest.fn(), })); jest.mock('../../utils/proposal.utils', () => ({ @@ -85,6 +95,7 @@ describe('ProposalContractingService', () => { let eventEngineService: jest.Mocked; let storageService: jest.Mocked; let statusChangeService: jest.Mocked; + let proposalUploadService: jest.Mocked; const request = { user: { @@ -137,6 +148,7 @@ describe('ProposalContractingService', () => { handleProposalUacApproval: jest.fn(), handleProposalStatusChange: jest.fn(), handleProposalContractSign: jest.fn(), + handleLocationDecision: jest.fn(), }, }, { @@ -151,6 +163,12 @@ describe('ProposalContractingService', () => { handleEffects: jest.fn(), }, }, + { + provide: ProposalUploadService, + useValue: { + handleEffects: jest.fn(), + }, + }, ], imports: [], }).compile(); @@ -160,6 +178,9 @@ describe('ProposalContractingService', () => { eventEngineService = module.get(EventEngineService) as jest.Mocked; storageService = module.get(StorageService) as jest.Mocked; statusChangeService = module.get(StatusChangeService) as jest.Mocked; + proposalUploadService = module.get( + ProposalUploadService, + ) as jest.Mocked; }); it('should be defined', () => { @@ -226,6 +247,29 @@ describe('ProposalContractingService', () => { }); }); + describe('revertLocationDecision', () => { + it('should revert the location decision', async () => { + const proposalDocument = getProposalDocument(); + jest.spyOn(proposalCrudService, 'findDocument').mockResolvedValueOnce(proposalDocument); + + await proposalContractingService.revertLocationDecision(proposalId, request.user.miiLocation, request.user); + + expect(validateRevertLocationDecision).toHaveBeenCalledWith(proposalDocument); + expect(handleLocationDecision).toHaveBeenCalledWith( + proposalDocument, + request.user.miiLocation, + request.user, + proposalUploadService, + ); + expect(addHistoryItemForRevertLocationDecision).toHaveBeenCalledWith( + proposalDocument, + request.user, + request.user.miiLocation, + ); + expect(proposalDocument.save).toHaveBeenCalledTimes(1); + }); + }); + describe('initContracting', () => { test.each([true, false])('should init the contracting', async (isValidStatus: boolean) => { const proposalStatus = isValidStatus ? ProposalStatus.LocationCheck : ProposalStatus.Draft; diff --git a/src/modules/proposal/services/proposal-contracting.service.ts b/src/modules/proposal/services/proposal-contracting.service.ts index d0c8249..d6b69b3 100644 --- a/src/modules/proposal/services/proposal-contracting.service.ts +++ b/src/modules/proposal/services/proposal-contracting.service.ts @@ -1,6 +1,6 @@ +import { ProposalUploadService } from './proposal-upload.service'; import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; import { plainToClass } from 'class-transformer'; -import { isNotEmptyObject } from 'class-validator'; import { Role } from 'src/shared/enums/role.enum'; import { IRequestUser } from 'src/shared/types/request-user.interface'; import { convertUserToGroups } from 'src/shared/utils/user-group.utils'; @@ -21,23 +21,26 @@ import { addUacApproval, addUacApprovalWithCondition, addUacConditionReview, -} from '../utils/add-location-vote.util'; + handleLocationDecision, +} from '../utils/handle-location-vote.util'; import { addHistoryItemForContractSign, addHistoryItemForDizApproval, addHistoryItemForStatus, addHistoryItemForUacApproval, addHistoryItemForUacCondition, + addHistoryItemForRevertLocationDecision, } from '../utils/proposal-history.util'; import { addUpload, getBlobName } from '../utils/proposal.utils'; import { validateContractSign } from '../utils/validate-contract-sign.util'; import { validateStatusChange } from '../utils/validate-status-change.util'; -import { validateDizApproval, validateUacApproval } from '../utils/validate-vote.util'; +import { validateDizApproval, validateUacApproval, validateRevertLocationDecision } from '../utils/validate-vote.util'; import { SetDizApprovalDto } from '../dto/set-diz-approval.dto'; import { InitContractingDto } from '../dto/proposal/init-contracting.dto'; import { ValidationErrorInfo } from 'src/shared/dto/validation/validation-error-info.dto'; import { ValidationException } from 'src/exceptions/validation/validation.exception'; import { BadRequestError } from 'src/shared/enums/bad-request-error.enum'; +import { MiiLocation } from 'src/shared/constants/mii-locations'; @Injectable() export class ProposalContractingService { @@ -46,6 +49,7 @@ export class ProposalContractingService { private eventEngineService: EventEngineService, private storageService: StorageService, private statusChangeService: StatusChangeService, + private proposalUploadService: ProposalUploadService, ) {} async setDizApproval(proposalId: string, vote: SetDizApprovalDto, user: IRequestUser): Promise { @@ -86,6 +90,16 @@ export class ProposalContractingService { await this.eventEngineService.handleProposalUacApproval(saveResult, vote.value, user.miiLocation); } + async revertLocationDecision(proposalId: string, location: MiiLocation, user: IRequestUser): Promise { + const toBeUpdated = await this.proposalCrudService.findDocument(proposalId, user, undefined, true); + validateRevertLocationDecision(toBeUpdated); + + await handleLocationDecision(toBeUpdated, location, user, this.proposalUploadService); + addHistoryItemForRevertLocationDecision(toBeUpdated, user, location); + + await toBeUpdated.save(); + } + async initContracting( proposalId: string, file: Express.Multer.File, diff --git a/src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/handle-location-vote.util.spec.ts similarity index 53% rename from src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts rename to src/modules/proposal/utils/__tests__/handle-location-vote.util.spec.ts index 0d2095e..6551e8a 100644 --- a/src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/handle-location-vote.util.spec.ts @@ -1,3 +1,4 @@ +import { getLocationState } from './../validate-access.util'; import { MiiLocation } from 'src/shared/constants/mii-locations'; import { Role } from 'src/shared/enums/role.enum'; import { FdpgRequest } from 'src/shared/types/request-user.interface'; @@ -8,17 +9,22 @@ import { FdpgTaskType } from '../../enums/fdpg-task-type.enum'; import { ProposalStatus } from '../../enums/proposal-status.enum'; import { ProposalDocument } from '../../schema/proposal.schema'; import { ConditionalApproval } from '../../schema/sub-schema/conditional-approval.schema'; +import { DeclineReason } from '../../schema/sub-schema/decline-reason.schema'; +import { UacApproval } from '../../schema/sub-schema/uac-approval.schema'; import { addFdpgTaskAndReturnId, removeFdpgTask } from '../add-fdpg-task.util'; import { addDizApproval, addUacApproval, addUacApprovalWithCondition, addUacConditionReview, -} from '../add-location-vote.util'; + handleLocationDecision, +} from '../handle-location-vote.util'; import { clearLocationsVotes } from '../location-flow.util'; +import { ProposalUploadService } from '../../services/proposal-upload.service'; jest.mock('../location-flow.util', () => ({ clearLocationsVotes: jest.fn(), + deleteConditionalUpload: jest.fn(), })); jest.mock('../add-fdpg-task.util', () => ({ @@ -26,6 +32,14 @@ jest.mock('../add-fdpg-task.util', () => ({ removeFdpgTask: jest.fn(), })); +jest.mock('../validate-access.util', () => ({ + getLocationState: jest.fn(), +})); + +const proposalUploadServiceMock = { + deleteUpload: jest.fn(), +} as any as ProposalUploadService; + describe('addLocationVoteUtil', () => { beforeEach(() => { jest.clearAllMocks(); @@ -229,4 +243,171 @@ describe('addLocationVoteUtil', () => { expect(proposal.requestedButExcludedLocations).toEqual([condition.location]); }); }); + + const locationStateDefault: ReturnType = { + isDizCheck: false, + dizApproved: false, + uacApproved: false, + isConditionalApproval: false, + conditionalApprovalAccepted: false, + conditionalApprovalDeclined: false, + contractAcceptedByResearcher: false, + contractRejectedByResearcher: false, + signedContract: false, + signedContractAndContractingDone: false, + requestedButExcluded: false, + }; + + describe('handleLocationDecision', () => { + const getLocationStateMock = jest.mocked(getLocationState); + + it('should delete the declineReason object', async () => { + const declineReason = new DeclineReason(); + declineReason.location = MiiLocation.UKRUB; + const proposal = getProposalDocument(); + proposal.declineReasons = [declineReason]; + proposal.openDizChecks = []; + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + requestedButExcluded: true, + }); + + expect(proposal.declineReasons).toEqual([declineReason]); + await handleLocationDecision(proposal, declineReason.location, request.user, proposalUploadServiceMock); + + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(proposal.declineReasons).not.toEqual([declineReason]); + expect(proposal.declineReasons.length).toEqual(0); + expect(clearLocationsVotes).toBeCalledWith(proposal, declineReason.location); + expect(proposal.requestedButExcludedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([declineReason.location]); + }); + + it('should delete the uac approval object', async () => { + const uacApproval = new UacApproval(); + uacApproval.location = MiiLocation.UKRUB; + const proposal = getProposalDocument(); + proposal.uacApprovals = [uacApproval]; + proposal.openDizChecks = []; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + uacApproved: true, + }); + + expect(proposal.uacApprovals).toEqual([uacApproval]); + await handleLocationDecision(proposal, uacApproval.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(proposal.uacApprovals.length).toEqual(0); + expect(proposal.uacApprovals).not.toEqual([uacApproval]); + expect(clearLocationsVotes).toBeCalledWith(proposal, uacApproval.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([uacApproval.location]); + }); + + describe('should remove Fdpg Task in case of expected data amount reached', () => { + const testCases = [ + { + name: 'expected data amount is reached even withouth the location data amount', + dataAmount: 10, + expectedDataAmountReached: true, + }, + { + name: 'expected data amount is not reached withouth the location data amount', + dataAmount: 12, + expectedDataAmountReached: false, + }, + ]; + + test.each(testCases)('%s', async ({ dataAmount, expectedDataAmountReached }) => { + const approval = new UacApproval(); + approval.location = MiiLocation.UKRUB; + approval.dataAmount = dataAmount; + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.fdpgTaskId = FdpgTaskType.ConditionApproval; + condition.dataAmount = 10; + const proposal = getProposalDocument(); + proposal.conditionalApprovals = [condition]; + proposal.uacApprovals = [approval]; + proposal.totalPromisedDataAmount = 21; + proposal.requestedData.desiredDataAmount = 11; + proposal.openDizChecks = []; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + conditionalApprovalAccepted: true, + }); + + expect(proposal.uacApprovals).toEqual([approval]); + await handleLocationDecision(proposal, approval.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + if (expectedDataAmountReached) { + expect(removeFdpgTask).not.toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); + } else { + expect(removeFdpgTask).toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); + } + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + }); + + it('should delete conditional approval object and fdpg task', async () => { + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.fdpgTaskId = FdpgTaskType.ConditionApproval; + condition.dataAmount = 10; + const proposal = getProposalDocument(); + proposal.conditionalApprovals = [condition]; + proposal.totalPromisedDataAmount = condition.dataAmount + 1; + proposal.requestedData.desiredDataAmount = 11; + proposal.openDizChecks = []; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + conditionalApprovalAccepted: true, + }); + + expect(proposal.conditionalApprovals).toEqual([condition]); + await handleLocationDecision(proposal, condition.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); + expect(proposal.conditionalApprovals).not.toEqual([condition]); + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + + it('should delete the upload blob', async () => { + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.uploadId = 'uploadId'; + const proposal = getProposalDocument(); + proposal.conditionalApprovals = [condition]; + proposal.openDizChecks = []; + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + isConditionalApproval: true, + }); + + await handleLocationDecision(proposal, condition.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(proposalUploadServiceMock.deleteUpload).toBeCalledTimes(1); + expect(proposalUploadServiceMock.deleteUpload).toBeCalledWith(proposal._id, condition.uploadId, request.user); + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + }); }); diff --git a/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts b/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts index 5e4229c..64a8c98 100644 --- a/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts @@ -13,6 +13,7 @@ import { addHistoryItemForContractSystemReject, addHistoryItemForProposalLock, addHistoryItemForUnselectedLocation, + addHistoryItemForRevertLocationDecision, } from '../proposal-history.util'; const proposalId = 'proposalId'; @@ -191,6 +192,20 @@ describe('ProposalHistoryUtil', () => { }); }); + describe('addHistoryItemForRevertLocationDecision', () => { + it('should add history item for reverted location decision', () => { + const request = getRequest(); + const proposal = getProposalDocument(); + const location = MiiLocation.UKRUB; + + addHistoryItemForRevertLocationDecision(proposal, request.user, location); + expect(proposal.history.length).toBe(1); + + const expectedType = HistoryEventType.FdpgRevertedLocationDecision; + expect(proposal.history[0].type).toBe(expectedType); + }); + }); + describe('addHistoryItemForUacCondition', () => { test.each([true, false])(`should add history item for uac condition (isApproved: %s)`, (isApproved) => { const request = getRequest(); diff --git a/src/modules/proposal/utils/add-location-vote.util.ts b/src/modules/proposal/utils/handle-location-vote.util.ts similarity index 75% rename from src/modules/proposal/utils/add-location-vote.util.ts rename to src/modules/proposal/utils/handle-location-vote.util.ts index 68fdda6..76572c4 100644 --- a/src/modules/proposal/utils/add-location-vote.util.ts +++ b/src/modules/proposal/utils/handle-location-vote.util.ts @@ -11,9 +11,12 @@ import { ConditionalApproval } from '../schema/sub-schema/conditional-approval.s import { UacApproval } from '../schema/sub-schema/uac-approval.schema'; import { addFdpgTaskAndReturnId, removeFdpgTask } from './add-fdpg-task.util'; import { clearLocationsVotes } from './location-flow.util'; +import { getLocationState } from './validate-access.util'; +import { ProposalUploadService } from '../services/proposal-upload.service'; export const addDizApproval = (proposal: Proposal, user: IRequestUser, vote: SetDizApprovalDto) => { clearLocationsVotes(proposal, user.miiLocation); + if (vote.value === true) { proposal.dizApprovedLocations.push(user.miiLocation); } else { @@ -33,12 +36,14 @@ export const addDizApproval = (proposal: Proposal, user: IRequestUser, vote: Set export const addUacApproval = (proposal: Proposal, user: IRequestUser, vote: SetUacApprovalDto) => { clearLocationsVotes(proposal, user.miiLocation); + if (vote.value === true) { const uacApproval: Omit = { location: user.miiLocation, dataAmount: vote.dataAmount, isContractSigned: false, }; + // Flow: proposal.uacApprovedLocations.push(user.miiLocation); // Persistent: @@ -46,6 +51,7 @@ export const addUacApproval = (proposal: Proposal, user: IRequestUser, vote: Set proposal.totalPromisedDataAmount = (proposal.totalPromisedDataAmount ?? 0) + (vote.dataAmount ?? 0); const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); + if (isDataAmountReached) { addFdpgTaskAndReturnId(proposal, FdpgTaskType.DataAmountReached); } @@ -98,6 +104,7 @@ export const addUacApprovalWithCondition = ( } // Flow: + clearLocationsVotes(proposal, location); if (vote.value === true) { proposal.uacApprovedLocations.push(location); @@ -141,3 +148,46 @@ export const addUacConditionReview = ( proposal.requestedButExcludedLocations.push(condition.location); } }; + +export const handleLocationDecision = async ( + proposal: Proposal, + location: MiiLocation, + user: IRequestUser, + proposalUploadService: ProposalUploadService, +) => { + const locationState = getLocationState(proposal, user); + + if (locationState.requestedButExcluded) { + proposal.declineReasons = proposal.declineReasons.filter((reason) => reason.location !== location); + } + + if (locationState.uacApproved) { + proposal.uacApprovals = proposal.uacApprovals.filter((approval) => approval.location !== location); + } + + if (locationState.conditionalApprovalAccepted) { + removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); + + proposal.conditionalApprovals = proposal.conditionalApprovals.filter( + (condition) => condition.location !== location, + ); + + const locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount; + + proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; + + const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); + if (!isDataAmountReached) { + removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); + } + } + if (locationState.isConditionalApproval) { + const uploadId = proposal.conditionalApprovals.find((approval) => approval.location === location)?.uploadId; + + if (uploadId) { + await proposalUploadService.deleteUpload(proposal._id, uploadId, user); + } + } + clearLocationsVotes(proposal, location); + proposal.openDizChecks.push(location); +}; diff --git a/src/modules/proposal/utils/proposal-history.util.ts b/src/modules/proposal/utils/proposal-history.util.ts index b45dce0..ecf00a5 100644 --- a/src/modules/proposal/utils/proposal-history.util.ts +++ b/src/modules/proposal/utils/proposal-history.util.ts @@ -131,6 +131,16 @@ export const addHistoryItemForUnselectedLocation = ( pushHistoryItem(proposalAfterChanges, user, type, location); }; +export const addHistoryItemForRevertLocationDecision = ( + proposalAfterChanges: Proposal, + user: IRequestUser, + location: MiiLocation, +): void => { + const type = HistoryEventType.FdpgRevertedLocationDecision; + + pushHistoryItem(proposalAfterChanges, user, type, location); +}; + export const addHistoryItemForContractSign = ( proposalAfterChanges: Proposal, user: IRequestUser, diff --git a/src/modules/proposal/utils/validate-vote.util.ts b/src/modules/proposal/utils/validate-vote.util.ts index b74a7df..37d328e 100644 --- a/src/modules/proposal/utils/validate-vote.util.ts +++ b/src/modules/proposal/utils/validate-vote.util.ts @@ -22,3 +22,9 @@ export const validateUacApproval = (proposal: Proposal, user: IRequestUser) => { throw new ForbiddenException('The location is not allowed to provide a vote. It might have already voted'); } }; + +export const validateRevertLocationDecision = (proposal: Proposal) => { + if (proposal.status !== ProposalStatus.LocationCheck) { + throw new ForbiddenException('The current status does not allow to revert the location decision'); + } +}; From 7a0f77ed2142c6f45871a7ae077e5ae57197b875 Mon Sep 17 00:00:00 2001 From: Aleksandra Frost Date: Tue, 30 Apr 2024 14:54:53 +0200 Subject: [PATCH 02/15] [FIX] import, split handle util, change naming to vote --- .../proposal-contracting.controller.spec.ts | 14 +- .../proposal-contracting.controller.ts | 18 +- ...ion.dto.ts => revert-location-vote.dto.ts} | 2 +- .../proposal/enums/history-event.enum.ts | 4 +- .../proposal-contracting.service.spec.ts | 46 ++-- .../services/proposal-contracting.service.ts | 36 +-- ...spec.ts => add-location-vote.util.spec.ts} | 182 +------------ .../__tests__/proposal-history.util.spec.ts | 10 +- .../revert-location-vote.util.spec.ts | 240 ++++++++++++++++++ ...vote.util.ts => add-location-vote.util.ts} | 45 ---- .../proposal/utils/proposal-history.util.ts | 4 +- .../utils/revert-location-vote.util.ts | 51 ++++ .../proposal/utils/validate-vote.util.ts | 4 +- 13 files changed, 360 insertions(+), 296 deletions(-) rename src/modules/proposal/dto/{revert-location-decision.dto.ts => revert-location-vote.dto.ts} (85%) rename src/modules/proposal/utils/__tests__/{handle-location-vote.util.spec.ts => add-location-vote.util.spec.ts} (53%) create mode 100644 src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts rename src/modules/proposal/utils/{handle-location-vote.util.ts => add-location-vote.util.ts} (75%) create mode 100644 src/modules/proposal/utils/revert-location-vote.util.ts diff --git a/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts b/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts index d3dd48f..c0af5ea 100644 --- a/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts +++ b/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts @@ -5,7 +5,7 @@ import { Role } from 'src/shared/enums/role.enum'; import { FdpgRequest } from 'src/shared/types/request-user.interface'; import { SetDizApprovalDto } from '../../dto/set-diz-approval.dto'; import { SetUacApprovalDto } from '../../dto/set-uac-approval.dto'; -import { RevertLocationDecisionDto } from '../../dto/revert-location-decision.dto'; +import { RevertLocationVoteDto } from '../../dto/revert-location-vote.dto'; import { SignContractDto } from '../../dto/sign-contract.dto'; import { ProposalContractingController } from '../proposal-contracting.controller'; import { ProposalContractingService } from '../../services/proposal-contracting.service'; @@ -76,16 +76,16 @@ describe('ProposalContractingController', () => { }); }); - describe('revertLocationDecision', () => { - it('should revert the location decision', async () => { + describe('revertLocationVote', () => { + it('should revert the location vote', async () => { const params = { id: 'mongoId', }; - const input = new RevertLocationDecisionDto(); - jest.spyOn(proposalContractingService, 'revertLocationDecision'); + const input = new RevertLocationVoteDto(); + jest.spyOn(proposalContractingService, 'handleLocationVote'); - await proposalContractingController.revertLocationDecision(params, input, request); - expect(proposalContractingService.revertLocationDecision).toHaveBeenCalledWith( + await proposalContractingController.revertLocationVote(params, input, request); + expect(proposalContractingService.handleLocationVote).toHaveBeenCalledWith( params.id, input.location, request.user, diff --git a/src/modules/proposal/controller/proposal-contracting.controller.ts b/src/modules/proposal/controller/proposal-contracting.controller.ts index d8c1a08..32cedf7 100644 --- a/src/modules/proposal/controller/proposal-contracting.controller.ts +++ b/src/modules/proposal/controller/proposal-contracting.controller.ts @@ -1,4 +1,3 @@ -import { ProposalContractingService } from './../services/proposal-contracting.service'; import { Body, HttpCode, @@ -25,9 +24,10 @@ import { ContractingUploadDto } from '../dto/contracting-upload.dto'; import { ProposalMarkConditionAcceptedReturnDto } from '../dto/proposal/proposal.dto'; import { SetDizApprovalDto } from '../dto/set-diz-approval.dto'; import { SetUacApprovalDto, SetUacApprovalWithFileDto } from '../dto/set-uac-approval.dto'; -import { RevertLocationDecisionDto } from '../dto/revert-location-decision.dto'; +import { RevertLocationVoteDto } from '../dto/revert-location-vote.dto'; import { SignContractDto, SignContractWithFileDto } from '../dto/sign-contract.dto'; import { InitContractingDto } from '../dto/proposal/init-contracting.dto'; +import { ProposalContractingService } from '../services/proposal-contracting.service'; @ApiController('proposals', undefined, 'contracting') export class ProposalContractingController { @@ -68,19 +68,19 @@ export class ProposalContractingController { } @Auth(Role.FdpgMember) - @Put(':id/revertLocationDecision') + @Put(':id/revertLocationVote') @UsePipes(ValidationPipe) @ApiNotFoundResponse({ description: 'Proposal could not be found' }) - @ApiOperation({ summary: 'Reverts the Location Decision' }) - @ApiNoContentResponse({ description: 'Location Decision reverted. No content returns.' }) + @ApiOperation({ summary: 'FDPG member reverts locations vote ' }) + @ApiNoContentResponse({ description: 'Locations vote reverted. No content returns.' }) @HttpCode(204) - @ApiBody({ type: RevertLocationDecisionDto }) - async revertLocationDecision( + @ApiBody({ type: RevertLocationVoteDto }) + async revertLocationVote( @Param() { id }: MongoIdParamDto, - @Body() { location }: RevertLocationDecisionDto, + @Body() { location }: RevertLocationVoteDto, @Request() { user }: FdpgRequest, ): Promise { - return await this.proposalContractingService.revertLocationDecision(id, location, user); + return await this.proposalContractingService.handleLocationVote(id, location, user); } @Auth(Role.FdpgMember) diff --git a/src/modules/proposal/dto/revert-location-decision.dto.ts b/src/modules/proposal/dto/revert-location-vote.dto.ts similarity index 85% rename from src/modules/proposal/dto/revert-location-decision.dto.ts rename to src/modules/proposal/dto/revert-location-vote.dto.ts index e113541..3c90227 100644 --- a/src/modules/proposal/dto/revert-location-decision.dto.ts +++ b/src/modules/proposal/dto/revert-location-vote.dto.ts @@ -3,7 +3,7 @@ import { IsEnum } from 'class-validator'; import { MiiLocation } from 'src/shared/constants/mii-locations'; @Exclude() -export class RevertLocationDecisionDto { +export class RevertLocationVoteDto { @Expose() @IsEnum(MiiLocation) location: MiiLocation; diff --git a/src/modules/proposal/enums/history-event.enum.ts b/src/modules/proposal/enums/history-event.enum.ts index f1d2763..9631bc6 100644 --- a/src/modules/proposal/enums/history-event.enum.ts +++ b/src/modules/proposal/enums/history-event.enum.ts @@ -29,8 +29,8 @@ export enum HistoryEventType { /** FDPG Veto */ FdpgApprovedLocationRemoved = 'FDPG_APPROVED_LOCATION_REMOVED', - /** FDPG reverts DIZ and UAC decision */ - FdpgRevertedLocationDecision = 'FDPG_REVERTED_LOCATION_DECISION', + /** FDPG reverts DIZ and UAC vote */ + FdpgRevertedLocationVote = 'FDPG_REVERTED_LOCATION_VOTE', /** Data Delivery */ diff --git a/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts b/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts index 0bba9bf..2d477a7 100644 --- a/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts +++ b/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts @@ -1,39 +1,35 @@ -import { handleLocationDecision } from './../../utils/handle-location-vote.util'; +import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { StorageService } from 'src/modules/storage/storage.service'; import { EventEngineService } from 'src/modules/event-engine/event-engine.service'; +import { StorageService } from 'src/modules/storage/storage.service'; import { MiiLocation } from 'src/shared/constants/mii-locations'; import { Role } from 'src/shared/enums/role.enum'; import { FdpgRequest } from 'src/shared/types/request-user.interface'; +import { NoErrorThrownError, getError } from 'test/get-error'; +import { InitContractingDto } from '../../dto/proposal/init-contracting.dto'; import { SetDizApprovalDto } from '../../dto/set-diz-approval.dto'; import { SetUacApprovalDto } from '../../dto/set-uac-approval.dto'; import { SignContractDto } from '../../dto/sign-contract.dto'; import { ProposalStatus } from '../../enums/proposal-status.enum'; import { ProposalDocument } from '../../schema/proposal.schema'; import { addContractSign } from '../../utils/add-contract-sign.util'; -import { addDizApproval, addUacApproval, addUacApprovalWithCondition } from '../../utils/handle-location-vote.util'; +import { addDizApproval, addUacApproval, addUacApprovalWithCondition } from '../../utils/add-location-vote.util'; import { addHistoryItemForContractSign, addHistoryItemForDizApproval, + addHistoryItemForRevertLocationVote, addHistoryItemForStatus, addHistoryItemForUacApproval, - addHistoryItemForRevertLocationDecision, } from '../../utils/proposal-history.util'; import { addUpload } from '../../utils/proposal.utils'; +import { revertLocationVote } from '../../utils/revert-location-vote.util'; import { validateContractSign } from '../../utils/validate-contract-sign.util'; import { validateStatusChange } from '../../utils/validate-status-change.util'; -import { - validateDizApproval, - validateRevertLocationDecision, - validateUacApproval, -} from '../../utils/validate-vote.util'; +import { validateDizApproval, validateRevertLocationVote, validateUacApproval } from '../../utils/validate-vote.util'; import { ProposalContractingService } from '../proposal-contracting.service'; import { ProposalCrudService } from '../proposal-crud.service'; -import { StatusChangeService } from '../status-change.service'; -import { ForbiddenException, NotFoundException } from '@nestjs/common'; -import { NoErrorThrownError, getError } from 'test/get-error'; -import { InitContractingDto } from '../../dto/proposal/init-contracting.dto'; import { ProposalUploadService } from '../proposal-upload.service'; +import { StatusChangeService } from '../status-change.service'; jest.mock('class-transformer', () => { const original = jest.requireActual('class-transformer'); @@ -46,15 +42,17 @@ jest.mock('class-transformer', () => { jest.mock('../../utils/validate-vote.util', () => ({ validateDizApproval: jest.fn(), validateUacApproval: jest.fn(), - validateRevertLocationDecision: jest.fn(), + validateRevertLocationVote: jest.fn(), })); -jest.mock('../../utils/handle-location-vote.util', () => ({ +jest.mock('../../utils/add-location-vote.util', () => ({ addDizApproval: jest.fn(), addUacApproval: jest.fn(), addUacApprovalWithCondition: jest.fn(), addUacConditionReview: jest.fn(), - handleLocationDecision: jest.fn(), +})); +jest.mock('../../utils/revert-location-vote.util', () => ({ + revertLocationVote: jest.fn(), })); jest.mock('../../utils/proposal-history.util', () => ({ @@ -63,7 +61,7 @@ jest.mock('../../utils/proposal-history.util', () => ({ addHistoryItemForStatus: jest.fn(), addHistoryItemForUacApproval: jest.fn(), addHistoryItemForUacCondition: jest.fn(), - addHistoryItemForRevertLocationDecision: jest.fn(), + addHistoryItemForRevertLocationVote: jest.fn(), })); jest.mock('../../utils/proposal.utils', () => ({ @@ -148,7 +146,7 @@ describe('ProposalContractingService', () => { handleProposalUacApproval: jest.fn(), handleProposalStatusChange: jest.fn(), handleProposalContractSign: jest.fn(), - handleLocationDecision: jest.fn(), + handleLocationVote: jest.fn(), }, }, { @@ -247,21 +245,21 @@ describe('ProposalContractingService', () => { }); }); - describe('revertLocationDecision', () => { - it('should revert the location decision', async () => { + describe('revertLocationVote', () => { + it('should revert the location vote', async () => { const proposalDocument = getProposalDocument(); jest.spyOn(proposalCrudService, 'findDocument').mockResolvedValueOnce(proposalDocument); - await proposalContractingService.revertLocationDecision(proposalId, request.user.miiLocation, request.user); + await proposalContractingService.handleLocationVote(proposalId, request.user.miiLocation, request.user); - expect(validateRevertLocationDecision).toHaveBeenCalledWith(proposalDocument); - expect(handleLocationDecision).toHaveBeenCalledWith( + expect(validateRevertLocationVote).toHaveBeenCalledWith(proposalDocument); + expect(revertLocationVote).toHaveBeenCalledWith( proposalDocument, request.user.miiLocation, request.user, proposalUploadService, ); - expect(addHistoryItemForRevertLocationDecision).toHaveBeenCalledWith( + expect(addHistoryItemForRevertLocationVote).toHaveBeenCalledWith( proposalDocument, request.user, request.user.miiLocation, diff --git a/src/modules/proposal/services/proposal-contracting.service.ts b/src/modules/proposal/services/proposal-contracting.service.ts index d6b69b3..ec33833 100644 --- a/src/modules/proposal/services/proposal-contracting.service.ts +++ b/src/modules/proposal/services/proposal-contracting.service.ts @@ -1,46 +1,46 @@ -import { ProposalUploadService } from './proposal-upload.service'; import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; import { plainToClass } from 'class-transformer'; +import { ValidationException } from 'src/exceptions/validation/validation.exception'; +import { MiiLocation } from 'src/shared/constants/mii-locations'; +import { ValidationErrorInfo } from 'src/shared/dto/validation/validation-error-info.dto'; +import { BadRequestError } from 'src/shared/enums/bad-request-error.enum'; import { Role } from 'src/shared/enums/role.enum'; import { IRequestUser } from 'src/shared/types/request-user.interface'; import { convertUserToGroups } from 'src/shared/utils/user-group.utils'; -import { StorageService } from '../../storage/storage.service'; import { EventEngineService } from '../../event-engine/event-engine.service'; +import { StorageService } from '../../storage/storage.service'; +import { InitContractingDto } from '../dto/proposal/init-contracting.dto'; import { ProposalMarkConditionAcceptedReturnDto } from '../dto/proposal/proposal.dto'; +import { SetDizApprovalDto } from '../dto/set-diz-approval.dto'; import { SetUacApprovalDto } from '../dto/set-uac-approval.dto'; import { SignContractDto } from '../dto/sign-contract.dto'; import { UploadDto } from '../dto/upload.dto'; import { ProposalValidation } from '../enums/porposal-validation.enum'; import { ProposalStatus } from '../enums/proposal-status.enum'; import { UseCaseUpload } from '../enums/upload-type.enum'; -import { ProposalCrudService } from './proposal-crud.service'; -import { StatusChangeService } from './status-change.service'; import { addContractSign } from '../utils/add-contract-sign.util'; import { addDizApproval, addUacApproval, addUacApprovalWithCondition, addUacConditionReview, - handleLocationDecision, -} from '../utils/handle-location-vote.util'; +} from '../utils/add-location-vote.util'; import { addHistoryItemForContractSign, addHistoryItemForDizApproval, + addHistoryItemForRevertLocationVote, addHistoryItemForStatus, addHistoryItemForUacApproval, addHistoryItemForUacCondition, - addHistoryItemForRevertLocationDecision, } from '../utils/proposal-history.util'; import { addUpload, getBlobName } from '../utils/proposal.utils'; +import { revertLocationVote } from '../utils/revert-location-vote.util'; import { validateContractSign } from '../utils/validate-contract-sign.util'; import { validateStatusChange } from '../utils/validate-status-change.util'; -import { validateDizApproval, validateUacApproval, validateRevertLocationDecision } from '../utils/validate-vote.util'; -import { SetDizApprovalDto } from '../dto/set-diz-approval.dto'; -import { InitContractingDto } from '../dto/proposal/init-contracting.dto'; -import { ValidationErrorInfo } from 'src/shared/dto/validation/validation-error-info.dto'; -import { ValidationException } from 'src/exceptions/validation/validation.exception'; -import { BadRequestError } from 'src/shared/enums/bad-request-error.enum'; -import { MiiLocation } from 'src/shared/constants/mii-locations'; +import { validateDizApproval, validateRevertLocationVote, validateUacApproval } from '../utils/validate-vote.util'; +import { ProposalCrudService } from './proposal-crud.service'; +import { ProposalUploadService } from './proposal-upload.service'; +import { StatusChangeService } from './status-change.service'; @Injectable() export class ProposalContractingService { @@ -90,12 +90,12 @@ export class ProposalContractingService { await this.eventEngineService.handleProposalUacApproval(saveResult, vote.value, user.miiLocation); } - async revertLocationDecision(proposalId: string, location: MiiLocation, user: IRequestUser): Promise { + async handleLocationVote(proposalId: string, location: MiiLocation, user: IRequestUser): Promise { const toBeUpdated = await this.proposalCrudService.findDocument(proposalId, user, undefined, true); - validateRevertLocationDecision(toBeUpdated); + validateRevertLocationVote(toBeUpdated); - await handleLocationDecision(toBeUpdated, location, user, this.proposalUploadService); - addHistoryItemForRevertLocationDecision(toBeUpdated, user, location); + await revertLocationVote(toBeUpdated, location, user, this.proposalUploadService); + addHistoryItemForRevertLocationVote(toBeUpdated, user, location); await toBeUpdated.save(); } diff --git a/src/modules/proposal/utils/__tests__/handle-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts similarity index 53% rename from src/modules/proposal/utils/__tests__/handle-location-vote.util.spec.ts rename to src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts index 6551e8a..7c61a57 100644 --- a/src/modules/proposal/utils/__tests__/handle-location-vote.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts @@ -1,4 +1,3 @@ -import { getLocationState } from './../validate-access.util'; import { MiiLocation } from 'src/shared/constants/mii-locations'; import { Role } from 'src/shared/enums/role.enum'; import { FdpgRequest } from 'src/shared/types/request-user.interface'; @@ -9,18 +8,14 @@ import { FdpgTaskType } from '../../enums/fdpg-task-type.enum'; import { ProposalStatus } from '../../enums/proposal-status.enum'; import { ProposalDocument } from '../../schema/proposal.schema'; import { ConditionalApproval } from '../../schema/sub-schema/conditional-approval.schema'; -import { DeclineReason } from '../../schema/sub-schema/decline-reason.schema'; -import { UacApproval } from '../../schema/sub-schema/uac-approval.schema'; import { addFdpgTaskAndReturnId, removeFdpgTask } from '../add-fdpg-task.util'; import { addDizApproval, addUacApproval, addUacApprovalWithCondition, addUacConditionReview, - handleLocationDecision, -} from '../handle-location-vote.util'; +} from '../add-location-vote.util'; import { clearLocationsVotes } from '../location-flow.util'; -import { ProposalUploadService } from '../../services/proposal-upload.service'; jest.mock('../location-flow.util', () => ({ clearLocationsVotes: jest.fn(), @@ -32,14 +27,6 @@ jest.mock('../add-fdpg-task.util', () => ({ removeFdpgTask: jest.fn(), })); -jest.mock('../validate-access.util', () => ({ - getLocationState: jest.fn(), -})); - -const proposalUploadServiceMock = { - deleteUpload: jest.fn(), -} as any as ProposalUploadService; - describe('addLocationVoteUtil', () => { beforeEach(() => { jest.clearAllMocks(); @@ -243,171 +230,4 @@ describe('addLocationVoteUtil', () => { expect(proposal.requestedButExcludedLocations).toEqual([condition.location]); }); }); - - const locationStateDefault: ReturnType = { - isDizCheck: false, - dizApproved: false, - uacApproved: false, - isConditionalApproval: false, - conditionalApprovalAccepted: false, - conditionalApprovalDeclined: false, - contractAcceptedByResearcher: false, - contractRejectedByResearcher: false, - signedContract: false, - signedContractAndContractingDone: false, - requestedButExcluded: false, - }; - - describe('handleLocationDecision', () => { - const getLocationStateMock = jest.mocked(getLocationState); - - it('should delete the declineReason object', async () => { - const declineReason = new DeclineReason(); - declineReason.location = MiiLocation.UKRUB; - const proposal = getProposalDocument(); - proposal.declineReasons = [declineReason]; - proposal.openDizChecks = []; - const request = getRequest(); - - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - requestedButExcluded: true, - }); - - expect(proposal.declineReasons).toEqual([declineReason]); - await handleLocationDecision(proposal, declineReason.location, request.user, proposalUploadServiceMock); - - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(proposal.declineReasons).not.toEqual([declineReason]); - expect(proposal.declineReasons.length).toEqual(0); - expect(clearLocationsVotes).toBeCalledWith(proposal, declineReason.location); - expect(proposal.requestedButExcludedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([declineReason.location]); - }); - - it('should delete the uac approval object', async () => { - const uacApproval = new UacApproval(); - uacApproval.location = MiiLocation.UKRUB; - const proposal = getProposalDocument(); - proposal.uacApprovals = [uacApproval]; - proposal.openDizChecks = []; - - const request = getRequest(); - - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - uacApproved: true, - }); - - expect(proposal.uacApprovals).toEqual([uacApproval]); - await handleLocationDecision(proposal, uacApproval.location, request.user, proposalUploadServiceMock); - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(proposal.uacApprovals.length).toEqual(0); - expect(proposal.uacApprovals).not.toEqual([uacApproval]); - expect(clearLocationsVotes).toBeCalledWith(proposal, uacApproval.location); - expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([uacApproval.location]); - }); - - describe('should remove Fdpg Task in case of expected data amount reached', () => { - const testCases = [ - { - name: 'expected data amount is reached even withouth the location data amount', - dataAmount: 10, - expectedDataAmountReached: true, - }, - { - name: 'expected data amount is not reached withouth the location data amount', - dataAmount: 12, - expectedDataAmountReached: false, - }, - ]; - - test.each(testCases)('%s', async ({ dataAmount, expectedDataAmountReached }) => { - const approval = new UacApproval(); - approval.location = MiiLocation.UKRUB; - approval.dataAmount = dataAmount; - const condition = new ConditionalApproval(); - condition.location = MiiLocation.UKRUB; - condition.fdpgTaskId = FdpgTaskType.ConditionApproval; - condition.dataAmount = 10; - const proposal = getProposalDocument(); - proposal.conditionalApprovals = [condition]; - proposal.uacApprovals = [approval]; - proposal.totalPromisedDataAmount = 21; - proposal.requestedData.desiredDataAmount = 11; - proposal.openDizChecks = []; - - const request = getRequest(); - - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - conditionalApprovalAccepted: true, - }); - - expect(proposal.uacApprovals).toEqual([approval]); - await handleLocationDecision(proposal, approval.location, request.user, proposalUploadServiceMock); - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - if (expectedDataAmountReached) { - expect(removeFdpgTask).not.toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); - } else { - expect(removeFdpgTask).toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); - } - expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); - expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([condition.location]); - }); - }); - - it('should delete conditional approval object and fdpg task', async () => { - const condition = new ConditionalApproval(); - condition.location = MiiLocation.UKRUB; - condition.fdpgTaskId = FdpgTaskType.ConditionApproval; - condition.dataAmount = 10; - const proposal = getProposalDocument(); - proposal.conditionalApprovals = [condition]; - proposal.totalPromisedDataAmount = condition.dataAmount + 1; - proposal.requestedData.desiredDataAmount = 11; - proposal.openDizChecks = []; - - const request = getRequest(); - - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - conditionalApprovalAccepted: true, - }); - - expect(proposal.conditionalApprovals).toEqual([condition]); - await handleLocationDecision(proposal, condition.location, request.user, proposalUploadServiceMock); - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); - expect(proposal.conditionalApprovals).not.toEqual([condition]); - expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); - expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([condition.location]); - }); - - it('should delete the upload blob', async () => { - const condition = new ConditionalApproval(); - condition.location = MiiLocation.UKRUB; - condition.uploadId = 'uploadId'; - const proposal = getProposalDocument(); - proposal.conditionalApprovals = [condition]; - proposal.openDizChecks = []; - const request = getRequest(); - - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - isConditionalApproval: true, - }); - - await handleLocationDecision(proposal, condition.location, request.user, proposalUploadServiceMock); - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(proposalUploadServiceMock.deleteUpload).toBeCalledTimes(1); - expect(proposalUploadServiceMock.deleteUpload).toBeCalledWith(proposal._id, condition.uploadId, request.user); - expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); - expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([condition.location]); - }); - }); }); diff --git a/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts b/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts index 64a8c98..3525393 100644 --- a/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts @@ -13,7 +13,7 @@ import { addHistoryItemForContractSystemReject, addHistoryItemForProposalLock, addHistoryItemForUnselectedLocation, - addHistoryItemForRevertLocationDecision, + addHistoryItemForRevertLocationVote, } from '../proposal-history.util'; const proposalId = 'proposalId'; @@ -192,16 +192,16 @@ describe('ProposalHistoryUtil', () => { }); }); - describe('addHistoryItemForRevertLocationDecision', () => { - it('should add history item for reverted location decision', () => { + describe('addHistoryItemForRevertLocationVote', () => { + it('should add history item for reverted location vote', () => { const request = getRequest(); const proposal = getProposalDocument(); const location = MiiLocation.UKRUB; - addHistoryItemForRevertLocationDecision(proposal, request.user, location); + addHistoryItemForRevertLocationVote(proposal, request.user, location); expect(proposal.history.length).toBe(1); - const expectedType = HistoryEventType.FdpgRevertedLocationDecision; + const expectedType = HistoryEventType.FdpgRevertedLocationVote; expect(proposal.history[0].type).toBe(expectedType); }); }); diff --git a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts new file mode 100644 index 0000000..e5ddac7 --- /dev/null +++ b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts @@ -0,0 +1,240 @@ +import { getLocationState } from '../validate-access.util'; +import { DeclineReason } from '../../schema/sub-schema/decline-reason.schema'; +import { UacApproval } from '../../schema/sub-schema/uac-approval.schema'; +import { revertLocationVote } from '../revert-location-vote.util'; +import { ProposalUploadService } from '../../services/proposal-upload.service'; +import { MiiLocation } from 'src/shared/constants/mii-locations'; +import { Role } from 'src/shared/enums/role.enum'; +import { FdpgRequest } from 'src/shared/types/request-user.interface'; +import { ProposalStatus } from '../../enums/proposal-status.enum'; +import { ProposalDocument } from '../../schema/proposal.schema'; +import { clearLocationsVotes } from '../location-flow.util'; +import { ConditionalApproval } from '../../schema/sub-schema/conditional-approval.schema'; +import { FdpgTaskType } from '../../enums/fdpg-task-type.enum'; +import { removeFdpgTask } from '../add-fdpg-task.util'; + +jest.mock('../location-flow.util', () => ({ + clearLocationsVotes: jest.fn(), +})); + +jest.mock('../add-fdpg-task.util', () => ({ + removeFdpgTask: jest.fn(), +})); +jest.mock('../validate-access.util', () => ({ + getLocationState: jest.fn(), +})); + +const proposalUploadServiceMock = { + deleteUpload: jest.fn(), +} as any as ProposalUploadService; + +describe('revertLocationVoteUtil', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + const requestContent = { + user: { + userId: 'userId', + firstName: 'firstName', + lastName: 'lastName', + fullName: 'fullName', + email: 'info@appsfactory.de', + username: 'username', + email_verified: true, + roles: [Role.Researcher], + singleKnownRole: Role.Researcher, + miiLocation: MiiLocation.UKL, + isFromLocation: false, + isKnownLocation: true, + }, + } as FdpgRequest; + + const getRequest = () => JSON.parse(JSON.stringify(requestContent)) as FdpgRequest; + + const proposalId = 'proposalId'; + + const proposalContent = { + _id: proposalId, + projectAbbreviation: 'projectAbbreviation', + status: ProposalStatus.FdpgCheck, + requestedData: {}, + dizApprovedLocations: [], + requestedButExcludedLocations: [], + uacApprovedLocations: [], + uacApprovals: [], + declineReasons: [], + }; + const getProposalDocument = () => { + const proposalDocument = { + ...JSON.parse(JSON.stringify(proposalContent)), + }; + return proposalDocument as any as ProposalDocument; + }; + + const locationStateDefault: ReturnType = { + isDizCheck: false, + dizApproved: false, + uacApproved: false, + isConditionalApproval: false, + conditionalApprovalAccepted: false, + conditionalApprovalDeclined: false, + contractAcceptedByResearcher: false, + contractRejectedByResearcher: false, + signedContract: false, + signedContractAndContractingDone: false, + requestedButExcluded: false, + }; + + describe('handleLocationVote', () => { + const getLocationStateMock = jest.mocked(getLocationState); + + it('should delete the declineReason object', async () => { + const declineReason = new DeclineReason(); + declineReason.location = MiiLocation.UKRUB; + const proposal = getProposalDocument(); + proposal.declineReasons = [declineReason]; + proposal.openDizChecks = []; + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + requestedButExcluded: true, + }); + + expect(proposal.declineReasons).toEqual([declineReason]); + await revertLocationVote(proposal, declineReason.location, request.user, proposalUploadServiceMock); + + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(proposal.declineReasons).not.toEqual([declineReason]); + expect(proposal.declineReasons.length).toEqual(0); + expect(clearLocationsVotes).toBeCalledWith(proposal, declineReason.location); + expect(proposal.requestedButExcludedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([declineReason.location]); + }); + + it('should delete the uac approval object', async () => { + const uacApproval = new UacApproval(); + uacApproval.location = MiiLocation.UKRUB; + const proposal = getProposalDocument(); + proposal.uacApprovals = [uacApproval]; + proposal.openDizChecks = []; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + uacApproved: true, + }); + + expect(proposal.uacApprovals).toEqual([uacApproval]); + await revertLocationVote(proposal, uacApproval.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(proposal.uacApprovals.length).toEqual(0); + expect(proposal.uacApprovals).not.toEqual([uacApproval]); + expect(clearLocationsVotes).toBeCalledWith(proposal, uacApproval.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([uacApproval.location]); + }); + + describe('should remove Fdpg Task in case of expected data amount reached', () => { + const testCases = [ + { + name: 'expected data amount is reached even withouth the location data amount', + dataAmount: 10, + expectedDataAmountReached: true, + }, + { + name: 'expected data amount is not reached withouth the location data amount', + dataAmount: 12, + expectedDataAmountReached: false, + }, + ]; + + test.each(testCases)('%s', async ({ dataAmount, expectedDataAmountReached }) => { + const approval = new UacApproval(); + approval.location = MiiLocation.UKRUB; + approval.dataAmount = dataAmount; + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.fdpgTaskId = FdpgTaskType.ConditionApproval; + condition.dataAmount = 10; + const proposal = getProposalDocument(); + proposal.conditionalApprovals = [condition]; + proposal.uacApprovals = [approval]; + proposal.totalPromisedDataAmount = 21; + proposal.requestedData.desiredDataAmount = 11; + proposal.openDizChecks = []; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + conditionalApprovalAccepted: true, + }); + + expect(proposal.uacApprovals).toEqual([approval]); + await revertLocationVote(proposal, approval.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + if (expectedDataAmountReached) { + expect(removeFdpgTask).not.toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); + } else { + expect(removeFdpgTask).toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); + } + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + }); + + it('should delete conditional approval object and fdpg task', async () => { + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.fdpgTaskId = FdpgTaskType.ConditionApproval; + condition.dataAmount = 10; + const proposal = getProposalDocument(); + proposal.conditionalApprovals = [condition]; + proposal.totalPromisedDataAmount = condition.dataAmount + 1; + proposal.requestedData.desiredDataAmount = 11; + proposal.openDizChecks = []; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + conditionalApprovalAccepted: true, + }); + + expect(proposal.conditionalApprovals).toEqual([condition]); + await revertLocationVote(proposal, condition.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); + expect(proposal.conditionalApprovals).not.toEqual([condition]); + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + + it('should delete the upload blob', async () => { + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.uploadId = 'uploadId'; + const proposal = getProposalDocument(); + proposal.conditionalApprovals = [condition]; + proposal.openDizChecks = []; + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + isConditionalApproval: true, + }); + + await revertLocationVote(proposal, condition.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(proposalUploadServiceMock.deleteUpload).toBeCalledTimes(1); + expect(proposalUploadServiceMock.deleteUpload).toBeCalledWith(proposal._id, condition.uploadId, request.user); + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + }); +}); diff --git a/src/modules/proposal/utils/handle-location-vote.util.ts b/src/modules/proposal/utils/add-location-vote.util.ts similarity index 75% rename from src/modules/proposal/utils/handle-location-vote.util.ts rename to src/modules/proposal/utils/add-location-vote.util.ts index 76572c4..74e539f 100644 --- a/src/modules/proposal/utils/handle-location-vote.util.ts +++ b/src/modules/proposal/utils/add-location-vote.util.ts @@ -11,8 +11,6 @@ import { ConditionalApproval } from '../schema/sub-schema/conditional-approval.s import { UacApproval } from '../schema/sub-schema/uac-approval.schema'; import { addFdpgTaskAndReturnId, removeFdpgTask } from './add-fdpg-task.util'; import { clearLocationsVotes } from './location-flow.util'; -import { getLocationState } from './validate-access.util'; -import { ProposalUploadService } from '../services/proposal-upload.service'; export const addDizApproval = (proposal: Proposal, user: IRequestUser, vote: SetDizApprovalDto) => { clearLocationsVotes(proposal, user.miiLocation); @@ -148,46 +146,3 @@ export const addUacConditionReview = ( proposal.requestedButExcludedLocations.push(condition.location); } }; - -export const handleLocationDecision = async ( - proposal: Proposal, - location: MiiLocation, - user: IRequestUser, - proposalUploadService: ProposalUploadService, -) => { - const locationState = getLocationState(proposal, user); - - if (locationState.requestedButExcluded) { - proposal.declineReasons = proposal.declineReasons.filter((reason) => reason.location !== location); - } - - if (locationState.uacApproved) { - proposal.uacApprovals = proposal.uacApprovals.filter((approval) => approval.location !== location); - } - - if (locationState.conditionalApprovalAccepted) { - removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); - - proposal.conditionalApprovals = proposal.conditionalApprovals.filter( - (condition) => condition.location !== location, - ); - - const locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount; - - proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; - - const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); - if (!isDataAmountReached) { - removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); - } - } - if (locationState.isConditionalApproval) { - const uploadId = proposal.conditionalApprovals.find((approval) => approval.location === location)?.uploadId; - - if (uploadId) { - await proposalUploadService.deleteUpload(proposal._id, uploadId, user); - } - } - clearLocationsVotes(proposal, location); - proposal.openDizChecks.push(location); -}; diff --git a/src/modules/proposal/utils/proposal-history.util.ts b/src/modules/proposal/utils/proposal-history.util.ts index ecf00a5..8241d81 100644 --- a/src/modules/proposal/utils/proposal-history.util.ts +++ b/src/modules/proposal/utils/proposal-history.util.ts @@ -131,12 +131,12 @@ export const addHistoryItemForUnselectedLocation = ( pushHistoryItem(proposalAfterChanges, user, type, location); }; -export const addHistoryItemForRevertLocationDecision = ( +export const addHistoryItemForRevertLocationVote = ( proposalAfterChanges: Proposal, user: IRequestUser, location: MiiLocation, ): void => { - const type = HistoryEventType.FdpgRevertedLocationDecision; + const type = HistoryEventType.FdpgRevertedLocationVote; pushHistoryItem(proposalAfterChanges, user, type, location); }; diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts new file mode 100644 index 0000000..8519185 --- /dev/null +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -0,0 +1,51 @@ +import { getLocationState } from './validate-access.util'; +import { ProposalUploadService } from '../services/proposal-upload.service'; +import { Proposal } from '../schema/proposal.schema'; +import { MiiLocation } from 'src/shared/constants/mii-locations'; +import { IRequestUser } from 'src/shared/types/request-user.interface'; +import { removeFdpgTask } from './add-fdpg-task.util'; +import { FdpgTaskType } from '../enums/fdpg-task-type.enum'; +import { clearLocationsVotes } from './location-flow.util'; + +export const revertLocationVote = async ( + proposal: Proposal, + location: MiiLocation, + user: IRequestUser, + proposalUploadService: ProposalUploadService, +) => { + const locationState = getLocationState(proposal, user); + + if (locationState.requestedButExcluded) { + proposal.declineReasons = proposal.declineReasons.filter((reason) => reason.location !== location); + } + + if (locationState.uacApproved) { + proposal.uacApprovals = proposal.uacApprovals.filter((approval) => approval.location !== location); + } + + if (locationState.conditionalApprovalAccepted) { + removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); + + proposal.conditionalApprovals = proposal.conditionalApprovals.filter( + (condition) => condition.location !== location, + ); + + const locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount; + + proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; + + const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); + if (!isDataAmountReached) { + removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); + } + } + if (locationState.isConditionalApproval) { + const uploadId = proposal.conditionalApprovals.find((approval) => approval.location === location)?.uploadId; + + if (uploadId) { + await proposalUploadService.deleteUpload(proposal._id, uploadId, user); + } + } + clearLocationsVotes(proposal, location); + proposal.openDizChecks.push(location); +}; diff --git a/src/modules/proposal/utils/validate-vote.util.ts b/src/modules/proposal/utils/validate-vote.util.ts index 37d328e..ad59ebc 100644 --- a/src/modules/proposal/utils/validate-vote.util.ts +++ b/src/modules/proposal/utils/validate-vote.util.ts @@ -23,8 +23,8 @@ export const validateUacApproval = (proposal: Proposal, user: IRequestUser) => { } }; -export const validateRevertLocationDecision = (proposal: Proposal) => { +export const validateRevertLocationVote = (proposal: Proposal) => { if (proposal.status !== ProposalStatus.LocationCheck) { - throw new ForbiddenException('The current status does not allow to revert the location decision'); + throw new ForbiddenException('The current status does not allow to revert the location vote'); } }; From 5f5a32db6808ddda3c32763271023161e7efd71e Mon Sep 17 00:00:00 2001 From: Aleksandra Frost Date: Thu, 2 May 2024 15:07:03 +0200 Subject: [PATCH 03/15] [ADD] missing remove fdpgtask --- .../revert-location-vote.util.spec.ts | 102 +++++++++++++++++- .../utils/revert-location-vote.util.ts | 31 ++++-- 2 files changed, 120 insertions(+), 13 deletions(-) diff --git a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts index e5ddac7..ff572ff 100644 --- a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts @@ -136,7 +136,7 @@ describe('revertLocationVoteUtil', () => { expect(proposal.openDizChecks).toEqual([uacApproval.location]); }); - describe('should remove Fdpg Task in case of expected data amount reached', () => { + describe('should remove Fdpg Task in case of uacApproved and expected data amount reached', () => { const testCases = [ { name: 'expected data amount is reached even withouth the location data amount', @@ -169,7 +169,7 @@ describe('revertLocationVoteUtil', () => { getLocationStateMock.mockReturnValueOnce({ ...locationStateDefault, - conditionalApprovalAccepted: true, + uacApproved: true, }); expect(proposal.uacApprovals).toEqual([approval]); @@ -186,6 +186,104 @@ describe('revertLocationVoteUtil', () => { }); }); + describe('should remove Fdpg Task in case of uacApproved and uacApprovalComplete', () => { + const testCases = [ + { + name: 'not completed', + isCompleted: false, + }, + { + name: 'completed', + isCompleted: true, + }, + ]; + + test.each(testCases)('%s', async ({ isCompleted }) => { + const approval = new UacApproval(); + approval.location = MiiLocation.UKRUB; + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.fdpgTaskId = FdpgTaskType.UacApprovalComplete; + const proposal = getProposalDocument(); + proposal.uacApprovedLocations = [MiiLocation.UKRUB]; + proposal.conditionalApprovals = [condition]; + proposal.uacApprovals = [approval]; + proposal.openDizChecks = []; + proposal.requestedButExcludedLocations = [MiiLocation.UMG, MiiLocation.UKL]; + + proposal.numberOfRequestedLocations = isCompleted ? 3 : 2; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + uacApproved: true, + }); + + expect(proposal.uacApprovals).toEqual([approval]); + await revertLocationVote(proposal, approval.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + if (!isCompleted) { + expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); + } else { + expect(removeFdpgTask).not.toBeCalledWith(proposal, condition.fdpgTaskId); + } + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + }); + + describe('should remove Fdpg Task in case of conditionalApprovalAccepted and expected data amount reached', () => { + const testCases = [ + { + name: 'expected data amount is reached even withouth the location data amount', + dataAmount: 10, + expectedDataAmountReached: true, + }, + { + name: 'expected data amount is not reached withouth the location data amount', + dataAmount: 12, + expectedDataAmountReached: false, + }, + ]; + + test.each(testCases)('%s', async ({ dataAmount, expectedDataAmountReached }) => { + const approval = new UacApproval(); + approval.location = MiiLocation.UKRUB; + approval.dataAmount = dataAmount; + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.fdpgTaskId = FdpgTaskType.DataAmountReached; + condition.dataAmount = 10; + const proposal = getProposalDocument(); + proposal.conditionalApprovals = [condition]; + proposal.uacApprovals = [approval]; + proposal.totalPromisedDataAmount = 21; + proposal.requestedData.desiredDataAmount = 11; + proposal.openDizChecks = []; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + conditionalApprovalAccepted: true, + }); + + expect(proposal.uacApprovals).toEqual([approval]); + await revertLocationVote(proposal, approval.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + if (expectedDataAmountReached) { + expect(removeFdpgTask).not.toBeCalledWith(proposal, condition.fdpgTaskId); + } else { + expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); + } + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + }); + it('should delete conditional approval object and fdpg task', async () => { const condition = new ConditionalApproval(); condition.location = MiiLocation.UKRUB; diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index 8519185..6a5fe0b 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -14,6 +14,9 @@ export const revertLocationVote = async ( proposalUploadService: ProposalUploadService, ) => { const locationState = getLocationState(proposal, user); + const locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount; + proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; + const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); if (locationState.requestedButExcluded) { proposal.declineReasons = proposal.declineReasons.filter((reason) => reason.location !== location); @@ -21,6 +24,23 @@ export const revertLocationVote = async ( if (locationState.uacApproved) { proposal.uacApprovals = proposal.uacApprovals.filter((approval) => approval.location !== location); + if (!isDataAmountReached) { + removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); + } + const isUacApprovalComplete = + proposal.uacApprovedLocations.length + proposal.requestedButExcludedLocations.length === + proposal.numberOfRequestedLocations; + if (!isUacApprovalComplete) { + removeFdpgTask(proposal, FdpgTaskType.UacApprovalComplete); + } + } + + if (locationState.isConditionalApproval) { + const uploadId = proposal.conditionalApprovals.find((approval) => approval.location === location)?.uploadId; + + if (uploadId) { + await proposalUploadService.deleteUpload(proposal._id, uploadId, user); + } } if (locationState.conditionalApprovalAccepted) { @@ -30,22 +50,11 @@ export const revertLocationVote = async ( (condition) => condition.location !== location, ); - const locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount; - - proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; - - const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); if (!isDataAmountReached) { removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); } } - if (locationState.isConditionalApproval) { - const uploadId = proposal.conditionalApprovals.find((approval) => approval.location === location)?.uploadId; - if (uploadId) { - await proposalUploadService.deleteUpload(proposal._id, uploadId, user); - } - } clearLocationsVotes(proposal, location); proposal.openDizChecks.push(location); }; From 4cc96986ab874e33e665f0a68936060f0262d404 Mon Sep 17 00:00:00 2001 From: Aleksandra Frost Date: Mon, 6 May 2024 11:50:29 +0200 Subject: [PATCH 04/15] [ADD] total data amount validation --- .../services/proposal-contracting.service.ts | 2 +- .../utils/revert-location-vote.util.ts | 11 ++++++++++- .../proposal/utils/validate-vote.util.ts | 18 +++++++++++++++++- src/shared/enums/bad-request-error.enum.ts | 1 + 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/modules/proposal/services/proposal-contracting.service.ts b/src/modules/proposal/services/proposal-contracting.service.ts index ec33833..8b68aeb 100644 --- a/src/modules/proposal/services/proposal-contracting.service.ts +++ b/src/modules/proposal/services/proposal-contracting.service.ts @@ -92,7 +92,7 @@ export class ProposalContractingService { async handleLocationVote(proposalId: string, location: MiiLocation, user: IRequestUser): Promise { const toBeUpdated = await this.proposalCrudService.findDocument(proposalId, user, undefined, true); - validateRevertLocationVote(toBeUpdated); + validateRevertLocationVote(toBeUpdated, location); await revertLocationVote(toBeUpdated, location, user, this.proposalUploadService); addHistoryItemForRevertLocationVote(toBeUpdated, user, location); diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index 6a5fe0b..cddf432 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -14,7 +14,16 @@ export const revertLocationVote = async ( proposalUploadService: ProposalUploadService, ) => { const locationState = getLocationState(proposal, user); - const locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount; + + let locationDataAmount = 0; + + if (locationState.isConditionalApproval) { + locationDataAmount = + proposal.conditionalApprovals.find((approval) => approval.location === location)?.dataAmount ?? 0; + } else { + locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount ?? 0; + } + proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); diff --git a/src/modules/proposal/utils/validate-vote.util.ts b/src/modules/proposal/utils/validate-vote.util.ts index ad59ebc..af8fac5 100644 --- a/src/modules/proposal/utils/validate-vote.util.ts +++ b/src/modules/proposal/utils/validate-vote.util.ts @@ -2,6 +2,10 @@ import { ForbiddenException } from '@nestjs/common'; import { IRequestUser } from 'src/shared/types/request-user.interface'; import { ProposalStatus } from '../enums/proposal-status.enum'; import { Proposal } from '../schema/proposal.schema'; +import { MiiLocation } from 'src/shared/constants/mii-locations'; +import { BadRequestError } from 'src/shared/enums/bad-request-error.enum'; +import { ValidationErrorInfo } from 'src/shared/dto/validation/validation-error-info.dto'; +import { ValidationException } from 'src/exceptions/validation/validation.exception'; export const validateDizApproval = (proposal: Proposal, user: IRequestUser) => { if (proposal.status !== ProposalStatus.LocationCheck) { @@ -23,7 +27,19 @@ export const validateUacApproval = (proposal: Proposal, user: IRequestUser) => { } }; -export const validateRevertLocationVote = (proposal: Proposal) => { +export const validateRevertLocationVote = (proposal: Proposal, location: MiiLocation) => { + const isOpenDizCheck = proposal.openDizChecks.includes(location); + + if (isOpenDizCheck) { + const errorInfo = new ValidationErrorInfo({ + constraint: 'validRevert', + message: `The location ${location} has not voted yet`, + property: `location`, + code: BadRequestError.LocationRevertValidation, + }); + throw new ValidationException([errorInfo]); + } + if (proposal.status !== ProposalStatus.LocationCheck) { throw new ForbiddenException('The current status does not allow to revert the location vote'); } diff --git a/src/shared/enums/bad-request-error.enum.ts b/src/shared/enums/bad-request-error.enum.ts index 6257802..1d15774 100644 --- a/src/shared/enums/bad-request-error.enum.ts +++ b/src/shared/enums/bad-request-error.enum.ts @@ -12,4 +12,5 @@ export enum BadRequestError { DeclineReason = '400-011', ReportsMaxUploadCount = '400-012', LocationNotAssignedToContract = '400-013', + LocationRevertValidation = '400-014', } From a867fd5cd03892d909bc9946993b01cf2f28eccd Mon Sep 17 00:00:00 2001 From: Aleksandra Frost Date: Mon, 6 May 2024 21:18:57 +0200 Subject: [PATCH 05/15] [FIX] test for validation --- .../services/__tests__/proposal-contracting.service.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts b/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts index 2d477a7..19bde84 100644 --- a/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts +++ b/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts @@ -248,11 +248,12 @@ describe('ProposalContractingService', () => { describe('revertLocationVote', () => { it('should revert the location vote', async () => { const proposalDocument = getProposalDocument(); + const location = MiiLocation.UKL; jest.spyOn(proposalCrudService, 'findDocument').mockResolvedValueOnce(proposalDocument); await proposalContractingService.handleLocationVote(proposalId, request.user.miiLocation, request.user); - expect(validateRevertLocationVote).toHaveBeenCalledWith(proposalDocument); + expect(validateRevertLocationVote).toHaveBeenCalledWith(proposalDocument, location); expect(revertLocationVote).toHaveBeenCalledWith( proposalDocument, request.user.miiLocation, From a525bf46874eb9523570fa8278ad209fea6e1a41 Mon Sep 17 00:00:00 2001 From: Aleksandra Frost Date: Tue, 7 May 2024 15:30:05 +0200 Subject: [PATCH 06/15] [ADD] expose location for history --- .../decorators/expose-history.decorator.ts | 18 +++++++++++++++--- .../dto/proposal/history-event.dto.ts | 5 +++++ .../services/proposal-upload.service.ts | 19 ++++++++++++------- .../utils/revert-location-vote.util.ts | 19 ++++++++----------- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/modules/proposal/decorators/expose-history.decorator.ts b/src/modules/proposal/decorators/expose-history.decorator.ts index e1ccb0e..8a56bd3 100644 --- a/src/modules/proposal/decorators/expose-history.decorator.ts +++ b/src/modules/proposal/decorators/expose-history.decorator.ts @@ -1,15 +1,27 @@ +import { RevertLocationVoteDto } from './../dto/revert-location-vote.dto'; import { Expose, plainToClass, Transform } from 'class-transformer'; import { parseGroupToUser } from 'src/shared/utils/user-group.utils'; import { HistoryEventGetDto } from '../dto/proposal/history-event.dto'; import { HistoryEvent } from '../schema/sub-schema/history-event.schema'; +import { HistoryEventType } from '../enums/history-event.enum'; export const ExposeHistory = () => (target: Object, propertyKey: string) => { Expose()(target, propertyKey); Transform((params) => { const user = parseGroupToUser(params.options.groups); - const filteredEvents = params.obj[propertyKey].filter((event: HistoryEvent) => - event.location ? event.location === user.miiLocation : true, + const isFdpgMember = user.singleKnownRole === 'FdpgMember'; + + const filteredEvents = params.obj[propertyKey].filter((event: HistoryEvent) => { + const isRevertEvent = event.type === HistoryEventType.FdpgRevertedLocationVote; + + if (isRevertEvent && isFdpgMember) { + return true; + } + return event.location ? event.location === user.miiLocation : true; + }); + + return filteredEvents.map((history) => + plainToClass(HistoryEventGetDto, history, { groups: params.options.groups }), ); - return filteredEvents.map((history) => plainToClass(HistoryEventGetDto, history)); })(target, propertyKey); }; diff --git a/src/modules/proposal/dto/proposal/history-event.dto.ts b/src/modules/proposal/dto/proposal/history-event.dto.ts index 9202dac..957c866 100644 --- a/src/modules/proposal/dto/proposal/history-event.dto.ts +++ b/src/modules/proposal/dto/proposal/history-event.dto.ts @@ -1,6 +1,8 @@ +import { MiiLocation } from 'src/shared/constants/mii-locations'; import { Exclude, Expose } from 'class-transformer'; import { Version } from 'src/shared/schema/version.schema'; import { HistoryEventType } from '../../enums/history-event.enum'; +import { Role } from 'src/shared/enums/role.enum'; @Exclude() export class HistoryEventGetDto { @@ -12,4 +14,7 @@ export class HistoryEventGetDto { @Expose() createdAt: Date; + + @Expose() + location: MiiLocation; } diff --git a/src/modules/proposal/services/proposal-upload.service.ts b/src/modules/proposal/services/proposal-upload.service.ts index 9f4b6b0..543484b 100644 --- a/src/modules/proposal/services/proposal-upload.service.ts +++ b/src/modules/proposal/services/proposal-upload.service.ts @@ -5,7 +5,7 @@ import { StorageService } from '../../storage/storage.service'; import { UploadDto, UploadGetDto } from '../dto/upload.dto'; import { DirectUpload } from '../enums/upload-type.enum'; import { ProposalCrudService } from './proposal-crud.service'; -import { Proposal } from '../schema/proposal.schema'; +import { Proposal, ProposalDocument } from '../schema/proposal.schema'; import { addUpload, getBlobName } from '../utils/proposal.utils'; import { validateUploadDeletion } from '../utils/validate-upload-deletion.util'; @@ -58,6 +58,17 @@ export class ProposalUploadService { async deleteUpload(proposalId: string, uploadId: string, user: IRequestUser): Promise { const proposal = await this.proposalCrudService.findDocument(proposalId, user, undefined, true); + + await this.delete2Upload(proposal, uploadId, user); + + try { + await proposal.save(); + } catch (error) { + throw new Error(error); + } + } + + async delete2Upload(proposal: ProposalDocument, uploadId: string, user: IRequestUser): Promise { const uploadIndex = proposal.uploads.findIndex((upload) => upload._id.toString() === uploadId); if (uploadIndex === -1) { @@ -71,11 +82,5 @@ export class ProposalUploadService { await this.storageService.deleteBlob(upload.blobName); proposal.uploads.splice(uploadIndex, 1); - - try { - await proposal.save(); - } catch (error) { - throw new Error(error); - } } } diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index cddf432..94ad059 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -1,6 +1,6 @@ import { getLocationState } from './validate-access.util'; import { ProposalUploadService } from '../services/proposal-upload.service'; -import { Proposal } from '../schema/proposal.schema'; +import { Proposal, ProposalDocument } from '../schema/proposal.schema'; import { MiiLocation } from 'src/shared/constants/mii-locations'; import { IRequestUser } from 'src/shared/types/request-user.interface'; import { removeFdpgTask } from './add-fdpg-task.util'; @@ -8,7 +8,7 @@ import { FdpgTaskType } from '../enums/fdpg-task-type.enum'; import { clearLocationsVotes } from './location-flow.util'; export const revertLocationVote = async ( - proposal: Proposal, + proposal: ProposalDocument, location: MiiLocation, user: IRequestUser, proposalUploadService: ProposalUploadService, @@ -17,10 +17,10 @@ export const revertLocationVote = async ( let locationDataAmount = 0; - if (locationState.isConditionalApproval) { + if (locationState.conditionalApprovalAccepted) { locationDataAmount = proposal.conditionalApprovals.find((approval) => approval.location === location)?.dataAmount ?? 0; - } else { + } else if (!locationState.isConditionalApproval && locationState.uacApproved) { locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount ?? 0; } @@ -48,20 +48,17 @@ export const revertLocationVote = async ( const uploadId = proposal.conditionalApprovals.find((approval) => approval.location === location)?.uploadId; if (uploadId) { - await proposalUploadService.deleteUpload(proposal._id, uploadId, user); + await proposalUploadService.delete2Upload(proposal, uploadId, user); } - } - - if (locationState.conditionalApprovalAccepted) { removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); proposal.conditionalApprovals = proposal.conditionalApprovals.filter( (condition) => condition.location !== location, ); + } - if (!isDataAmountReached) { - removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); - } + if (locationState.conditionalApprovalAccepted && !isDataAmountReached) { + removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); } clearLocationsVotes(proposal, location); From 7afd756a7859d245d0b1b7bbd40c1b6fabd05ce4 Mon Sep 17 00:00:00 2001 From: Aleksandra Frost Date: Wed, 8 May 2024 11:40:03 +0200 Subject: [PATCH 07/15] [ADJUST] tests for new delete upload --- .../proposal-upload.controller.spec.ts | 2 +- .../controller/proposal-upload.controller.ts | 2 +- .../services/proposal-upload.service.ts | 26 +++++++++---------- .../revert-location-vote.util.spec.ts | 17 +++++------- .../utils/revert-location-vote.util.ts | 5 ++-- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/modules/proposal/controller/__tests__/proposal-upload.controller.spec.ts b/src/modules/proposal/controller/__tests__/proposal-upload.controller.spec.ts index 22a0c09..00be563 100644 --- a/src/modules/proposal/controller/__tests__/proposal-upload.controller.spec.ts +++ b/src/modules/proposal/controller/__tests__/proposal-upload.controller.spec.ts @@ -87,7 +87,7 @@ describe('ProposalUploadController', () => { jest.spyOn(proposalUploadService, 'deleteUpload'); await proposalUploadController.deleteUpload(params, request); - expect(proposalUploadService.deleteUpload).toHaveBeenCalledWith(params.mainId, params.subId, request.user); + expect(proposalUploadService.saveDeletedUpload).toHaveBeenCalledWith(params.mainId, params.subId, request.user); }); }); }); diff --git a/src/modules/proposal/controller/proposal-upload.controller.ts b/src/modules/proposal/controller/proposal-upload.controller.ts index 9ab10a0..380298a 100644 --- a/src/modules/proposal/controller/proposal-upload.controller.ts +++ b/src/modules/proposal/controller/proposal-upload.controller.ts @@ -65,6 +65,6 @@ export class ProposalUploadController { @HttpCode(204) @ApiOperation({ summary: 'Deletes a proposal upload' }) async deleteUpload(@Param() { mainId, subId }: MongoTwoIdsParamDto, @Request() { user }: FdpgRequest): Promise { - return await this.proposalUploadService.deleteUpload(mainId, subId, user); + return await this.proposalUploadService.saveDeletedUpload(mainId, subId, user); } } diff --git a/src/modules/proposal/services/proposal-upload.service.ts b/src/modules/proposal/services/proposal-upload.service.ts index 543484b..814b276 100644 --- a/src/modules/proposal/services/proposal-upload.service.ts +++ b/src/modules/proposal/services/proposal-upload.service.ts @@ -56,19 +56,7 @@ export class ProposalUploadService { } } - async deleteUpload(proposalId: string, uploadId: string, user: IRequestUser): Promise { - const proposal = await this.proposalCrudService.findDocument(proposalId, user, undefined, true); - - await this.delete2Upload(proposal, uploadId, user); - - try { - await proposal.save(); - } catch (error) { - throw new Error(error); - } - } - - async delete2Upload(proposal: ProposalDocument, uploadId: string, user: IRequestUser): Promise { + async deleteUpload(proposal: ProposalDocument, uploadId: string, user: IRequestUser): Promise { const uploadIndex = proposal.uploads.findIndex((upload) => upload._id.toString() === uploadId); if (uploadIndex === -1) { @@ -83,4 +71,16 @@ export class ProposalUploadService { proposal.uploads.splice(uploadIndex, 1); } + + async saveDeletedUpload(proposalId: string, uploadId: string, user: IRequestUser): Promise { + const proposal = await this.proposalCrudService.findDocument(proposalId, user, undefined, true); + + await this.deleteUpload(proposal, uploadId, user); + + try { + await proposal.save(); + } catch (error) { + throw new Error(error); + } + } } diff --git a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts index ff572ff..a3227c8 100644 --- a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts @@ -238,7 +238,7 @@ describe('revertLocationVoteUtil', () => { const testCases = [ { name: 'expected data amount is reached even withouth the location data amount', - dataAmount: 10, + dataAmount: 9, expectedDataAmountReached: true, }, { @@ -251,16 +251,15 @@ describe('revertLocationVoteUtil', () => { test.each(testCases)('%s', async ({ dataAmount, expectedDataAmountReached }) => { const approval = new UacApproval(); approval.location = MiiLocation.UKRUB; - approval.dataAmount = dataAmount; const condition = new ConditionalApproval(); condition.location = MiiLocation.UKRUB; condition.fdpgTaskId = FdpgTaskType.DataAmountReached; - condition.dataAmount = 10; + condition.dataAmount = dataAmount; const proposal = getProposalDocument(); proposal.conditionalApprovals = [condition]; proposal.uacApprovals = [approval]; - proposal.totalPromisedDataAmount = 21; - proposal.requestedData.desiredDataAmount = 11; + proposal.totalPromisedDataAmount = 20; + proposal.requestedData.desiredDataAmount = 10; proposal.openDizChecks = []; const request = getRequest(); @@ -287,12 +286,12 @@ describe('revertLocationVoteUtil', () => { it('should delete conditional approval object and fdpg task', async () => { const condition = new ConditionalApproval(); condition.location = MiiLocation.UKRUB; - condition.fdpgTaskId = FdpgTaskType.ConditionApproval; + condition.fdpgTaskId = FdpgTaskType.DataAmountReached; condition.dataAmount = 10; const proposal = getProposalDocument(); proposal.conditionalApprovals = [condition]; proposal.totalPromisedDataAmount = condition.dataAmount + 1; - proposal.requestedData.desiredDataAmount = 11; + proposal.requestedData.desiredDataAmount = 12; proposal.openDizChecks = []; const request = getRequest(); @@ -302,11 +301,9 @@ describe('revertLocationVoteUtil', () => { conditionalApprovalAccepted: true, }); - expect(proposal.conditionalApprovals).toEqual([condition]); await revertLocationVote(proposal, condition.location, request.user, proposalUploadServiceMock); expect(getLocationStateMock).toBeCalledWith(proposal, request.user); expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); - expect(proposal.conditionalApprovals).not.toEqual([condition]); expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); expect(proposal.openDizChecks).toEqual([condition.location]); @@ -329,7 +326,7 @@ describe('revertLocationVoteUtil', () => { await revertLocationVote(proposal, condition.location, request.user, proposalUploadServiceMock); expect(getLocationStateMock).toBeCalledWith(proposal, request.user); expect(proposalUploadServiceMock.deleteUpload).toBeCalledTimes(1); - expect(proposalUploadServiceMock.deleteUpload).toBeCalledWith(proposal._id, condition.uploadId, request.user); + expect(proposalUploadServiceMock.deleteUpload).toBeCalledWith(proposal, condition.uploadId, request.user); expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); expect(proposal.openDizChecks).toEqual([condition.location]); diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index 94ad059..93028c9 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -1,6 +1,6 @@ import { getLocationState } from './validate-access.util'; import { ProposalUploadService } from '../services/proposal-upload.service'; -import { Proposal, ProposalDocument } from '../schema/proposal.schema'; +import { ProposalDocument } from '../schema/proposal.schema'; import { MiiLocation } from 'src/shared/constants/mii-locations'; import { IRequestUser } from 'src/shared/types/request-user.interface'; import { removeFdpgTask } from './add-fdpg-task.util'; @@ -48,7 +48,7 @@ export const revertLocationVote = async ( const uploadId = proposal.conditionalApprovals.find((approval) => approval.location === location)?.uploadId; if (uploadId) { - await proposalUploadService.delete2Upload(proposal, uploadId, user); + await proposalUploadService.deleteUpload(proposal, uploadId, user); } removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); @@ -60,7 +60,6 @@ export const revertLocationVote = async ( if (locationState.conditionalApprovalAccepted && !isDataAmountReached) { removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); } - clearLocationsVotes(proposal, location); proposal.openDizChecks.push(location); }; From 7403f25df5fe89c2392bc2fc025cd664f12a53e0 Mon Sep 17 00:00:00 2001 From: Aleksandra Frost Date: Tue, 21 May 2024 11:17:59 +0200 Subject: [PATCH 08/15] [ADD] pr comments --- .../__tests__/proposal-contracting.controller.spec.ts | 4 ++-- .../controller/__tests__/proposal-upload.controller.spec.ts | 6 +++++- .../proposal/controller/proposal-contracting.controller.ts | 4 ++-- .../proposal/controller/proposal-upload.controller.ts | 2 +- src/modules/proposal/decorators/expose-history.decorator.ts | 2 +- src/modules/proposal/enums/history-event.enum.ts | 2 +- .../services/__tests__/proposal-contracting.service.spec.ts | 3 +-- .../proposal/services/proposal-contracting.service.ts | 2 +- src/modules/proposal/services/proposal-upload.service.ts | 2 +- .../proposal/utils/__tests__/add-location-vote.util.spec.ts | 1 - .../proposal/utils/__tests__/proposal-history.util.spec.ts | 4 ++-- .../utils/__tests__/revert-location-vote.util.spec.ts | 2 +- src/modules/proposal/utils/proposal-history.util.ts | 2 +- src/modules/proposal/utils/revert-location-vote.util.ts | 4 +--- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts b/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts index c0af5ea..e935c24 100644 --- a/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts +++ b/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts @@ -82,10 +82,10 @@ describe('ProposalContractingController', () => { id: 'mongoId', }; const input = new RevertLocationVoteDto(); - jest.spyOn(proposalContractingService, 'handleLocationVote'); + jest.spyOn(proposalContractingService, 'revertLocationVote'); await proposalContractingController.revertLocationVote(params, input, request); - expect(proposalContractingService.handleLocationVote).toHaveBeenCalledWith( + expect(proposalContractingService.revertLocationVote).toHaveBeenCalledWith( params.id, input.location, request.user, diff --git a/src/modules/proposal/controller/__tests__/proposal-upload.controller.spec.ts b/src/modules/proposal/controller/__tests__/proposal-upload.controller.spec.ts index 00be563..7b3f619 100644 --- a/src/modules/proposal/controller/__tests__/proposal-upload.controller.spec.ts +++ b/src/modules/proposal/controller/__tests__/proposal-upload.controller.spec.ts @@ -87,7 +87,11 @@ describe('ProposalUploadController', () => { jest.spyOn(proposalUploadService, 'deleteUpload'); await proposalUploadController.deleteUpload(params, request); - expect(proposalUploadService.saveDeletedUpload).toHaveBeenCalledWith(params.mainId, params.subId, request.user); + expect(proposalUploadService.deleteUploadAndSaveProposal).toHaveBeenCalledWith( + params.mainId, + params.subId, + request.user, + ); }); }); }); diff --git a/src/modules/proposal/controller/proposal-contracting.controller.ts b/src/modules/proposal/controller/proposal-contracting.controller.ts index 32cedf7..1a50019 100644 --- a/src/modules/proposal/controller/proposal-contracting.controller.ts +++ b/src/modules/proposal/controller/proposal-contracting.controller.ts @@ -68,7 +68,7 @@ export class ProposalContractingController { } @Auth(Role.FdpgMember) - @Put(':id/revertLocationVote') + @Post(':id/revertLocationVote') @UsePipes(ValidationPipe) @ApiNotFoundResponse({ description: 'Proposal could not be found' }) @ApiOperation({ summary: 'FDPG member reverts locations vote ' }) @@ -80,7 +80,7 @@ export class ProposalContractingController { @Body() { location }: RevertLocationVoteDto, @Request() { user }: FdpgRequest, ): Promise { - return await this.proposalContractingService.handleLocationVote(id, location, user); + return await this.proposalContractingService.revertLocationVote(id, location, user); } @Auth(Role.FdpgMember) diff --git a/src/modules/proposal/controller/proposal-upload.controller.ts b/src/modules/proposal/controller/proposal-upload.controller.ts index 380298a..ae23785 100644 --- a/src/modules/proposal/controller/proposal-upload.controller.ts +++ b/src/modules/proposal/controller/proposal-upload.controller.ts @@ -65,6 +65,6 @@ export class ProposalUploadController { @HttpCode(204) @ApiOperation({ summary: 'Deletes a proposal upload' }) async deleteUpload(@Param() { mainId, subId }: MongoTwoIdsParamDto, @Request() { user }: FdpgRequest): Promise { - return await this.proposalUploadService.saveDeletedUpload(mainId, subId, user); + return await this.proposalUploadService.deleteUploadAndSaveProposal(mainId, subId, user); } } diff --git a/src/modules/proposal/decorators/expose-history.decorator.ts b/src/modules/proposal/decorators/expose-history.decorator.ts index 8a56bd3..5da35b2 100644 --- a/src/modules/proposal/decorators/expose-history.decorator.ts +++ b/src/modules/proposal/decorators/expose-history.decorator.ts @@ -12,7 +12,7 @@ export const ExposeHistory = () => (target: Object, propertyKey: string) => { const isFdpgMember = user.singleKnownRole === 'FdpgMember'; const filteredEvents = params.obj[propertyKey].filter((event: HistoryEvent) => { - const isRevertEvent = event.type === HistoryEventType.FdpgRevertedLocationVote; + const isRevertEvent = event.type === HistoryEventType.FdpgLocationVoteReverted; if (isRevertEvent && isFdpgMember) { return true; diff --git a/src/modules/proposal/enums/history-event.enum.ts b/src/modules/proposal/enums/history-event.enum.ts index 9631bc6..cfca91c 100644 --- a/src/modules/proposal/enums/history-event.enum.ts +++ b/src/modules/proposal/enums/history-event.enum.ts @@ -30,7 +30,7 @@ export enum HistoryEventType { FdpgApprovedLocationRemoved = 'FDPG_APPROVED_LOCATION_REMOVED', /** FDPG reverts DIZ and UAC vote */ - FdpgRevertedLocationVote = 'FDPG_REVERTED_LOCATION_VOTE', + FdpgLocationVoteReverted = 'FDPG_REVERTED_LOCATION_VOTE', /** Data Delivery */ diff --git a/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts b/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts index 19bde84..4c30942 100644 --- a/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts +++ b/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts @@ -146,7 +146,6 @@ describe('ProposalContractingService', () => { handleProposalUacApproval: jest.fn(), handleProposalStatusChange: jest.fn(), handleProposalContractSign: jest.fn(), - handleLocationVote: jest.fn(), }, }, { @@ -251,7 +250,7 @@ describe('ProposalContractingService', () => { const location = MiiLocation.UKL; jest.spyOn(proposalCrudService, 'findDocument').mockResolvedValueOnce(proposalDocument); - await proposalContractingService.handleLocationVote(proposalId, request.user.miiLocation, request.user); + await proposalContractingService.revertLocationVote(proposalId, request.user.miiLocation, request.user); expect(validateRevertLocationVote).toHaveBeenCalledWith(proposalDocument, location); expect(revertLocationVote).toHaveBeenCalledWith( diff --git a/src/modules/proposal/services/proposal-contracting.service.ts b/src/modules/proposal/services/proposal-contracting.service.ts index 8b68aeb..2b7e9b0 100644 --- a/src/modules/proposal/services/proposal-contracting.service.ts +++ b/src/modules/proposal/services/proposal-contracting.service.ts @@ -90,7 +90,7 @@ export class ProposalContractingService { await this.eventEngineService.handleProposalUacApproval(saveResult, vote.value, user.miiLocation); } - async handleLocationVote(proposalId: string, location: MiiLocation, user: IRequestUser): Promise { + async revertLocationVote(proposalId: string, location: MiiLocation, user: IRequestUser): Promise { const toBeUpdated = await this.proposalCrudService.findDocument(proposalId, user, undefined, true); validateRevertLocationVote(toBeUpdated, location); diff --git a/src/modules/proposal/services/proposal-upload.service.ts b/src/modules/proposal/services/proposal-upload.service.ts index 814b276..8a092d1 100644 --- a/src/modules/proposal/services/proposal-upload.service.ts +++ b/src/modules/proposal/services/proposal-upload.service.ts @@ -72,7 +72,7 @@ export class ProposalUploadService { proposal.uploads.splice(uploadIndex, 1); } - async saveDeletedUpload(proposalId: string, uploadId: string, user: IRequestUser): Promise { + async deleteUploadAndSaveProposal(proposalId: string, uploadId: string, user: IRequestUser): Promise { const proposal = await this.proposalCrudService.findDocument(proposalId, user, undefined, true); await this.deleteUpload(proposal, uploadId, user); diff --git a/src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts index 7c61a57..0d2095e 100644 --- a/src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts @@ -19,7 +19,6 @@ import { clearLocationsVotes } from '../location-flow.util'; jest.mock('../location-flow.util', () => ({ clearLocationsVotes: jest.fn(), - deleteConditionalUpload: jest.fn(), })); jest.mock('../add-fdpg-task.util', () => ({ diff --git a/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts b/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts index 3525393..d2f0b0a 100644 --- a/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts @@ -196,12 +196,12 @@ describe('ProposalHistoryUtil', () => { it('should add history item for reverted location vote', () => { const request = getRequest(); const proposal = getProposalDocument(); - const location = MiiLocation.UKRUB; + const location = MiiLocation.UKL; addHistoryItemForRevertLocationVote(proposal, request.user, location); expect(proposal.history.length).toBe(1); - const expectedType = HistoryEventType.FdpgRevertedLocationVote; + const expectedType = HistoryEventType.FdpgLocationVoteReverted; expect(proposal.history[0].type).toBe(expectedType); }); }); diff --git a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts index a3227c8..cd51fb3 100644 --- a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts @@ -105,7 +105,7 @@ describe('revertLocationVoteUtil', () => { await revertLocationVote(proposal, declineReason.location, request.user, proposalUploadServiceMock); expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(proposal.declineReasons).not.toEqual([declineReason]); + expect(proposal.declineReasons).toEqual([declineReason]); expect(proposal.declineReasons.length).toEqual(0); expect(clearLocationsVotes).toBeCalledWith(proposal, declineReason.location); expect(proposal.requestedButExcludedLocations).not.toEqual([request.user.miiLocation]); diff --git a/src/modules/proposal/utils/proposal-history.util.ts b/src/modules/proposal/utils/proposal-history.util.ts index 8241d81..65b08a6 100644 --- a/src/modules/proposal/utils/proposal-history.util.ts +++ b/src/modules/proposal/utils/proposal-history.util.ts @@ -136,7 +136,7 @@ export const addHistoryItemForRevertLocationVote = ( user: IRequestUser, location: MiiLocation, ): void => { - const type = HistoryEventType.FdpgRevertedLocationVote; + const type = HistoryEventType.FdpgLocationVoteReverted; pushHistoryItem(proposalAfterChanges, user, type, location); }; diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index 93028c9..1fda12b 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -27,9 +27,7 @@ export const revertLocationVote = async ( proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); - if (locationState.requestedButExcluded) { - proposal.declineReasons = proposal.declineReasons.filter((reason) => reason.location !== location); - } + proposal.declineReasons = proposal.declineReasons.filter((reason) => reason.location !== location); if (locationState.uacApproved) { proposal.uacApprovals = proposal.uacApprovals.filter((approval) => approval.location !== location); From 1b4628e3d1c9b0a76a80498df8d0b575d862a0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Tue, 21 May 2024 11:53:57 +0200 Subject: [PATCH 09/15] [ADJUST] revert location function --- .../utils/revert-location-vote.util.ts | 53 +++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index 93028c9..279470c 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -15,51 +15,38 @@ export const revertLocationVote = async ( ) => { const locationState = getLocationState(proposal, user); - let locationDataAmount = 0; + const uploadId = proposal.conditionalApprovals?.find((approval) => approval.location === location)?.uploadId; + if (uploadId) { + await proposalUploadService.deleteUpload(proposal, uploadId, user); + } + let locationDataAmount = 0; if (locationState.conditionalApprovalAccepted) { locationDataAmount = - proposal.conditionalApprovals.find((approval) => approval.location === location)?.dataAmount ?? 0; + proposal.conditionalApprovals?.find((approval) => approval.location === location)?.dataAmount ?? 0; } else if (!locationState.isConditionalApproval && locationState.uacApproved) { - locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount ?? 0; + locationDataAmount = proposal.uacApprovals?.find((approval) => approval.location === location)?.dataAmount ?? 0; } - proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; - const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); - if (locationState.requestedButExcluded) { - proposal.declineReasons = proposal.declineReasons.filter((reason) => reason.location !== location); - } + proposal.uacApprovals = proposal.uacApprovals?.filter((approval) => approval.location !== location); + proposal.conditionalApprovals = proposal.conditionalApprovals?.filter((condition) => condition.location !== location); + proposal.declineReasons = proposal.declineReasons?.filter((reason) => reason.location !== location); - if (locationState.uacApproved) { - proposal.uacApprovals = proposal.uacApprovals.filter((approval) => approval.location !== location); - if (!isDataAmountReached) { - removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); - } - const isUacApprovalComplete = - proposal.uacApprovedLocations.length + proposal.requestedButExcludedLocations.length === - proposal.numberOfRequestedLocations; - if (!isUacApprovalComplete) { - removeFdpgTask(proposal, FdpgTaskType.UacApprovalComplete); - } - } + clearLocationsVotes(proposal, location); + proposal.openDizChecks.push(location); if (locationState.isConditionalApproval) { - const uploadId = proposal.conditionalApprovals.find((approval) => approval.location === location)?.uploadId; - - if (uploadId) { - await proposalUploadService.deleteUpload(proposal, uploadId, user); - } removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); - - proposal.conditionalApprovals = proposal.conditionalApprovals.filter( - (condition) => condition.location !== location, - ); } - - if (locationState.conditionalApprovalAccepted && !isDataAmountReached) { + const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); + if (!isDataAmountReached) { removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); } - clearLocationsVotes(proposal, location); - proposal.openDizChecks.push(location); + const isUacApprovalComplete = + proposal.uacApprovedLocations.length + proposal.requestedButExcludedLocations.length === + proposal.numberOfRequestedLocations; + if (!isUacApprovalComplete) { + removeFdpgTask(proposal, FdpgTaskType.UacApprovalComplete); + } }; From 74bc8fafb411720cfcc21c3edcdd7505a0e7290e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Tue, 21 May 2024 13:01:12 +0200 Subject: [PATCH 10/15] [ADJUST] formatting --- src/modules/proposal/utils/revert-location-vote.util.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index 279470c..1d15821 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -39,10 +39,12 @@ export const revertLocationVote = async ( if (locationState.isConditionalApproval) { removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); } + const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); if (!isDataAmountReached) { removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); } + const isUacApprovalComplete = proposal.uacApprovedLocations.length + proposal.requestedButExcludedLocations.length === proposal.numberOfRequestedLocations; From 44da641d781e1c743bee822dc327075345fc0142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Tue, 21 May 2024 13:14:42 +0200 Subject: [PATCH 11/15] [FIX] small issues --- src/modules/proposal/enums/history-event.enum.ts | 2 +- .../proposal/utils/__tests__/revert-location-vote.util.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/proposal/enums/history-event.enum.ts b/src/modules/proposal/enums/history-event.enum.ts index cfca91c..7b4459f 100644 --- a/src/modules/proposal/enums/history-event.enum.ts +++ b/src/modules/proposal/enums/history-event.enum.ts @@ -30,7 +30,7 @@ export enum HistoryEventType { FdpgApprovedLocationRemoved = 'FDPG_APPROVED_LOCATION_REMOVED', /** FDPG reverts DIZ and UAC vote */ - FdpgLocationVoteReverted = 'FDPG_REVERTED_LOCATION_VOTE', + FdpgLocationVoteReverted = 'FDPG_LOCATION_VOTE_REVERTED', /** Data Delivery */ diff --git a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts index cd51fb3..a3227c8 100644 --- a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts @@ -105,7 +105,7 @@ describe('revertLocationVoteUtil', () => { await revertLocationVote(proposal, declineReason.location, request.user, proposalUploadServiceMock); expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(proposal.declineReasons).toEqual([declineReason]); + expect(proposal.declineReasons).not.toEqual([declineReason]); expect(proposal.declineReasons.length).toEqual(0); expect(clearLocationsVotes).toBeCalledWith(proposal, declineReason.location); expect(proposal.requestedButExcludedLocations).not.toEqual([request.user.miiLocation]); From e509a37c3da3b44b20caa7bbfe1f739f91343bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Tue, 21 May 2024 13:39:05 +0200 Subject: [PATCH 12/15] [ADJUST] revert function to better remove existing tasks --- .../proposal/utils/revert-location-vote.util.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index 1d15821..04222f7 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -36,7 +36,11 @@ export const revertLocationVote = async ( clearLocationsVotes(proposal, location); proposal.openDizChecks.push(location); - if (locationState.isConditionalApproval) { + if ( + proposal.conditionalApprovals.every((conditionalApproval) => + proposal.uacApprovedLocations.some((approvedLocation) => approvedLocation === conditionalApproval.location), + ) + ) { removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); } @@ -44,11 +48,6 @@ export const revertLocationVote = async ( if (!isDataAmountReached) { removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); } - - const isUacApprovalComplete = - proposal.uacApprovedLocations.length + proposal.requestedButExcludedLocations.length === - proposal.numberOfRequestedLocations; - if (!isUacApprovalComplete) { - removeFdpgTask(proposal, FdpgTaskType.UacApprovalComplete); - } + + removeFdpgTask(proposal, FdpgTaskType.UacApprovalComplete); }; From 1ae27d8335c3e5330b6c9d8992897bcd67bee2f9 Mon Sep 17 00:00:00 2001 From: Aleksandra Frost Date: Tue, 21 May 2024 15:12:29 +0200 Subject: [PATCH 13/15] [FIX] remove task by type --- .../revert-location-vote.util.spec.ts | 81 +++++++------------ .../proposal/utils/add-fdpg-task.util.ts | 6 +- .../utils/revert-location-vote.util.ts | 12 +-- 3 files changed, 40 insertions(+), 59 deletions(-) diff --git a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts index a3227c8..1e8a20a 100644 --- a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts @@ -11,14 +11,14 @@ import { ProposalDocument } from '../../schema/proposal.schema'; import { clearLocationsVotes } from '../location-flow.util'; import { ConditionalApproval } from '../../schema/sub-schema/conditional-approval.schema'; import { FdpgTaskType } from '../../enums/fdpg-task-type.enum'; -import { removeFdpgTask } from '../add-fdpg-task.util'; +import { removeFdpgTaskByType } from '../add-fdpg-task.util'; jest.mock('../location-flow.util', () => ({ clearLocationsVotes: jest.fn(), })); jest.mock('../add-fdpg-task.util', () => ({ - removeFdpgTask: jest.fn(), + removeFdpgTaskByType: jest.fn(), })); jest.mock('../validate-access.util', () => ({ getLocationState: jest.fn(), @@ -176,9 +176,9 @@ describe('revertLocationVoteUtil', () => { await revertLocationVote(proposal, approval.location, request.user, proposalUploadServiceMock); expect(getLocationStateMock).toBeCalledWith(proposal, request.user); if (expectedDataAmountReached) { - expect(removeFdpgTask).not.toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); + expect(removeFdpgTaskByType).not.toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); } else { - expect(removeFdpgTask).toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); + expect(removeFdpgTaskByType).toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); } expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); @@ -186,52 +186,33 @@ describe('revertLocationVoteUtil', () => { }); }); - describe('should remove Fdpg Task in case of uacApproved and uacApprovalComplete', () => { - const testCases = [ - { - name: 'not completed', - isCompleted: false, - }, - { - name: 'completed', - isCompleted: true, - }, - ]; - - test.each(testCases)('%s', async ({ isCompleted }) => { - const approval = new UacApproval(); - approval.location = MiiLocation.UKRUB; - const condition = new ConditionalApproval(); - condition.location = MiiLocation.UKRUB; - condition.fdpgTaskId = FdpgTaskType.UacApprovalComplete; - const proposal = getProposalDocument(); - proposal.uacApprovedLocations = [MiiLocation.UKRUB]; - proposal.conditionalApprovals = [condition]; - proposal.uacApprovals = [approval]; - proposal.openDizChecks = []; - proposal.requestedButExcludedLocations = [MiiLocation.UMG, MiiLocation.UKL]; - - proposal.numberOfRequestedLocations = isCompleted ? 3 : 2; - - const request = getRequest(); + it('should remove Fdpg Task with type UacApprovalComplete', async () => { + const approval = new UacApproval(); + approval.location = MiiLocation.UKRUB; + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.fdpgTaskId = FdpgTaskType.UacApprovalComplete; + const proposal = getProposalDocument(); + proposal.uacApprovedLocations = [MiiLocation.UKRUB]; + proposal.conditionalApprovals = [condition]; + proposal.uacApprovals = [approval]; + proposal.openDizChecks = []; + proposal.requestedButExcludedLocations = [MiiLocation.UMG, MiiLocation.UKL]; - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - uacApproved: true, - }); + const request = getRequest(); - expect(proposal.uacApprovals).toEqual([approval]); - await revertLocationVote(proposal, approval.location, request.user, proposalUploadServiceMock); - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - if (!isCompleted) { - expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); - } else { - expect(removeFdpgTask).not.toBeCalledWith(proposal, condition.fdpgTaskId); - } - expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); - expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([condition.location]); + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + uacApproved: true, }); + + expect(proposal.uacApprovals).toEqual([approval]); + await revertLocationVote(proposal, approval.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(removeFdpgTaskByType).toBeCalledWith(proposal, condition.fdpgTaskId); + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); }); describe('should remove Fdpg Task in case of conditionalApprovalAccepted and expected data amount reached', () => { @@ -273,9 +254,9 @@ describe('revertLocationVoteUtil', () => { await revertLocationVote(proposal, approval.location, request.user, proposalUploadServiceMock); expect(getLocationStateMock).toBeCalledWith(proposal, request.user); if (expectedDataAmountReached) { - expect(removeFdpgTask).not.toBeCalledWith(proposal, condition.fdpgTaskId); + expect(removeFdpgTaskByType).not.toBeCalledWith(proposal, condition.fdpgTaskId); } else { - expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); + expect(removeFdpgTaskByType).toBeCalledWith(proposal, condition.fdpgTaskId); } expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); @@ -303,7 +284,7 @@ describe('revertLocationVoteUtil', () => { await revertLocationVote(proposal, condition.location, request.user, proposalUploadServiceMock); expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); + expect(removeFdpgTaskByType).toBeCalledWith(proposal, condition.fdpgTaskId); expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); expect(proposal.openDizChecks).toEqual([condition.location]); diff --git a/src/modules/proposal/utils/add-fdpg-task.util.ts b/src/modules/proposal/utils/add-fdpg-task.util.ts index dd152aa..1423d75 100644 --- a/src/modules/proposal/utils/add-fdpg-task.util.ts +++ b/src/modules/proposal/utils/add-fdpg-task.util.ts @@ -48,16 +48,16 @@ export const removeFdpgTasksForContracting = (proposal: Proposal): void => { FdpgTaskType.DueDateReached, ]; - toBeRemoved.forEach((type) => removeByType(proposal, type)); + toBeRemoved.forEach((type) => removeFdpgTaskByType(proposal, type)); }; export const removeFdpgTasksForDataDelivery = (proposal: Proposal): void => { const toBeRemoved = [FdpgTaskType.ContractComplete, FdpgTaskType.DueDateReached]; - toBeRemoved.forEach((type) => removeByType(proposal, type)); + toBeRemoved.forEach((type) => removeFdpgTaskByType(proposal, type)); }; -const removeByType = (proposal: Proposal, type: FdpgTaskType): void => { +export const removeFdpgTaskByType = (proposal: Proposal, type: FdpgTaskType): void => { const taskIndex = proposal.openFdpgTasks.findIndex((task) => task.type === type); if (taskIndex !== -1) { proposal.openFdpgTasks.splice(taskIndex, 1); diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index 04222f7..3498ebc 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -3,7 +3,7 @@ import { ProposalUploadService } from '../services/proposal-upload.service'; import { ProposalDocument } from '../schema/proposal.schema'; import { MiiLocation } from 'src/shared/constants/mii-locations'; import { IRequestUser } from 'src/shared/types/request-user.interface'; -import { removeFdpgTask } from './add-fdpg-task.util'; +import { removeFdpgTask, removeFdpgTaskByType } from './add-fdpg-task.util'; import { FdpgTaskType } from '../enums/fdpg-task-type.enum'; import { clearLocationsVotes } from './location-flow.util'; @@ -37,17 +37,17 @@ export const revertLocationVote = async ( proposal.openDizChecks.push(location); if ( - proposal.conditionalApprovals.every((conditionalApproval) => - proposal.uacApprovedLocations.some((approvedLocation) => approvedLocation === conditionalApproval.location), + proposal.conditionalApprovals?.every((conditionalApproval) => + proposal.uacApprovedLocations?.some((approvedLocation) => approvedLocation === conditionalApproval.location), ) ) { - removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); + removeFdpgTaskByType(proposal, FdpgTaskType.ConditionApproval); } const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); if (!isDataAmountReached) { - removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); + removeFdpgTaskByType(proposal, FdpgTaskType.DataAmountReached); } - removeFdpgTask(proposal, FdpgTaskType.UacApprovalComplete); + removeFdpgTaskByType(proposal, FdpgTaskType.UacApprovalComplete); }; From 2e48797fe50ed9c1fad81b5c3ad337ab20e146aa Mon Sep 17 00:00:00 2001 From: Aleksandra Frost Date: Tue, 21 May 2024 15:53:27 +0200 Subject: [PATCH 14/15] [FIX] remove Task by id --- .../__tests__/revert-location-vote.util.spec.ts | 15 +++++++-------- .../proposal/utils/revert-location-vote.util.ts | 11 +++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts index 1e8a20a..a438163 100644 --- a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts @@ -11,7 +11,7 @@ import { ProposalDocument } from '../../schema/proposal.schema'; import { clearLocationsVotes } from '../location-flow.util'; import { ConditionalApproval } from '../../schema/sub-schema/conditional-approval.schema'; import { FdpgTaskType } from '../../enums/fdpg-task-type.enum'; -import { removeFdpgTaskByType } from '../add-fdpg-task.util'; +import { removeFdpgTask, removeFdpgTaskByType } from '../add-fdpg-task.util'; jest.mock('../location-flow.util', () => ({ clearLocationsVotes: jest.fn(), @@ -19,6 +19,7 @@ jest.mock('../location-flow.util', () => ({ jest.mock('../add-fdpg-task.util', () => ({ removeFdpgTaskByType: jest.fn(), + removeFdpgTask: jest.fn(), })); jest.mock('../validate-access.util', () => ({ getLocationState: jest.fn(), @@ -191,7 +192,6 @@ describe('revertLocationVoteUtil', () => { approval.location = MiiLocation.UKRUB; const condition = new ConditionalApproval(); condition.location = MiiLocation.UKRUB; - condition.fdpgTaskId = FdpgTaskType.UacApprovalComplete; const proposal = getProposalDocument(); proposal.uacApprovedLocations = [MiiLocation.UKRUB]; proposal.conditionalApprovals = [condition]; @@ -209,7 +209,7 @@ describe('revertLocationVoteUtil', () => { expect(proposal.uacApprovals).toEqual([approval]); await revertLocationVote(proposal, approval.location, request.user, proposalUploadServiceMock); expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(removeFdpgTaskByType).toBeCalledWith(proposal, condition.fdpgTaskId); + expect(removeFdpgTaskByType).toBeCalledWith(proposal, FdpgTaskType.UacApprovalComplete); expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); expect(proposal.openDizChecks).toEqual([condition.location]); @@ -234,7 +234,6 @@ describe('revertLocationVoteUtil', () => { approval.location = MiiLocation.UKRUB; const condition = new ConditionalApproval(); condition.location = MiiLocation.UKRUB; - condition.fdpgTaskId = FdpgTaskType.DataAmountReached; condition.dataAmount = dataAmount; const proposal = getProposalDocument(); proposal.conditionalApprovals = [condition]; @@ -254,9 +253,9 @@ describe('revertLocationVoteUtil', () => { await revertLocationVote(proposal, approval.location, request.user, proposalUploadServiceMock); expect(getLocationStateMock).toBeCalledWith(proposal, request.user); if (expectedDataAmountReached) { - expect(removeFdpgTaskByType).not.toBeCalledWith(proposal, condition.fdpgTaskId); + expect(removeFdpgTaskByType).not.toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); } else { - expect(removeFdpgTaskByType).toBeCalledWith(proposal, condition.fdpgTaskId); + expect(removeFdpgTaskByType).toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); } expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); @@ -267,7 +266,7 @@ describe('revertLocationVoteUtil', () => { it('should delete conditional approval object and fdpg task', async () => { const condition = new ConditionalApproval(); condition.location = MiiLocation.UKRUB; - condition.fdpgTaskId = FdpgTaskType.DataAmountReached; + condition.fdpgTaskId = 'string'; condition.dataAmount = 10; const proposal = getProposalDocument(); proposal.conditionalApprovals = [condition]; @@ -284,7 +283,7 @@ describe('revertLocationVoteUtil', () => { await revertLocationVote(proposal, condition.location, request.user, proposalUploadServiceMock); expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(removeFdpgTaskByType).toBeCalledWith(proposal, condition.fdpgTaskId); + expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); expect(proposal.openDizChecks).toEqual([condition.location]); diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index 3498ebc..fa93fc2 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -19,6 +19,9 @@ export const revertLocationVote = async ( if (uploadId) { await proposalUploadService.deleteUpload(proposal, uploadId, user); } + const conditionalApprovalFdpgTaskId = proposal.conditionalApprovals?.find( + (approval) => approval.location === location, + )?.fdpgTaskId; let locationDataAmount = 0; if (locationState.conditionalApprovalAccepted) { @@ -36,12 +39,8 @@ export const revertLocationVote = async ( clearLocationsVotes(proposal, location); proposal.openDizChecks.push(location); - if ( - proposal.conditionalApprovals?.every((conditionalApproval) => - proposal.uacApprovedLocations?.some((approvedLocation) => approvedLocation === conditionalApproval.location), - ) - ) { - removeFdpgTaskByType(proposal, FdpgTaskType.ConditionApproval); + if (conditionalApprovalFdpgTaskId) { + removeFdpgTask(proposal, conditionalApprovalFdpgTaskId); } const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); From fa8b8d7c2376fe196c032360ef97eb76cca3b0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20M=C3=BCller?= Date: Wed, 22 May 2024 08:56:13 +0200 Subject: [PATCH 15/15] [ADJUST] ordering for better readability and fault tolerance --- .../proposal/utils/revert-location-vote.util.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts index fa93fc2..c21cf2a 100644 --- a/src/modules/proposal/utils/revert-location-vote.util.ts +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -16,9 +16,6 @@ export const revertLocationVote = async ( const locationState = getLocationState(proposal, user); const uploadId = proposal.conditionalApprovals?.find((approval) => approval.location === location)?.uploadId; - if (uploadId) { - await proposalUploadService.deleteUpload(proposal, uploadId, user); - } const conditionalApprovalFdpgTaskId = proposal.conditionalApprovals?.find( (approval) => approval.location === location, )?.fdpgTaskId; @@ -30,12 +27,11 @@ export const revertLocationVote = async ( } else if (!locationState.isConditionalApproval && locationState.uacApproved) { locationDataAmount = proposal.uacApprovals?.find((approval) => approval.location === location)?.dataAmount ?? 0; } - proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; + proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; proposal.uacApprovals = proposal.uacApprovals?.filter((approval) => approval.location !== location); proposal.conditionalApprovals = proposal.conditionalApprovals?.filter((condition) => condition.location !== location); proposal.declineReasons = proposal.declineReasons?.filter((reason) => reason.location !== location); - clearLocationsVotes(proposal, location); proposal.openDizChecks.push(location); @@ -49,4 +45,8 @@ export const revertLocationVote = async ( } removeFdpgTaskByType(proposal, FdpgTaskType.UacApprovalComplete); + + if (uploadId) { + await proposalUploadService.deleteUpload(proposal, uploadId, user); + } };