Files
jira/src/lib/milestoneStatus.ts
Bastien COIGNOUX ca4c64bbb0 init
2026-04-24 11:50:39 +02:00

78 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { StoryGroup } from '../types/jira'
import type { Milestone } from './dashboardConfig'
import { getRemainingEstimateUnits } from './jiraFieldExtractors'
import type { StatusBucketConfig } from './statusBuckets'
import { resolveWorkBucketFromIssue } from './statusBuckets'
import { subtaskDoneRatioPercent } from './storyMetrics'
/** Pour les jalons : story sans sous-tâche = considérée comme « livrée » côté sous-tâches. */
function storyCompletionForMilestone(g: StoryGroup, cfg: StatusBucketConfig): number {
if (g.subtasks.length === 0) return 100
return subtaskDoneRatioPercent(g.subtasks, cfg)
}
function endOfDay(isoDate: string): Date {
const d = new Date(isoDate + 'T12:00:00')
d.setHours(23, 59, 59, 999)
return d
}
/** Stories du périmètre du jalon (clés liées si renseignées, sinon tout le chargement). */
export function milestoneLinkedGroups(m: Milestone, groups: StoryGroup[]): StoryGroup[] {
const keys =
m.linkedStoryKeys && m.linkedStoryKeys.length > 0
? m.linkedStoryKeys
: groups.map((g) => g.story.key)
const set = new Set(keys)
return groups.filter((g) => set.has(g.story.key))
}
/** Moyenne des % sous-tâches terminées sur les stories du périmètre (0100). */
export function milestoneAverageCompletionPercent(
m: Milestone,
groups: StoryGroup[],
cfg: StatusBucketConfig,
): number {
const linked = milestoneLinkedGroups(m, groups)
if (linked.length === 0) return 100
const sum = linked.reduce((acc, g) => acc + storyCompletionForMilestone(g, cfg), 0)
return Math.round(sum / linked.length)
}
/** Somme du reste à faire (unités Jira) sur sous-tâches non terminées / non annulées du périmètre. */
export function milestoneOpenRemainingUnits(
m: Milestone,
groups: StoryGroup[],
cfg: StatusBucketConfig,
): number {
let u = 0
for (const g of milestoneLinkedGroups(m, groups)) {
for (const s of g.subtasks) {
const b = resolveWorkBucketFromIssue(s, cfg)
if (b !== 'done' && b !== 'cancel') u += getRemainingEstimateUnits(s)
}
}
return u
}
/** Jours calendaires jusquà la fin du jour du jalon (négatif = retard). */
export function milestoneCalendarDaysUntil(m: Milestone): number {
const end = endOfDay(m.date)
return Math.ceil((end.getTime() - Date.now()) / 86400000)
}
/** Jalon en retard : date dépassée et au moins une story concernée nest pas à 100 %. */
export function isMilestoneLate(
m: Milestone,
groups: StoryGroup[],
cfg: StatusBucketConfig,
): boolean {
if (groups.length === 0) return false
const deadline = endOfDay(m.date)
if (new Date() <= deadline) return false
for (const g of milestoneLinkedGroups(m, groups)) {
if (storyCompletionForMilestone(g, cfg) < 100) return true
}
return false
}