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; } }