File

apps/recallassess/recallassess-api/src/api/client/participant/participant-count.service.ts

Description

Service to manage participant count denormalization on Company model This keeps Company.participant_count_* fields in sync with actual participant data

Extends

CLBaseService

Index

Properties
Methods

Methods

Async decrementCounts
decrementCounts(companyId: number, isActive: boolean, isReplaced: boolean)

Decrement counts when a participant is deleted

Parameters :
Name Type Optional Description
companyId number No
  • Company ID
isActive boolean No
  • Whether the participant was active
isReplaced boolean No
  • Whether the participant was replaced
Returns : Promise<void>
Async incrementCounts
incrementCounts(companyId: number, isActive: boolean, isReplaced: boolean)

Increment counts when a new participant is added

Parameters :
Name Type Optional Description
companyId number No
  • Company ID
isActive boolean No
  • Whether the participant is active
isReplaced boolean No
  • Whether the participant is replaced
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 :
Name Type Optional Description
companyId number No
  • Company ID to update counts for
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 :
Name Type Optional Description
companyId number No
  • Company ID
oldIsActive boolean No
  • Previous is_active value
newIsActive boolean No
  • New is_active value
oldIsReplaced boolean No
  • Previous is_replaced value
newIsReplaced boolean No
  • New is_replaced value
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 :
Name Type Optional Description
companyId number No
  • Company ID to scope queries to
additionalWhere Record<string | any> Yes
  • Additional where conditions to merge
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 :
Name Type Optional Description
searchFields string[] No
  • Array of field names to search in
searchQuery string Yes
  • Search query string
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 :
Name Type Optional Description
entityName string No
  • Prisma model name
entityId number No
  • Entity ID
companyId number No
  • Company ID to verify ownership
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 :
Name Type Optional Description
repoName string No
  • The name of the Prisma repository (table)
Returns : any

The Prisma repository instance

Protected toDto
toDto(entity: any, dtoClass: unknown)
Inherited from CLBaseService
Defined in CLBaseService:20
Type parameters :
  • TDto

Transform database entity to DTO using class-transformer

Parameters :
Name Type Optional Description
entity any No
  • Raw database entity
dtoClass unknown No
  • DTO class constructor
Returns : TDto

Transformed DTO instance

Protected toDtoArray
toDtoArray(entities: any[], dtoClass: unknown)
Inherited from CLBaseService
Defined in CLBaseService:30
Type parameters :
  • TDto

Transform array of database entities to DTOs

Parameters :
Name Type Optional Description
entities any[] No
  • Array of raw database entities
dtoClass unknown No
  • DTO class constructor
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 :
Name Type Optional Description
entityName string No
  • Prisma model name (e.g., 'participant', 'participantGroup')
entityId number No
  • Entity ID to check
companyId number No
  • Company ID to verify ownership
Returns : Promise<boolean>

True if entity belongs to company, false otherwise

Properties

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,
    });
  }
}

results matching ""

    No results matching ""