import { Inject, Injectable } from '@nestjs/common'; import { ISSUE_STATUSES, type IssueStatusValue } from '../../../../issue/domain/value-objects/issue-status.vo.js'; import { ISSUE_HISTORY_REPOSITORY, type IIssueHistoryRepository, } from '../../../domain/repositories/issue-history.repository.interface.js'; import { StatsPeriod } from '../../../domain/value-objects/stats-period.vo.js'; import { DailyIssueStatsDayDto, DailyIssueStatsDto, IssueStatusCounts } from '../../dto/daily-issue-stats.dto.js'; import { GetDailyIssueStatsQuery } from './get-daily-issue-stats.query.js'; /** * Статистика задач по дням, считается по истории изменений (issue_change). * Видимость — как у списка задач: только задачи, где пользователь reporter/assignee/наблюдатель. */ @Injectable() export class GetDailyIssueStatsUseCase { constructor( @Inject(ISSUE_HISTORY_REPOSITORY) private readonly historyRepository: IIssueHistoryRepository, ) {} async execute(query: GetDailyIssueStatsQuery): Promise { const period = StatsPeriod.create({ from: query.from, to: query.to, timeZone: query.timeZone }); const range = { userId: query.userId, projectId: query.projectId, from: period.from, to: period.to, timeZone: period.timeZone, }; const [events, statuses] = await Promise.all([ this.historyRepository.countDailyEvents(range), this.historyRepository.countDailyStatuses(range), ]); // Каждый день периода попадает в ответ, даже если в нём ничего не было const days = new Map( period.days.map((date) => [ date, { date, created: 0, transitions: emptyStatusCounts(), byStatus: emptyStatusCounts() }, ]), ); for (const event of events) { const day = days.get(event.date); if (!day) continue; if (event.kind === 'created') { day.created += event.count; } else if (isKnownStatus(event.status)) { day.transitions[event.status] += event.count; } } for (const entry of statuses) { const day = days.get(entry.date); if (day && isKnownStatus(entry.status)) { day.byStatus[entry.status] += entry.count; } } return { from: period.from, to: period.to, timeZone: period.timeZone, days: [...days.values()] }; } } function emptyStatusCounts(): IssueStatusCounts { return Object.fromEntries(ISSUE_STATUSES.map((status) => [status.alias, 0])) as IssueStatusCounts; } function isKnownStatus(status: string): status is IssueStatusValue { return ISSUE_STATUSES.some((known) => known.alias === status); }