feat: definition of done per project

A project can ask that tickets reach a DONE status only with their
checklist ticked (settings.checklistBeforeDone, off by default; absent
when saving means unchanged). Members get a 422 telling how many steps are
left; owners and admins may still close it. Settings has the switch and
the ticket's checklist says when the project asks for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 14:39:24 +02:00
parent 5b0396bc10
commit e598f76e1c
25 changed files with 285 additions and 4 deletions

View File

@@ -27,6 +27,8 @@ export interface SettingsPayload {
template?: { description?: string | null; checklist?: string[] } | null;
}[];
areas: { id: string; name: string; archived: boolean }[];
/** Definition of done. Absent when saving: unchanged. */
checklistBeforeDone?: boolean;
/** Absent when saving: unchanged. */
customFields?: {
id: string;
@@ -41,6 +43,7 @@ export function toSettingsPayload(settings: ProjectSettings): SettingsPayload {
return {
...settings,
customFields: settings.customFields ?? [],
checklistBeforeDone: !!settings.checklistBeforeDone,
transitions: settings.statuses.map((s) => ({
from: s.id,
to: settings.transitions[s.id] ?? [],
@@ -72,6 +75,9 @@ export function fromSettingsPayload(payload: SettingsPayload): ProjectSettings {
: {}),
})),
areas: payload.areas,
...(payload.checklistBeforeDone != null
? { checklistBeforeDone: payload.checklistBeforeDone }
: {}),
...(payload.customFields
? {
customFields: payload.customFields.map((f) => ({

View File

@@ -772,6 +772,15 @@ export class ProjectSettingsInput {
@ValidateNested({ each: true })
@Type(() => CustomFieldDefinitionInput)
customFields?: CustomFieldDefinitionInput[];
@Field({
nullable: true,
description:
'Definition of done: DONE only with the checklist ticked. Absent: unchanged',
})
@IsOptional()
@IsBoolean()
checklistBeforeDone?: boolean;
}
@InputType({

View File

@@ -115,6 +115,11 @@ export class ProjectSettingsGql {
@Field(() => [AreaOptionGql]) areas!: AreaOptionGql[];
@Field(() => [CustomFieldDefinitionGql])
customFields!: CustomFieldDefinitionGql[];
@Field({
description:
'Definition of done: tickets reach DONE only with their checklist ticked (owners and admins may override)',
})
checklistBeforeDone!: boolean;
}
@ObjectType('Project')

View File

@@ -318,6 +318,14 @@ export class ProjectSettingsBodyDto {
@ValidateNested({ each: true })
@Type(() => CustomFieldDefinitionBodyDto)
customFields?: CustomFieldDefinitionBodyDto[];
@ApiPropertyOptional({
description:
'Definition of done: tickets reach a DONE status only with their checklist ticked (owners and admins may override). Absent: unchanged',
})
@IsOptional()
@IsBoolean()
checklistBeforeDone?: boolean;
}
export class AnalyticsQueryDto {

View File

@@ -103,6 +103,11 @@ export class ProjectSettingsDto {
@ApiProperty({ type: [AreaOptionDto] }) areas!: AreaOptionDto[];
@ApiProperty({ type: [CustomFieldDefinitionDto] })
customFields!: CustomFieldDefinitionDto[];
@ApiProperty({
description:
'Definition of done: tickets reach DONE only with their checklist ticked',
})
checklistBeforeDone!: boolean;
}
export class ProjectDto {