import type { UniqueID } from "@repo/rdx-ddd"; import { DomainEntity } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; import { ResourceCatalogError } from "./resource-definition"; import { TemplateSection } from "./template-section.entity"; import { ResourceDefinitionValues } from "./value-objects/resource-definition-values.vo"; export class TemplateField extends DomainEntity<{ fieldId: UniqueID; required: boolean }> { static create(fieldId: UniqueID, required: boolean, id?: UniqueID) { return new TemplateField({ fieldId, required }, id); } static rehydrate(fieldId: UniqueID, required: boolean, id: UniqueID) { return new TemplateField({ fieldId, required }, id); } get fieldId() { return this.props.fieldId; } get required() { return this.props.required; } } type DefinitionProps = { companyId: Maybe; stockKey: Maybe; values: ResourceDefinitionValues; active: boolean; bindings: readonly TemplateField[]; }; export class ResourceDefinition extends DomainEntity { private constructor(props: DefinitionProps, id?: UniqueID) { super({ ...props, bindings: Object.freeze([...props.bindings]) }, id); } static create( companyId: Maybe, values: ResourceDefinitionValues, active: boolean, stockKey = Maybe.none() ) { const bindings = values.fieldIds.map((id, index) => TemplateField.create( id, values.data.kind === "template" && (values.data.fields[index]?.required ?? false) ) ); return new ResourceDefinition({ companyId, stockKey, values, active, bindings }); } static rehydrate(props: DefinitionProps, id: UniqueID) { return new ResourceDefinition(props, id); } get companyId() { return this.props.companyId; } get stockKey() { return this.props.stockKey; } get values() { return this.props.values; } get active() { return this.props.active; } get sections() { return this.values.data.kind === "template" ? (this.values.data.sections ?? []).map((section) => TemplateSection.rehydrate(section)) : []; } get bindings() { return this.props.bindings; } update(values: ResourceDefinitionValues): Result { const previous = this.values.data; const next = values.data; if ( previous.kind !== next.kind || (previous.kind === "template" && next.kind === "template" && previous.usage !== next.usage) || (previous.kind === "field" && next.kind === "field" && (previous.type !== next.type || previous.choices.some((choice) => !next.choices.includes(choice)))) ) { return Result.fail(new ResourceCatalogError("FIELD_TYPE_FIXED")); } if (this.values.equals(values)) { return Result.ok(this); } const bindings = values.fieldIds.map((fieldId, index) => TemplateField.create( fieldId, next.kind === "template" && (next.fields[index]?.required ?? false), this.bindings.find((field) => field.fieldId.equals(fieldId))?.id ) ); return Result.ok(new ResourceDefinition({ ...this.props, values, bindings }, this.id)); } /** Ampliación idempotente de presentación de serie; conserva datos y disponibilidad. */ initializeSections(source: ResourceDefinitionValues) { const previous = this.values.data; const next = source.data; if ( !this.stockKey.getOrUndefined() || previous.kind !== "template" || next.kind !== "template" || previous.sections !== undefined || !next.sections ) { return this; } // Solo asociar secciones a campos ya presentes, sin cambiar orden ni obligatoriedad. const known = new Set(previous.fields.map((field) => field.fieldId)); const sections = next.sections.filter((section) => next.fields.some((field) => field.sectionId === section.id && known.has(field.fieldId)) ); const sectionIds = new Set(sections.map((section) => section.id)); const fields = previous.fields.map((field) => { const sectionId = next.fields.find((entry) => entry.fieldId === field.fieldId)?.sectionId; return { ...field, ...(sectionId && sectionIds.has(sectionId) ? { sectionId } : {}) }; }); const values = ResourceDefinitionValues.create({ ...previous, sections, fields }); if (!values.isSuccess) { throw new Error("Secciones iniciales de plantilla inconsistentes."); } return new ResourceDefinition({ ...this.props, values: values.data }, this.id); } setActive(active: boolean) { return active === this.active ? this : new ResourceDefinition({ ...this.props, active }, this.id); } } export class CatalogAvailability extends DomainEntity<{ companyId: UniqueID; definitionId: UniqueID; active: Maybe; }> { static create( companyId: UniqueID, definitionId: UniqueID, active: Maybe, id?: UniqueID ) { return new CatalogAvailability({ companyId, definitionId, active }, id); } static rehydrate( companyId: UniqueID, definitionId: UniqueID, active: Maybe, id: UniqueID ) { return new CatalogAvailability({ companyId, definitionId, active }, id); } get companyId() { return this.props.companyId; } get definitionId() { return this.props.definitionId; } get active() { return this.props.active; } update(active: Maybe) { return active.getOrUndefined() === this.active.getOrUndefined() ? this : CatalogAvailability.create(this.companyId, this.definitionId, active, this.id); } }