import { DRIZZLE_PROVIDER } from 'src/database/drizzle-provider'; import { Inject, Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { NodePgDatabase } from 'drizzle-orm/node-postgres'; import * as schema from 'src/database/index'; import { trainingSurveys } from 'src/database/index'; import { eq, like, or, and, gte, lte, SQL, sql } from 'drizzle-orm'; import { CreateTrainingDto } from './dto/create-training.dto'; import { UpdateTrainingDto } from './dto/update-training.dto'; import { TrainingStatisticsFilterDto } from './dto/training-statistics-filter.dto'; import { states } from 'src/database/index'; import { PaginationDto } from '../../common/dto/pagination.dto'; @Injectable() export class TrainingService { constructor( @Inject(DRIZZLE_PROVIDER) private drizzle: NodePgDatabase, ) { } async findAll(paginationDto?: PaginationDto) { const { page = 1, limit = 10, search = '', sortBy = 'id', sortOrder = 'asc' } = paginationDto || {}; const offset = (page - 1) * limit; let searchCondition: SQL | undefined; if (search) { searchCondition = or( like(trainingSurveys.firstname, `%${search}%`), like(trainingSurveys.lastname, `%${search}%`), like(trainingSurveys.ospName, `%${search}%`), like(trainingSurveys.ospRif, `%${search}%`) ); } const orderBy = sortOrder === 'asc' ? sql`${trainingSurveys[sortBy as keyof typeof trainingSurveys]} asc` : sql`${trainingSurveys[sortBy as keyof typeof trainingSurveys]} desc`; const totalCountResult = await this.drizzle .select({ count: sql`count(*)` }) .from(trainingSurveys) .where(searchCondition); const totalCount = Number(totalCountResult[0].count); const totalPages = Math.ceil(totalCount / limit); const data = await this.drizzle .select() .from(trainingSurveys) .where(searchCondition) .orderBy(orderBy) .limit(limit) .offset(offset); const meta = { page, limit, totalCount, totalPages, hasNextPage: page < totalPages, hasPreviousPage: page > 1, nextPage: page < totalPages ? page + 1 : null, previousPage: page > 1 ? page - 1 : null, }; return { data, meta }; } async getStatistics(filterDto: TrainingStatisticsFilterDto) { const { startDate, endDate, stateId, municipalityId, parishId, ospType } = filterDto; const filters: SQL[] = []; if (startDate) { filters.push(gte(trainingSurveys.visitDate, new Date(startDate))); } if (endDate) { filters.push(lte(trainingSurveys.visitDate, new Date(endDate))); } if (stateId) { filters.push(eq(trainingSurveys.state, stateId)); } if (municipalityId) { filters.push(eq(trainingSurveys.municipality, municipalityId)); } if (parishId) { filters.push(eq(trainingSurveys.parish, parishId)); } if (ospType) { filters.push(eq(trainingSurveys.ospType, ospType)); } const whereCondition = filters.length > 0 ? and(...filters) : undefined; const totalOspsResult = await this.drizzle .select({ count: sql`count(*)` }) .from(trainingSurveys) .where(whereCondition); const totalOsps = Number(totalOspsResult[0].count); const totalProducersResult = await this.drizzle .select({ sum: sql`sum(${trainingSurveys.producerCount})` }) .from(trainingSurveys) .where(whereCondition); const totalProducers = Number(totalProducersResult[0].sum || 0); const statusDistribution = await this.drizzle .select({ name: trainingSurveys.currentStatus, value: sql`count(*)` }) .from(trainingSurveys) .where(whereCondition) .groupBy(trainingSurveys.currentStatus); const activityDistribution = await this.drizzle .select({ name: trainingSurveys.productiveActivity, value: sql`count(*)` }) .from(trainingSurveys) .where(whereCondition) .groupBy(trainingSurveys.productiveActivity); const typeDistribution = await this.drizzle .select({ name: trainingSurveys.ospType, value: sql`count(*)` }) .from(trainingSurveys) .where(whereCondition) .groupBy(trainingSurveys.ospType); // New Aggregations const stateDistribution = await this.drizzle .select({ name: states.name, value: sql`count(${trainingSurveys.id})` }) .from(trainingSurveys) .leftJoin(states, eq(trainingSurveys.state, states.id)) .where(whereCondition) .groupBy(states.name); const yearDistribution = await this.drizzle .select({ name: sql`cast(${trainingSurveys.companyConstitutionYear} as text)`, value: sql`count(*)` }) .from(trainingSurveys) .where(whereCondition) .groupBy(trainingSurveys.companyConstitutionYear) .orderBy(trainingSurveys.companyConstitutionYear); return { totalOsps, totalProducers, statusDistribution: statusDistribution.map(item => ({ ...item, value: Number(item.value) })), activityDistribution: activityDistribution.map(item => ({ ...item, value: Number(item.value) })), typeDistribution: typeDistribution.map(item => ({ ...item, value: Number(item.value) })), stateDistribution: stateDistribution.map(item => ({ ...item, value: Number(item.value) })), yearDistribution: yearDistribution.map(item => ({ ...item, value: Number(item.value) })), }; } async findOne(id: number) { const find = await this.drizzle .select() .from(trainingSurveys) .where(eq(trainingSurveys.id, id)); if (find.length === 0) { throw new HttpException('Training record not found', HttpStatus.NOT_FOUND); } return find[0]; } async create(createTrainingDto: CreateTrainingDto) { const [newRecord] = await this.drizzle .insert(trainingSurveys) .values({ ...createTrainingDto, visitDate: new Date(createTrainingDto.visitDate), }) .returning(); return newRecord; } async update(id: number, updateTrainingDto: UpdateTrainingDto) { await this.findOne(id); const updateData: any = { ...updateTrainingDto }; if (updateTrainingDto.visitDate) { updateData.visitDate = new Date(updateTrainingDto.visitDate); } const [updatedRecord] = await this.drizzle .update(trainingSurveys) .set(updateData) .where(eq(trainingSurveys.id, id)) .returning(); return updatedRecord; } async remove(id: number) { await this.findOne(id); const [deletedRecord] = await this.drizzle .delete(trainingSurveys) .where(eq(trainingSurveys.id, id)) .returning(); return { message: 'Training record deleted successfully', data: deletedRecord }; } }