From de4d157f8782fd5b8f50d30a971ae1b71d9ae7a1 Mon Sep 17 00:00:00 2001 From: Vittorio Caprio Date: Mon, 27 May 2024 12:35:33 +0200 Subject: [PATCH] Fix conflicts on agreement services and processors --- .../services/agreementActivationProcessor.ts | 310 ++++-------------- .../src/services/agreementService.ts | 130 ++++---- .../services/agreementSubmissionProcessor.ts | 199 +---------- 3 files changed, 117 insertions(+), 522 deletions(-) diff --git a/packages/agreement-process/src/services/agreementActivationProcessor.ts b/packages/agreement-process/src/services/agreementActivationProcessor.ts index 4db342f439..1f04c3f095 100644 --- a/packages/agreement-process/src/services/agreementActivationProcessor.ts +++ b/packages/agreement-process/src/services/agreementActivationProcessor.ts @@ -1,34 +1,23 @@ -import { - AuthData, - CreateEvent, - FileManager, - Logger, - PDFGenerator, -} from "pagopa-interop-commons"; +/* eslint-disable max-params */ +import { AuthData, CreateEvent } from "pagopa-interop-commons"; import { Agreement, - AgreementEvent, - AgreementId, - Descriptor, EService, - SelfcareId, Tenant, - WithMetadata, agreementState, + AgreementEvent, + AgreementState, + Descriptor, + genericError, + AgreementEventV2, + WithMetadata, + UserId, } from "pagopa-interop-models"; import { - agreementArchivableStates, - assertActivableState, - assertAgreementExist, - assertEServiceExist, - assertRequesterIsConsumerOrProducer, - assertTenantExist, - failOnActivationFailure, matchingCertifiedAttributes, matchingDeclaredAttributes, matchingVerifiedAttributes, - validateActivationOnDescriptor, - verifyConsumerDoesNotActivatePending, + agreementArchivableStates, } from "../model/domain/validators.js"; import { toCreateEventAgreementActivated, @@ -36,117 +25,40 @@ import { toCreateEventAgreementUnsuspendedByProducer, } from "../model/domain/toEvent.js"; import { UpdateAgreementSeed } from "../model/domain/models.js"; -import { ApiAgreementDocumentSeed } from "../model/types.js"; import { apiAgreementDocumentToAgreementDocument } from "../model/domain/apiConverter.js"; -import { AgreementProcessConfig } from "../utilities/config.js"; -import { contractBuilder } from "./agreementContractBuilder.js"; import { + createStamp, suspendedByConsumerStamp, suspendedByProducerStamp, } from "./agreementStampUtils.js"; -import { - agreementStateByFlags, - nextState, - suspendedByConsumerFlag, - suspendedByProducerFlag, -} from "./agreementStateProcessor.js"; -import { AgreementQuery } from "./readmodel/agreementQuery.js"; -import { AttributeQuery } from "./readmodel/attributeQuery.js"; -import { EserviceQuery } from "./readmodel/eserviceQuery.js"; -import { TenantQuery } from "./readmodel/tenantQuery.js"; +import { createAgreementArchivedByUpgradeEvent } from "./agreementService.js"; +import { ReadModelService } from "./readModelService.js"; +import { ContractBuilder } from "./agreementContractBuilder.js"; -export async function activateAgreementLogic( - agreementId: AgreementId, - agreementQuery: AgreementQuery, - eserviceQuery: EserviceQuery, - tenantQuery: TenantQuery, - attributeQuery: AttributeQuery, - authData: AuthData, - correlationId: string, - fileManager: FileManager, - pdfGenerator: PDFGenerator, - config: AgreementProcessConfig, - logger: Logger -): Promise<[Agreement, Array>]> { - const agreement = await agreementQuery.getAgreementById(agreementId); - assertAgreementExist(agreementId, agreement); - - assertRequesterIsConsumerOrProducer(agreement.data, authData); - verifyConsumerDoesNotActivatePending(agreement.data, authData); - assertActivableState(agreement.data); - - const eservice = await eserviceQuery.getEServiceById( - agreement.data.eserviceId - ); - assertEServiceExist(agreement.data.eserviceId, eservice); - - const descriptor = validateActivationOnDescriptor( - eservice, - agreement.data.descriptorId - ); - - const tenant = await tenantQuery.getTenantById(agreement.data.consumerId); - assertTenantExist(agreement.data.consumerId, tenant); - - return activateAgreement( - agreement, - eservice, - descriptor, - tenant, - authData, - tenantQuery, - agreementQuery, - attributeQuery, - correlationId, - fileManager, - pdfGenerator, - config, - logger - ); -} - -async function activateAgreement( - agreementData: WithMetadata, - eservice: EService, - descriptor: Descriptor, - consumer: Tenant, - authData: AuthData, - tenantQuery: TenantQuery, - agreementQuery: AgreementQuery, - attributeQuery: AttributeQuery, - correlationId: string, - fileManager: FileManager, - pdfGenerator: PDFGenerator, - config: AgreementProcessConfig, - logger: Logger -): Promise<[Agreement, Array>]> { - const agreement = agreementData.data; - const nextAttributesState = nextState(agreement, descriptor, consumer); - - const suspendedByConsumer = suspendedByConsumerFlag( - agreement, - authData.organizationId, - agreementState.active - ); - const suspendedByProducer = suspendedByProducerFlag( - agreement, - authData.organizationId, - agreementState.active - ); - - const newState = agreementStateByFlags( - nextAttributesState, - suspendedByProducer, - suspendedByConsumer - ); - - failOnActivationFailure(newState, agreement); - - const firstActivation = - agreement.state === agreementState.pending && - newState === agreementState.active; +export function createActivationUpdateAgreementSeed({ + firstActivation, + newState, + descriptor, + consumer, + eservice, + authData, + agreement, + suspendedByConsumer, + suspendedByProducer, +}: { + firstActivation: boolean; + newState: AgreementState; + descriptor: Descriptor; + consumer: Tenant; + eservice: EService; + authData: AuthData; + agreement: Agreement; + suspendedByConsumer: boolean | undefined; + suspendedByProducer: boolean | undefined; +}): UpdateAgreementSeed { + const stamp = createStamp(authData.userId); - const updatedAgreementSeed: UpdateAgreementSeed = firstActivation + return firstActivation ? { state: newState, certifiedAttributes: matchingCertifiedAttributes(descriptor, consumer), @@ -187,105 +99,35 @@ async function activateAgreement( ? undefined : agreement.suspendedAt, }; - - const updatedAgreement = { - ...agreement, - ...updatedAgreementSeed, - }; - - const activationEvent = await match(firstActivation) - .with(true, async () => { - const contract = apiAgreementDocumentToAgreementDocument( - await createContract( - updatedAgreement, - updatedAgreementSeed, - eservice, - consumer, - attributeQuery, - tenantQuery, - authData.selfcareId, - fileManager, - pdfGenerator, - config, - logger - ) - ); - - return toCreateEventAgreementActivated( - { ...updatedAgreement, contract }, - agreementData.metadata.version, - correlationId - ); - }) - .with(false, () => { - if (authData.organizationId === agreement.producerId) { - return toCreateEventAgreementUnsuspendedByProducer( - updatedAgreement, - agreementData.metadata.version, - correlationId - ); - } else if (authData.organizationId === agreement.consumerId) { - return toCreateEventAgreementUnsuspendedByConsumer( - updatedAgreement, - agreementData.metadata.version, - correlationId - ); - } else { - throw new Error( - `Unexpected organizationId ${authData.organizationId} in activateAgreement` - ); - } - }) - .exhaustive(); - - const archiveEvents = await archiveRelatedToAgreements( - agreement, - authData, - agreementQuery, - correlationId - ); - - return [updatedAgreement, [activationEvent, ...archiveEvents]]; } -export async function createActivationEvent({ - firstActivation, - agreement, - updatedAgreement, - updatedAgreementSeed, - eservice, - consumer, - authData, - correlationId, - readModelService, - storeFile, - logger, -}: { - firstActivation: boolean; - agreement: WithMetadata; - updatedAgreement: Agreement; - updatedAgreementSeed: UpdateAgreementSeed; - eservice: EService; - consumer: Tenant; - authData: AuthData; - correlationId: string; - readModelService: ReadModelService; - storeFile: FileManager["storeBytes"]; - logger: Logger; -}): Promise> { +export async function createActivationEvent( + firstActivation: boolean, + agreement: WithMetadata, + updatedAgreement: Agreement, + updatedAgreementSeed: UpdateAgreementSeed, + eservice: EService, + consumer: Tenant, + producer: Tenant, + authData: AuthData, + correlationId: string, + contractBuilder: ContractBuilder +): Promise> { if (firstActivation) { - const contract = await createContract({ - agreement: updatedAgreement, - updateSeed: updatedAgreementSeed, + const agreementContract = await contractBuilder.createContract( + authData.selfcareId, + updatedAgreement, eservice, consumer, - readModelService, - selfcareId: authData.selfcareId, - storeFile, - logger, - }); + producer, + updatedAgreementSeed + ); + return toCreateEventAgreementActivated( - { ...updatedAgreement, contract }, + { + ...updatedAgreement, + contract: apiAgreementDocumentToAgreementDocument(agreementContract), + }, agreement.metadata.version, correlationId ); @@ -331,35 +173,3 @@ export const archiveRelatedToAgreements = async ( createAgreementArchivedByUpgradeEvent(agreementData, userId, correlationId) ); }; - -const createContract = async ( - agreement: Agreement, - updateSeed: UpdateAgreementSeed, - eservice: EService, - consumer: Tenant, - attributeQuery: AttributeQuery, - tenantQuery: TenantQuery, - selfcareId: SelfcareId, - fileManager: FileManager, - pdfGenerator: PDFGenerator, - config: AgreementProcessConfig, - logger: Logger -): Promise => { - const producer = await tenantQuery.getTenantById(agreement.producerId); - assertTenantExist(agreement.producerId, producer); - - return await contractBuilder( - attributeQuery, - pdfGenerator, - fileManager, - config, - logger - ).createContract( - selfcareId, - agreement, - eservice, - consumer, - producer, - updateSeed - ); -}; diff --git a/packages/agreement-process/src/services/agreementService.ts b/packages/agreement-process/src/services/agreementService.ts index fd736ced73..950c3f52f3 100644 --- a/packages/agreement-process/src/services/agreementService.ts +++ b/packages/agreement-process/src/services/agreementService.ts @@ -19,7 +19,6 @@ import { EService, EServiceId, ListResult, - SelfcareId, Tenant, TenantId, UserId, @@ -192,10 +191,7 @@ function retrieveAgreementDocument( // eslint-disable-next-line @typescript-eslint/explicit-function-return-type, max-params export function agreementServiceBuilder( dbInstance: DB, - agreementQuery: AgreementQuery, - tenantQuery: TenantQuery, - eserviceQuery: EserviceQuery, - attributeQuery: AttributeQuery, + readModelService: ReadModelService, fileManager: FileManager, pdfGenerator: PDFGenerator ) { @@ -358,21 +354,15 @@ export function agreementServiceBuilder( payload: ApiAgreementSubmissionPayload, { authData, correlationId, logger }: WithLogger ): Promise { - ctx.logger.info(`Submitting agreement ${agreementId}`); - const [agreement, updatesEvents] = await submitAgreementLogic( - agreementId, - payload, - contractBuilder( - attributeQuery, - pdfGenerator, - fileManager, - config, - ctx.logger - ), - eserviceQuery, - agreementQuery, - tenantQuery, - ctx + logger.info(`Submitting agreement ${agreementId}`); + + const agreement = await retrieveAgreement(agreementId, readModelService); + + assertRequesterIsConsumer(agreement.data, authData); + assertSubmittableState(agreement.data.state, agreement.data.id); + await verifySubmissionConflictingAgreements( + agreement.data, + readModelService ); if (agreement.data.state === agreementState.draft) { @@ -394,6 +384,11 @@ export function agreementServiceBuilder( readModelService ); + const producer = await retrieveTenant( + agreement.data.producerId, + readModelService + ); + const nextStateByAttributes = nextState( agreement.data, descriptor, @@ -433,21 +428,27 @@ export function agreementServiceBuilder( ...updateSeed, }; + const contract = await contractBuilder( + readModelService, + pdfGenerator, + fileManager, + config, + logger + ).createContract( + authData.selfcareId, + agreement.data, + eservice, + consumer, + producer, + updateSeed + ); + const submittedAgreement = updatedAgreement.state === agreementState.active && agreements.length === 0 ? { ...updatedAgreement, - contract: await createContract({ - agreement: updatedAgreement, - eservice, - consumer, - updateSeed, - readModelService, - storeFile: fileManager.storeBytes, - selfcareId: authData.selfcareId, - logger, - }), + contract: apiAgreementDocumentToAgreementDocument(contract), } : updatedAgreement; @@ -864,20 +865,26 @@ export function agreementServiceBuilder( { authData, correlationId, logger }: WithLogger ): Promise { logger.info(`Activating agreement ${agreementId}`); - const [agreement, updatesEvents] = await activateAgreementLogic( - agreementId, - agreementQuery, - eserviceQuery, - tenantQuery, - attributeQuery, - authData, - correlationId, - fileManager, + + const contractBuilderInstance = contractBuilder( + readModelService, pdfGenerator, + fileManager, config, logger ); + const agreement = await retrieveAgreement(agreementId, readModelService); + + assertRequesterIsConsumerOrProducer(agreement.data, authData); + verifyConsumerDoesNotActivatePending(agreement.data, authData); + assertActivableState(agreement.data); + + const eservice = await retrieveEService( + agreement.data.eserviceId, + readModelService + ); + const descriptor = validateActivationOnDescriptor( eservice, agreement.data.descriptorId @@ -888,6 +895,11 @@ export function agreementServiceBuilder( readModelService ); + const producer = await retrieveTenant( + agreement.data.producerId, + readModelService + ); + const nextAttributesState = nextState( agreement.data, descriptor, @@ -935,19 +947,18 @@ export function agreementServiceBuilder( ...updatedAgreementSeed, }; - const activationEvent = await createActivationEvent({ + const activationEvent = await createActivationEvent( firstActivation, agreement, updatedAgreement, updatedAgreementSeed, eservice, consumer, + producer, authData, correlationId, - readModelService, - storeFile: fileManager.storeBytes, - logger, - }); + contractBuilderInstance + ); const archiveEvents = await archiveRelatedToAgreements( agreement.data, @@ -1033,37 +1044,6 @@ export async function createAndCopyDocumentsForClonedAgreement( })); } -export async function createContract({ - agreement, - updateSeed, - eservice, - consumer, - readModelService, - selfcareId, - storeFile, - logger, -}: { - agreement: Agreement; - updateSeed: UpdateAgreementSeed; - eservice: EService; - consumer: Tenant; - readModelService: ReadModelService; - selfcareId: SelfcareId; - storeFile: FileManager["storeBytes"]; - logger: Logger; -}): Promise { - const producer = await retrieveTenant(agreement.producerId, readModelService); - - const contract = await contractBuilder( - selfcareId, - readModelService, - storeFile, - logger - ).createContract(agreement, eservice, consumer, producer, updateSeed); - - return apiAgreementDocumentToAgreementDocument(contract); -} - export function createAgreementArchivedByUpgradeEvent( agreement: WithMetadata, userId: UserId, diff --git a/packages/agreement-process/src/services/agreementSubmissionProcessor.ts b/packages/agreement-process/src/services/agreementSubmissionProcessor.ts index 5c967b69d8..adb2e8041d 100644 --- a/packages/agreement-process/src/services/agreementSubmissionProcessor.ts +++ b/packages/agreement-process/src/services/agreementSubmissionProcessor.ts @@ -8,7 +8,6 @@ import { AgreementState, Descriptor, EService, - SelfcareId, Tenant, UserId, agreementState, @@ -36,203 +35,9 @@ export type AgremeentSubmissionResults = { version: number; }; -export async function submitAgreementLogic( - agreementId: AgreementId, - payload: ApiAgreementSubmissionPayload, - contractBuilder: ContractBuilder, - eserviceQuery: EserviceQuery, - agreementQuery: AgreementQuery, - tenantQuery: TenantQuery, - ctx: WithLogger -): Promise<[Agreement, Array>]> { - const agreement = await agreementQuery.getAgreementById(agreementId); - - if (!agreement) { - throw agreementNotFound(agreementId); - } - - assertRequesterIsConsumer(agreement.data, ctx.authData); - assertSubmittableState(agreement.data.state, agreement.data.id); - await verifySubmissionConflictingAgreements(agreement.data, agreementQuery); - - const eservice = await eserviceQuery.getEServiceById( - agreement.data.eserviceId - ); - if (!eservice) { - throw eServiceNotFound(agreement.data.eserviceId); - } - - const descriptor = await validateSubmitOnDescriptor( - eservice, - agreement.data.descriptorId - ); - - const consumer = await tenantQuery.getTenantById(agreement.data.consumerId); - - if (!consumer) { - throw tenantNotFound(agreement.data.consumerId); - } - - return await submitAgreement( - agreement, - eservice, - descriptor, - consumer, - payload, - agreementQuery, - tenantQuery, - contractBuilder, - ctx - ); -} - -const submitAgreement = async ( - agreementData: WithMetadata, - eservice: EService, - descriptor: Descriptor, - consumer: Tenant, - payload: ApiAgreementSubmissionPayload, - agreementQuery: AgreementQuery, - tenantQuery: TenantQuery, - contractBuilder: ContractBuilder, - { authData, correlationId }: WithLogger -): Promise<[Agreement, Array>]> => { - const agreement = agreementData.data; - const nextStateByAttributes = nextState(agreement, descriptor, consumer); - - const newState = agreementStateByFlags( - nextStateByAttributes, - undefined, - undefined - ); - - if (agreement.state === agreementState.draft) { - await validateConsumerEmail(agreement, tenantQuery); - } - const stamp = createStamp(authData); - const stamps = calculateStamps(agreement, newState, stamp); - const updateSeed = getUpdateSeed( - descriptor, - consumer, - eservice, - agreement, - payload, - stamps, - newState, - false - ); - - const agreements = ( - await agreementQuery.getAllAgreements({ - producerId: agreement.producerId, - consumerId: agreement.consumerId, - eserviceId: agreement.eserviceId, - agreementStates: [agreementState.active, agreementState.suspended], - }) - ).filter((a: WithMetadata) => a.data.id !== agreement.id); - - const newAgreement = { - ...agreement, - ...updateSeed, - }; - - const submittedAgreement = - newAgreement.state === agreementState.active && agreements.length === 0 - ? { - ...newAgreement, - contract: await createContract( - authData.selfcareId, - newAgreement, - eservice, - consumer, - updateSeed, - tenantQuery, - contractBuilder - ), - } - : newAgreement; - - const submittedAgreementEvent = toCreateEventAgreementSubmitted( - submittedAgreement, - agreementData.metadata.version, - correlationId - ); - - const archivedAgreementsUpdates: Array> = - isActiveOrSuspended(newState) - ? await Promise.all( - agreements.map( - async ( - agreement: WithMetadata - ): Promise> => { - const updateSeed: UpdateAgreementSeed = { - state: agreementState.archived, - stamps: { - ...agreement.data.stamps, - archiving: createStamp(authData), - }, - }; - - return toCreateEventAgreementArchivedByUpgrade( - { - ...agreement.data, - ...updateSeed, - }, - agreement.metadata.version, - correlationId - ); - } - ) - ) - : []; - - validateActiveOrPendingAgreement(agreement.id, newState); - - return [ - submittedAgreement, - [submittedAgreementEvent, ...archivedAgreementsUpdates], - ]; -}; - -const createContract = async ( - selfcareId: SelfcareId, - agreement: Agreement, - eservice: EService, - consumer: Tenant, - seed: UpdateAgreementSeed, - tenantQuery: TenantQuery, - contractBuilder: ContractBuilder -): Promise => { - const producer = await tenantQuery.getTenantById(agreement.producerId); - - if (!producer) { - throw tenantNotFound(agreement.producerId); - } - - if (agreement.contract) { - throw contractAlreadyExists(agreement.id); - } - - const newContract = await contractBuilder.createContract( - selfcareId, - agreement, - eservice, - consumer, - producer, - seed - ); - const agreementdocumentSeed: AgreementDocument = { - ...newContract, - id: unsafeBrandId(newContract.id), - createdAt: new Date(), - }; - - return agreementdocumentSeed; -}; - -const validateConsumerEmail = async ( +export const validateConsumerEmail = async ( agreement: Agreement, - tenantQuery: TenantQuery + readModelService: ReadModelService ): Promise => { const consumer = await retrieveTenant(agreement.consumerId, readModelService);