feat: sprints with burndown and carry-over

Plan sprints with dates and a goal, run one at a time per project, and
put tickets in them from the ticket page. The Sprints tab follows the one
under way with its progress and a burndown in tickets or points against
the ideal line. Closing a sprint sends what was left to the next one or
back to the backlog; the board can be filtered by sprint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 14:59:01 +02:00
parent d2e31789a7
commit 50d3ffd1dd
104 changed files with 7209 additions and 33 deletions

View File

@@ -0,0 +1,85 @@
import { Command } from '@nestjs/cqrs';
import { SprintDetails } from '@task-stream/api/tracker/domain';
import { CommandResult } from './command-result';
export class CreateSprintCommand extends Command<CommandResult> {
constructor(
public readonly actorId: string,
public readonly projectId: string,
public readonly name: string,
public readonly startDate: string,
public readonly endDate: string,
public readonly goal: string | null = null,
) {
super();
}
}
export class UpdateSprintCommand extends Command<CommandResult> {
constructor(
public readonly actorId: string,
public readonly sprintId: string,
public readonly changes: SprintDetails,
) {
super();
}
}
export class StartSprintCommand extends Command<CommandResult> {
constructor(
public readonly actorId: string,
public readonly sprintId: string,
) {
super();
}
}
/** Ends a sprint; unfinished tickets go to `carryOverTo` (null: backlog). */
export class CloseSprintCommand extends Command<CommandResult> {
constructor(
public readonly actorId: string,
public readonly sprintId: string,
public readonly carryOverTo: string | null,
) {
super();
}
}
/** A planned sprint (people), or any sprint of a deleted project (`cascade`, saga). */
export class DeleteSprintCommand extends Command<CommandResult | void> {
constructor(
public readonly actorId: string,
public readonly sprintId: string,
public readonly cascade = false,
) {
super();
}
}
/** Puts a ticket in a sprint (null: back to the backlog). */
export class SetIssueSprintCommand extends Command<CommandResult> {
constructor(
public readonly actorId: string,
public readonly issueId: string,
public readonly sprintId: string | null,
) {
super();
}
}
/**
* Saga only: a sprint closed (or was deleted), so a ticket of it moves to
* `toSprintId` (null: backlog). With `onlyUnfinished`, finished ones stay as
* the record of what the sprint achieved.
*/
export class CarryOverIssueCommand extends Command<void> {
constructor(
public readonly actorId: string,
public readonly issueId: string,
public readonly fromSprintId: string,
public readonly toSprintId: string | null,
public readonly onlyUnfinished: boolean,
) {
super();
}
}