- Added @repo/rdx-audit as a dependency to the suppliers module. - Implemented audit recording in SupplierCreator and SupplierUpdater services. - Created supplier-audit module to handle audit actions and state transformations. - Updated supplier aggregate to reflect changes in update method return type. - Added integration tests for supplier audit functionality, ensuring correct audit records are created on supplier creation and updates. - Refactored dependency injection to include audit service in supplier-related use cases.
364 lines
9.1 KiB
TypeScript
364 lines
9.1 KiB
TypeScript
import {
|
|
AggregateRoot,
|
|
type CurrencyCode,
|
|
type EmailAddress,
|
|
type LanguageCode,
|
|
type Name,
|
|
type PhoneNumber,
|
|
PostalAddress,
|
|
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>;
|
|
|
|
// 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;
|
|
}
|
|
|
|
export type SupplierInternalProps = Omit<ISupplierCreateProps, "address" | "status"> & {
|
|
address: PostalAddress;
|
|
status: SupplierStatus;
|
|
};
|
|
|
|
/**
|
|
* 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,
|
|
status: props.status ?? SupplierStatus.active(),
|
|
},
|
|
id
|
|
);
|
|
|
|
// Reglas de negocio / validaciones
|
|
// ...
|
|
// ...
|
|
|
|
// Disparar eventos de dominio
|
|
// ...
|
|
// ...
|
|
|
|
return Result.ok(contact);
|
|
}
|
|
|
|
private static validateCreateProps(_props: ISupplierCreateProps): Result<void, Error> {
|
|
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> {
|
|
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;
|
|
}
|
|
|
|
if (partialAddress) {
|
|
const addressResult = PostalAddress.create({
|
|
...this.address.getProps(),
|
|
...partialAddress,
|
|
});
|
|
|
|
if (addressResult.isFailure) {
|
|
return Result.fail(addressResult.error);
|
|
}
|
|
|
|
if (!this.address.equals(addressResult.data)) {
|
|
nextProps.address = addressResult.data;
|
|
hasChanges = true;
|
|
}
|
|
}
|
|
|
|
if (hasChanges) {
|
|
Object.assign(this.props, nextProps);
|
|
}
|
|
|
|
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()
|
|
);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|