FactuGES_ERP/modules/suppliers/src/api/domain/aggregates/supplier.aggregate.ts

364 lines
9.1 KiB
TypeScript
Raw Normal View History

2026-03-30 11:57:42 +00:00
import {
AggregateRoot,
type CurrencyCode,
type EmailAddress,
type LanguageCode,
type Name,
type PhoneNumber,
PostalAddress,
2026-03-30 11:57:42 +00:00
type PostalAddressPatchProps,
type PostalAddressProps,
type TINNumber,
type URLAddress,
type UniqueID,
} from "@repo/rdx-ddd";
import { type Maybe, Result } from "@repo/rdx-utils";
import { SupplierStatus } from "../value-objects/supplier-status.value-object";
/**
* Props de creación
*/
export interface ISupplierCreateProps {
companyId: UniqueID;
status?: SupplierStatus;
reference: Maybe<Name>;
isCompany: boolean;
name: Name;
tradeName: Maybe<Name>;
tin: Maybe<TINNumber>;
address: PostalAddressProps;
emailPrimary: Maybe<EmailAddress>;
emailSecondary: Maybe<EmailAddress>;
phonePrimary: Maybe<PhoneNumber>;
phoneSecondary: Maybe<PhoneNumber>;
mobilePrimary: Maybe<PhoneNumber>;
mobileSecondary: Maybe<PhoneNumber>;
fax: Maybe<PhoneNumber>;
website: Maybe<URLAddress>;
languageCode: LanguageCode;
currencyCode: CurrencyCode;
}
export type SupplierPatchProps = Partial<
Omit<ISupplierCreateProps, "companyId" | "address" | "status">
> & {
address?: PostalAddressPatchProps;
};
// Supplier
export interface ISupplier {
// comportamiento
update(partialSupplier: SupplierPatchProps): Result<boolean, Error>;
2026-03-30 11:57:42 +00:00
// propiedades (getters)
readonly isIndividual: boolean;
readonly isCompany: boolean;
readonly isActive: boolean;
readonly companyId: UniqueID;
readonly reference: Maybe<Name>;
readonly name: Name;
readonly tradeName: Maybe<Name>;
readonly tin: Maybe<TINNumber>;
readonly address: PostalAddress;
readonly emailPrimary: Maybe<EmailAddress>;
readonly emailSecondary: Maybe<EmailAddress>;
readonly phonePrimary: Maybe<PhoneNumber>;
readonly phoneSecondary: Maybe<PhoneNumber>;
readonly mobilePrimary: Maybe<PhoneNumber>;
readonly mobileSecondary: Maybe<PhoneNumber>;
readonly fax: Maybe<PhoneNumber>;
readonly website: Maybe<URLAddress>;
readonly languageCode: LanguageCode;
readonly currencyCode: CurrencyCode;
}
2026-08-24 14:24:58 +00:00
export type SupplierInternalProps = Omit<ISupplierCreateProps, "address" | "status"> & {
address: PostalAddress;
status: SupplierStatus;
2026-03-30 11:57:42 +00:00
};
/**
* Aggregate Root: Supplier
*/
export class Supplier extends AggregateRoot<SupplierInternalProps> implements ISupplier {
static create(props: ISupplierCreateProps, id?: UniqueID): Result<Supplier, Error> {
const validationResult = Supplier.validateCreateProps(props);
if (validationResult.isFailure) {
return Result.fail(validationResult.error);
}
const { address, ...internalProps } = props;
const postalAddressResult = PostalAddress.create(address);
if (postalAddressResult.isFailure) {
return Result.fail(postalAddressResult.error);
}
const contact = new Supplier(
{
...internalProps,
address: postalAddressResult.data,
2026-08-24 14:24:58 +00:00
status: props.status ?? SupplierStatus.active(),
2026-03-30 11:57:42 +00:00
},
id
);
// Reglas de negocio / validaciones
// ...
// ...
// Disparar eventos de dominio
// ...
// ...
return Result.ok(contact);
}
2026-08-24 14:24:58 +00:00
private static validateCreateProps(_props: ISupplierCreateProps): Result<void, Error> {
2026-03-30 11:57:42 +00:00
return Result.ok();
}
// Rehidratación desde persistencia
static rehydrate(props: SupplierInternalProps, id: UniqueID): Supplier {
return new Supplier(props, id);
}
// -------------------------
// BEHAVIOUR
// -------------------------
public update(partialSupplier: SupplierPatchProps): Result<boolean, Error> {
2026-03-30 11:57:42 +00:00
const { address: partialAddress, ...rest } = partialSupplier;
const nextProps: SupplierInternalProps = { ...this.props };
let hasChanges = false;
if (
rest.reference !== undefined &&
!Supplier.sameMaybeValueObject(this.reference, rest.reference)
) {
nextProps.reference = rest.reference;
hasChanges = true;
}
if (rest.isCompany !== undefined && this.isCompany !== rest.isCompany) {
nextProps.isCompany = rest.isCompany;
hasChanges = true;
}
if (rest.name !== undefined && !this.name.equals(rest.name)) {
nextProps.name = rest.name;
hasChanges = true;
}
if (
rest.tradeName !== undefined &&
!Supplier.sameMaybeValueObject(this.tradeName, rest.tradeName)
) {
nextProps.tradeName = rest.tradeName;
hasChanges = true;
}
if (rest.tin !== undefined && !Supplier.sameMaybeValueObject(this.tin, rest.tin)) {
nextProps.tin = rest.tin;
hasChanges = true;
}
if (
rest.emailPrimary !== undefined &&
!Supplier.sameMaybeValueObject(this.emailPrimary, rest.emailPrimary)
) {
nextProps.emailPrimary = rest.emailPrimary;
hasChanges = true;
}
if (
rest.emailSecondary !== undefined &&
!Supplier.sameMaybeValueObject(this.emailSecondary, rest.emailSecondary)
) {
nextProps.emailSecondary = rest.emailSecondary;
hasChanges = true;
}
if (
rest.phonePrimary !== undefined &&
!Supplier.sameMaybeValueObject(this.phonePrimary, rest.phonePrimary)
) {
nextProps.phonePrimary = rest.phonePrimary;
hasChanges = true;
}
if (
rest.phoneSecondary !== undefined &&
!Supplier.sameMaybeValueObject(this.phoneSecondary, rest.phoneSecondary)
) {
nextProps.phoneSecondary = rest.phoneSecondary;
hasChanges = true;
}
if (
rest.mobilePrimary !== undefined &&
!Supplier.sameMaybeValueObject(this.mobilePrimary, rest.mobilePrimary)
) {
nextProps.mobilePrimary = rest.mobilePrimary;
hasChanges = true;
}
if (
rest.mobileSecondary !== undefined &&
!Supplier.sameMaybeValueObject(this.mobileSecondary, rest.mobileSecondary)
) {
nextProps.mobileSecondary = rest.mobileSecondary;
hasChanges = true;
}
if (rest.fax !== undefined && !Supplier.sameMaybeValueObject(this.fax, rest.fax)) {
nextProps.fax = rest.fax;
hasChanges = true;
}
if (rest.website !== undefined && !Supplier.sameMaybeValueObject(this.website, rest.website)) {
nextProps.website = rest.website;
hasChanges = true;
}
if (rest.languageCode !== undefined && !this.languageCode.equals(rest.languageCode)) {
nextProps.languageCode = rest.languageCode;
hasChanges = true;
}
if (rest.currencyCode !== undefined && !this.currencyCode.equals(rest.currencyCode)) {
nextProps.currencyCode = rest.currencyCode;
hasChanges = true;
}
2026-03-30 11:57:42 +00:00
if (partialAddress) {
2026-08-24 14:24:58 +00:00
const addressResult = PostalAddress.create({
...this.address.getProps(),
...partialAddress,
});
2026-03-30 11:57:42 +00:00
if (addressResult.isFailure) {
return Result.fail(addressResult.error);
}
2026-08-24 14:24:58 +00:00
if (!this.address.equals(addressResult.data)) {
nextProps.address = addressResult.data;
hasChanges = true;
}
2026-03-30 11:57:42 +00:00
}
if (hasChanges) {
Object.assign(this.props, nextProps);
}
2026-08-24 14:24:58 +00:00
return Result.ok(hasChanges);
}
private static sameMaybeValueObject<T extends { equals(other: T): boolean }>(
current: Maybe<T>,
next: Maybe<T>
): boolean {
return current.match(
(currentValue) =>
next.match(
(nextValue) => currentValue.equals(nextValue),
() => false
),
() => next.isNone()
);
2026-03-30 11:57:42 +00:00
}
public activate(): Result<Supplier, Error> {
this.props.status = SupplierStatus.active();
return Result.ok(this);
}
public deactivate(): Result<Supplier, Error> {
this.props.status = SupplierStatus.inactive();
return Result.ok(this);
}
// -------------------------
// GETTERS
// -------------------------
public get isIndividual(): boolean {
return !this.props.isCompany;
}
public get isCompany(): boolean {
return this.props.isCompany;
}
public get isActive(): boolean {
return this.props.status.isActive();
}
public get companyId(): UniqueID {
return this.props.companyId;
}
public get reference(): Maybe<Name> {
return this.props.reference;
}
public get name(): Name {
return this.props.name;
}
public get tradeName(): Maybe<Name> {
return this.props.tradeName;
}
public get tin(): Maybe<TINNumber> {
return this.props.tin;
}
public get address(): PostalAddress {
return this.props.address;
}
public get emailPrimary(): Maybe<EmailAddress> {
return this.props.emailPrimary;
}
public get emailSecondary(): Maybe<EmailAddress> {
return this.props.emailSecondary;
}
public get phonePrimary(): Maybe<PhoneNumber> {
return this.props.phonePrimary;
}
public get phoneSecondary(): Maybe<PhoneNumber> {
return this.props.phoneSecondary;
}
public get mobilePrimary(): Maybe<PhoneNumber> {
return this.props.mobilePrimary;
}
public get mobileSecondary(): Maybe<PhoneNumber> {
return this.props.mobileSecondary;
}
public get fax(): Maybe<PhoneNumber> {
return this.props.fax;
}
public get website(): Maybe<URLAddress> {
return this.props.website;
}
public get languageCode(): LanguageCode {
return this.props.languageCode;
}
public get currencyCode(): CurrencyCode {
return this.props.currencyCode;
}
}