import type { UniqueID } from "@repo/rdx-ddd"; import { AggregateRoot } from "@repo/rdx-ddd"; import { Maybe, Result } from "@repo/rdx-utils"; import { ResourceCatalogError } from "./resource-definition"; import { CatalogAvailability, ResourceDefinition } from "./resource-definition.entity"; import type { ResourceDefinitionValues } from "./value-objects/resource-definition-values.vo"; type CatalogProps = { definitions: readonly ResourceDefinition[]; availability: readonly CatalogAvailability[]; }; export class ResourceCatalog extends AggregateRoot { private constructor(props: CatalogProps, id?: UniqueID) { super( { definitions: Object.freeze([...props.definitions]), availability: Object.freeze([...props.availability]), }, id ); } static create() { return new ResourceCatalog({ definitions: [], availability: [] }); } static rehydrate(props: CatalogProps, id: UniqueID) { return new ResourceCatalog(props, id); } get definitions() { return this.props.definitions; } get availability() { return this.props.availability; } visible(companyId: Maybe) { return this.definitions.filter( (item) => !item.companyId.getOrUndefined() || item.companyId.getOrUndefined()?.equals(companyId.getOrUndefined()) ); } active(item: ResourceDefinition, companyId: Maybe) { const override = this.availability.find( (entry) => entry.companyId.equals(companyId.getOrUndefined()) && entry.definitionId.equals(item.id) ); return override?.active.getOrUndefined() ?? item.active; } save( companyId: Maybe, id: Maybe, values: ResourceDefinitionValues ): Result { const previous = id.getOrUndefined() ? this.definitions.find((item) => item.id.equals(id.getOrUndefined())) : undefined; if (id.getOrUndefined() && !(previous && sameScope(previous.companyId, companyId))) { return Result.fail(new ResourceCatalogError("DEFINITION_NOT_FOUND")); } const candidate = previous ? previous.update(values) : Result.ok(ResourceDefinition.create(companyId, values, true)); if (!candidate.isSuccess) { return candidate; } return this.replace(candidate.data, companyId, previous); } private replace( candidate: ResourceDefinition, scope: Maybe, previous?: ResourceDefinition ): Result { const conflicts = scope.getOrUndefined() ? this.visible(scope) : this.definitions; if ( conflicts.some( (item) => !item.id.equals(candidate.id) && item.values.data.kind === candidate.values.data.kind && item.values.data.name.toLocaleLowerCase("es") === candidate.values.data.name.toLocaleLowerCase("es") ) ) { return Result.fail(new ResourceCatalogError("NAME_EXISTS")); } const invalidField = this.invalidField(candidate, scope, previous); if (invalidField) { return Result.fail(new ResourceCatalogError("FIELD_UNAVAILABLE")); } if (candidate === previous) { return Result.ok(this); } return Result.ok( new ResourceCatalog( { ...this.props, definitions: previous ? this.definitions.map((item) => (item.id.equals(candidate.id) ? candidate : item)) : [...this.definitions, candidate], }, this.id ) ); } private invalidField( candidate: ResourceDefinition, scope: Maybe, previous?: ResourceDefinition ) { return candidate.values.fieldIds.some((id) => { const field = this.visible(scope).find((item) => item.id.equals(id)); if (!field || field.values.data.kind !== "field") { return true; } return !( this.active(field, scope) || previous?.values.fieldIds.some((existing) => existing.equals(id)) ); }); } seed( stockKey: string, values: ResourceDefinitionValues ): Result { const previous = this.definitions.find((item) => item.stockKey.getOrUndefined() === stockKey); if (previous) { const upgraded = previous.initializeSections(values); if (upgraded === previous) { return Result.ok(this); } return Result.ok( new ResourceCatalog( { ...this.props, definitions: this.definitions.map((item) => item.id.equals(previous.id) ? upgraded : item ), }, this.id ) ); } return this.replace( ResourceDefinition.create(Maybe.none(), values, true, Maybe.some(stockKey)), Maybe.none() ); } setActive( scope: Maybe, id: UniqueID, active: boolean | null ): Result { const item = this.visible(scope).find((entry) => entry.id.equals(id)); if (!item) { return Result.fail(new ResourceCatalogError("DEFINITION_NOT_FOUND")); } const company = scope.getOrUndefined(); if (company && !item.companyId.getOrUndefined()) { const previous = this.availability.find( (entry) => entry.companyId.equals(company) && entry.definitionId.equals(id) ); const value = active === null ? Maybe.none() : Maybe.some(active); const changed = previous ? previous.update(value) : CatalogAvailability.create(company, id, value); if (previous === changed || (!previous && active === null)) { return Result.ok(this); } return Result.ok( new ResourceCatalog( { ...this.props, availability: previous ? this.availability.map((entry) => (entry.id.equals(previous.id) ? changed : entry)) : [...this.availability, changed], }, this.id ) ); } if (active === null) { return Result.fail(new ResourceCatalogError("INVALID_REQUEST")); } const changed = item.setActive(active); if (changed === item) { return Result.ok(this); } return Result.ok( new ResourceCatalog( { ...this.props, definitions: this.definitions.map((entry) => (entry.id.equals(id) ? changed : entry)), }, this.id ) ); } } function sameScope(a: Maybe, b: Maybe) { return a.getOrUndefined()?.toPrimitive() === b.getOrUndefined()?.toPrimitive(); }