2026-10-08 14:43:34 +00:00
|
|
|
import type { UniqueID } from "@repo/rdx-ddd";
|
|
|
|
|
import { AggregateRoot } from "@repo/rdx-ddd";
|
2026-10-07 07:34:18 +00:00
|
|
|
import { Maybe, Result } from "@repo/rdx-utils";
|
|
|
|
|
import { z } from "zod/v4";
|
|
|
|
|
|
|
|
|
|
import { PersonProcessRevision } from "./process-revision.entity";
|
|
|
|
|
import { ProcessTask } from "./process-task.entity";
|
2026-10-08 14:43:34 +00:00
|
|
|
import type { ProcessReferenceInput } from "./process-values";
|
|
|
|
|
import { ProcessError } from "./process-values";
|
2026-10-09 08:38:19 +00:00
|
|
|
import { ProcessValues } from "./value-objects/process-values.vo";
|
2026-10-07 07:34:18 +00:00
|
|
|
|
|
|
|
|
type Props = Readonly<{
|
|
|
|
|
companyId: UniqueID;
|
|
|
|
|
values: ProcessValues;
|
|
|
|
|
history: readonly PersonProcessRevision[];
|
|
|
|
|
}>;
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
export class PersonProcess extends AggregateRoot<Props> {
|
|
|
|
|
private constructor(props: Props, id?: UniqueID) {
|
|
|
|
|
super(Object.freeze({ ...props, history: Object.freeze([...props.history]) }), id);
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
static create(
|
|
|
|
|
companyId: UniqueID,
|
|
|
|
|
values: ProcessValues,
|
|
|
|
|
actor: Maybe<UniqueID>,
|
|
|
|
|
recordedAt: string
|
|
|
|
|
): Result<PersonProcess, ProcessError> {
|
|
|
|
|
if (
|
|
|
|
|
!(z.iso.datetime().safeParse(recordedAt).success && actor.getOrUndefined()) ||
|
|
|
|
|
values.state !== "open" ||
|
|
|
|
|
values.tasks.some((t) => t.toPrimitive().status !== "pending")
|
|
|
|
|
) {
|
|
|
|
|
return Result.fail(new ProcessError("INVALID_REQUEST"));
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
return Result.ok(
|
|
|
|
|
new PersonProcess({
|
|
|
|
|
companyId,
|
|
|
|
|
values,
|
|
|
|
|
history: [PersonProcessRevision.create({ values, actorId: actor, recordedAt })],
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
static rehydrate(props: Props, id: UniqueID) {
|
|
|
|
|
return new PersonProcess(props, id);
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
get companyId() {
|
|
|
|
|
return this.props.companyId;
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
get values() {
|
|
|
|
|
return this.props.values;
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
get history() {
|
|
|
|
|
return this.props.history;
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
update(
|
|
|
|
|
values: ProcessValues,
|
|
|
|
|
actor: UniqueID,
|
|
|
|
|
recordedAt: string
|
|
|
|
|
): Result<PersonProcess, ProcessError> {
|
|
|
|
|
if (this.values.equals(values)) {
|
|
|
|
|
return Result.ok(this);
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
!z.iso.datetime().safeParse(recordedAt).success ||
|
|
|
|
|
recordedAt < (this.history.at(-1)?.recordedAt ?? "")
|
|
|
|
|
) {
|
|
|
|
|
return Result.fail(new ProcessError("INVALID_REQUEST"));
|
|
|
|
|
}
|
|
|
|
|
if (this.values.closed) {
|
|
|
|
|
return Result.fail(new ProcessError("PROCESS_CLOSED"));
|
|
|
|
|
}
|
|
|
|
|
if (!this.values.sameSubject(values)) {
|
|
|
|
|
return Result.fail(new ProcessError("PROCESS_SUBJECT_IMMUTABLE"));
|
|
|
|
|
}
|
|
|
|
|
if (!this.values.sameTaskDefinitions(values)) {
|
|
|
|
|
return Result.fail(new ProcessError("PROCESS_TASKS_IMMUTABLE"));
|
|
|
|
|
}
|
|
|
|
|
if (values.state === "completed" && !values.complete) {
|
|
|
|
|
return Result.fail(new ProcessError("PROCESS_PENDING_TASKS"));
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
return Result.ok(
|
|
|
|
|
new PersonProcess(
|
|
|
|
|
{
|
|
|
|
|
companyId: this.companyId,
|
|
|
|
|
values,
|
|
|
|
|
history: [
|
|
|
|
|
...this.history,
|
|
|
|
|
PersonProcessRevision.create({ values, actorId: Maybe.some(actor), recordedAt }),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
this.id
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
appendReviews(
|
|
|
|
|
references: readonly ProcessReferenceInput[],
|
|
|
|
|
actor: UniqueID,
|
|
|
|
|
recordedAt: string
|
|
|
|
|
): Result<PersonProcess, ProcessError> {
|
|
|
|
|
if (this.values.closed) {
|
|
|
|
|
return Result.fail(new ProcessError("PROCESS_CLOSED"));
|
|
|
|
|
}
|
|
|
|
|
if (this.values.kind === "onboarding") {
|
|
|
|
|
return Result.ok(this);
|
|
|
|
|
}
|
|
|
|
|
const known = new Set(
|
|
|
|
|
this.values.tasks.flatMap((t) => {
|
|
|
|
|
const ref = t.toPrimitive().reference;
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
return ref ? [`${ref.kind}:${ref.id}`] : [];
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
const additions = references
|
|
|
|
|
.filter((ref) => !known.has(`${ref.kind}:${ref.id}`))
|
|
|
|
|
.map((ref) => ProcessTask.create(ref.label.slice(0, 160), ref));
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
if (!additions.length) {
|
|
|
|
|
return Result.ok(this);
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
!z.iso.datetime().safeParse(recordedAt).success ||
|
|
|
|
|
recordedAt < (this.history.at(-1)?.recordedAt ?? "")
|
|
|
|
|
) {
|
|
|
|
|
return Result.fail(new ProcessError("INVALID_REQUEST"));
|
|
|
|
|
}
|
|
|
|
|
const values = ProcessValues.create({
|
|
|
|
|
...this.values.toPrimitive(),
|
|
|
|
|
tasks: [...this.values.tasks, ...additions].map((t) => t.toPrimitive()),
|
|
|
|
|
});
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
if (!values.isSuccess) {
|
|
|
|
|
return values;
|
|
|
|
|
}
|
2026-10-10 10:01:58 +00:00
|
|
|
|
2026-10-07 07:34:18 +00:00
|
|
|
return Result.ok(
|
|
|
|
|
new PersonProcess(
|
|
|
|
|
{
|
|
|
|
|
companyId: this.companyId,
|
|
|
|
|
values: values.data,
|
|
|
|
|
history: [
|
|
|
|
|
...this.history,
|
|
|
|
|
PersonProcessRevision.create({
|
|
|
|
|
values: values.data,
|
|
|
|
|
actorId: Maybe.some(actor),
|
|
|
|
|
recordedAt,
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
this.id
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|