diff --git a/backend/src/api/documents/document-contract.controller.ts b/backend/src/api/documents/document-contract.controller.ts index 58e4af91..b5ef1aef 100644 --- a/backend/src/api/documents/document-contract.controller.ts +++ b/backend/src/api/documents/document-contract.controller.ts @@ -24,7 +24,9 @@ import { import { GetManyDocumentContractsDto } from './dto/get-many-document-contracts.dto'; import { UuidValidationPipe } from 'src/infrastructure/pipes/uuid.pipe'; import { ApproveDocumentContractByNgoUsecase } from 'src/usecases/documents/new_contracts/approve-document-contract-by-ngo.usecase'; -import { SignDocumentContractByNGO } from 'src/usecases/documents/new_contracts/sign-document-contract-by-ngo.usecase'; +import { SignDocumentContractByNgoUsecase } from 'src/usecases/documents/new_contracts/sign-document-contract-by-ngo.usecase'; +import { RejectDocumentContractByNgoUsecase } from 'src/usecases/documents/new_contracts/reject-document-contract-by-ngo.usecase'; +import { RejectDocumentContractByNgoDTO } from './dto/reject-document-contract.dto'; @ApiBearerAuth() @UseGuards(WebJwtAuthGuard) @@ -34,7 +36,8 @@ export class DocumentContractController { private readonly createDocumentContractUsecase: CreateDocumentContractUsecase, private readonly getManyDocumentContractsUsecase: GetManyDocumentContractsUsecase, private readonly approveDocumentContractByNgoUsecase: ApproveDocumentContractByNgoUsecase, - private readonly signDocumentContractByNGO: SignDocumentContractByNGO, + private readonly rejectDocumentContractByNgoUsecase: RejectDocumentContractByNgoUsecase, + private readonly signDocumentContractByNGO: SignDocumentContractByNgoUsecase, ) {} @Post() @@ -85,4 +88,17 @@ export class DocumentContractController { ): Promise { await this.signDocumentContractByNGO.execute(id, organizationId); } + + @Patch(':id/reject') + async rejectDocumentContract( + @Param('id', UuidValidationPipe) id: string, + @ExtractUser() { organizationId }: IAdminUserModel, + @Body() { rejectionReason }: RejectDocumentContractByNgoDTO, + ): Promise { + await this.rejectDocumentContractByNgoUsecase.execute({ + documentContractId: id, + organizationId, + rejectionReason: rejectionReason, + }); + } } diff --git a/backend/src/api/documents/document-template.controller.ts b/backend/src/api/documents/document-template.controller.ts index 41a99417..e07a2060 100644 --- a/backend/src/api/documents/document-template.controller.ts +++ b/backend/src/api/documents/document-template.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Delete, Get, Param, Post, @@ -23,6 +24,7 @@ import { ApiPaginatedResponse, PaginatedPresenter, } from 'src/infrastructure/presenters/generic-paginated.presenter'; +import { DeleteDocumentTemplateUsecase } from 'src/usecases/documents/new_contracts/delete-document-template.usecase'; @ApiBearerAuth() @UseGuards(WebJwtAuthGuard) @@ -32,6 +34,7 @@ export class DocumentTemplateController { private readonly createDocumentTemplateUsecase: CreateDocumentTemplateUsecase, private readonly getOneDocumentTemplateUsecase: GetOneDocumentTemplateUseCase, private readonly getManyDocumentTemplatesUsecase: GetManyDocumentTemplatesUsecase, + private readonly deleteDocumentTemplateUsecase: DeleteDocumentTemplateUsecase, ) {} @ApiBody({ type: CreateDocumentTemplateDto }) @@ -63,6 +66,15 @@ export class DocumentTemplateController { return new DocumentTemplatePresenter(documentTemplate); } + @ApiParam({ name: 'id', type: 'string' }) + @Delete(':id') + async delete( + @Param('id', UuidValidationPipe) id: string, + @ExtractUser() { organizationId }: IAdminUserModel, + ): Promise { + return this.deleteDocumentTemplateUsecase.execute(id, organizationId); + } + @Get() @ApiPaginatedResponse(DocumentTemplateListViewItemPresenter) async getMany( diff --git a/backend/src/api/documents/dto/reject-document-contract.dto.ts b/backend/src/api/documents/dto/reject-document-contract.dto.ts new file mode 100644 index 00000000..46455c8b --- /dev/null +++ b/backend/src/api/documents/dto/reject-document-contract.dto.ts @@ -0,0 +1,7 @@ +import { IsOptional, IsString } from 'class-validator'; + +export class RejectDocumentContractByNgoDTO { + @IsString() + @IsOptional() + rejectionReason: string; +} diff --git a/backend/src/modules/documents/models/document-contract.model.ts b/backend/src/modules/documents/models/document-contract.model.ts index 97254f89..64e810b8 100644 --- a/backend/src/modules/documents/models/document-contract.model.ts +++ b/backend/src/modules/documents/models/document-contract.model.ts @@ -78,7 +78,7 @@ export type UpdateDocumentContractOptions = { export type FindOneDocumentContractOptions = Partial< Pick< IDocumentContractModel, - 'id' | 'volunteerId' | 'organizationId' | 'status' + 'id' | 'volunteerId' | 'organizationId' | 'status' | 'documentTemplateId' > >; diff --git a/backend/src/modules/documents/models/document-template.model.ts b/backend/src/modules/documents/models/document-template.model.ts index 6d305eae..c0cd153d 100644 --- a/backend/src/modules/documents/models/document-template.model.ts +++ b/backend/src/modules/documents/models/document-template.model.ts @@ -33,6 +33,11 @@ export type FindOneDocumentTemplateOptions = Partial< Pick >; +export type DeleteOneDocumentTemplateOptions = { + id: string; + organizationId: string; +}; + export class DocumentTemplateTransformer { static fromEntity(entity: DocumentTemplateEntity): IDocumentTemplateModel { if (!entity) { diff --git a/backend/src/modules/documents/repositories/document-template.repository.ts b/backend/src/modules/documents/repositories/document-template.repository.ts index 348ab9b3..7d32eb6d 100644 --- a/backend/src/modules/documents/repositories/document-template.repository.ts +++ b/backend/src/modules/documents/repositories/document-template.repository.ts @@ -5,6 +5,7 @@ import { DocumentTemplateEntity } from '../entities/document-template.entity'; import { IDocumentTemplateRepository } from '../interfaces/document-template-repository.interface'; import { CreateDocumentTemplateOptions, + DeleteOneDocumentTemplateOptions, DocumentTemplateTransformer, FindOneDocumentTemplateOptions, IDocumentTemplateModel, @@ -43,4 +44,15 @@ export class DocumentTemplateRepositoryService return DocumentTemplateTransformer.fromEntity(documentTemplate); } + + async delete(options: DeleteOneDocumentTemplateOptions): Promise { + const template = await this.documentTemplateRepository.findOneBy(options); + + if (template) { + await this.documentTemplateRepository.remove(template); + return options.id; + } + + return null; + } } diff --git a/backend/src/modules/documents/services/document-template.facade.ts b/backend/src/modules/documents/services/document-template.facade.ts index 839fc883..d85d9477 100644 --- a/backend/src/modules/documents/services/document-template.facade.ts +++ b/backend/src/modules/documents/services/document-template.facade.ts @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common'; import { DocumentTemplateRepositoryService } from '../repositories/document-template.repository'; import { CreateDocumentTemplateOptions, + DeleteOneDocumentTemplateOptions, FindOneDocumentTemplateOptions, IDocumentTemplateModel, } from '../models/document-template.model'; @@ -36,4 +37,8 @@ export class DocumentTemplateFacade { ): Promise> { return this.documentTemplateListViewRepository.findMany(findOptions); } + + async delete(options: DeleteOneDocumentTemplateOptions): Promise { + return this.documentTemplateRepository.delete(options); + } } diff --git a/backend/src/usecases/documents/new_contracts/delete-document-template.usecase.ts b/backend/src/usecases/documents/new_contracts/delete-document-template.usecase.ts new file mode 100644 index 00000000..15bc1b60 --- /dev/null +++ b/backend/src/usecases/documents/new_contracts/delete-document-template.usecase.ts @@ -0,0 +1,63 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { JSONStringifyError } from 'src/common/helpers/utils'; +import { IUseCaseService } from 'src/common/interfaces/use-case-service.interface'; +import { ExceptionsService } from 'src/infrastructure/exceptions/exceptions.service'; +import { DocumentTemplateExceptionMessages } from 'src/modules/documents/exceptions/documente-template.exceptions'; +import { DocumentContractFacade } from 'src/modules/documents/services/document-contract.facade'; +import { DocumentTemplateFacade } from 'src/modules/documents/services/document-template.facade'; + +@Injectable() +export class DeleteDocumentTemplateUsecase implements IUseCaseService { + private readonly logger = new Logger(DeleteDocumentTemplateUsecase.name); + constructor( + private readonly documentTemplateFacade: DocumentTemplateFacade, + private readonly documentContractFacade: DocumentContractFacade, + private readonly exceptionService: ExceptionsService, + ) {} + + public async execute(id: string, organizationId: string): Promise { + try { + // 1. Templates can be deleted if are not linked with a contract + const isUsed = await this.documentContractFacade.exists({ + documentTemplateId: id, + organizationId: organizationId, + }); + + if (isUsed) { + this.exceptionService.badRequestException({ + message: 'Used templates cannot be deleted', + code_error: 'DELETE_DOCUMENT_TEMPLATE_USED', + }); + } + + // 2. Try to delete it + const deleted = await this.documentTemplateFacade.delete({ + id, + organizationId, + }); + + if (!deleted) { + this.exceptionService.badRequestException({ + message: + 'The template does not exist or is not part of your organization', + code_error: 'DELETE_TEMPLATE_ERR', + }); + } + + return deleted; + } catch (error) { + if (error.code_error) { + // Rethrow errors that we've thrown above, and catch the others + throw error; + } + + this.logger.error({ + ...DocumentTemplateExceptionMessages.TEMPLATE_002, + error: JSONStringifyError(error), + }); + this.exceptionService.internalServerErrorException({ + message: 'Could not delete the template', + }); + } + } +} diff --git a/backend/src/usecases/documents/new_contracts/reject-document-contract-by-ngo.usecase.ts b/backend/src/usecases/documents/new_contracts/reject-document-contract-by-ngo.usecase.ts index 15774850..ba6baa3b 100644 --- a/backend/src/usecases/documents/new_contracts/reject-document-contract-by-ngo.usecase.ts +++ b/backend/src/usecases/documents/new_contracts/reject-document-contract-by-ngo.usecase.ts @@ -6,7 +6,9 @@ import { ContractExceptionMessages } from 'src/modules/documents/exceptions/cont import { DocumentContractFacade } from 'src/modules/documents/services/document-contract.facade'; @Injectable() -export class RejectDocumentContractByNGO implements IUseCaseService { +export class RejectDocumentContractByNgoUsecase + implements IUseCaseService +{ constructor( private readonly documentContractFacade: DocumentContractFacade, private readonly exceptionService: ExceptionsService, diff --git a/backend/src/usecases/documents/new_contracts/sign-document-contract-by-ngo.usecase.ts b/backend/src/usecases/documents/new_contracts/sign-document-contract-by-ngo.usecase.ts index 92559cd6..e5bb3d0a 100644 --- a/backend/src/usecases/documents/new_contracts/sign-document-contract-by-ngo.usecase.ts +++ b/backend/src/usecases/documents/new_contracts/sign-document-contract-by-ngo.usecase.ts @@ -6,7 +6,7 @@ import { ContractExceptionMessages } from 'src/modules/documents/exceptions/cont import { DocumentContractFacade } from 'src/modules/documents/services/document-contract.facade'; @Injectable() -export class SignDocumentContractByNGO implements IUseCaseService { +export class SignDocumentContractByNgoUsecase implements IUseCaseService { constructor( private readonly documentContractFacade: DocumentContractFacade, private readonly exceptionService: ExceptionsService, diff --git a/backend/src/usecases/use-case.module.ts b/backend/src/usecases/use-case.module.ts index e058ecac..5ca4d3c0 100644 --- a/backend/src/usecases/use-case.module.ts +++ b/backend/src/usecases/use-case.module.ts @@ -148,7 +148,9 @@ import { GeneratePDFsUseCase } from './documents/new_contracts/generate-pdfs.use import { GetManyDocumentContractsByVolunteerUsecase } from './documents/new_contracts/get-many-document-contracts-by-volunteer.usecase'; import { GetOneDocumentContractForVolunteerUsecase } from './documents/new_contracts/get-one-document-contract-for-volunteer.usecase'; import { ApproveDocumentContractByNgoUsecase } from './documents/new_contracts/approve-document-contract-by-ngo.usecase'; -import { SignDocumentContractByNGO } from './documents/new_contracts/sign-document-contract-by-ngo.usecase'; +import { SignDocumentContractByNgoUsecase } from './documents/new_contracts/sign-document-contract-by-ngo.usecase'; +import { DeleteDocumentTemplateUsecase } from './documents/new_contracts/delete-document-template.usecase'; +import { RejectDocumentContractByNgoUsecase } from './documents/new_contracts/reject-document-contract-by-ngo.usecase'; const providers = [ // Organization @@ -281,6 +283,7 @@ const providers = [ CreateDocumentTemplateUsecase, GetOneDocumentTemplateUseCase, GetManyDocumentTemplatesUsecase, + DeleteDocumentTemplateUsecase, // Contracts CreateContractUsecase, GetManyContractsUsecase, @@ -303,7 +306,8 @@ const providers = [ GetManyDocumentContractsByVolunteerUsecase, GetOneDocumentContractForVolunteerUsecase, ApproveDocumentContractByNgoUsecase, - SignDocumentContractByNGO, + SignDocumentContractByNgoUsecase, + RejectDocumentContractByNgoUsecase, // Notifications UpdateSettingsUsecase, // Testing PDFs