import { prisma } from '@/lib/prisma' import { productAccessFilter } from '@/lib/product-access' export interface BurndownDay { day: string remaining: number ideal: number } export interface BurndownSprint { sprintId: string sprintCode: string productId: string productName: string sprintGoal: string days: BurndownDay[] } const DAY_MS = 86_400_000 function toUTCMidnight(d: Date): Date { return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())) } export function computeBurndownDays( tasks: { status: string; updated_at: Date }[], startDate: Date, endDate: Date, ): BurndownDay[] { const start = toUTCMidnight(startDate) const end = toUTCMidnight(endDate) const total = tasks.length // n = number of intervals (end - start in days) const n = Math.round((end.getTime() - start.getTime()) / DAY_MS) const days: BurndownDay[] = [] for (let i = 0; ; i++) { const dayStart = new Date(start.getTime() + i * DAY_MS) if (dayStart > end) break const nextDay = new Date(dayStart.getTime() + DAY_MS) const done = tasks.filter(t => t.status === 'DONE' && t.updated_at < nextDay).length const ideal = n === 0 ? 0 : Math.round((total * (n - i) / n) * 100) / 100 days.push({ day: dayStart.toISOString().slice(0, 10), remaining: total - done, ideal, }) } return days } export async function getBurndownData(userId: string): Promise { const now = new Date() const sprints = await prisma.sprint.findMany({ where: { status: 'OPEN', product: productAccessFilter(userId), }, select: { id: true, code: true, sprint_goal: true, created_at: true, completed_at: true, product: { select: { id: true, name: true } }, tasks: { select: { status: true, updated_at: true } }, }, }) return sprints .map(sprint => { const endDate = sprint.completed_at ?? now if (endDate <= sprint.created_at) return null return { sprintId: sprint.id, sprintCode: sprint.code, productId: sprint.product.id, productName: sprint.product.name, sprintGoal: sprint.sprint_goal, days: computeBurndownDays(sprint.tasks, sprint.created_at, endDate), } }) .filter((s): s is BurndownSprint => s !== null) }