Files
ulitka/src/modules/issue/domain/entities/issue.entity.ts
T
vadimandClaude Opus 5.5 2696f33a7e Keep issues when assignee or reporter is deleted
Switch issue.assigneeId/reporterId FKs from CASCADE to SET NULL and make
reporter nullable through the entity, DTOs and GraphQL types.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:23:51 +03:00

196 lines
5.0 KiB
TypeScript

import { IssueTitle } from '../value-objects/issue-title.vo.js';
import { IssueStatus } from '../value-objects/issue-status.vo.js';
import { IssuePriority } from '../value-objects/issue-priority.vo.js';
import { IssueCreatedEvent } from '../events/issue-created.event.js';
import { IssueUpdatedEvent } from '../events/issue-updated.event.js';
interface IssueProps {
id: number | null;
key: string;
title: IssueTitle;
content: string | null;
status: IssueStatus;
priority: IssuePriority;
assignee: string | null;
/** null — автор задачи удалил аккаунт */
reporter: string | null;
projectId: string;
dueDate: Date | null;
createdAt: Date;
updatedAt: Date;
}
/**
* Доменная сущность. Никогда не импортирует ничего из infrastructure/
* presentation — только чистая бизнес-логика и инварианты.
*
* `id` — Int, генерируется базой данных (SERIAL), поэтому у новой задачи
* он равен null до сохранения в репозитории. `key` (например, "ISSUE-1")
* присваивается на уровне приложения при создании и не зависит от id.
*/
export class Issue {
private domainEvents: unknown[] = [];
private constructor(private readonly props: IssueProps) {}
/** Фабричный метод для создания НОВОЙ задачи (порождает доменное событие) */
static create(params: {
key: string;
title: string;
content?: string | null;
priority?: number;
assignee?: string | null;
reporter: string;
projectId: string;
dueDate?: Date | null;
}): Issue {
const issue = new Issue({
id: null,
key: params.key,
title: IssueTitle.create(params.title),
content: params.content ?? null,
status: IssueStatus.open(),
priority: params.priority !== undefined ? IssuePriority.create(params.priority) : IssuePriority.default(),
assignee: params.assignee ?? null,
reporter: params.reporter,
projectId: params.projectId,
dueDate: params.dueDate ?? null,
createdAt: new Date(),
updatedAt: new Date(),
});
issue.addDomainEvent(new IssueCreatedEvent(issue.key));
return issue;
}
/** Восстановление сущности из персистентного слоя (без событий) */
static reconstitute(props: {
id: number;
key: string;
title: string;
content: string | null;
status: string;
priority: number;
assignee: string | null;
reporter: string | null;
projectId: string;
dueDate: Date | null;
createdAt: Date;
updatedAt: Date;
}): Issue {
return new Issue({
id: props.id,
key: props.key,
title: IssueTitle.create(props.title),
content: props.content,
status: IssueStatus.create(props.status),
priority: IssuePriority.create(props.priority),
assignee: props.assignee,
reporter: props.reporter,
projectId: props.projectId,
dueDate: props.dueDate,
createdAt: props.createdAt,
updatedAt: props.updatedAt,
});
}
get id(): number | null {
return this.props.id;
}
get key(): string {
return this.props.key;
}
get title(): string {
return this.props.title.value;
}
get content(): string | null {
return this.props.content;
}
get status(): string {
return this.props.status.value;
}
get priority(): number {
return this.props.priority.value;
}
get assignee(): string | null {
return this.props.assignee;
}
get reporter(): string | null {
return this.props.reporter;
}
get projectId(): string {
return this.props.projectId;
}
get dueDate(): Date | null {
return this.props.dueDate;
}
get createdAt(): Date {
return this.props.createdAt;
}
get updatedAt(): Date {
return this.props.updatedAt;
}
updateTitle(title: string): void {
this.props.title = IssueTitle.create(title);
this.touch();
}
updateContent(content: string | null): void {
this.props.content = content;
this.touch();
}
changeStatus(status: string): void {
this.props.status = IssueStatus.create(status);
this.touch();
}
changePriority(priority: number): void {
this.props.priority = IssuePriority.create(priority);
this.touch();
}
assignTo(userId: string): void {
this.props.assignee = userId;
this.touch();
}
unassign(): void {
this.props.assignee = null;
this.touch();
}
updateDueDate(dueDate: Date | null): void {
this.props.dueDate = dueDate;
this.touch();
}
private touch(): void {
this.props.updatedAt = new Date();
this.addDomainEvent(new IssueUpdatedEvent(this.key));
}
private addDomainEvent(event: unknown): void {
this.domainEvents.push(event);
}
pullDomainEvents(): unknown[] {
const events = [...this.domainEvents];
this.domainEvents = [];
return events;
}
}