apps/recallassess/recallassess-api/src/api/client/participant/participant-count.service.ts
Service to manage participant count denormalization on Company model This keeps Company.participant_count_* fields in sync with actual participant data
Properties |
|
Methods |
|
| Async decrementCounts | ||||||||||||||||
decrementCounts(companyId: number, isActive: boolean, isReplaced: boolean)
|
||||||||||||||||
|
Decrement counts when a participant is deleted
Parameters :
Returns :
Promise<void>
|
| Async incrementCounts | ||||||||||||||||
incrementCounts(companyId: number, isActive: boolean, isReplaced: boolean)
|
||||||||||||||||
|
Increment counts when a new participant is added
Parameters :
Returns :
Promise<void>
|
| Async recalculateCounts | ||||||||
recalculateCounts(companyId: number)
|
||||||||
|
Recalculate and update all participant counts for a company This is the most reliable method as it counts actual data
Parameters :
Returns :
Promise<void>
|
| Async updateCountsOnStatusChange | ||||||||||||||||||||||||
updateCountsOnStatusChange(companyId: number, oldIsActive: boolean, newIsActive: boolean, oldIsReplaced: boolean, newIsReplaced: boolean)
|
||||||||||||||||||||||||
|
Update counts when a participant's status changes
Parameters :
Returns :
Promise<void>
|
| Protected buildCompanyWhere | ||||||||||||
buildCompanyWhere(companyId: number, additionalWhere?: Record
|
||||||||||||
|
Inherited from
CLBaseService
|
||||||||||||
|
Defined in
CLBaseService:82
|
||||||||||||
|
Build base WHERE clause with company scope Ensures all queries are scoped to the user's company
Parameters :
Returns :
Record<string, any>
Complete where clause object |
| Protected buildSearchWhere | ||||||||||||
buildSearchWhere(searchFields: string[], searchQuery?: string)
|
||||||||||||
|
Inherited from
CLBaseService
|
||||||||||||
|
Defined in
CLBaseService:64
|
||||||||||||
|
Build a WHERE clause for search functionality Creates OR conditions for multiple fields
Parameters :
Returns :
[] | undefined
Array of search conditions or undefined if no query |
| Protected Async findByIdWithCompanyScope | ||||||||||||||||
findByIdWithCompanyScope(entityName: string, entityId: number, companyId: number)
|
||||||||||||||||
|
Inherited from
CLBaseService
|
||||||||||||||||
|
Defined in
CLBaseService:111
|
||||||||||||||||
|
Find entity by ID with company scope verification Common pattern: get entity and ensure it belongs to the company
Parameters :
Returns :
Promise<any | null>
Entity if found and belongs to company, null otherwise |
| Protected getRepo | ||||||||
getRepo(repoName: string)
|
||||||||
|
Inherited from
CLBaseService
|
||||||||
|
Defined in
CLBaseService:96
|
||||||||
|
Get a Prisma repository (table) dynamically Useful for generic operations across different entities
Parameters :
Returns :
any
The Prisma repository instance |
| Protected toDto | ||||||||||||
toDto(entity: any, dtoClass: unknown)
|
||||||||||||
|
Inherited from
CLBaseService
|
||||||||||||
|
Defined in
CLBaseService:20
|
||||||||||||
Type parameters :
|
||||||||||||
|
Transform database entity to DTO using class-transformer
Parameters :
Returns :
TDto
Transformed DTO instance |
| Protected toDtoArray | ||||||||||||
toDtoArray(entities: any[], dtoClass: unknown)
|
||||||||||||
|
Inherited from
CLBaseService
|
||||||||||||
|
Defined in
CLBaseService:30
|
||||||||||||
Type parameters :
|
||||||||||||
|
Transform array of database entities to DTOs
Parameters :
Returns :
TDto[]
Array of transformed DTO instances |
| Protected Async verifyCompanyOwnership | ||||||||||||||||
verifyCompanyOwnership(entityName: string, entityId: number, companyId: number)
|
||||||||||||||||
|
Inherited from
CLBaseService
|
||||||||||||||||
|
Defined in
CLBaseService:42
|
||||||||||||||||
|
Verify that an entity belongs to a specific company Common security check to prevent cross-company data access
Parameters :
Returns :
Promise<boolean>
True if entity belongs to company, false otherwise |
| Protected Readonly prisma |
Type : BNestPrismaService
|
Decorators :
@Inject()
|
|
Inherited from
CLBaseService
|
|
Defined in
CLBaseService:12
|
import { CLBaseService } from "@api/shared/services";
import { Injectable } from "@nestjs/common";
/**
* Service to manage participant count denormalization on Company model
* This keeps Company.participant_count_* fields in sync with actual participant data
*/
@Injectable()
export class CLParticipantCountService extends CLBaseService {
/**
* Recalculate and update all participant counts for a company
* This is the most reliable method as it counts actual data
*
* @param companyId - Company ID to update counts for
*/
async recalculateCounts(companyId: number): Promise<void> {
// Get actual counts from database
const totalCount = await this.prisma.client.participant.count({
where: { company_id: companyId },
});
const activeCount = await this.prisma.client.participant.count({
where: {
company_id: companyId,
is_active: true,
is_replaced: false,
},
});
const inactiveCount = await this.prisma.client.participant.count({
where: {
company_id: companyId,
is_active: false,
is_replaced: false,
},
});
const replacedCount = await this.prisma.client.participant.count({
where: {
company_id: companyId,
is_replaced: true,
},
});
// Update company counts in a single transaction
await this.prisma.client.company.update({
where: { id: companyId },
data: {
participant_count_total: totalCount,
participant_count_active: activeCount,
participant_count_inactive: inactiveCount,
participant_count_replaced: replacedCount,
},
});
}
/**
* Increment counts when a new participant is added
*
* @param companyId - Company ID
* @param isActive - Whether the participant is active
* @param isReplaced - Whether the participant is replaced
*/
async incrementCounts(companyId: number, isActive: boolean, isReplaced: boolean): Promise<void> {
const updates: Record<string, { increment: number }> = {
participant_count_total: { increment: 1 },
};
if (isReplaced) {
updates["participant_count_replaced"] = { increment: 1 };
} else if (isActive) {
updates["participant_count_active"] = { increment: 1 };
} else {
updates["participant_count_inactive"] = { increment: 1 };
}
await this.prisma.client.company.update({
where: { id: companyId },
data: updates,
});
}
/**
* Update counts when a participant's status changes
*
* @param companyId - Company ID
* @param oldIsActive - Previous is_active value
* @param newIsActive - New is_active value
* @param oldIsReplaced - Previous is_replaced value
* @param newIsReplaced - New is_replaced value
*/
async updateCountsOnStatusChange(
companyId: number,
oldIsActive: boolean,
newIsActive: boolean,
oldIsReplaced: boolean,
newIsReplaced: boolean,
): Promise<void> {
// If status hasn't changed, no need to update
if (oldIsActive === newIsActive && oldIsReplaced === newIsReplaced) {
return;
}
// Build atomic increment/decrement operations
const updates: Record<string, { increment?: number; decrement?: number }> = {};
// Handle replaced status change
if (oldIsReplaced !== newIsReplaced) {
if (newIsReplaced) {
// Participant is now replaced
updates["participant_count_replaced"] = { increment: 1 };
// Decrement from previous status
if (oldIsActive) {
updates["participant_count_active"] = { decrement: 1 };
} else {
updates["participant_count_inactive"] = { decrement: 1 };
}
} else {
// Participant is no longer replaced
updates["participant_count_replaced"] = { decrement: 1 };
// Increment to new status
if (newIsActive) {
updates["participant_count_active"] = { increment: 1 };
} else {
updates["participant_count_inactive"] = { increment: 1 };
}
}
} else if (!newIsReplaced && oldIsActive !== newIsActive) {
// Active/Inactive toggle (not replaced)
if (newIsActive) {
updates["participant_count_active"] = { increment: 1 };
updates["participant_count_inactive"] = { decrement: 1 };
} else {
updates["participant_count_active"] = { decrement: 1 };
updates["participant_count_inactive"] = { increment: 1 };
}
}
// Apply updates if there are any
if (Object.keys(updates).length > 0) {
await this.prisma.client.company.update({
where: { id: companyId },
data: updates,
});
}
}
/**
* Decrement counts when a participant is deleted
*
* @param companyId - Company ID
* @param isActive - Whether the participant was active
* @param isReplaced - Whether the participant was replaced
*/
async decrementCounts(companyId: number, isActive: boolean, isReplaced: boolean): Promise<void> {
const updates: Record<string, { decrement: number }> = {
participant_count_total: { decrement: 1 },
};
if (isReplaced) {
updates["participant_count_replaced"] = { decrement: 1 };
} else if (isActive) {
updates["participant_count_active"] = { decrement: 1 };
} else {
updates["participant_count_inactive"] = { decrement: 1 };
}
await this.prisma.client.company.update({
where: { id: companyId },
data: updates,
});
}
}