Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/data/entity/m2m/opportunity-volunteer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IsEnum } from "class-validator";
import { OpportunityVolunteerStatusType } from "need4deed-sdk";
import {
AfterInsert,
AfterUpdate,
Column,
CreateDateColumn,
Expand Down Expand Up @@ -59,9 +60,17 @@ export default class OpportunityVolunteer {
@Column()
volunteerId: number;

@AfterInsert()
async afterInsertHook() {
const { updateOpportunityMatching, updateVolunteerMatching } = await import("../../utils");
updateOpportunityMatching(this.opportunityId);
updateVolunteerMatching(this.volunteerId);
}

@AfterUpdate()
async afterUpdateHook() {
const { updateVolunteerMatching } = await import("../../utils");
const { updateOpportunityMatching, updateVolunteerMatching } = await import("../../utils");
updateOpportunityMatching(this.opportunityId);
updateVolunteerMatching(this.volunteerId);
}
}
9 changes: 9 additions & 0 deletions src/data/entity/opportunity/opportunity.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
OpportunityStatusType,
OpportunityType,
TranslatedIntoType,
VolunteerStateMatchType,
} from "need4deed-sdk";
import {
Column,
Expand Down Expand Up @@ -51,6 +52,14 @@ export default class Opportunity {
@IsEnum(OpportunityStatusType)
status: OpportunityStatusType;

@Column({
type: "enum",
enum: VolunteerStateMatchType,
default: VolunteerStateMatchType.NO_MATCHES,
})
@IsEnum(VolunteerStateMatchType)
statusMatch: VolunteerStateMatchType;

@Column({ default: 1 })
@IsInt()
numberVolunteers: number;
Expand Down
1 change: 1 addition & 0 deletions src/data/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./categorize";
export * from "./resolve-opp-matching";
export * from "./revolve-vol-matching";
export * from "./snake-case";
46 changes: 46 additions & 0 deletions src/data/lib/resolve-opp-matching.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
OpportunityMatchStatusType,
OpportunityStatusType,
OpportunityVolunteerStatusType,
} from "need4deed-sdk";

export function resolveOpportunityMatchStatus(
volunteers: { status: OpportunityVolunteerStatusType }[],
): OpportunityMatchStatusType {
const hasMatched = volunteers.some(
(v) =>
v.status === OpportunityVolunteerStatusType.MATCHED ||
v.status === OpportunityVolunteerStatusType.ACTIVE,
);
const hasPending = volunteers.some(
(v) => v.status === OpportunityVolunteerStatusType.PENDING,
);
const hasPast = volunteers.some(
(v) => v.status === OpportunityVolunteerStatusType.PAST,
);

if (hasMatched) return OpportunityMatchStatusType.MATCHED;
if (hasPending) return OpportunityMatchStatusType.PENDING_MATCH;
if (hasPast) return OpportunityMatchStatusType.PAST;
return OpportunityMatchStatusType.NO_MATCHES;
}

export function resolveOpportunityStatus(
volunteers: { status: OpportunityVolunteerStatusType }[],
currentStatus: OpportunityStatusType,
): OpportunityStatusType {
const hasActive = volunteers.some(
(v) => v.status === OpportunityVolunteerStatusType.ACTIVE,
);
const hasPendingOrMatched = volunteers.some(
(v) =>
v.status === OpportunityVolunteerStatusType.PENDING ||
v.status === OpportunityVolunteerStatusType.MATCHED,
);

if (hasActive) return OpportunityStatusType.ACTIVE;
if (hasPendingOrMatched && currentStatus === OpportunityStatusType.NEW) {
return OpportunityStatusType.SEARCHING;
}
return currentStatus;
}
21 changes: 21 additions & 0 deletions src/data/migrations/1777284196924-add-opp-status-match.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class AddOppStatusMatch1777284196924 implements MigrationInterface {
name = "AddOppStatusMatch1777284196924";

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TYPE "public"."volunteer_status_match_enum" ADD VALUE IF NOT EXISTS 'vol-past'`,
);
await queryRunner.query(
`ALTER TABLE "opportunity" ADD COLUMN IF NOT EXISTS "status_match" "volunteer_status_match_enum" NOT NULL DEFAULT 'vol-no-matches'`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "opportunity" DROP COLUMN IF EXISTS "status_match"`,
);
// PostgreSQL does not support removing enum values; vol-past remains in volunteer_status_match_enum.
}
}
1 change: 1 addition & 0 deletions src/data/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export * from "./getStartEndDates";
export * from "./passwd";
export * from "./refresh-materialized-view";
export * from "./remove-data";
export * from "./update-opportunity-matching";
export * from "./update-volunteer-matching";
42 changes: 42 additions & 0 deletions src/data/utils/update-opportunity-matching.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import logger from "../../logger";
import { tryCatch } from "../../services/utils";
import { dataSource } from "../data-source";
import OpportunityVolunteer from "../entity/m2m/opportunity-volunteer";
import Opportunity from "../entity/opportunity/opportunity.entity";
import { resolveOpportunityMatchStatus, resolveOpportunityStatus } from "../lib";
import { getRepository } from "./get-repository";

export async function updateOpportunityMatching(id: number): Promise<void> {
const opportunityRepository = getRepository(dataSource, Opportunity);
const opportunity = await opportunityRepository.findOneBy({ id });
if (!opportunity) {
logger.warn(`Opportunity id:${id} not found during match status update.`);
return;
}

const opportunityVolunteerRepository = getRepository(
dataSource,
OpportunityVolunteer,
);
const volunteersLinked = await opportunityVolunteerRepository.find({
where: { opportunityId: id },
});

const statusMatch = resolveOpportunityMatchStatus(volunteersLinked);
const status = resolveOpportunityStatus(volunteersLinked, opportunity.status);

const changed =
statusMatch !== opportunity.statusMatch || status !== opportunity.status;

if (changed) {
const [, error] = await tryCatch(
opportunityRepository.save(
Object.assign(opportunity, { statusMatch, status }),
),
);

if (error) {
logger.warn(`During saving opportunity (id:${id}) occurred: ${error}`);
}
}
}
3 changes: 3 additions & 0 deletions src/services/dto/dto-opportunity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ApiOpportunityGetList,
ApiVolunteerOpportunityGetList,
OpportunityType,
VolunteerStateMatchType,
} from "need4deed-sdk";
import Comment from "../../data/entity/comment.entity";
import Opportunity from "../../data/entity/opportunity/opportunity.entity";
Expand Down Expand Up @@ -47,6 +48,7 @@ export function dtoOpportunityGetList(
category: { id: opportunity.deal.profile.categoryId },
volunteerType: opportunity.type,
statusOpportunity: opportunity.status,
statusMatch: opportunity.statusMatch ?? VolunteerStateMatchType.NO_MATCHES,
createdAt: opportunity.createdAt,
languages: opportunity.deal.profile.profileLanguage
.filter(Boolean)
Expand Down Expand Up @@ -108,6 +110,7 @@ export function dtoOpportunityGet(
title: opportunityComments.title,
volunteerType: opportunityComments.type,
statusOpportunity: opportunityComments.status,
statusMatch: opportunityComments.statusMatch ?? VolunteerStateMatchType.NO_MATCHES,
createdAt: opportunityComments.createdAt,
category: { id: opportunityComments.deal.profile.categoryId },
description: getOpportunityDescription(opportunityComments),
Expand Down