201 lines
6.5 KiB
TypeScript
201 lines
6.5 KiB
TypeScript
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<CatalogProps> {
|
|
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<UniqueID>) {
|
|
return this.definitions.filter(
|
|
(item) =>
|
|
!item.companyId.getOrUndefined() ||
|
|
item.companyId.getOrUndefined()?.equals(companyId.getOrUndefined())
|
|
);
|
|
}
|
|
active(item: ResourceDefinition, companyId: Maybe<UniqueID>) {
|
|
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<UniqueID>,
|
|
id: Maybe<UniqueID>,
|
|
values: ResourceDefinitionValues
|
|
): Result<ResourceCatalog, ResourceCatalogError> {
|
|
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<UniqueID>,
|
|
previous?: ResourceDefinition
|
|
): Result<ResourceCatalog, ResourceCatalogError> {
|
|
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<UniqueID>,
|
|
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<ResourceCatalog, ResourceCatalogError> {
|
|
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<UniqueID>,
|
|
id: UniqueID,
|
|
active: boolean | null
|
|
): Result<ResourceCatalog, ResourceCatalogError> {
|
|
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<boolean>() : 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<UniqueID>, b: Maybe<UniqueID>) {
|
|
return a.getOrUndefined()?.toPrimitive() === b.getOrUndefined()?.toPrimitive();
|
|
}
|