Skip to content

Commit

Permalink
feat: [Contracts] Endpoint for Reject Document Contract by volunteer
Browse files Browse the repository at this point in the history
  • Loading branch information
radulescuandrew committed Sep 11, 2024
1 parent 566835d commit 7077b55
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 3 deletions.
22 changes: 20 additions & 2 deletions backend/src/api/_mobile/documents/documents-contract.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { ExtractUser } from 'src/common/decorators/extract-user.decorator';
import { IRegularUserModel } from 'src/modules/user/models/regular-user.model';
import { UuidValidationPipe } from 'src/infrastructure/pipes/uuid.pipe';
import { SignDocumentContractByVolunteerUsecase } from 'src/usecases/documents/new_contracts/sign-document-contract-by-volunteer.usecase';
import { SignDocumentContractDto } from './dto/SignDocumentContract.dto';
import { RejectDocumentContractByVolunteerUsecase } from 'src/usecases/documents/new_contracts/reject-document-contact-by-volunteer.usecase';
import { SignDocumentContractByVolunteerDto } from './dto/SignDocumentContractByVolunteer.dto';
import { RejectDocumentContractByVolunteerDto } from './dto/RejectDocumentContractByVolunteer.dto';

// @UseGuards(MobileJwtAuthGuard, ContractVolunteerGuard)
@UseGuards(MobileJwtAuthGuard)
Expand All @@ -15,12 +17,13 @@ import { SignDocumentContractDto } from './dto/SignDocumentContract.dto';
export class MobileDocumentsContractController {
constructor(
private readonly signDocumentContractByVolunteerUsecase: SignDocumentContractByVolunteerUsecase,
private readonly rejectDocumentContractByVolunteerUsecase: RejectDocumentContractByVolunteerUsecase,
) {}

@ApiParam({ name: 'contractId', type: 'string' })
@Patch(':contractId/sign')
async sign(
@Body() body: SignDocumentContractDto,
@Body() body: SignDocumentContractByVolunteerDto,
@ExtractUser() { id }: IRegularUserModel,
@Param('contractId', UuidValidationPipe) contractId: string,
): Promise<void> {
Expand All @@ -34,4 +37,19 @@ export class MobileDocumentsContractController {

return contract;
}

@ApiParam({ name: 'contractId', type: 'string' })
@Patch(':contractId/reject')
async reject(
@Body() body: RejectDocumentContractByVolunteerDto,
@ExtractUser() { id }: IRegularUserModel,
@Param('contractId', UuidValidationPipe) contractId: string,
): Promise<void> {
await this.rejectDocumentContractByVolunteerUsecase.execute({
contractId,
userId: id,
organizationId: body.organizationId,
rejectionReason: body.reason,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IsString, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class RejectDocumentContractByVolunteerDto {
@ApiProperty({ description: 'Organization ID' })
@IsString()
organizationId: string;

@ApiProperty({ description: 'Reason for rejecting the contract' })
@IsString()
@IsOptional()
reason?: string;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IsString, IsOptional, IsUUID } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class SignDocumentContractDto {
export class SignDocumentContractByVolunteerDto {
@ApiProperty({ description: 'The ID of the organization' })
@IsUUID()
organizationId: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { Injectable } from '@nestjs/common';
import { IUseCaseService } from 'src/common/interfaces/use-case-service.interface';
import { ExceptionsService } from 'src/infrastructure/exceptions/exceptions.service';
import { DocumentContractStatus } from 'src/modules/documents/enums/contract-status.enum';
import { ContractExceptionMessages } from 'src/modules/documents/exceptions/contract.exceptions';
import { DocumentContractFacade } from 'src/modules/documents/services/document-contract.facade';
import { VolunteerFacade } from 'src/modules/volunteer/services/volunteer.facade';

// ┌─────────────────────────────────────────────────────────────────────────┐
// │ Business Rules for RejectDocumentContractByVolunteerUsecase: │
// │ │
// │ 1. Volunteer Authentication: │
// │ - The volunteer must exist and be associated with the given │
// │ organization. │
// │ - If the volunteer is not found or not part of the organization, │
// │ throw a not found exception. │
// │ │
// │ 2. Contract Validation: │
// │ - The contract must exist and be in the PENDING_VOLUNTEER_SIGNATURE │
// │ status. │
// │ - The contract must be assigned to the current volunteer. │
// │ - The contract must belong to the user's organization. │
// │ - If any of these conditions are not met, throw a not found │
// │ exception. │
// │ │
// │ 4. Contract Update: │
// │ - Update the contract status to REJECTED_BY_VOLUNTEER. │
// │ │
// │ 5. Error Handling: │
// │ - Any failures in the process should throw appropriate exceptions. │
// │ - Use the ExceptionsService to handle and throw standardized │
// │ exceptions. │
// │ │
// │ 6. Transactional Integrity: // TODO: Implement this │
// │ - Ensure that all database operations are performed atomically. │
// │ - If any part of the process fails, all changes should be rolled │
// │ back. │
// │ │
// │ 7. Audit Trail: // TODO: Implement this │
// │ - Track the rejection event in an Actions Archive for auditing │
// │ purposes. │
// │ - Store the rejection reason in the Actions Archive. │
// │ │
// │ 8. Authorization: │
// │ - Ensure that only the assigned volunteer can reject their own │
// │ contract. │
// │ │
// │ 9. Notification: │
// │ - Notify relevant parties (e.g., NGO administrators) about the │
// │ contract rejection. │
// │ │
// │ 10. Data Validation: │
// │ - Validate the format and content of the rejection reason before │
// │ processing. │
// └─────────────────────────────────────────────────────────────────────────┘

@Injectable()
export class RejectDocumentContractByVolunteerUsecase
implements IUseCaseService<void>
{
constructor(
private readonly documentContractFacade: DocumentContractFacade,
private readonly volunteerFacade: VolunteerFacade,
private readonly exceptionService: ExceptionsService,
) {}

public async execute({
contractId,
userId,
organizationId,
rejectionReason,
}: {
contractId: string;
userId: string;
organizationId: string;
rejectionReason: string;
}): Promise<void> {
/* ┌─────────────────────────────────────────────────────────────────────┐
* │ Verify volunteer existence: │
* │ │
* │ 1. Volunteer must exist and be part of the organization │
* │ │
* │ This ensures that the volunteer is valid and authorized to reject. │
* └─────────────────────────────────────────────────────────────────────┘
*/
const volunteer = await this.volunteerFacade.find({
userId: userId,
organizationId,
});
if (!volunteer) {
this.exceptionService.notFoundException({
message: 'Volunteer is not part of the organization',
code_error: 'VOLUNTEER_NOT_PART_OF_ORGANIZATION',
});
}

/* ┌─────────────────────────────────────────────────────────────────────┐
* │ Verify contract existence and eligibility: │
* │ │
* │ 1. Status must be PENDING_VOLUNTEER_SIGNATURE │
* │ 2. Contract is assigned to the current volunteer │
* │ 3. Contract belongs to the user's organization │
* │ │
* │ This ensures that the contract is valid and authorized to be │
* │ rejected. │
* └─────────────────────────────────────────────────────────────────────┘
*/
const contractExists = await this.documentContractFacade.exists({
id: contractId,
volunteerId: volunteer.id,
organizationId,
status: DocumentContractStatus.PENDING_VOLUNTEER_SIGNATURE,
});

if (!contractExists) {
this.exceptionService.notFoundException(
ContractExceptionMessages.CONTRACT_002,
);
}

/* ┌─────────────────────────────────────────────────────────────────────┐
* │ Update contract status: │
* │ │
* │ 1. Update the contract status to REJECTED_VOLUNTEER. │
* └─────────────────────────────────────────────────────────────────────┘
*/
await this.documentContractFacade.update(contractId, {
status: DocumentContractStatus.REJECTED_VOLUNTEER,
});

/* ┌─────────────────────────────────────────────────────────────────────┐
* │ Audit trail logging: │
* │ │
* │ 1. Log the contract rejection event in the Actions Archive together │
* │ with the rejection reason. │
* └─────────────────────────────────────────────────────────────────────┘
*/
// TODO: Implement audit trail logging
console.log('rejectionReason', rejectionReason);

// TODO: Implement notification to relevant parties

return;
}
}
2 changes: 2 additions & 0 deletions backend/src/usecases/use-case.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ import { CreateDocumentContractUsecase } from './documents/new_contracts/create-
import { GetManyDocumentContractsUsecase } from './documents/new_contracts/get-many-document-contracts.usecase';
import { GetManyDocumentTemplatesUsecase } from './documents/new_contracts/get-many-document-templates.usecase';
import { SignDocumentContractByVolunteerUsecase } from './documents/new_contracts/sign-document-contract-by-volunteer.usecase';
import { RejectDocumentContractByVolunteerUsecase } from './documents/new_contracts/reject-document-contact-by-volunteer.usecase';

const providers = [
// Organization
Expand Down Expand Up @@ -294,6 +295,7 @@ const providers = [
CreateDocumentContractUsecase,
GetManyDocumentContractsUsecase,
SignDocumentContractByVolunteerUsecase,
RejectDocumentContractByVolunteerUsecase,
// Notifications
UpdateSettingsUsecase,
// Testing PDFs
Expand Down

0 comments on commit 7077b55

Please sign in to comment.