Skip to content

Commit

Permalink
Merge pull request #397 from code4romania/feature/actions-archive-con…
Browse files Browse the repository at this point in the history
…tracts

feat(actions-archive): track validate contract and sign by NGO events in actions archive
  • Loading branch information
radulescuandrew authored Sep 25, 2024
2 parents 7f67828 + eada2c4 commit dadd24e
Show file tree
Hide file tree
Showing 20 changed files with 2,644 additions and 1,585 deletions.
8 changes: 4 additions & 4 deletions backend/src/api/documents/document-contract.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
} from 'src/infrastructure/presenters/generic-paginated.presenter';
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 { ValidateDocumentContractByNgoUsecase } from 'src/usecases/documents/new_contracts/validate-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';
Expand All @@ -40,7 +40,7 @@ export class DocumentContractController {
constructor(
private readonly createDocumentContractUsecase: CreateDocumentContractUsecase,
private readonly getManyDocumentContractsUsecase: GetManyDocumentContractsUsecase,
private readonly approveDocumentContractByNgoUsecase: ApproveDocumentContractByNgoUsecase,
private readonly validateDocumentContractByNgoUsecase: ValidateDocumentContractByNgoUsecase,
private readonly rejectDocumentContractByNgoUsecase: RejectDocumentContractByNgoUsecase,
private readonly signDocumentContractByNGO: SignDocumentContractByNgoUsecase,
private readonly getOneDocumentContractForNgoUsecase: GetOneDocumentContractForNgoUsecase,
Expand Down Expand Up @@ -114,9 +114,9 @@ export class DocumentContractController {
@Patch(':id/approve')
async approveDocumentContract(
@Param('id', UuidValidationPipe) id: string,
@ExtractUser() { organizationId }: IAdminUserModel,
@ExtractUser() admin: IAdminUserModel,
): Promise<void> {
await this.approveDocumentContractByNgoUsecase.execute(id, organizationId);
await this.validateDocumentContractByNgoUsecase.execute(id, admin);
}

@Patch(':id/sign')
Expand Down
14 changes: 7 additions & 7 deletions backend/src/common/constants/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,24 @@ export const NOTIFICATIONS = {
REJECT_CONTRACT: {
PUSH: {
title: 'VIC',
body: (organizationName: string, reason: string): string =>
`Contractul tău cu ${organizationName} a fost respins. Motiv: ${reason}`,
body: (organizationName: string): string =>
`Contractul tău cu ${organizationName} a fost respins. Vezi motiv`,
},
EMAIL: {
subject: (organizationName: string, reason: string): string =>
`Contractul tău cu ${organizationName} a fost respins. Motiv: ${reason}`,
subject: (organizationName: string): string =>
`Contractul tău cu ${organizationName} a fost respins. Vezi motiv`,
body: '',
},
},
APPROVE_CONTRACT: {
SIGN_CONTRACT_BY_NGO: {
PUSH: {
title: 'VIC',
body: (organizationName: string): string =>
`Contractul tău cu ${organizationName} a fost aprobat. Descarcă documentul direct din aplicație`,
`Contractul tău cu ${organizationName} a fost aprobat și semnat`,
},
EMAIL: {
subject: (organizationName: string): string =>
`Contractul tău cu ${organizationName} a fost respins. Descarcă documentul direct din aplicație`,
`Contractul tău cu ${organizationName} a fost aprobat și semnat`,
body: '',
},
},
Expand Down
1 change: 0 additions & 1 deletion backend/src/infrastructure/config/email-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export class EmailConfigService {
defaults: {
from: '"No Reply" <no-reply@localhost>',
},
preview: true,
template: {
dir: __dirname + '/../../modules/mail/templates',
adapter: new HandlebarsAdapter({ asset_url: this.createAssetUrl }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,9 @@ export const EVENTS = {
GENERATE_CONTRACT: 'contract.generate',
APPROVE_CONTRACT: 'contract.approve',
REJECT_CONATRCT: 'contract.reject',

// new document events
SIGN_CONTRACT_BY_NGO: 'contract.sign.ngo',
REJECT_CONTRACT_BY_NGO: 'contract.reject.ngo',
},
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import BaseEvent from '../base-event.class';

export default class ApproveContractEvent extends BaseEvent {
export default class SignContractEvent extends BaseEvent {
constructor(
_organizationId: string,
_userId: string,
Expand Down
38 changes: 16 additions & 22 deletions backend/src/modules/notifications/listeners/documents.listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { NOTIFICATIONS } from 'src/common/constants/notifications';
import { PushNotificationsFacade } from '../notifications.facade';
import { MailService } from 'src/modules/mail/services/mail.service';
import GenerateContractEvent from '../events/documents/generate-contract.event';
import ApproveContractEvent from '../events/documents/approve-contract.event';
import RejectContractEvent from '../events/documents/reject-contract.event';
import SignContractEvent from '../events/documents/sign-contract.event';

@Injectable()
export class DocumentsListener {
Expand Down Expand Up @@ -35,7 +35,6 @@ export class DocumentsListener {
organizationId,
},
};

this.pushNotificationsFacade.send({
userIds: [userId],
title: NOTIFICATIONS.NEW_CONTRACT.PUSH.title,
Expand All @@ -57,8 +56,8 @@ export class DocumentsListener {
}
}

@OnEvent(EVENTS.DOCUMENTS.APPROVE_CONTRACT)
async onApproveContractEvent(payload: ApproveContractEvent): Promise<void> {
@OnEvent(EVENTS.DOCUMENTS.SIGN_CONTRACT_BY_NGO)
async onSignContractEvent(payload: SignContractEvent): Promise<void> {
const {
userId,
organizationId,
Expand All @@ -71,7 +70,7 @@ export class DocumentsListener {

if (notificationsViaPush) {
const notificationData = {
key: EVENTS.DOCUMENTS.APPROVE_CONTRACT,
key: EVENTS.DOCUMENTS.SIGN_CONTRACT_BY_NGO,
payload: {
contractId,
organizationId,
Expand All @@ -80,8 +79,8 @@ export class DocumentsListener {

this.pushNotificationsFacade.send({
userIds: [userId],
title: NOTIFICATIONS.APPROVE_CONTRACT.PUSH.title,
body: NOTIFICATIONS.APPROVE_CONTRACT.PUSH.body(organizationName),
title: NOTIFICATIONS.SIGN_CONTRACT_BY_NGO.PUSH.title,
body: NOTIFICATIONS.SIGN_CONTRACT_BY_NGO.PUSH.body(organizationName),
data: notificationData,
});
}
Expand All @@ -90,16 +89,18 @@ export class DocumentsListener {
// 4. Send mail to user with link to the volunteer profile for the organization
await this.mailService.sendEmail({
to: userEmail,
subject: NOTIFICATIONS.APPROVE_CONTRACT.EMAIL.subject(organizationName),
subject:
NOTIFICATIONS.SIGN_CONTRACT_BY_NGO.EMAIL.subject(organizationName),
context: {
title: NOTIFICATIONS.APPROVE_CONTRACT.EMAIL.subject(organizationName),
subtitle: NOTIFICATIONS.APPROVE_CONTRACT.EMAIL.body,
title:
NOTIFICATIONS.SIGN_CONTRACT_BY_NGO.EMAIL.subject(organizationName),
subtitle: NOTIFICATIONS.SIGN_CONTRACT_BY_NGO.EMAIL.body,
},
});
}
}

@OnEvent(EVENTS.DOCUMENTS.REJECT_CONATRCT)
@OnEvent(EVENTS.DOCUMENTS.REJECT_CONTRACT_BY_NGO)
async onRejectContractEvent(payload: RejectContractEvent): Promise<void> {
const {
userId,
Expand All @@ -109,12 +110,11 @@ export class DocumentsListener {
contractId,
userEmail,
notificationsViaEmail,
reason,
} = payload;

if (notificationsViaPush) {
const notificationData = {
key: EVENTS.DOCUMENTS.REJECT_CONATRCT,
key: EVENTS.DOCUMENTS.REJECT_CONTRACT_BY_NGO,
payload: {
contractId,
organizationId,
Expand All @@ -124,7 +124,7 @@ export class DocumentsListener {
this.pushNotificationsFacade.send({
userIds: [userId],
title: NOTIFICATIONS.REJECT_CONTRACT.PUSH.title,
body: NOTIFICATIONS.REJECT_CONTRACT.PUSH.body(organizationName, reason),
body: NOTIFICATIONS.REJECT_CONTRACT.PUSH.body(organizationName),
data: notificationData,
});
}
Expand All @@ -133,15 +133,9 @@ export class DocumentsListener {
// 4. Send mail to user with link to the volunteer profile for the organization
await this.mailService.sendEmail({
to: userEmail,
subject: NOTIFICATIONS.REJECT_CONTRACT.EMAIL.subject(
organizationName,
reason,
),
subject: NOTIFICATIONS.REJECT_CONTRACT.EMAIL.subject(organizationName),
context: {
title: NOTIFICATIONS.REJECT_CONTRACT.EMAIL.subject(
organizationName,
reason,
),
title: NOTIFICATIONS.REJECT_CONTRACT.EMAIL.subject(organizationName),
subtitle: NOTIFICATIONS.REJECT_CONTRACT.EMAIL.body,
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Injectable, Logger } from '@nestjs/common';
import { isSameYear } from 'date-fns';
import { isOver16FromCNP } from 'src/common/helpers/utils';
Expand All @@ -14,6 +15,9 @@ import {
import { IDocumentTemplateModel } from 'src/modules/documents/models/document-template.model';
import { DocumentContractFacade } from 'src/modules/documents/services/document-contract.facade';
import { DocumentTemplateFacade } from 'src/modules/documents/services/document-template.facade';
import { EVENTS } from 'src/modules/notifications/constants/events.constants';
import GenerateContractEvent from 'src/modules/notifications/events/documents/generate-contract.event';
import { IOrganizationModel } from 'src/modules/organization/models/organization.model';
import { DocumentPDFGenerator } from 'src/modules/documents/services/document-pdf-generator';
import { IAdminUserModel } from 'src/modules/user/models/admin-user.model';
import {
Expand Down Expand Up @@ -50,6 +54,7 @@ export class CreateDocumentContractUsecase implements IUseCaseService<string> {
private readonly volunteerFacade: VolunteerFacade,
private readonly exceptionsService: ExceptionsService,
private readonly actionsArchiveFacade: ActionsArchiveFacade,
private readonly eventEmitter: EventEmitter2,
private readonly documentPDFGenerator: DocumentPDFGenerator,
) {}

Expand All @@ -62,9 +67,12 @@ export class CreateDocumentContractUsecase implements IUseCaseService<string> {
): Promise<string> {
let volunteer: IVolunteerModel;
let template: IDocumentTemplateModel;
let organization: IOrganizationModel;
try {
// 1. Check if the organization exists
await this.getOrganizationUsecase.execute(newContract.organizationId);
organization = await this.getOrganizationUsecase.execute(
newContract.organizationId,
);

// 2. Check if the volunteer exists
volunteer = await this.checkVolunteerExists(
Expand Down Expand Up @@ -150,6 +158,18 @@ export class CreateDocumentContractUsecase implements IUseCaseService<string> {
}

// 9. Send notification to the volunteer to sign the contract if the status is PENDING_VOLUNTEER_SIGNATURE
this.eventEmitter.emit(
EVENTS.DOCUMENTS.GENERATE_CONTRACT,
new GenerateContractEvent(
organization.id,
volunteer.user.id,
organization.name,
volunteer.user.notificationsSettings.notificationsViaPush,
volunteer.user.notificationsSettings.notificationsViaEmail,
volunteer.user.email,
contract.id,
),
);

// 10. Track event
this.actionsArchiveFacade.trackEvent(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { IUseCaseService } from 'src/common/interfaces/use-case-service.interface';
import { ExceptionsService } from 'src/infrastructure/exceptions/exceptions.service';
import { ActionsArchiveFacade } from 'src/modules/actions-archive/actions-archive.facade';
Expand All @@ -7,7 +8,10 @@ import { DocumentContractStatus } from 'src/modules/documents/enums/contract-sta
import { ContractExceptionMessages } from 'src/modules/documents/exceptions/contract.exceptions';
import { IDocumentContractModel } from 'src/modules/documents/models/document-contract.model';
import { DocumentContractFacade } from 'src/modules/documents/services/document-contract.facade';
import { EVENTS } from 'src/modules/notifications/constants/events.constants';
import RejectContractEvent from 'src/modules/notifications/events/documents/reject-contract.event';
import { IAdminUserModel } from 'src/modules/user/models/admin-user.model';
import { VolunteerFacade } from 'src/modules/volunteer/services/volunteer.facade';

@Injectable()
export class RejectDocumentContractByNgoUsecase
Expand All @@ -17,6 +21,8 @@ export class RejectDocumentContractByNgoUsecase
private readonly documentContractFacade: DocumentContractFacade,
private readonly exceptionService: ExceptionsService,
private readonly actionsArchiveFacade: ActionsArchiveFacade,
private readonly eventEmitter: EventEmitter2,
private readonly volunteerFacade: VolunteerFacade,
) {}

public async execute({
Expand Down Expand Up @@ -84,8 +90,24 @@ export class RejectDocumentContractByNgoUsecase
admin,
);

// TODO: Send notification to Volunteer including Rejection Reason if exists (Contract was rejected)
// get volunteer data to build the mail/notification subject/body
const volunteer = await this.volunteerFacade.find({
id: updatedContract.volunteerId,
});

console.log(rejectionReason);
// send push notifications and or email
this.eventEmitter.emit(
EVENTS.DOCUMENTS.REJECT_CONTRACT_BY_NGO,
new RejectContractEvent(
contract.organizationId,
volunteer.user.id,
volunteer.organization.name,
volunteer.user.notificationsSettings.notificationsViaPush,
volunteer.user.notificationsSettings.notificationsViaEmail,
volunteer.user.email,
contract.id,
rejectionReason || '',
),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { IUseCaseService } from 'src/common/interfaces/use-case-service.interface';
import { ExceptionsService } from 'src/infrastructure/exceptions/exceptions.service';
import { ActionsArchiveFacade } from 'src/modules/actions-archive/actions-archive.facade';
import { TrackedEventName } from 'src/modules/actions-archive/enums/action-resource-types.enum';
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 { EVENTS } from 'src/modules/notifications/constants/events.constants';
import { DocumentSignatureFacade } from 'src/modules/documents/services/document-signature.facade';
import { DocumentPDFGenerator } from 'src/modules/documents/services/document-pdf-generator';
import { IAdminUserModel } from 'src/modules/user/models/admin-user.model';
import { VolunteerFacade } from 'src/modules/volunteer/services/volunteer.facade';
import { IDocumentContractModel } from 'src/modules/documents/models/document-contract.model';
import SignContractEvent from 'src/modules/notifications/events/documents/sign-contract.event';

@Injectable()
export class SignDocumentContractByNgoUsecase implements IUseCaseService<void> {
constructor(
private readonly documentContractFacade: DocumentContractFacade,
private readonly documentSignatureFacade: DocumentSignatureFacade,
private readonly exceptionService: ExceptionsService,
private readonly actionsArchiveFacade: ActionsArchiveFacade,
private readonly eventEmitter: EventEmitter2,
private readonly documentPDFGenerator: DocumentPDFGenerator,
private readonly volunteerFacade: VolunteerFacade,
) {}

public async execute(
Expand All @@ -34,13 +44,14 @@ export class SignDocumentContractByNgoUsecase implements IUseCaseService<void> {
);
}

let contract: IDocumentContractModel;
try {
const signatureId = await this.documentSignatureFacade.create({
userId: admin.id,
signature: signatureBase64,
});

await this.documentContractFacade.signDocumentContractByNGO(
contract = await this.documentContractFacade.signDocumentContractByNGO(
documentContractId,
signatureId,
);
Expand All @@ -54,7 +65,36 @@ export class SignDocumentContractByNgoUsecase implements IUseCaseService<void> {

this.documentPDFGenerator.generateContractPDF(documentContractId);

// TODO: Send notification to Volunteer (Contract is now active)
// TODO: Track Event
// Sign Document Contract by NGO
this.actionsArchiveFacade.trackEvent(
TrackedEventName.SIGN_DOCUMENT_CONTRACT_BY_NGO,
{
organizationId: admin.organizationId,
documentContractId: contract.id,
documentContractNumber: contract.documentNumber,
volunteerId: contract.volunteerId,
volunteerName: contract.volunteerData.name,
},
admin,
);

// get volunteer data to build the mail/notification subject/body
const volunteer = await this.volunteerFacade.find({
id: contract.volunteerId,
});

// send push notifications and or email
this.eventEmitter.emit(
EVENTS.DOCUMENTS.SIGN_CONTRACT_BY_NGO,
new SignContractEvent(
contract.organizationId,
volunteer.user.id,
volunteer.organization.name,
volunteer.user.notificationsSettings.notificationsViaPush,
volunteer.user.notificationsSettings.notificationsViaEmail,
volunteer.user.email,
contract.id,
),
);
}
}
Loading

0 comments on commit dadd24e

Please sign in to comment.