feat: move unfinished tickets between releases

- Bulk editing gets «Release…» (open releases or none), and the list
  view a release filter, also reachable from each release («Ver en la
  lista», ?release=).
- Archiving a release asks where its unfinished tickets go, as closing
  a sprint does: ReleaseArchivedEvent carries `moveTo` (an open release
  of the same project, or none) and the saga moves them there; finished
  ones stay. In GraphQL, REST and the MCP's archive_release.
- Chapter 10 lists the next features and the ideas to weigh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 21:55:45 +02:00
parent 0a73eda9b2
commit 993f80b4b9
34 changed files with 408 additions and 48 deletions

View File

@@ -146,7 +146,14 @@ export class ReleaseResolver {
archiveRelease(
@CurrentUser() me: User,
@Args('releaseId', { type: () => ID }) releaseId: string,
@Args('moveTo', {
type: () => ID,
nullable: true,
description:
'Open release of the same project that takes its unfinished tickets; none: they leave it',
})
moveTo?: string | null,
) {
return this.releases.archive(me.id, releaseId);
return this.releases.archive(me.id, releaseId, moveTo ?? null);
}
}

View File

@@ -1148,6 +1148,19 @@ export class SetIssueSprintDto {
sprintId!: string | null;
}
export class ArchiveReleaseDto {
@ApiPropertyOptional({
type: String,
nullable: true,
format: 'uuid',
description:
'Open release of the same project that takes its unfinished tickets; null or absent: they leave it',
})
@IsOptional()
@IsUUID()
moveTo?: string | null;
}
export class PublishReleaseDto {
@ApiPropertyOptional({
type: Boolean,

View File

@@ -34,6 +34,7 @@ import {
import {
CreateReleaseDto,
PageQueryDto,
ArchiveReleaseDto,
PublishReleaseDto,
ReleaseNotesDto,
UpcomingReleasesQueryDto,
@@ -234,10 +235,18 @@ export class ReleasesController {
@Post('releases/:releaseId/archive')
@HttpCode(200)
@ApiOperation({ summary: 'Archive a planned release (its tickets leave it)' })
@ApiOperation({
summary: 'Archive a planned release',
description:
'Its unfinished tickets move to `moveTo` (an open release of the same project) or leave it.',
})
@ApiOkResponse({ type: CommandResultDto })
@ApiCommandErrors()
archive(@CurrentUser() me: User, @Param('releaseId') releaseId: string) {
return this.releases.archive(me.id, releaseId);
archive(
@CurrentUser() me: User,
@Param('releaseId') releaseId: string,
@Body() body: ArchiveReleaseDto,
) {
return this.releases.archive(me.id, releaseId, body?.moveTo ?? null);
}
}