Facturas de cliente y clientes
This commit is contained in:
parent
4a36097290
commit
ad66580d85
@ -34,6 +34,9 @@ export abstract class ExpressController {
|
|||||||
} satisfies ApiErrorContext;
|
} satisfies ApiErrorContext;
|
||||||
|
|
||||||
const body = toProblemJson(apiError, ctx);
|
const body = toProblemJson(apiError, ctx);
|
||||||
|
|
||||||
|
console.trace(body);
|
||||||
|
|
||||||
return res.type("application/problem+json").status(apiError.status).json(body);
|
return res.type("application/problem+json").status(apiError.status).json(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { AggregateRoot, MoneyValue, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
import { AggregateRoot, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { CustomerInvoiceCustomer, CustomerInvoiceItem, CustomerInvoiceItems } from "../entities";
|
import { CustomerInvoiceCustomer, CustomerInvoiceItem, CustomerInvoiceItems } from "../entities";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { IMoneyValueProps, MoneyValue } from "@repo/rdx-ddd";
|
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
export class CustomerInvoiceItemSubtotalPrice extends MoneyValue {
|
export class CustomerInvoiceItemSubtotalPrice extends MoneyValue {
|
||||||
public static DEFAULT_SCALE = 4;
|
public static DEFAULT_SCALE = 4;
|
||||||
|
|
||||||
static create({ amount, currency_code, scale }: IMoneyValueProps) {
|
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||||
const props = {
|
const props = {
|
||||||
amount: Number(amount),
|
amount: Number(amount),
|
||||||
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { IMoneyValueProps, MoneyValue } from "@repo/rdx-ddd";
|
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
export class CustomerInvoiceItemTotalPrice extends MoneyValue {
|
export class CustomerInvoiceItemTotalPrice extends MoneyValue {
|
||||||
public static DEFAULT_SCALE = 4;
|
public static DEFAULT_SCALE = 4;
|
||||||
|
|
||||||
static create({ amount, currency_code, scale }: IMoneyValueProps) {
|
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||||
const props = {
|
const props = {
|
||||||
amount: Number(amount),
|
amount: Number(amount),
|
||||||
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { IMoneyValueProps, MoneyValue } from "@repo/rdx-ddd";
|
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
export class CustomerInvoiceItemUnitPrice extends MoneyValue {
|
export class CustomerInvoiceItemUnitPrice extends MoneyValue {
|
||||||
public static DEFAULT_SCALE = 4;
|
public static DEFAULT_SCALE = 4;
|
||||||
|
|
||||||
static create({ amount, currency_code, scale }: IMoneyValueProps) {
|
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||||
const props = {
|
const props = {
|
||||||
amount: Number(amount),
|
amount: Number(amount),
|
||||||
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
||||||
|
|||||||
@ -24,7 +24,8 @@
|
|||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/react": "^19.1.2",
|
"@types/react": "^19.1.2",
|
||||||
"@types/react-i18next": "^8.1.0",
|
"@types/react-i18next": "^8.1.0",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3",
|
||||||
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ag-grid-community/locale": "34.0.0",
|
"@ag-grid-community/locale": "34.0.0",
|
||||||
|
|||||||
@ -1,35 +1,43 @@
|
|||||||
|
import { toEmptyString } from "@repo/rdx-ddd";
|
||||||
import { CustomerCreationResponseDTO } from "../../../../common";
|
import { CustomerCreationResponseDTO } from "../../../../common";
|
||||||
import { Customer } from "../../../domain";
|
import { Customer } from "../../../domain";
|
||||||
|
|
||||||
export class CreateCustomersAssembler {
|
export class CreateCustomersAssembler {
|
||||||
public toDTO(customer: Customer): CustomerCreationResponseDTO {
|
public toDTO(customer: Customer): CustomerCreationResponseDTO {
|
||||||
|
const address = customer.address.toPrimitive();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: customer.id.toPrimitive(),
|
id: customer.id.toPrimitive(),
|
||||||
company_id: customer.companyId.toPrimitive(),
|
company_id: customer.companyId.toPrimitive(),
|
||||||
reference: customer.reference,
|
|
||||||
is_company: customer.isCompany,
|
|
||||||
name: customer.name,
|
|
||||||
trade_name: customer.tradeName,
|
|
||||||
tin: customer.tin.toPrimitive(),
|
|
||||||
|
|
||||||
email: customer.email.toPrimitive(),
|
reference: toEmptyString(customer.reference, (value) => value.toPrimitive()),
|
||||||
phone: customer.phone.toPrimitive(),
|
|
||||||
fax: customer.fax.toPrimitive(),
|
|
||||||
website: customer.website,
|
|
||||||
|
|
||||||
default_tax: customer.defaultTax,
|
is_company: String(customer.isCompany),
|
||||||
legal_record: customer.legalRecord,
|
name: customer.name.toPrimitive(),
|
||||||
lang_code: customer.langCode,
|
|
||||||
currency_code: customer.currencyCode,
|
trade_name: toEmptyString(customer.tradeName, (value) => value.toPrimitive()),
|
||||||
|
|
||||||
|
tin: toEmptyString(customer.tin, (value) => value.toPrimitive()),
|
||||||
|
|
||||||
|
street: toEmptyString(address.street, (value) => value.toPrimitive()),
|
||||||
|
street2: toEmptyString(address.street2, (value) => value.toPrimitive()),
|
||||||
|
city: toEmptyString(address.city, (value) => value.toPrimitive()),
|
||||||
|
state: toEmptyString(address.province, (value) => value.toPrimitive()),
|
||||||
|
postal_code: toEmptyString(address.postalCode, (value) => value.toPrimitive()),
|
||||||
|
country: toEmptyString(address.country, (value) => value.toPrimitive()),
|
||||||
|
|
||||||
|
email: toEmptyString(customer.email, (value) => value.toPrimitive()),
|
||||||
|
phone: toEmptyString(customer.phone, (value) => value.toPrimitive()),
|
||||||
|
fax: toEmptyString(customer.fax, (value) => value.toPrimitive()),
|
||||||
|
website: toEmptyString(customer.website, (value) => value.toPrimitive()),
|
||||||
|
|
||||||
|
legal_record: toEmptyString(customer.legalRecord, (value) => value.toPrimitive()),
|
||||||
|
|
||||||
|
default_taxes: customer.defaultTaxes.map((item) => item.toPrimitive()),
|
||||||
|
|
||||||
status: customer.isActive ? "active" : "inactive",
|
status: customer.isActive ? "active" : "inactive",
|
||||||
|
language_code: customer.languageCode.toPrimitive(),
|
||||||
street: customer.address.street,
|
currency_code: customer.currencyCode.toPrimitive(),
|
||||||
street2: customer.address.street2,
|
|
||||||
city: customer.address.city,
|
|
||||||
state: customer.address.state,
|
|
||||||
postal_code: customer.address.postalCode,
|
|
||||||
country: customer.address.country,
|
|
||||||
|
|
||||||
metadata: {
|
metadata: {
|
||||||
entity: "customer",
|
entity: "customer",
|
||||||
|
|||||||
@ -3,9 +3,9 @@ import { UniqueID } from "@repo/rdx-ddd";
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { Transaction } from "sequelize";
|
import { Transaction } from "sequelize";
|
||||||
import { CreateCustomerRequestDTO } from "../../../common";
|
import { CreateCustomerRequestDTO } from "../../../common";
|
||||||
import { ICustomerService } from "../../domain";
|
import { CustomerService } from "../../domain";
|
||||||
import { mapDTOToCustomerProps } from "../../helpers";
|
|
||||||
import { CreateCustomersAssembler } from "./assembler";
|
import { CreateCustomersAssembler } from "./assembler";
|
||||||
|
import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props";
|
||||||
|
|
||||||
type CreateCustomerUseCaseInput = {
|
type CreateCustomerUseCaseInput = {
|
||||||
dto: CreateCustomerRequestDTO;
|
dto: CreateCustomerRequestDTO;
|
||||||
@ -13,7 +13,7 @@ type CreateCustomerUseCaseInput = {
|
|||||||
|
|
||||||
export class CreateCustomerUseCase {
|
export class CreateCustomerUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly service: ICustomerService,
|
private readonly service: CustomerService,
|
||||||
private readonly transactionManager: ITransactionManager,
|
private readonly transactionManager: ITransactionManager,
|
||||||
private readonly assembler: CreateCustomersAssembler
|
private readonly assembler: CreateCustomersAssembler
|
||||||
) {}
|
) {}
|
||||||
@ -22,7 +22,7 @@ export class CreateCustomerUseCase {
|
|||||||
const { dto } = params;
|
const { dto } = params;
|
||||||
|
|
||||||
// 1) Mapear DTO → props de dominio
|
// 1) Mapear DTO → props de dominio
|
||||||
const dtoResult = mapDTOToCustomerProps(dto);
|
const dtoResult = mapDTOToCreateCustomerProps(dto);
|
||||||
if (dtoResult.isFailure) {
|
if (dtoResult.isFailure) {
|
||||||
return Result.fail(dtoResult.error);
|
return Result.fail(dtoResult.error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,228 @@
|
|||||||
|
import {
|
||||||
|
DomainError,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
import {
|
||||||
|
City,
|
||||||
|
Country,
|
||||||
|
CurrencyCode,
|
||||||
|
EmailAddress,
|
||||||
|
LanguageCode,
|
||||||
|
Name,
|
||||||
|
PhoneNumber,
|
||||||
|
PostalAddress,
|
||||||
|
PostalCode,
|
||||||
|
Province,
|
||||||
|
Street,
|
||||||
|
TINNumber,
|
||||||
|
TaxCode,
|
||||||
|
TextValue,
|
||||||
|
URLAddress,
|
||||||
|
UniqueID,
|
||||||
|
maybeFromNullableVO,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
|
import { CreateCustomerRequestDTO } from "../../../common/dto";
|
||||||
|
import { CustomerProps, CustomerStatus } from "../../domain";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convierte el DTO a las props validadas (CustomerProps).
|
||||||
|
* No construye directamente el agregado.
|
||||||
|
*
|
||||||
|
* @param dto - DTO con los datos de la factura de cliente
|
||||||
|
* @returns
|
||||||
|
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function mapDTOToCreateCustomerProps(dto: CreateCustomerRequestDTO) {
|
||||||
|
try {
|
||||||
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
|
const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
||||||
|
const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_id", errors);
|
||||||
|
|
||||||
|
const isCompany = dto.is_company;
|
||||||
|
const status = extractOrPushError(CustomerStatus.create(dto.status), "status", errors);
|
||||||
|
const reference = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.reference, (value) => Name.create(value)),
|
||||||
|
"reference",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const name = extractOrPushError(Name.create(dto.name), "name", errors);
|
||||||
|
|
||||||
|
const tradeName = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.trade_name, (value) => Name.create(value)),
|
||||||
|
"trade_name",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const tinNumber = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.tin, (value) => TINNumber.create(value)),
|
||||||
|
"tin",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const street = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.street, (value) => Street.create(value)),
|
||||||
|
"street",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const street2 = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.street2, (value) => Street.create(value)),
|
||||||
|
"street2",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const city = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.city, (value) => City.create(value)),
|
||||||
|
"city",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const province = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.province, (value) => Province.create(value)),
|
||||||
|
"province",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const postalCode = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.postal_code, (value) => PostalCode.create(value)),
|
||||||
|
"postal_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const country = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.country, (value) => Country.create(value)),
|
||||||
|
"country",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const emailAddress = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.email, (value) => EmailAddress.create(value)),
|
||||||
|
"email",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const phoneNumber = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.phone, (value) => PhoneNumber.create(value)),
|
||||||
|
"phone",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const faxNumber = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.fax, (value) => PhoneNumber.create(value)),
|
||||||
|
"fax",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const website = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.website, (value) => URLAddress.create(value)),
|
||||||
|
"website",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const legalRecord = extractOrPushError(
|
||||||
|
maybeFromNullableVO(dto.legal_record, (value) => TextValue.create(value)),
|
||||||
|
"legal_record",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const languageCode = extractOrPushError(
|
||||||
|
LanguageCode.create(dto.language_code),
|
||||||
|
"language_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const currencyCode = extractOrPushError(
|
||||||
|
CurrencyCode.create(dto.currency_code),
|
||||||
|
"currency_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultTaxes = new Collection<TaxCode>();
|
||||||
|
|
||||||
|
dto.default_taxes.map((taxCode, index) => {
|
||||||
|
const tax = extractOrPushError(TaxCode.create(taxCode), `default_taxes.${index}`, errors);
|
||||||
|
if (tax) {
|
||||||
|
defaultTaxes.add(tax!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error(errors);
|
||||||
|
return Result.fail(new ValidationErrorCollection("Customer props mapping failed", errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
const postalAddressProps = {
|
||||||
|
street: street!,
|
||||||
|
street2: street2!,
|
||||||
|
city: city!,
|
||||||
|
postalCode: postalCode!,
|
||||||
|
province: province!,
|
||||||
|
country: country!,
|
||||||
|
};
|
||||||
|
|
||||||
|
const postalAddress = extractOrPushError(
|
||||||
|
PostalAddress.create(postalAddressProps),
|
||||||
|
"address",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
console.debug("Mapped customer props:", {
|
||||||
|
companyId,
|
||||||
|
status,
|
||||||
|
reference,
|
||||||
|
isCompany,
|
||||||
|
name,
|
||||||
|
tradeName,
|
||||||
|
tinNumber,
|
||||||
|
street,
|
||||||
|
street2,
|
||||||
|
city,
|
||||||
|
province,
|
||||||
|
postalCode,
|
||||||
|
country,
|
||||||
|
emailAddress,
|
||||||
|
phoneNumber,
|
||||||
|
faxNumber,
|
||||||
|
website,
|
||||||
|
legalRecord,
|
||||||
|
languageCode,
|
||||||
|
currencyCode,
|
||||||
|
defaultTaxes,
|
||||||
|
});
|
||||||
|
|
||||||
|
const customerProps: CustomerProps = {
|
||||||
|
companyId: companyId!,
|
||||||
|
status: status!,
|
||||||
|
reference: reference!,
|
||||||
|
|
||||||
|
isCompany: isCompany,
|
||||||
|
name: name!,
|
||||||
|
tradeName: tradeName!,
|
||||||
|
tin: tinNumber!,
|
||||||
|
|
||||||
|
address: postalAddress!,
|
||||||
|
|
||||||
|
email: emailAddress!,
|
||||||
|
phone: phoneNumber!,
|
||||||
|
fax: faxNumber!,
|
||||||
|
website: website!,
|
||||||
|
|
||||||
|
legalRecord: legalRecord!,
|
||||||
|
defaultTaxes: defaultTaxes!,
|
||||||
|
languageCode: languageCode!,
|
||||||
|
currencyCode: currencyCode!,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Result.ok({ id: customerId!, props: customerProps });
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(err);
|
||||||
|
return Result.fail(new DomainError("Customer props mapping failed", { cause: err }));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,36 +1,43 @@
|
|||||||
import { EntityNotFoundError, ITransactionManager } from "@erp/core/api";
|
import { EntityNotFoundError, ITransactionManager } from "@erp/core/api";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { ICustomerService } from "../../domain";
|
import { CustomerService } from "../../domain";
|
||||||
|
|
||||||
|
type DeleteCustomerUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class DeleteCustomerUseCase {
|
export class DeleteCustomerUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly service: ICustomerService,
|
private readonly service: CustomerService,
|
||||||
private readonly transactionManager: ITransactionManager
|
private readonly transactionManager: ITransactionManager
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(dto: DeleteCustomerByIdQueryDTO) {
|
public execute(params: DeleteCustomerUseCaseInput) {
|
||||||
const idOrError = UniqueID.create(dto.id);
|
const { companyId, id } = params;
|
||||||
|
|
||||||
|
const idOrError = UniqueID.create(id);
|
||||||
|
|
||||||
if (idOrError.isFailure) {
|
if (idOrError.isFailure) {
|
||||||
return Result.fail(idOrError.error);
|
return Result.fail(idOrError.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = idOrError.data;
|
const validId = idOrError.data;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
const existsCheck = await this.service.existsByIdInCompany(id, transaction);
|
const existsCheck = await this.service.existsByIdInCompany(companyId, validId, transaction);
|
||||||
|
|
||||||
if (existsCheck.isFailure) {
|
if (existsCheck.isFailure) {
|
||||||
return Result.fail(existsCheck.error);
|
return Result.fail(existsCheck.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existsCheck.data) {
|
if (!existsCheck.data) {
|
||||||
return Result.fail(new EntityNotFoundError("Customer", "id", id.toString()));
|
return Result.fail(new EntityNotFoundError("Customer", "id", validId.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.service.deleteCustomerByIdInCompany(id, transaction);
|
return await this.service.deleteCustomerByIdInCompany(validId, transaction);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
return Result.fail(error as Error);
|
return Result.fail(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,63 +1,43 @@
|
|||||||
|
import { toEmptyString } from "@repo/rdx-ddd";
|
||||||
import { GetCustomerByIdResponseDTO } from "../../../../common/dto";
|
import { GetCustomerByIdResponseDTO } from "../../../../common/dto";
|
||||||
import { Customer } from "../../../domain";
|
import { Customer } from "../../../domain";
|
||||||
|
|
||||||
export class GetCustomerAssembler {
|
export class GetCustomerAssembler {
|
||||||
toDTO(customer: Customer): GetCustomerByIdResponseDTO {
|
toDTO(customer: Customer): GetCustomerByIdResponseDTO {
|
||||||
|
const address = customer.address.toPrimitive();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: customer.id.toPrimitive(),
|
id: customer.id.toPrimitive(),
|
||||||
reference: customer.reference,
|
company_id: customer.companyId.toPrimitive(),
|
||||||
|
|
||||||
is_company: customer.isCompany,
|
reference: toEmptyString(customer.reference, (value) => value.toPrimitive()),
|
||||||
name: customer.name,
|
|
||||||
trade_name: customer.tradeName ?? "",
|
|
||||||
tin: customer.tin.toPrimitive(),
|
|
||||||
|
|
||||||
metadata: {
|
is_company: String(customer.isCompany),
|
||||||
entity: "customer",
|
name: customer.name.toPrimitive(),
|
||||||
//updated_at: customer.updatedAt.toDateString(),
|
|
||||||
//created_at: customer.createdAt.toDateString(),
|
|
||||||
},
|
|
||||||
|
|
||||||
//subtotal: customer.calculateSubtotal().toPrimitive(),
|
trade_name: toEmptyString(customer.tradeName, (value) => value.toPrimitive()),
|
||||||
|
|
||||||
//total: customer.calculateTotal().toPrimitive(),
|
tin: toEmptyString(customer.tin, (value) => value.toPrimitive()),
|
||||||
|
|
||||||
/*items:
|
street: toEmptyString(address.street, (value) => value.toPrimitive()),
|
||||||
customer.items.size() > 0
|
street2: toEmptyString(address.street2, (value) => value.toPrimitive()),
|
||||||
? customer.items.map((item: CustomerItem) => ({
|
city: toEmptyString(address.city, (value) => value.toPrimitive()),
|
||||||
description: item.description.toString(),
|
state: toEmptyString(address.province, (value) => value.toPrimitive()),
|
||||||
quantity: item.quantity.toPrimitive(),
|
postal_code: toEmptyString(address.postalCode, (value) => value.toPrimitive()),
|
||||||
unit_measure: "",
|
country: toEmptyString(address.country, (value) => value.toPrimitive()),
|
||||||
unit_price: item.unitPrice.toPrimitive(),
|
|
||||||
subtotal: item.calculateSubtotal().toPrimitive(),
|
|
||||||
//tax_amount: item.calculateTaxAmount().toPrimitive(),
|
|
||||||
total: item.calculateTotal().toPrimitive(),
|
|
||||||
}))
|
|
||||||
: [],*/
|
|
||||||
|
|
||||||
//sender: {}, //await CustomerParticipantAssembler(customer.senderId, context),
|
email: toEmptyString(customer.email, (value) => value.toPrimitive()),
|
||||||
|
phone: toEmptyString(customer.phone, (value) => value.toPrimitive()),
|
||||||
|
fax: toEmptyString(customer.fax, (value) => value.toPrimitive()),
|
||||||
|
website: toEmptyString(customer.website, (value) => value.toPrimitive()),
|
||||||
|
|
||||||
/*recipient: await CustomerParticipantAssembler(customer.recipient, context),
|
legal_record: toEmptyString(customer.legalRecord, (value) => value.toPrimitive()),
|
||||||
items: customerItemAssembler(customer.items, context),
|
|
||||||
|
|
||||||
payment_term: {
|
default_taxes: customer.defaultTaxes.map((item) => item.toPrimitive()),
|
||||||
payment_type: "",
|
|
||||||
due_date: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
due_amount: {
|
status: customer.isActive ? "active" : "inactive",
|
||||||
currency: customer.currency.toString(),
|
language_code: customer.languageCode.toPrimitive(),
|
||||||
precision: 2,
|
currency_code: customer.currencyCode.toPrimitive(),
|
||||||
amount: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
custom_fields: [],
|
|
||||||
|
|
||||||
metadata: {
|
|
||||||
create_time: "",
|
|
||||||
last_updated_time: "",
|
|
||||||
delete_time: "",
|
|
||||||
},*/
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,24 @@
|
|||||||
import { ITransactionManager } from "@erp/core/api";
|
import { ITransactionManager } from "@erp/core/api";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { ICustomerService } from "../../domain";
|
import { CustomerService } from "../../domain";
|
||||||
import { GetCustomerAssembler } from "./assembler";
|
import { GetCustomerAssembler } from "./assembler";
|
||||||
|
|
||||||
type GetCustomerUseCaseInput = {
|
type GetCustomerUseCaseInput = {
|
||||||
tenantId: string;
|
companyId: UniqueID;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class GetCustomerUseCase {
|
export class GetCustomerUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly service: ICustomerService,
|
private readonly service: CustomerService,
|
||||||
private readonly transactionManager: ITransactionManager,
|
private readonly transactionManager: ITransactionManager,
|
||||||
private readonly assembler: GetCustomerAssembler
|
private readonly assembler: GetCustomerAssembler
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(params: GetCustomerUseCaseInput) {
|
public execute(params: GetCustomerUseCaseInput) {
|
||||||
const { id, tenantId: companyId } = params;
|
console.log(params);
|
||||||
|
const { id, companyId } = params;
|
||||||
|
|
||||||
const idOrError = UniqueID.create(id);
|
const idOrError = UniqueID.create(id);
|
||||||
|
|
||||||
@ -25,24 +26,22 @@ export class GetCustomerUseCase {
|
|||||||
return Result.fail(idOrError.error);
|
return Result.fail(idOrError.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const companyIdOrError = UniqueID.create(companyId);
|
|
||||||
|
|
||||||
if (companyIdOrError.isFailure) {
|
|
||||||
return Result.fail(companyIdOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
const customerOrError = await this.service.getCustomerByIdInCompany(
|
const customerOrError = await this.service.getCustomerByIdInCompany(
|
||||||
companyIdOrError.data,
|
companyId,
|
||||||
idOrError.data,
|
idOrError.data,
|
||||||
transaction
|
transaction
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(customerOrError);
|
||||||
|
|
||||||
if (customerOrError.isFailure) {
|
if (customerOrError.isFailure) {
|
||||||
return Result.fail(customerOrError.error);
|
return Result.fail(customerOrError.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDTO = this.assembler.toDTO(customerOrError.data);
|
const getDTO = this.assembler.toDTO(customerOrError.data);
|
||||||
|
console.log(getDTO);
|
||||||
return Result.ok(getDTO);
|
return Result.ok(getDTO);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
return Result.fail(error as Error);
|
return Result.fail(error as Error);
|
||||||
|
|||||||
@ -2,4 +2,4 @@ export * from "./create-customer";
|
|||||||
export * from "./delete-customer";
|
export * from "./delete-customer";
|
||||||
export * from "./get-customer";
|
export * from "./get-customer";
|
||||||
export * from "./list-customers";
|
export * from "./list-customers";
|
||||||
//export * from "./update-customer";
|
export * from "./update-customer";
|
||||||
|
|||||||
@ -10,30 +10,72 @@ export class ListCustomersAssembler {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: customer.id.toPrimitive(),
|
id: customer.id.toPrimitive(),
|
||||||
reference: customer.reference,
|
company_id: customer.companyId.toPrimitive(),
|
||||||
|
|
||||||
|
reference: customer.reference.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
is_company: customer.isCompany,
|
is_company: customer.isCompany,
|
||||||
name: customer.name,
|
name: customer.name.toPrimitive(),
|
||||||
trade_name: customer.tradeName ?? "",
|
trade_name: customer.tradeName.match(
|
||||||
tin: customer.tin.toPrimitive(),
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
tin: customer.tin.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
street: address.street,
|
street: address.street.match(
|
||||||
city: address.city,
|
(value) => value.toPrimitive(),
|
||||||
state: address.state,
|
() => ""
|
||||||
postal_code: address.postalCode,
|
),
|
||||||
country: address.country,
|
city: address.city.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
state: address.province.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
postal_code: address.postalCode.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
country: address.country.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
email: customer.email.toPrimitive(),
|
email: customer.email.match(
|
||||||
phone: customer.phone.toPrimitive(),
|
(value) => value.toPrimitive(),
|
||||||
fax: customer.fax.toPrimitive(),
|
() => ""
|
||||||
website: customer.website ?? "",
|
),
|
||||||
|
phone: customer.phone.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
fax: customer.fax.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
website: customer.website.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
legal_record: customer.legalRecord,
|
legal_record: customer.legalRecord.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
default_taxes: customer.defaultTaxes.map((item) => item.toPrimitive()),
|
||||||
|
|
||||||
default_tax: customer.defaultTax,
|
|
||||||
status: customer.isActive ? "active" : "inactive",
|
status: customer.isActive ? "active" : "inactive",
|
||||||
lang_code: customer.langCode,
|
language_code: customer.languageCode.toPrimitive(),
|
||||||
currency_code: customer.currencyCode,
|
currency_code: customer.currencyCode.toPrimitive(),
|
||||||
|
|
||||||
metadata: {
|
metadata: {
|
||||||
entity: "customer",
|
entity: "customer",
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { ITransactionManager } from "@erp/core/api";
|
import { ITransactionManager } from "@erp/core/api";
|
||||||
import { Criteria } from "@repo/rdx-criteria/server";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { Transaction } from "sequelize";
|
import { Transaction } from "sequelize";
|
||||||
import { CustomerListResponsetDTO } from "../../../common/dto";
|
import { CustomerListResponsetDTO } from "../../../common/dto";
|
||||||
import { ICustomerService } from "../../domain";
|
import { CustomerService } from "../../domain";
|
||||||
import { ListCustomersAssembler } from "./assembler";
|
import { ListCustomersAssembler } from "./assembler";
|
||||||
|
|
||||||
type ListCustomersUseCaseInput = {
|
type ListCustomersUseCaseInput = {
|
||||||
@ -13,7 +14,7 @@ type ListCustomersUseCaseInput = {
|
|||||||
|
|
||||||
export class ListCustomersUseCase {
|
export class ListCustomersUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly customerService: ICustomerService,
|
private readonly customerService: CustomerService,
|
||||||
private readonly transactionManager: ITransactionManager,
|
private readonly transactionManager: ITransactionManager,
|
||||||
private readonly assembler: ListCustomersAssembler
|
private readonly assembler: ListCustomersAssembler
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
//export * from "./participantAddressFinder";
|
|
||||||
//export * from "./participantFinder";
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
/* import {
|
|
||||||
ApplicationServiceError,
|
|
||||||
type IApplicationServiceError,
|
|
||||||
} from "@/contexts/common/application/services/ApplicationServiceError";
|
|
||||||
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
|
||||||
import { Result, UniqueID } from "@shared/contexts";
|
|
||||||
import { NullOr } from "@shared/utilities";
|
|
||||||
import { ICustomerParticipantAddress, ICustomerParticipantAddressRepository } from "../../domain";
|
|
||||||
|
|
||||||
export const participantAddressFinder = async (
|
|
||||||
addressId: UniqueID,
|
|
||||||
adapter: IAdapter,
|
|
||||||
repository: RepositoryBuilder<ICustomerParticipantAddressRepository>
|
|
||||||
) => {
|
|
||||||
if (addressId.isNull()) {
|
|
||||||
return Result.fail<IApplicationServiceError>(
|
|
||||||
ApplicationServiceError.create(
|
|
||||||
ApplicationServiceError.INVALID_REQUEST_PARAM,
|
|
||||||
`Participant address ID required`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const transaction = adapter.startTransaction();
|
|
||||||
let address: NullOr<ICustomerParticipantAddress> = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await transaction.complete(async (t) => {
|
|
||||||
address = await repository({ transaction: t }).getById(addressId);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (address === null) {
|
|
||||||
return Result.fail<IApplicationServiceError>(
|
|
||||||
ApplicationServiceError.create(ApplicationServiceError.NOT_FOUND_ERROR, "", {
|
|
||||||
id: addressId.toString(),
|
|
||||||
entity: "participant address",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok<ICustomerParticipantAddress>(address);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const _error = error as Error;
|
|
||||||
|
|
||||||
if (repository().isRepositoryError(_error)) {
|
|
||||||
return Result.fail<IApplicationServiceError>(
|
|
||||||
ApplicationServiceError.create(
|
|
||||||
ApplicationServiceError.REPOSITORY_ERROR,
|
|
||||||
_error.message,
|
|
||||||
_error
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.fail<IApplicationServiceError>(
|
|
||||||
ApplicationServiceError.create(
|
|
||||||
ApplicationServiceError.UNEXCEPTED_ERROR,
|
|
||||||
_error.message,
|
|
||||||
_error
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
/* import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
|
|
||||||
import { UniqueID } from "@shared/contexts";
|
|
||||||
import { ICustomerParticipantRepository } from "../../domain";
|
|
||||||
import { CustomerCustomer } from "../../domain/entities/customer-customer/customer-customer";
|
|
||||||
|
|
||||||
export const participantFinder = async (
|
|
||||||
participantId: UniqueID,
|
|
||||||
adapter: IAdapter,
|
|
||||||
repository: RepositoryBuilder<ICustomerParticipantRepository>
|
|
||||||
): Promise<CustomerCustomer | undefined> => {
|
|
||||||
if (!participantId || (participantId && participantId.isNull())) {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
const participant = await adapter
|
|
||||||
.startTransaction()
|
|
||||||
.complete((t) => repository({ transaction: t }).getById(participantId));
|
|
||||||
|
|
||||||
return Promise.resolve(participant ? participant : undefined);
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./update-customer.assembler";
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
import { GetCustomerByIdResponseDTO as UpdateCustomerByIdResponseDTO } from "../../../../common/dto";
|
||||||
|
import { Customer } from "../../../domain";
|
||||||
|
|
||||||
|
export class UpdateCustomerAssembler {
|
||||||
|
toDTO(customer: Customer): UpdateCustomerByIdResponseDTO {
|
||||||
|
const address = customer.address.toPrimitive();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: customer.id.toPrimitive(),
|
||||||
|
reference: customer.reference.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
is_company: customer.isCompany,
|
||||||
|
name: customer.name.toPrimitive(),
|
||||||
|
trade_name: customer.tradeName.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
tin: customer.tin.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
street: address.street.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
city: address.city.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
state: address.province.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
postal_code: address.postalCode.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
country: address.country.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
email: customer.email.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
phone: customer.phone.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
fax: customer.fax.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
website: customer.website.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
legal_record: customer.legalRecord.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
|
default_taxes: customer.defaultTaxes.map((item) => item.toPrimitive()),
|
||||||
|
|
||||||
|
status: customer.isActive ? "active" : "inactive",
|
||||||
|
language_code: customer.languageCode.toPrimitive(),
|
||||||
|
currency_code: customer.currencyCode.toPrimitive(),
|
||||||
|
|
||||||
|
metadata: {
|
||||||
|
entity: "customer",
|
||||||
|
id: customer.id.toPrimitive(),
|
||||||
|
//created_at: customer.createdAt.toPrimitive(),
|
||||||
|
//updated_at: customer.updatedAt.toPrimitive()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
|
export * from "./assembler";
|
||||||
export * from "./update-customer.use-case";
|
export * from "./update-customer.use-case";
|
||||||
|
|||||||
@ -0,0 +1,245 @@
|
|||||||
|
import {
|
||||||
|
DomainError,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
import {
|
||||||
|
City,
|
||||||
|
Country,
|
||||||
|
CurrencyCode,
|
||||||
|
EmailAddress,
|
||||||
|
LanguageCode,
|
||||||
|
Name,
|
||||||
|
PhoneNumber,
|
||||||
|
PostalAddressPatchProps,
|
||||||
|
PostalCode,
|
||||||
|
Province,
|
||||||
|
Street,
|
||||||
|
TINNumber,
|
||||||
|
TaxCode,
|
||||||
|
TextValue,
|
||||||
|
URLAddress,
|
||||||
|
maybeFromNullableVO,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Collection, Result, isNullishOrEmpty, toPatchField } from "@repo/rdx-utils";
|
||||||
|
import { UpdateCustomerRequestDTO } from "../../../common/dto";
|
||||||
|
import { CustomerPatchProps } from "../../domain";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mapDTOToUpdateCustomerPatchProps
|
||||||
|
* Convierte el DTO a las props validadas (CustomerProps).
|
||||||
|
* No construye directamente el agregado.
|
||||||
|
* Tri-estado:
|
||||||
|
* - campo omitido → no se cambia
|
||||||
|
* - campo con valor null/"" → set(None()),
|
||||||
|
* - campo con valor no-vacío → set(Some(VO)).
|
||||||
|
*
|
||||||
|
* @param dto - DTO con los datos a cambiar en el cliente
|
||||||
|
* @returns Cambios en las propiedades del cliente
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function mapDTOToUpdateCustomerPatchProps(dto: UpdateCustomerRequestDTO) {
|
||||||
|
try {
|
||||||
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
const customerPatchProps: CustomerPatchProps = {};
|
||||||
|
|
||||||
|
toPatchField(dto.reference).ifSet((reference) => {
|
||||||
|
customerPatchProps.reference = extractOrPushError(
|
||||||
|
maybeFromNullableVO(reference, (value) => Name.create(value)),
|
||||||
|
"reference",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.is_company).ifSet((is_company) => {
|
||||||
|
if (isNullishOrEmpty(is_company)) {
|
||||||
|
errors.push({ path: "is_company", message: "is_company cannot be empty" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
customerPatchProps.isCompany = extractOrPushError(
|
||||||
|
Result.ok(Boolean(is_company!)),
|
||||||
|
"is_company",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.name).ifSet((name) => {
|
||||||
|
if (isNullishOrEmpty(name)) {
|
||||||
|
errors.push({ path: "name", message: "Name cannot be empty" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
customerPatchProps.name = extractOrPushError(Name.create(name!), "name", errors);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.trade_name).ifSet((trade_name) => {
|
||||||
|
customerPatchProps.tradeName = extractOrPushError(
|
||||||
|
maybeFromNullableVO(trade_name, (value) => Name.create(value)),
|
||||||
|
"trade_name",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.tin).ifSet((tin) => {
|
||||||
|
customerPatchProps.tin = extractOrPushError(
|
||||||
|
maybeFromNullableVO(tin, (value) => TINNumber.create(value)),
|
||||||
|
"tin",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.email).ifSet((email) => {
|
||||||
|
customerPatchProps.email = extractOrPushError(
|
||||||
|
maybeFromNullableVO(email, (value) => EmailAddress.create(value)),
|
||||||
|
"email",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.phone).ifSet((phone) => {
|
||||||
|
customerPatchProps.phone = extractOrPushError(
|
||||||
|
maybeFromNullableVO(phone, (value) => PhoneNumber.create(value)),
|
||||||
|
"phone",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.fax).ifSet((fax) => {
|
||||||
|
customerPatchProps.fax = extractOrPushError(
|
||||||
|
maybeFromNullableVO(fax, (value) => PhoneNumber.create(value)),
|
||||||
|
"fax",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.website).ifSet((website) => {
|
||||||
|
customerPatchProps.website = extractOrPushError(
|
||||||
|
maybeFromNullableVO(website, (value) => URLAddress.create(value)),
|
||||||
|
"website",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.legal_record).ifSet((legalRecord) => {
|
||||||
|
customerPatchProps.legalRecord = extractOrPushError(
|
||||||
|
maybeFromNullableVO(legalRecord, (value) => TextValue.create(value)),
|
||||||
|
"legal_record",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.language_code).ifSet((languageCode) => {
|
||||||
|
if (isNullishOrEmpty(languageCode)) {
|
||||||
|
errors.push({ path: "language_code", message: "Language code cannot be empty" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
customerPatchProps.languageCode = extractOrPushError(
|
||||||
|
LanguageCode.create(languageCode!),
|
||||||
|
"language_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.currency_code).ifSet((currencyCode) => {
|
||||||
|
if (isNullishOrEmpty(currencyCode)) {
|
||||||
|
errors.push({ path: "currency_code", message: "Currency code cannot be empty" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
customerPatchProps.currencyCode = extractOrPushError(
|
||||||
|
CurrencyCode.create(currencyCode!),
|
||||||
|
"currency_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default taxes
|
||||||
|
const defaultTaxesCollection = new Collection<TaxCode>();
|
||||||
|
toPatchField(dto.default_taxes).ifSet((defaultTaxes) => {
|
||||||
|
customerPatchProps.defaultTaxes = defaultTaxesCollection;
|
||||||
|
|
||||||
|
if (isNullishOrEmpty(defaultTaxes)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultTaxes!.map((taxCode, index) => {
|
||||||
|
const tax = extractOrPushError(TaxCode.create(taxCode), `default_taxes.${index}`, errors);
|
||||||
|
if (tax && customerPatchProps.defaultTaxes) {
|
||||||
|
customerPatchProps.defaultTaxes.add(tax);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// PostalAddress
|
||||||
|
customerPatchProps.address = mapDTOToUpdatePostalAddressPatchProps(dto, errors);
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error(errors);
|
||||||
|
return Result.fail(new ValidationErrorCollection("Customer props mapping failed", errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(customerPatchProps);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
console.error(err);
|
||||||
|
return Result.fail(new DomainError("Customer props mapping failed", { cause: err }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDTOToUpdatePostalAddressPatchProps(
|
||||||
|
dto: UpdateCustomerRequestDTO,
|
||||||
|
errors: ValidationErrorDetail[]
|
||||||
|
): PostalAddressPatchProps {
|
||||||
|
const postalAddressPatchProps: PostalAddressPatchProps = {};
|
||||||
|
|
||||||
|
toPatchField(dto.street).ifSet((street) => {
|
||||||
|
postalAddressPatchProps.street = extractOrPushError(
|
||||||
|
maybeFromNullableVO(street, (value) => Street.create(value)),
|
||||||
|
"street",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.street2).ifSet((street2) => {
|
||||||
|
postalAddressPatchProps.street2 = extractOrPushError(
|
||||||
|
maybeFromNullableVO(street2, (value) => Street.create(value)),
|
||||||
|
"street2",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.city).ifSet((city) => {
|
||||||
|
postalAddressPatchProps.city = extractOrPushError(
|
||||||
|
maybeFromNullableVO(city, (value) => City.create(value)),
|
||||||
|
"city",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.province).ifSet((province) => {
|
||||||
|
postalAddressPatchProps.province = extractOrPushError(
|
||||||
|
maybeFromNullableVO(province, (value) => Province.create(value)),
|
||||||
|
"province",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.postal_code).ifSet((postalCode) => {
|
||||||
|
postalAddressPatchProps.postalCode = extractOrPushError(
|
||||||
|
maybeFromNullableVO(postalCode, (value) => PostalCode.create(value)),
|
||||||
|
"postal_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
toPatchField(dto.country).ifSet((country) => {
|
||||||
|
postalAddressPatchProps.country = extractOrPushError(
|
||||||
|
maybeFromNullableVO(country, (value) => Country.create(value)),
|
||||||
|
"country",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return postalAddressPatchProps;
|
||||||
|
}
|
||||||
@ -1,401 +1,62 @@
|
|||||||
import { UniqueID } from "@/core/common/domain";
|
import { ITransactionManager } from "@erp/core/api";
|
||||||
import { ITransactionManager } from "@/core/common/infrastructure/database";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { IUpdateCustomerRequestDTO } from "../../common/dto";
|
import { UpdateCustomerRequestDTO } from "../../../common";
|
||||||
import { Customer, ICustomerService } from "../domain";
|
import { CustomerPatchProps, CustomerService } from "../../domain";
|
||||||
|
import { UpdateCustomerAssembler } from "./assembler";
|
||||||
|
import { mapDTOToUpdateCustomerPatchProps } from "./map-dto-to-update-customer-props";
|
||||||
|
|
||||||
export class CreateCustomerUseCase {
|
type UpdateCustomerUseCaseInput = {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: string;
|
||||||
|
dto: UpdateCustomerRequestDTO;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class UpdateCustomerUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly customerService: ICustomerService,
|
private readonly service: CustomerService,
|
||||||
private readonly transactionManager: ITransactionManager
|
private readonly transactionManager: ITransactionManager,
|
||||||
|
private readonly assembler: UpdateCustomerAssembler
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(
|
public execute(params: UpdateCustomerUseCaseInput) {
|
||||||
customerID: UniqueID,
|
const { companyId, id, dto } = params;
|
||||||
dto: Partial<IUpdateCustomerRequestDTO>
|
|
||||||
): Promise<Result<Customer, Error>> {
|
const idOrError = UniqueID.create(id);
|
||||||
|
if (idOrError.isFailure) {
|
||||||
|
return Result.fail(idOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const customerId = idOrError.data;
|
||||||
|
|
||||||
|
// Mapear DTO → props de dominio
|
||||||
|
const patchPropsResult = mapDTOToUpdateCustomerPatchProps(dto);
|
||||||
|
if (patchPropsResult.isFailure) {
|
||||||
|
return Result.fail(patchPropsResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const patchProps: CustomerPatchProps = patchPropsResult.data;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
return Result.fail(new Error("No implementado"));
|
|
||||||
/*
|
|
||||||
try {
|
try {
|
||||||
const validOrErrors = this.validateCustomerData(dto);
|
const updatedCustomer = await this.service.updateCustomerByIdInCompany(
|
||||||
if (validOrErrors.isFailure) {
|
companyId,
|
||||||
return Result.fail(validOrErrors.error);
|
customerId,
|
||||||
|
patchProps,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (updatedCustomer.isFailure) {
|
||||||
|
return Result.fail(updatedCustomer.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = validOrErrors.data;
|
const savedCustomer = await this.service.saveCustomer(updatedCustomer.data, transaction);
|
||||||
|
|
||||||
// Update customer with dto
|
const getDTO = this.assembler.toDTO(savedCustomer.data);
|
||||||
return await this.customerService.updateCustomerById(customerID, data, transaction);
|
return Result.ok(getDTO);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error(error as Error);
|
|
||||||
return Result.fail(error as Error);
|
return Result.fail(error as Error);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* private validateCustomerData(
|
|
||||||
dto: Partial<IUpdateCustomerRequestDTO>
|
|
||||||
): Result<Partial<ICustomerProps>, Error> {
|
|
||||||
const errors: Error[] = [];
|
|
||||||
const validatedData: Partial<ICustomerProps> = {};
|
|
||||||
|
|
||||||
// Create customer
|
|
||||||
let customer_status = CustomerStatus.create(customerDTO.status).object;
|
|
||||||
if (customer_status.isEmpty()) {
|
|
||||||
customer_status = CustomerStatus.createDraft();
|
|
||||||
}
|
|
||||||
|
|
||||||
let customer_series = CustomerSeries.create(customerDTO.customer_series).object;
|
|
||||||
if (customer_series.isEmpty()) {
|
|
||||||
customer_series = CustomerSeries.create(customerDTO.customer_series).object;
|
|
||||||
}
|
|
||||||
|
|
||||||
let issue_date = CustomerDate.create(customerDTO.issue_date).object;
|
|
||||||
if (issue_date.isEmpty()) {
|
|
||||||
issue_date = CustomerDate.createCurrentDate().object;
|
|
||||||
}
|
|
||||||
|
|
||||||
let operation_date = CustomerDate.create(customerDTO.operation_date).object;
|
|
||||||
if (operation_date.isEmpty()) {
|
|
||||||
operation_date = CustomerDate.createCurrentDate().object;
|
|
||||||
}
|
|
||||||
|
|
||||||
let customerCurrency = Currency.createFromCode(customerDTO.currency).object;
|
|
||||||
|
|
||||||
if (customerCurrency.isEmpty()) {
|
|
||||||
customerCurrency = Currency.createDefaultCode().object;
|
|
||||||
}
|
|
||||||
|
|
||||||
let customerLanguage = Language.createFromCode(customerDTO.language_code).object;
|
|
||||||
|
|
||||||
if (customerLanguage.isEmpty()) {
|
|
||||||
customerLanguage = Language.createDefaultCode().object;
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = new Collection<CustomerItem>(
|
|
||||||
customerDTO.items?.map(
|
|
||||||
(item) =>
|
|
||||||
CustomerSimpleItem.create({
|
|
||||||
description: Description.create(item.description).object,
|
|
||||||
quantity: Quantity.create(item.quantity).object,
|
|
||||||
unitPrice: UnitPrice.create({
|
|
||||||
amount: item.unit_price.amount,
|
|
||||||
currencyCode: item.unit_price.currency,
|
|
||||||
precision: item.unit_price.precision,
|
|
||||||
}).object,
|
|
||||||
}).object
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!customer_status.isDraft()) {
|
|
||||||
throw Error("Error al crear una factura que no es borrador");
|
|
||||||
}
|
|
||||||
|
|
||||||
return DraftCustomer.create(
|
|
||||||
{
|
|
||||||
customerSeries: customer_series,
|
|
||||||
issueDate: issue_date,
|
|
||||||
operationDate: operation_date,
|
|
||||||
customerCurrency,
|
|
||||||
language: customerLanguage,
|
|
||||||
customerNumber: CustomerNumber.create(undefined).object,
|
|
||||||
//notes: Note.create(customerDTO.notes).object,
|
|
||||||
|
|
||||||
//senderId: UniqueID.create(null).object,
|
|
||||||
recipient,
|
|
||||||
|
|
||||||
items,
|
|
||||||
},
|
|
||||||
customerId
|
|
||||||
);
|
|
||||||
} */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* export type UpdateCustomerResponseOrError =
|
|
||||||
| Result<never, IUseCaseError> // Misc errors (value objects)
|
|
||||||
| Result<Customer, never>; // Success!
|
|
||||||
|
|
||||||
export class UpdateCustomerUseCase2
|
|
||||||
implements
|
|
||||||
IUseCase<{ id: UniqueID; data: IUpdateCustomer_DTO }, Promise<UpdateCustomerResponseOrError>>
|
|
||||||
{
|
|
||||||
private _context: IInvoicingContext;
|
|
||||||
private _adapter: ISequelizeAdapter;
|
|
||||||
private _repositoryManager: IRepositoryManager;
|
|
||||||
|
|
||||||
constructor(context: IInvoicingContext) {
|
|
||||||
this._context = context;
|
|
||||||
this._adapter = context.adapter;
|
|
||||||
this._repositoryManager = context.repositoryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getRepository<T>(name: string) {
|
|
||||||
return this._repositoryManager.getRepository<T>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleValidationFailure(
|
|
||||||
validationError: Error,
|
|
||||||
message?: string
|
|
||||||
): Result<never, IUseCaseError> {
|
|
||||||
return Result.fail<IUseCaseError>(
|
|
||||||
UseCaseError.create(
|
|
||||||
UseCaseError.INVALID_INPUT_DATA,
|
|
||||||
message ? message : validationError.message,
|
|
||||||
validationError
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async execute(request: {
|
|
||||||
id: UniqueID;
|
|
||||||
data: IUpdateCustomer_DTO;
|
|
||||||
}): Promise<UpdateCustomerResponseOrError> {
|
|
||||||
const { id, data: customerDTO } = request;
|
|
||||||
|
|
||||||
// Validaciones
|
|
||||||
const customerDTOOrError = ensureUpdateCustomer_DTOIsValid(customerDTO);
|
|
||||||
if (customerDTOOrError.isFailure) {
|
|
||||||
return this.handleValidationFailure(customerDTOOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const transaction = this._adapter.startTransaction();
|
|
||||||
|
|
||||||
const customerRepoBuilder = this.getRepository<ICustomerRepository>("Customer");
|
|
||||||
|
|
||||||
let customer: Customer | null = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await transaction.complete(async (t) => {
|
|
||||||
customer = await customerRepoBuilder({ transaction: t }).getById(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (customer === null) {
|
|
||||||
return Result.fail<IUseCaseError>(
|
|
||||||
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, `Customer not found`, {
|
|
||||||
id: request.id.toString(),
|
|
||||||
entity: "customer",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok<Customer>(customer);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const _error = error as Error;
|
|
||||||
if (customerRepoBuilder().isRepositoryError(_error)) {
|
|
||||||
return this.handleRepositoryError(error as BaseError, customerRepoBuilder());
|
|
||||||
} else {
|
|
||||||
return this.handleUnexceptedError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recipient validations
|
|
||||||
const recipientIdOrError = ensureParticipantIdIsValid(
|
|
||||||
customerDTO?.recipient?.id,
|
|
||||||
);
|
|
||||||
if (recipientIdOrError.isFailure) {
|
|
||||||
return this.handleValidationFailure(
|
|
||||||
recipientIdOrError.error,
|
|
||||||
"Recipient ID not valid",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const recipientId = recipientIdOrError.object;
|
|
||||||
|
|
||||||
const recipientBillingIdOrError = ensureParticipantAddressIdIsValid(
|
|
||||||
customerDTO?.recipient?.billing_address_id,
|
|
||||||
);
|
|
||||||
if (recipientBillingIdOrError.isFailure) {
|
|
||||||
return this.handleValidationFailure(
|
|
||||||
recipientBillingIdOrError.error,
|
|
||||||
"Recipient billing address ID not valid",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const recipientBillingId = recipientBillingIdOrError.object;
|
|
||||||
|
|
||||||
const recipientShippingIdOrError = ensureParticipantAddressIdIsValid(
|
|
||||||
customerDTO?.recipient?.shipping_address_id,
|
|
||||||
);
|
|
||||||
if (recipientShippingIdOrError.isFailure) {
|
|
||||||
return this.handleValidationFailure(
|
|
||||||
recipientShippingIdOrError.error,
|
|
||||||
"Recipient shipping address ID not valid",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const recipientShippingId = recipientShippingIdOrError.object;
|
|
||||||
|
|
||||||
const recipientContact = await this.findContact(
|
|
||||||
recipientId,
|
|
||||||
recipientBillingId,
|
|
||||||
recipientShippingId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!recipientContact) {
|
|
||||||
return this.handleValidationFailure(
|
|
||||||
new Error(`Recipient with ID ${recipientId.toString()} does not exist`),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crear customer
|
|
||||||
const customerOrError = await this.tryUpdateCustomerInstance(
|
|
||||||
customerDTO,
|
|
||||||
customerIdOrError.object,
|
|
||||||
//senderId,
|
|
||||||
//senderBillingId,
|
|
||||||
//senderShippingId,
|
|
||||||
recipientContact,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (customerOrError.isFailure) {
|
|
||||||
const { error: domainError } = customerOrError;
|
|
||||||
let errorCode = "";
|
|
||||||
let message = "";
|
|
||||||
|
|
||||||
switch (domainError.code) {
|
|
||||||
case Customer.ERROR_CUSTOMER_WITHOUT_NAME:
|
|
||||||
errorCode = UseCaseError.INVALID_INPUT_DATA;
|
|
||||||
message =
|
|
||||||
"El cliente debe ser una compañía o tener nombre y apellidos.";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
errorCode = UseCaseError.UNEXCEPTED_ERROR;
|
|
||||||
message = "";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.fail<IUseCaseError>(
|
|
||||||
UseCaseError.create(errorCode, message, domainError),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.saveCustomer(customerOrError.object);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private async tryUpdateCustomerInstance(customerDTO, customerId, recipient) {
|
|
||||||
// Create customer
|
|
||||||
let customer_status = CustomerStatus.create(customerDTO.status).object;
|
|
||||||
if (customer_status.isEmpty()) {
|
|
||||||
customer_status = CustomerStatus.createDraft();
|
|
||||||
}
|
|
||||||
|
|
||||||
let customer_series = CustomerSeries.create(customerDTO.customer_series).object;
|
|
||||||
if (customer_series.isEmpty()) {
|
|
||||||
customer_series = CustomerSeries.create(customerDTO.customer_series).object;
|
|
||||||
}
|
|
||||||
|
|
||||||
let issue_date = CustomerDate.create(customerDTO.issue_date).object;
|
|
||||||
if (issue_date.isEmpty()) {
|
|
||||||
issue_date = CustomerDate.createCurrentDate().object;
|
|
||||||
}
|
|
||||||
|
|
||||||
let operation_date = CustomerDate.create(customerDTO.operation_date).object;
|
|
||||||
if (operation_date.isEmpty()) {
|
|
||||||
operation_date = CustomerDate.createCurrentDate().object;
|
|
||||||
}
|
|
||||||
|
|
||||||
let customerCurrency = Currency.createFromCode(customerDTO.currency).object;
|
|
||||||
|
|
||||||
if (customerCurrency.isEmpty()) {
|
|
||||||
customerCurrency = Currency.createDefaultCode().object;
|
|
||||||
}
|
|
||||||
|
|
||||||
let customerLanguage = Language.createFromCode(customerDTO.language_code).object;
|
|
||||||
|
|
||||||
if (customerLanguage.isEmpty()) {
|
|
||||||
customerLanguage = Language.createDefaultCode().object;
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = new Collection<CustomerItem>(
|
|
||||||
customerDTO.items?.map(
|
|
||||||
(item) =>
|
|
||||||
CustomerSimpleItem.create({
|
|
||||||
description: Description.create(item.description).object,
|
|
||||||
quantity: Quantity.create(item.quantity).object,
|
|
||||||
unitPrice: UnitPrice.create({
|
|
||||||
amount: item.unit_price.amount,
|
|
||||||
currencyCode: item.unit_price.currency,
|
|
||||||
precision: item.unit_price.precision,
|
|
||||||
}).object,
|
|
||||||
}).object
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!customer_status.isDraft()) {
|
|
||||||
throw Error("Error al crear una factura que no es borrador");
|
|
||||||
}
|
|
||||||
|
|
||||||
return DraftCustomer.create(
|
|
||||||
{
|
|
||||||
customerSeries: customer_series,
|
|
||||||
issueDate: issue_date,
|
|
||||||
operationDate: operation_date,
|
|
||||||
customerCurrency,
|
|
||||||
language: customerLanguage,
|
|
||||||
customerNumber: CustomerNumber.create(undefined).object,
|
|
||||||
//notes: Note.create(customerDTO.notes).object,
|
|
||||||
|
|
||||||
//senderId: UniqueID.create(null).object,
|
|
||||||
recipient,
|
|
||||||
|
|
||||||
items,
|
|
||||||
},
|
|
||||||
customerId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async findContact(
|
|
||||||
contactId: UniqueID,
|
|
||||||
billingAddressId: UniqueID,
|
|
||||||
shippingAddressId: UniqueID
|
|
||||||
) {
|
|
||||||
const contactRepoBuilder = this.getRepository<IContactRepository>("Contact");
|
|
||||||
|
|
||||||
const contact = await contactRepoBuilder().getById2(
|
|
||||||
contactId,
|
|
||||||
billingAddressId,
|
|
||||||
shippingAddressId
|
|
||||||
);
|
|
||||||
|
|
||||||
return contact;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async saveCustomer(customer: DraftCustomer) {
|
|
||||||
const transaction = this._adapter.startTransaction();
|
|
||||||
const customerRepoBuilder = this.getRepository<ICustomerRepository>("Customer");
|
|
||||||
|
|
||||||
try {
|
|
||||||
await transaction.complete(async (t) => {
|
|
||||||
const customerRepo = customerRepoBuilder({ transaction: t });
|
|
||||||
await customerRepo.save(customer);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Result.ok<DraftCustomer>(customer);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const _error = error as Error;
|
|
||||||
if (customerRepoBuilder().isRepositoryError(_error)) {
|
|
||||||
return this.handleRepositoryError(error as BaseError, customerRepoBuilder());
|
|
||||||
} else {
|
|
||||||
return this.handleUnexceptedError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleUnexceptedError(error): Result<never, IUseCaseError> {
|
|
||||||
return Result.fail<IUseCaseError>(
|
|
||||||
UseCaseError.create(UseCaseError.UNEXCEPTED_ERROR, error.message, error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleRepositoryError(
|
|
||||||
error: BaseError,
|
|
||||||
repository: ICustomerRepository
|
|
||||||
): Result<never, IUseCaseError> {
|
|
||||||
const { message, details } = repository.handleRepositoryError(error);
|
|
||||||
return Result.fail<IUseCaseError>(
|
|
||||||
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, message, details)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|||||||
@ -1,66 +1,77 @@
|
|||||||
import {
|
import {
|
||||||
AggregateRoot,
|
AggregateRoot,
|
||||||
|
CurrencyCode,
|
||||||
EmailAddress,
|
EmailAddress,
|
||||||
|
LanguageCode,
|
||||||
|
Name,
|
||||||
PhoneNumber,
|
PhoneNumber,
|
||||||
PostalAddress,
|
PostalAddress,
|
||||||
|
PostalAddressPatchProps,
|
||||||
|
PostalAddressSnapshot,
|
||||||
TINNumber,
|
TINNumber,
|
||||||
|
TaxCode,
|
||||||
|
TextValue,
|
||||||
|
URLAddress,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
} from "@repo/rdx-ddd";
|
} from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
import { CustomerStatus } from "../value-objects";
|
import { CustomerStatus } from "../value-objects";
|
||||||
|
|
||||||
export interface CustomerProps {
|
export interface CustomerProps {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
status: CustomerStatus;
|
status: CustomerStatus;
|
||||||
reference: string;
|
reference: Maybe<Name>;
|
||||||
|
|
||||||
isCompany: boolean;
|
isCompany: boolean;
|
||||||
name: string;
|
name: Name;
|
||||||
tradeName: string;
|
tradeName: Maybe<Name>;
|
||||||
tin: TINNumber;
|
tin: Maybe<TINNumber>;
|
||||||
|
|
||||||
address: PostalAddress;
|
address: PostalAddress;
|
||||||
|
|
||||||
email: EmailAddress;
|
email: Maybe<EmailAddress>;
|
||||||
phone: PhoneNumber;
|
phone: Maybe<PhoneNumber>;
|
||||||
fax: PhoneNumber;
|
fax: Maybe<PhoneNumber>;
|
||||||
website: string;
|
website: Maybe<URLAddress>;
|
||||||
|
|
||||||
legalRecord: string;
|
legalRecord: Maybe<TextValue>;
|
||||||
defaultTax: string[];
|
defaultTaxes: Collection<TaxCode>;
|
||||||
|
|
||||||
langCode: string;
|
languageCode: LanguageCode;
|
||||||
currencyCode: string;
|
currencyCode: CurrencyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICustomer {
|
export interface CustomerSnapshot {
|
||||||
id: UniqueID;
|
id: string;
|
||||||
companyId: UniqueID;
|
companyId: string;
|
||||||
reference: string;
|
status: string;
|
||||||
|
reference: string | null;
|
||||||
|
|
||||||
tin: TINNumber;
|
|
||||||
name: string;
|
|
||||||
tradeName: string;
|
|
||||||
|
|
||||||
address: PostalAddress;
|
|
||||||
|
|
||||||
email: EmailAddress;
|
|
||||||
phone: PhoneNumber;
|
|
||||||
fax: PhoneNumber;
|
|
||||||
website: string;
|
|
||||||
|
|
||||||
legalRecord: string;
|
|
||||||
defaultTax: string[];
|
|
||||||
|
|
||||||
langCode: string;
|
|
||||||
currencyCode: string;
|
|
||||||
|
|
||||||
isIndividual: boolean;
|
|
||||||
isCompany: boolean;
|
isCompany: boolean;
|
||||||
isActive: boolean;
|
name: string;
|
||||||
|
tradeName: string | null;
|
||||||
|
|
||||||
|
tin: string | null;
|
||||||
|
|
||||||
|
address: PostalAddressSnapshot; // snapshot serializable del VO PostalAddress
|
||||||
|
|
||||||
|
email: string | null;
|
||||||
|
phone: string | null;
|
||||||
|
fax: string | null;
|
||||||
|
website: string | null;
|
||||||
|
|
||||||
|
legalRecord: string | null;
|
||||||
|
defaultTaxes: string[];
|
||||||
|
|
||||||
|
languageCode: string;
|
||||||
|
currencyCode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Customer extends AggregateRoot<CustomerProps> implements ICustomer {
|
export type CustomerPatchProps = Partial<Omit<CustomerProps, "companyId" | "address">> & {
|
||||||
|
address?: PostalAddressPatchProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Customer extends AggregateRoot<CustomerProps> {
|
||||||
static create(props: CustomerProps, id?: UniqueID): Result<Customer, Error> {
|
static create(props: CustomerProps, id?: UniqueID): Result<Customer, Error> {
|
||||||
const contact = new Customer(props, id);
|
const contact = new Customer(props, id);
|
||||||
|
|
||||||
@ -75,76 +86,122 @@ export class Customer extends AggregateRoot<CustomerProps> implements ICustomer
|
|||||||
return Result.ok(contact);
|
return Result.ok(contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(partial: Partial<Omit<CustomerProps, "companyId">>): Result<Customer, Error> {
|
public update(partial: CustomerPatchProps): Result<Customer, Error> {
|
||||||
const updatedCustomer = new Customer({ ...this.props, ...partial }, this.id);
|
const updatedProps = {
|
||||||
|
...this.props,
|
||||||
|
...partial,
|
||||||
|
} as CustomerProps;
|
||||||
|
|
||||||
|
if (partial.address) {
|
||||||
|
const updatedAddressOrError = PostalAddress.update(this.props.address, partial.address);
|
||||||
|
if (updatedAddressOrError.isFailure) {
|
||||||
|
return Result.fail(updatedAddressOrError.error);
|
||||||
|
}
|
||||||
|
updatedProps.address = updatedAddressOrError.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedCustomer = new Customer(updatedProps, this.id);
|
||||||
return Result.ok(updatedCustomer);
|
return Result.ok(updatedCustomer);
|
||||||
}
|
}
|
||||||
|
|
||||||
get companyId(): UniqueID {
|
public toSnapshot(): CustomerSnapshot {
|
||||||
return this.props.companyId;
|
return {
|
||||||
|
id: this.id.toPrimitive(),
|
||||||
|
companyId: this.props.companyId.toPrimitive(),
|
||||||
|
status: this.props.status.toPrimitive(),
|
||||||
|
reference: this.props.reference.isSome()
|
||||||
|
? this.props.reference.unwrap()!.toPrimitive()
|
||||||
|
: null,
|
||||||
|
|
||||||
|
isCompany: this.props.isCompany,
|
||||||
|
name: this.props.name.toPrimitive(),
|
||||||
|
tradeName: this.props.tradeName.isSome()
|
||||||
|
? this.props.tradeName.unwrap()!.toPrimitive()
|
||||||
|
: null,
|
||||||
|
tin: this.props.tin.isSome() ? this.props.tin.unwrap()!.toPrimitive() : null,
|
||||||
|
|
||||||
|
address: this.props.address.toSnapshot(),
|
||||||
|
|
||||||
|
email: this.props.email.isSome() ? this.props.email.unwrap()!.toPrimitive() : null,
|
||||||
|
phone: this.props.phone.isSome() ? this.props.phone.unwrap()!.toPrimitive() : null,
|
||||||
|
fax: this.props.fax.isSome() ? this.props.fax.unwrap()!.toPrimitive() : null,
|
||||||
|
website: this.props.website.isSome() ? this.props.website.unwrap()!.toPrimitive() : null,
|
||||||
|
|
||||||
|
legalRecord: this.props.legalRecord.isSome()
|
||||||
|
? this.props.legalRecord.unwrap()!.toPrimitive()
|
||||||
|
: null,
|
||||||
|
defaultTaxes: this.props.defaultTaxes.map((tax) => tax.toPrimitive()),
|
||||||
|
|
||||||
|
languageCode: this.props.languageCode.toPrimitive(),
|
||||||
|
currencyCode: this.props.currencyCode.toPrimitive(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get reference() {
|
public get isIndividual(): boolean {
|
||||||
return this.props.reference;
|
|
||||||
}
|
|
||||||
|
|
||||||
get name() {
|
|
||||||
return this.props.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
get tradeName() {
|
|
||||||
return this.props.tradeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
get tin(): TINNumber {
|
|
||||||
return this.props.tin;
|
|
||||||
}
|
|
||||||
|
|
||||||
get address(): PostalAddress {
|
|
||||||
return this.props.address;
|
|
||||||
}
|
|
||||||
|
|
||||||
get email(): EmailAddress {
|
|
||||||
return this.props.email;
|
|
||||||
}
|
|
||||||
|
|
||||||
get phone(): PhoneNumber {
|
|
||||||
return this.props.phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
get fax(): PhoneNumber {
|
|
||||||
return this.props.fax;
|
|
||||||
}
|
|
||||||
|
|
||||||
get website(): string {
|
|
||||||
return this.props.website;
|
|
||||||
}
|
|
||||||
|
|
||||||
get legalRecord() {
|
|
||||||
return this.props.legalRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
get defaultTax() {
|
|
||||||
return this.props.defaultTax;
|
|
||||||
}
|
|
||||||
|
|
||||||
get langCode() {
|
|
||||||
return this.props.langCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
get currencyCode() {
|
|
||||||
return this.props.currencyCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isIndividual(): boolean {
|
|
||||||
return !this.props.isCompany;
|
return !this.props.isCompany;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isCompany(): boolean {
|
public get isCompany(): boolean {
|
||||||
return this.props.isCompany;
|
return this.props.isCompany;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isActive(): boolean {
|
public get isActive(): boolean {
|
||||||
return this.props.status.isActive();
|
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 email(): Maybe<EmailAddress> {
|
||||||
|
return this.props.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get phone(): Maybe<PhoneNumber> {
|
||||||
|
return this.props.phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get fax(): Maybe<PhoneNumber> {
|
||||||
|
return this.props.fax;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get website(): Maybe<URLAddress> {
|
||||||
|
return this.props.website;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get legalRecord(): Maybe<TextValue> {
|
||||||
|
return this.props.legalRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get defaultTaxes(): Collection<TaxCode> {
|
||||||
|
return this.props.defaultTaxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get languageCode(): LanguageCode {
|
||||||
|
return this.props.languageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get currencyCode(): CurrencyCode {
|
||||||
|
return this.props.currencyCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,66 +0,0 @@
|
|||||||
import { Criteria } from "@repo/rdx-criteria/server";
|
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
|
||||||
import { Customer, CustomerProps } from "../aggregates";
|
|
||||||
|
|
||||||
export interface ICustomerService {
|
|
||||||
/**
|
|
||||||
* Construye un nuevo Customer validando todos sus value objects.
|
|
||||||
*/
|
|
||||||
buildCustomerInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
props: Omit<CustomerProps, "companyId">,
|
|
||||||
customerId?: UniqueID
|
|
||||||
): Result<Customer, Error>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Guarda un Customer (nuevo o modificado) en base de datos.
|
|
||||||
*/
|
|
||||||
saveCustomer(customer: Customer, transaction: any): Promise<Result<Customer, Error>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Comprueba si existe un Customer con ese ID en la empresa indicada.
|
|
||||||
*/
|
|
||||||
existsByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
customerId: UniqueID,
|
|
||||||
transaction?: any
|
|
||||||
): Promise<Result<boolean, Error>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lista todos los customers que cumplan el criterio, dentro de una empresa.
|
|
||||||
*/
|
|
||||||
findCustomerByCriteriaInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
criteria: Criteria,
|
|
||||||
transaction?: any
|
|
||||||
): Promise<Result<Collection<Customer>, Error>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recupera un Customer por su ID dentro de una empresa.
|
|
||||||
*/
|
|
||||||
getCustomerByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
customerId: UniqueID,
|
|
||||||
transaction?: any
|
|
||||||
): Promise<Result<Customer>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actualiza parcialmente los datos de un Customer.
|
|
||||||
*/
|
|
||||||
updateCustomerByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
customerId: UniqueID,
|
|
||||||
partial: Partial<Omit<CustomerProps, "companyId">>,
|
|
||||||
transaction?: any
|
|
||||||
): Promise<Result<Customer, Error>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Elimina un Customer por ID dentro de una empresa.
|
|
||||||
*/
|
|
||||||
deleteCustomerByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
customerId: UniqueID,
|
|
||||||
transaction?: any
|
|
||||||
): Promise<Result<void, Error>>;
|
|
||||||
}
|
|
||||||
@ -1,11 +1,10 @@
|
|||||||
import { Criteria } from "@repo/rdx-criteria/server";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { Customer, CustomerProps } from "../aggregates";
|
import { Customer, CustomerPatchProps, CustomerProps } from "../aggregates";
|
||||||
import { ICustomerRepository } from "../repositories";
|
import { ICustomerRepository } from "../repositories";
|
||||||
import { ICustomerService } from "./customer-service.interface";
|
|
||||||
|
|
||||||
export class CustomerService implements ICustomerService {
|
export class CustomerService {
|
||||||
constructor(private readonly repository: ICustomerRepository) {}
|
constructor(private readonly repository: ICustomerRepository) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,6 +86,7 @@ export class CustomerService implements ICustomerService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Actualiza parcialmente un cliente existente con nuevos datos.
|
* Actualiza parcialmente un cliente existente con nuevos datos.
|
||||||
|
* No lo guarda en el repositorio.
|
||||||
*
|
*
|
||||||
* @param companyId - Identificador de la empresa a la que pertenece el cliente.
|
* @param companyId - Identificador de la empresa a la que pertenece el cliente.
|
||||||
* @param customerId - Identificador del cliente a actualizar.
|
* @param customerId - Identificador del cliente a actualizar.
|
||||||
@ -97,9 +97,9 @@ export class CustomerService implements ICustomerService {
|
|||||||
async updateCustomerByIdInCompany(
|
async updateCustomerByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
customerId: UniqueID,
|
customerId: UniqueID,
|
||||||
partial: Partial<Omit<CustomerProps, "companyId">>,
|
partial: CustomerPatchProps,
|
||||||
transaction?: any
|
transaction?: any
|
||||||
): Promise<Result<Customer>> {
|
): Promise<Result<Customer, Error>> {
|
||||||
const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction);
|
const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction);
|
||||||
|
|
||||||
if (customerResult.isFailure) {
|
if (customerResult.isFailure) {
|
||||||
@ -113,7 +113,7 @@ export class CustomerService implements ICustomerService {
|
|||||||
return Result.fail(updatedCustomer.error);
|
return Result.fail(updatedCustomer.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.saveCustomer(updatedCustomer.data, transaction);
|
return Result.ok(updatedCustomer.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
export * from "./customer-service.interface";
|
|
||||||
export * from "./customer.service";
|
export * from "./customer.service";
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export * from "./map-dto-to-customer-props";
|
export * from "../application/create-customer/map-dto-to-create-customer-props";
|
||||||
|
|||||||
@ -1,109 +0,0 @@
|
|||||||
import {
|
|
||||||
DomainError,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
} from "@erp/core/api";
|
|
||||||
import { EmailAddress, PhoneNumber, PostalAddress, TINNumber, UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
import { CreateCustomerRequestDTO } from "../../common/dto";
|
|
||||||
import { CustomerProps, CustomerStatus } from "../domain";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convierte el DTO a las props validadas (CustomerProps).
|
|
||||||
* No construye directamente el agregado.
|
|
||||||
*
|
|
||||||
* @param dto - DTO con los datos de la factura de cliente
|
|
||||||
* @returns
|
|
||||||
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function mapDTOToCustomerProps(dto: CreateCustomerRequestDTO) {
|
|
||||||
try {
|
|
||||||
const errors: ValidationErrorDetail[] = [];
|
|
||||||
|
|
||||||
const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
|
||||||
const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_id", errors);
|
|
||||||
const status = extractOrPushError(CustomerStatus.create(dto.status), "status", errors);
|
|
||||||
const reference = dto.reference?.trim() === "" ? undefined : dto.reference;
|
|
||||||
|
|
||||||
const isCompany = dto.is_company ?? true;
|
|
||||||
const name = dto.name?.trim() === "" ? undefined : dto.name;
|
|
||||||
const tradeName = dto.trade_name?.trim() === "" ? undefined : dto.trade_name;
|
|
||||||
|
|
||||||
const tinNumber = extractOrPushError(TINNumber.create(dto.tin), "tin", errors);
|
|
||||||
|
|
||||||
const address = extractOrPushError(
|
|
||||||
PostalAddress.create({
|
|
||||||
street: dto.street,
|
|
||||||
city: dto.city,
|
|
||||||
postalCode: dto.postal_code,
|
|
||||||
state: dto.state,
|
|
||||||
country: dto.country,
|
|
||||||
}),
|
|
||||||
"address",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const emailAddress = extractOrPushError(EmailAddress.create(dto.email), "email", errors);
|
|
||||||
const phoneNumber = extractOrPushError(PhoneNumber.create(dto.phone), "phone", errors);
|
|
||||||
const faxNumber = extractOrPushError(PhoneNumber.create(dto.fax), "fax", errors);
|
|
||||||
const website = dto.website?.trim() === "" ? undefined : dto.website;
|
|
||||||
|
|
||||||
const legalRecord = dto.legal_record?.trim() === "" ? undefined : dto.legal_record;
|
|
||||||
const langCode = dto.lang_code?.trim() === "" ? undefined : dto.lang_code;
|
|
||||||
const currencyCode = dto.currency_code?.trim() === "" ? undefined : dto.currency_code;
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
console.error(errors);
|
|
||||||
return Result.fail(new ValidationErrorCollection("Customer props mapping failed", errors));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug("Mapped customer props:", {
|
|
||||||
companyId,
|
|
||||||
status,
|
|
||||||
reference,
|
|
||||||
isCompany,
|
|
||||||
name,
|
|
||||||
tradeName,
|
|
||||||
tinNumber,
|
|
||||||
address,
|
|
||||||
emailAddress,
|
|
||||||
phoneNumber,
|
|
||||||
faxNumber,
|
|
||||||
website,
|
|
||||||
legalRecord,
|
|
||||||
langCode,
|
|
||||||
currencyCode,
|
|
||||||
});
|
|
||||||
|
|
||||||
const customerProps: CustomerProps = {
|
|
||||||
companyId: companyId!,
|
|
||||||
status: status!,
|
|
||||||
reference: reference!,
|
|
||||||
|
|
||||||
isCompany: isCompany,
|
|
||||||
name: name!,
|
|
||||||
tradeName: tradeName!,
|
|
||||||
tin: tinNumber!,
|
|
||||||
|
|
||||||
address: address!,
|
|
||||||
|
|
||||||
email: emailAddress!,
|
|
||||||
phone: phoneNumber!,
|
|
||||||
fax: faxNumber!,
|
|
||||||
website: website!,
|
|
||||||
|
|
||||||
legalRecord: legalRecord!,
|
|
||||||
defaultTax: [],
|
|
||||||
langCode: langCode!,
|
|
||||||
currencyCode: currencyCode!,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Result.ok({ id: customerId!, props: customerProps });
|
|
||||||
} catch (err: unknown) {
|
|
||||||
console.error(err);
|
|
||||||
return Result.fail(new DomainError("Customer props mapping failed", { cause: err }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -9,8 +9,10 @@ import {
|
|||||||
GetCustomerUseCase,
|
GetCustomerUseCase,
|
||||||
ListCustomersAssembler,
|
ListCustomersAssembler,
|
||||||
ListCustomersUseCase,
|
ListCustomersUseCase,
|
||||||
|
UpdateCustomerAssembler,
|
||||||
|
UpdateCustomerUseCase,
|
||||||
} from "../application";
|
} from "../application";
|
||||||
import { CustomerService, ICustomerService } from "../domain";
|
import { CustomerService } from "../domain";
|
||||||
import { CustomerMapper } from "./mappers";
|
import { CustomerMapper } from "./mappers";
|
||||||
import { CustomerRepository } from "./sequelize";
|
import { CustomerRepository } from "./sequelize";
|
||||||
|
|
||||||
@ -18,18 +20,18 @@ type CustomerDeps = {
|
|||||||
transactionManager: SequelizeTransactionManager;
|
transactionManager: SequelizeTransactionManager;
|
||||||
repo: CustomerRepository;
|
repo: CustomerRepository;
|
||||||
mapper: CustomerMapper;
|
mapper: CustomerMapper;
|
||||||
service: ICustomerService;
|
service: CustomerService;
|
||||||
assemblers: {
|
assemblers: {
|
||||||
list: ListCustomersAssembler;
|
list: ListCustomersAssembler;
|
||||||
get: GetCustomerAssembler;
|
get: GetCustomerAssembler;
|
||||||
create: CreateCustomersAssembler;
|
create: CreateCustomersAssembler;
|
||||||
//update: UpdateCustomerAssembler;
|
update: UpdateCustomerAssembler;
|
||||||
};
|
};
|
||||||
build: {
|
build: {
|
||||||
list: () => ListCustomersUseCase;
|
list: () => ListCustomersUseCase;
|
||||||
get: () => GetCustomerUseCase;
|
get: () => GetCustomerUseCase;
|
||||||
create: () => CreateCustomerUseCase;
|
create: () => CreateCustomerUseCase;
|
||||||
//update: () => UpdateCustomerUseCase;
|
update: () => UpdateCustomerUseCase;
|
||||||
delete: () => DeleteCustomerUseCase;
|
delete: () => DeleteCustomerUseCase;
|
||||||
};
|
};
|
||||||
presenters: {
|
presenters: {
|
||||||
@ -39,7 +41,7 @@ type CustomerDeps = {
|
|||||||
|
|
||||||
let _repo: CustomerRepository | null = null;
|
let _repo: CustomerRepository | null = null;
|
||||||
let _mapper: CustomerMapper | null = null;
|
let _mapper: CustomerMapper | null = null;
|
||||||
let _service: ICustomerService | null = null;
|
let _service: CustomerService | null = null;
|
||||||
let _assemblers: CustomerDeps["assemblers"] | null = null;
|
let _assemblers: CustomerDeps["assemblers"] | null = null;
|
||||||
|
|
||||||
export function getCustomerDependencies(params: ModuleParams): CustomerDeps {
|
export function getCustomerDependencies(params: ModuleParams): CustomerDeps {
|
||||||
@ -55,7 +57,7 @@ export function getCustomerDependencies(params: ModuleParams): CustomerDeps {
|
|||||||
list: new ListCustomersAssembler(), // transforma domain → ListDTO
|
list: new ListCustomersAssembler(), // transforma domain → ListDTO
|
||||||
get: new GetCustomerAssembler(), // transforma domain → DetailDTO
|
get: new GetCustomerAssembler(), // transforma domain → DetailDTO
|
||||||
create: new CreateCustomersAssembler(), // transforma domain → CreatedDTO
|
create: new CreateCustomersAssembler(), // transforma domain → CreatedDTO
|
||||||
//update: new UpdateCustomerAssembler(), // transforma domain -> UpdateDTO
|
update: new UpdateCustomerAssembler(), // transforma domain -> UpdateDTO
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,8 +71,7 @@ export function getCustomerDependencies(params: ModuleParams): CustomerDeps {
|
|||||||
list: () => new ListCustomersUseCase(_service!, transactionManager!, _assemblers!.list),
|
list: () => new ListCustomersUseCase(_service!, transactionManager!, _assemblers!.list),
|
||||||
get: () => new GetCustomerUseCase(_service!, transactionManager!, _assemblers!.get),
|
get: () => new GetCustomerUseCase(_service!, transactionManager!, _assemblers!.get),
|
||||||
create: () => new CreateCustomerUseCase(_service!, transactionManager!, _assemblers!.create),
|
create: () => new CreateCustomerUseCase(_service!, transactionManager!, _assemblers!.create),
|
||||||
/*update: () =>
|
update: () => new UpdateCustomerUseCase(_service!, transactionManager!, _assemblers!.update),
|
||||||
new UpdateCustomerUseCase(_service!, transactionManager!, _assemblers!.update),*/
|
|
||||||
delete: () => new DeleteCustomerUseCase(_service!, transactionManager!),
|
delete: () => new DeleteCustomerUseCase(_service!, transactionManager!),
|
||||||
},
|
},
|
||||||
presenters: {
|
presenters: {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||||
import { CreateCustomerRequestDTO } from "../../../../common/dto";
|
import { UpdateCustomerRequestDTO } from "../../../../common/dto";
|
||||||
import { CreateCustomerUseCase } from "../../../application";
|
import { CreateCustomerUseCase } from "../../../application";
|
||||||
|
|
||||||
export class CreateCustomerController extends ExpressController {
|
export class CreateCustomerController extends ExpressController {
|
||||||
@ -11,7 +11,7 @@ export class CreateCustomerController extends ExpressController {
|
|||||||
|
|
||||||
protected async executeImpl() {
|
protected async executeImpl() {
|
||||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||||
const dto = this.req.body as CreateCustomerRequestDTO;
|
const dto = this.req.body as UpdateCustomerRequestDTO;
|
||||||
|
|
||||||
// Inyectar empresa del usuario autenticado (ownership)
|
// Inyectar empresa del usuario autenticado (ownership)
|
||||||
dto.company_id = companyId.toString();
|
dto.company_id = companyId.toString();
|
||||||
|
|||||||
@ -9,10 +9,10 @@ export class DeleteCustomerController extends ExpressController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async executeImpl(): Promise<any> {
|
async executeImpl(): Promise<any> {
|
||||||
const tenantId = this.getTenantId()!; // garantizado por tenantGuard
|
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||||
const { id } = this.req.params;
|
const { id } = this.req.params;
|
||||||
|
|
||||||
const result = await this.useCase.execute({ id, tenantId });
|
const result = await this.useCase.execute({ id, companyId });
|
||||||
|
|
||||||
return result.match(
|
return result.match(
|
||||||
(data) => this.ok(data),
|
(data) => this.ok(data),
|
||||||
|
|||||||
@ -9,10 +9,12 @@ export class GetCustomerController extends ExpressController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async executeImpl() {
|
protected async executeImpl() {
|
||||||
const tenantId = this.getTenantId()!; // garantizado por tenantGuard
|
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||||
const { id } = this.req.params;
|
const { id } = this.req.params;
|
||||||
|
|
||||||
const result = await this.useCase.execute({ id, tenantId });
|
console.log(id);
|
||||||
|
|
||||||
|
const result = await this.useCase.execute({ id, companyId });
|
||||||
|
|
||||||
return result.match(
|
return result.match(
|
||||||
(data) => this.ok(data),
|
(data) => this.ok(data),
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||||
|
import { UpdateCustomerRequestDTO } from "../../../../common/dto";
|
||||||
|
import { UpdateCustomerUseCase } from "../../../application";
|
||||||
|
|
||||||
|
export class UpdateCustomerController extends ExpressController {
|
||||||
|
public constructor(private readonly useCase: UpdateCustomerUseCase) {
|
||||||
|
super();
|
||||||
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
|
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeImpl() {
|
||||||
|
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||||
|
const { id } = this.req.params;
|
||||||
|
const dto = this.req.body as UpdateCustomerRequestDTO;
|
||||||
|
|
||||||
|
const result = await this.useCase.execute({ id, companyId, dto });
|
||||||
|
|
||||||
|
return result.match(
|
||||||
|
(data) => this.created(data),
|
||||||
|
(err) => this.handleError(err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,72 +0,0 @@
|
|||||||
import { IInvoicingContext } from "#/server/intrastructure";
|
|
||||||
import { ExpressController } from "@rdx/core";
|
|
||||||
import { IUpdateCustomerPresenter } from "./presenter";
|
|
||||||
|
|
||||||
export class UpdateCustomerController extends ExpressController {
|
|
||||||
private useCase: UpdateCustomerUseCase2;
|
|
||||||
private presenter: IUpdateCustomerPresenter;
|
|
||||||
private context: IInvoicingContext;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
props: {
|
|
||||||
useCase: UpdateCustomerUseCase;
|
|
||||||
presenter: IUpdateCustomerPresenter;
|
|
||||||
},
|
|
||||||
context: IInvoicingContext
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
const { useCase, presenter } = props;
|
|
||||||
this.useCase = useCase;
|
|
||||||
this.presenter = presenter;
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeImpl(): Promise<any> {
|
|
||||||
const { customerId } = this.req.params;
|
|
||||||
const request: IUpdateCustomer_DTO = this.req.body;
|
|
||||||
|
|
||||||
if (RuleValidator.validate(RuleValidator.RULE_NOT_NULL_OR_UNDEFINED, customerId).isFailure) {
|
|
||||||
return this.invalidInputError("Customer Id param is required!");
|
|
||||||
}
|
|
||||||
|
|
||||||
const idOrError = UniqueID.create(customerId);
|
|
||||||
if (idOrError.isFailure) {
|
|
||||||
return this.invalidInputError("Invalid customer Id param!");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await this.useCase.execute({
|
|
||||||
id: idOrError.object,
|
|
||||||
data: request,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.isFailure) {
|
|
||||||
const { error } = result;
|
|
||||||
|
|
||||||
switch (error.code) {
|
|
||||||
case UseCaseError.NOT_FOUND_ERROR:
|
|
||||||
return this.notFoundError("Customer not found", error);
|
|
||||||
|
|
||||||
case UseCaseError.INVALID_INPUT_DATA:
|
|
||||||
return this.invalidInputError(error.message);
|
|
||||||
|
|
||||||
case UseCaseError.UNEXCEPTED_ERROR:
|
|
||||||
return this.internalServerError(result.error.message, result.error);
|
|
||||||
|
|
||||||
case UseCaseError.REPOSITORY_ERROR:
|
|
||||||
return this.conflictError(result.error, result.error.details);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return this.clientError(result.error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const customer = <Customer>result.object;
|
|
||||||
|
|
||||||
return this.ok<IUpdateCustomer_Response_DTO>(this.presenter.map(customer, this.context));
|
|
||||||
} catch (e: unknown) {
|
|
||||||
return this.fail(e as IServerError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,10 +3,10 @@ import { ILogger, ModuleParams, validateRequest } from "@erp/core/api";
|
|||||||
import { Application, NextFunction, Request, Response, Router } from "express";
|
import { Application, NextFunction, Request, Response, Router } from "express";
|
||||||
import { Sequelize } from "sequelize";
|
import { Sequelize } from "sequelize";
|
||||||
import {
|
import {
|
||||||
CreateCustomerRequestSchema,
|
|
||||||
CustomerListRequestSchema,
|
CustomerListRequestSchema,
|
||||||
DeleteCustomerByIdRequestSchema,
|
DeleteCustomerByIdRequestSchema,
|
||||||
GetCustomerByIdRequestSchema,
|
GetCustomerByIdRequestSchema,
|
||||||
|
UpdateCustomerRequestSchema,
|
||||||
} from "../../../common/dto";
|
} from "../../../common/dto";
|
||||||
import { getCustomerDependencies } from "../dependencies";
|
import { getCustomerDependencies } from "../dependencies";
|
||||||
import {
|
import {
|
||||||
@ -64,7 +64,7 @@ export const customersRouter = (params: ModuleParams) => {
|
|||||||
"/",
|
"/",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
|
|
||||||
validateRequest(CreateCustomerRequestSchema),
|
validateRequest(UpdateCustomerRequestSchema),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.build.create();
|
const useCase = deps.build.create();
|
||||||
const controller = new CreateCustomerController(useCase);
|
const controller = new CreateCustomerController(useCase);
|
||||||
|
|||||||
@ -6,8 +6,27 @@ import {
|
|||||||
ValidationErrorDetail,
|
ValidationErrorDetail,
|
||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import { EmailAddress, PhoneNumber, PostalAddress, TINNumber, UniqueID } from "@repo/rdx-ddd";
|
import {
|
||||||
import { Result } from "@repo/rdx-utils";
|
City,
|
||||||
|
Country,
|
||||||
|
CurrencyCode,
|
||||||
|
EmailAddress,
|
||||||
|
LanguageCode,
|
||||||
|
Name,
|
||||||
|
PhoneNumber,
|
||||||
|
PostalAddress,
|
||||||
|
PostalCode,
|
||||||
|
Province,
|
||||||
|
Street,
|
||||||
|
TINNumber,
|
||||||
|
TaxCode,
|
||||||
|
TextValue,
|
||||||
|
URLAddress,
|
||||||
|
UniqueID,
|
||||||
|
maybeFromNullableVO,
|
||||||
|
toNullable,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
import { Collection, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
||||||
import { Customer, CustomerProps, CustomerStatus } from "../../domain";
|
import { Customer, CustomerProps, CustomerStatus } from "../../domain";
|
||||||
import { CustomerCreationAttributes, CustomerModel } from "../sequelize";
|
import { CustomerCreationAttributes, CustomerModel } from "../sequelize";
|
||||||
|
|
||||||
@ -28,41 +47,140 @@ export class CustomerMapper
|
|||||||
"company_id",
|
"company_id",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isCompany = source.is_company;
|
||||||
const status = extractOrPushError(CustomerStatus.create(source.status), "status", errors);
|
const status = extractOrPushError(CustomerStatus.create(source.status), "status", errors);
|
||||||
const reference = source.reference?.trim() === "" ? undefined : source.reference;
|
const reference = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.reference, (value) => Name.create(value)),
|
||||||
const isCompany = source.is_company ?? true;
|
"reference",
|
||||||
const name = source.name?.trim() === "" ? undefined : source.name;
|
|
||||||
const tradeName = source.trade_name?.trim() === "" ? undefined : source.trade_name;
|
|
||||||
|
|
||||||
const tinNumber = extractOrPushError(TINNumber.create(source.tin), "tin", errors);
|
|
||||||
|
|
||||||
const address = extractOrPushError(
|
|
||||||
PostalAddress.create({
|
|
||||||
street: source.street,
|
|
||||||
city: source.city,
|
|
||||||
postalCode: source.postal_code,
|
|
||||||
state: source.state,
|
|
||||||
country: source.country,
|
|
||||||
}),
|
|
||||||
"address",
|
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
const emailAddress = extractOrPushError(EmailAddress.create(source.email), "email", errors);
|
const name = extractOrPushError(Name.create(source.name), "name", errors);
|
||||||
const phoneNumber = extractOrPushError(PhoneNumber.create(source.phone), "phone", errors);
|
|
||||||
const faxNumber = extractOrPushError(PhoneNumber.create(source.fax), "fax", errors);
|
|
||||||
const website = source.website?.trim() === "" ? undefined : source.website;
|
|
||||||
|
|
||||||
const legalRecord = source.legal_record?.trim() === "" ? undefined : source.legal_record;
|
const tradeName = extractOrPushError(
|
||||||
const langCode = source.lang_code?.trim() === "" ? undefined : source.lang_code;
|
maybeFromNullableVO(source.trade_name, (value) => Name.create(value)),
|
||||||
const currencyCode = source.currency_code?.trim() === "" ? undefined : source.currency_code;
|
"trade_name",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const tinNumber = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.tin, (value) => TINNumber.create(value)),
|
||||||
|
"tin",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const street = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.street, (value) => Street.create(value)),
|
||||||
|
"street",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const street2 = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.street2, (value) => Street.create(value)),
|
||||||
|
"street2",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const city = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.city, (value) => City.create(value)),
|
||||||
|
"city",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const province = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.province, (value) => Province.create(value)),
|
||||||
|
"province",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const postalCode = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.postal_code, (value) => PostalCode.create(value)),
|
||||||
|
"postal_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const country = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.country, (value) => Country.create(value)),
|
||||||
|
"country",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const emailAddress = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.email, (value) => EmailAddress.create(value)),
|
||||||
|
"email",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const phoneNumber = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.phone, (value) => PhoneNumber.create(value)),
|
||||||
|
"phone",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const faxNumber = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.fax, (value) => PhoneNumber.create(value)),
|
||||||
|
"fax",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const website = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.website, (value) => URLAddress.create(value)),
|
||||||
|
"website",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const legalRecord = extractOrPushError(
|
||||||
|
maybeFromNullableVO(source.legal_record, (value) => TextValue.create(value)),
|
||||||
|
"legal_record",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const languageCode = extractOrPushError(
|
||||||
|
LanguageCode.create(source.language_code),
|
||||||
|
"language_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const currencyCode = extractOrPushError(
|
||||||
|
CurrencyCode.create(source.currency_code),
|
||||||
|
"currency_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
// source.default_taxes is stored as a comma-separated string
|
||||||
|
const defaultTaxes = new Collection<TaxCode>();
|
||||||
|
if (!isNullishOrEmpty(source.default_taxes)) {
|
||||||
|
source.default_taxes.split(",").map((taxCode, index) => {
|
||||||
|
const tax = extractOrPushError(TaxCode.create(taxCode), `default_taxes.${index}`, errors);
|
||||||
|
if (tax) {
|
||||||
|
defaultTaxes.add(tax!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
console.error(errors);
|
console.error(errors);
|
||||||
return Result.fail(new ValidationErrorCollection("Customer props mapping failed", errors));
|
return Result.fail(new ValidationErrorCollection("Customer props mapping failed", errors));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const postalAddressProps = {
|
||||||
|
street: street!,
|
||||||
|
street2: street2!,
|
||||||
|
city: city!,
|
||||||
|
postalCode: postalCode!,
|
||||||
|
province: province!,
|
||||||
|
country: country!,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(postalAddressProps);
|
||||||
|
|
||||||
|
const postalAddress = extractOrPushError(
|
||||||
|
PostalAddress.create(postalAddressProps),
|
||||||
|
"address",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
const customerProps: CustomerProps = {
|
const customerProps: CustomerProps = {
|
||||||
companyId: companyId!,
|
companyId: companyId!,
|
||||||
status: status!,
|
status: status!,
|
||||||
@ -73,7 +191,7 @@ export class CustomerMapper
|
|||||||
tradeName: tradeName!,
|
tradeName: tradeName!,
|
||||||
tin: tinNumber!,
|
tin: tinNumber!,
|
||||||
|
|
||||||
address: address!,
|
address: postalAddress!,
|
||||||
|
|
||||||
email: emailAddress!,
|
email: emailAddress!,
|
||||||
phone: phoneNumber!,
|
phone: phoneNumber!,
|
||||||
@ -81,8 +199,8 @@ export class CustomerMapper
|
|||||||
website: website!,
|
website: website!,
|
||||||
|
|
||||||
legalRecord: legalRecord!,
|
legalRecord: legalRecord!,
|
||||||
defaultTax: [],
|
defaultTaxes: defaultTaxes!,
|
||||||
langCode: langCode!,
|
languageCode: languageCode!,
|
||||||
currencyCode: currencyCode!,
|
currencyCode: currencyCode!,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -96,30 +214,38 @@ export class CustomerMapper
|
|||||||
return {
|
return {
|
||||||
id: source.id.toPrimitive(),
|
id: source.id.toPrimitive(),
|
||||||
company_id: source.companyId.toPrimitive(),
|
company_id: source.companyId.toPrimitive(),
|
||||||
reference: source.reference,
|
|
||||||
|
reference: source.reference.match(
|
||||||
|
(value) => value.toPrimitive(),
|
||||||
|
() => ""
|
||||||
|
),
|
||||||
|
|
||||||
is_company: source.isCompany,
|
is_company: source.isCompany,
|
||||||
name: source.name,
|
name: source.name.toPrimitive(),
|
||||||
trade_name: source.tradeName,
|
trade_name: toNullable(source.tradeName, (trade_name) => trade_name.toPrimitive()),
|
||||||
tin: source.tin.toPrimitive(),
|
tin: toNullable(source.tin, (tin) => tin.toPrimitive()),
|
||||||
|
|
||||||
email: source.email.toPrimitive(),
|
street: toNullable(source.address.street, (street) => street.toPrimitive()),
|
||||||
phone: source.phone.toPrimitive(),
|
street2: toNullable(source.address.street2, (street2) => street2.toPrimitive()),
|
||||||
fax: source.fax.toPrimitive(),
|
city: toNullable(source.address.city, (city) => city.toPrimitive()),
|
||||||
website: source.website,
|
province: toNullable(source.address.province, (province) => province.toPrimitive()),
|
||||||
|
postal_code: toNullable(source.address.postalCode, (postal_code) =>
|
||||||
|
postal_code.toPrimitive()
|
||||||
|
),
|
||||||
|
country: toNullable(source.address.country, (country) => country.toPrimitive()),
|
||||||
|
|
||||||
default_tax: source.defaultTax.toString(),
|
email: toNullable(source.email, (email) => email.toPrimitive()),
|
||||||
legal_record: source.legalRecord,
|
phone: toNullable(source.phone, (phone) => phone.toPrimitive()),
|
||||||
lang_code: source.langCode,
|
fax: toNullable(source.fax, (fax) => fax.toPrimitive()),
|
||||||
currency_code: source.currencyCode,
|
website: toNullable(source.website, (website) => website.toPrimitive()),
|
||||||
|
|
||||||
|
legal_record: toNullable(source.legalRecord, (legal_record) => legal_record.toPrimitive()),
|
||||||
|
|
||||||
|
default_taxes: source.defaultTaxes.map((item) => item.toPrimitive()).join(", "),
|
||||||
|
|
||||||
status: source.isActive ? "active" : "inactive",
|
status: source.isActive ? "active" : "inactive",
|
||||||
|
language_code: source.languageCode.toPrimitive(),
|
||||||
street: source.address.street,
|
currency_code: source.currencyCode.toPrimitive(),
|
||||||
street2: source.address.street2,
|
|
||||||
city: source.address.city,
|
|
||||||
state: source.address.state,
|
|
||||||
postal_code: source.address.postalCode,
|
|
||||||
country: source.address.country,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export class CustomerModel extends Model<
|
|||||||
declare street: string;
|
declare street: string;
|
||||||
declare street2: string;
|
declare street2: string;
|
||||||
declare city: string;
|
declare city: string;
|
||||||
declare state: string;
|
declare province: string;
|
||||||
declare postal_code: string;
|
declare postal_code: string;
|
||||||
declare country: string;
|
declare country: string;
|
||||||
|
|
||||||
@ -34,9 +34,9 @@ export class CustomerModel extends Model<
|
|||||||
|
|
||||||
declare legal_record: string;
|
declare legal_record: string;
|
||||||
|
|
||||||
declare default_tax: string;
|
declare default_taxes: string;
|
||||||
declare status: string;
|
declare status: string;
|
||||||
declare lang_code: string;
|
declare language_code: string;
|
||||||
declare currency_code: string;
|
declare currency_code: string;
|
||||||
|
|
||||||
static associate(database: Sequelize) {}
|
static associate(database: Sequelize) {}
|
||||||
@ -95,7 +95,7 @@ export default (database: Sequelize) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
},
|
},
|
||||||
state: {
|
province: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
@ -143,13 +143,13 @@ export default (database: Sequelize) => {
|
|||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
default_tax: {
|
default_taxes: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: true,
|
||||||
defaultValue: "",
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
lang_code: {
|
language_code: {
|
||||||
type: DataTypes.STRING(2),
|
type: DataTypes.STRING(2),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: "es",
|
defaultValue: "es",
|
||||||
@ -180,6 +180,7 @@ export default (database: Sequelize) => {
|
|||||||
|
|
||||||
indexes: [
|
indexes: [
|
||||||
{ name: "company_idx", fields: ["company_id"], unique: false },
|
{ name: "company_idx", fields: ["company_id"], unique: false },
|
||||||
|
{ name: "idx_company_idx", fields: ["id", "company_id"], unique: true },
|
||||||
{ name: "email_idx", fields: ["email"], unique: true },
|
{ name: "email_idx", fields: ["email"], unique: true },
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SequelizeRepository, translateSequelizeError } from "@erp/core/api";
|
import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } from "@erp/core/api";
|
||||||
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
@ -89,10 +89,12 @@ export class CustomerRepository
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!row) {
|
if (!row) {
|
||||||
return Result.fail(new Error(`Customer ${id.toString()} not found`));
|
return Result.fail(new EntityNotFoundError("Customer", "id", id.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.mapper.mapToDomain(row);
|
const customer = this.mapper.mapToDomain(row);
|
||||||
|
console.log(customer);
|
||||||
|
return customer;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return Result.fail(translateSequelizeError(error));
|
return Result.fail(translateSequelizeError(error));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,14 +5,15 @@ export const CreateCustomerRequestSchema = z.object({
|
|||||||
company_id: z.uuid(),
|
company_id: z.uuid(),
|
||||||
reference: z.string().default(""),
|
reference: z.string().default(""),
|
||||||
|
|
||||||
is_company: z.boolean().default(true),
|
is_company: z.boolean().default(false),
|
||||||
name: z.string().default(""),
|
name: z.string().default(""),
|
||||||
trade_name: z.string().default(""),
|
trade_name: z.string().default(""),
|
||||||
tin: z.string().default(""),
|
tin: z.string().default(""),
|
||||||
|
|
||||||
street: z.string().default(""),
|
street: z.string().default(""),
|
||||||
|
street2: z.string().default(""),
|
||||||
city: z.string().default(""),
|
city: z.string().default(""),
|
||||||
state: z.string().default(""),
|
province: z.string().default(""),
|
||||||
postal_code: z.string().default(""),
|
postal_code: z.string().default(""),
|
||||||
country: z.string().default(""),
|
country: z.string().default(""),
|
||||||
|
|
||||||
@ -23,9 +24,9 @@ export const CreateCustomerRequestSchema = z.object({
|
|||||||
|
|
||||||
legal_record: z.string().default(""),
|
legal_record: z.string().default(""),
|
||||||
|
|
||||||
default_tax: z.array(z.string()).default([]),
|
default_taxes: z.array(z.string()).default([]),
|
||||||
status: z.string().default("active"),
|
status: z.string().default("active"),
|
||||||
lang_code: z.string().default("es"),
|
language_code: z.string().default("es"),
|
||||||
currency_code: z.string().default("EUR"),
|
currency_code: z.string().default("EUR"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
|
export const UpdateCustomerRequestSchema = z.object({
|
||||||
|
reference: z.string().optional(),
|
||||||
|
|
||||||
|
is_company: z.boolean().optional(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
trade_name: z.string().optional(),
|
||||||
|
tin: z.string().optional(),
|
||||||
|
|
||||||
|
street: z.string().optional(),
|
||||||
|
street2: z.string().optional(),
|
||||||
|
city: z.string().optional(),
|
||||||
|
province: z.string().optional(),
|
||||||
|
postal_code: z.string().optional(),
|
||||||
|
country: z.string().optional(),
|
||||||
|
|
||||||
|
email: z.string().optional(),
|
||||||
|
phone: z.string().optional(),
|
||||||
|
fax: z.string().optional(),
|
||||||
|
website: z.string().optional(),
|
||||||
|
|
||||||
|
legal_record: z.string().optional(),
|
||||||
|
|
||||||
|
default_taxes: z.array(z.string()).optional(), // completo (sustituye), o null => vaciar
|
||||||
|
language_code: z.string().optional(),
|
||||||
|
currency_code: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type UpdateCustomerRequestDTO = z.infer<typeof UpdateCustomerRequestSchema>;
|
||||||
@ -1,12 +1,12 @@
|
|||||||
import { MetadataSchema } from "@erp/core";
|
import { MetadataSchema } from "@erp/core";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
export const CustomerCreationResponseSchema = z.object({
|
export const CreateCustomerResponseSchema = z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
company_id: z.uuid(),
|
company_id: z.uuid(),
|
||||||
reference: z.string(),
|
reference: z.string(),
|
||||||
|
|
||||||
is_company: z.boolean(),
|
is_company: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
trade_name: z.string(),
|
trade_name: z.string(),
|
||||||
tin: z.string(),
|
tin: z.string(),
|
||||||
@ -25,12 +25,12 @@ export const CustomerCreationResponseSchema = z.object({
|
|||||||
|
|
||||||
legal_record: z.string(),
|
legal_record: z.string(),
|
||||||
|
|
||||||
default_tax: z.array(z.string()),
|
default_taxes: z.array(z.string()),
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
lang_code: z.string(),
|
language_code: z.string(),
|
||||||
currency_code: z.string(),
|
currency_code: z.string(),
|
||||||
|
|
||||||
metadata: MetadataSchema.optional(),
|
metadata: MetadataSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CustomerCreationResponseDTO = z.infer<typeof CustomerCreationResponseSchema>;
|
export type CustomerCreationResponseDTO = z.infer<typeof CreateCustomerResponseSchema>;
|
||||||
@ -24,9 +24,9 @@ export const CustomerListResponseSchema = createListViewResponseSchema(
|
|||||||
|
|
||||||
legal_record: z.string(),
|
legal_record: z.string(),
|
||||||
|
|
||||||
default_tax: z.number(),
|
default_taxes: z.array(z.string()),
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
lang_code: z.string(),
|
language_code: z.string(),
|
||||||
currency_code: z.string(),
|
currency_code: z.string(),
|
||||||
|
|
||||||
metadata: MetadataSchema.optional(),
|
metadata: MetadataSchema.optional(),
|
||||||
|
|||||||
@ -3,14 +3,16 @@ import * as z from "zod/v4";
|
|||||||
|
|
||||||
export const GetCustomerByIdResponseSchema = z.object({
|
export const GetCustomerByIdResponseSchema = z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
|
company_id: z.uuid(),
|
||||||
reference: z.string(),
|
reference: z.string(),
|
||||||
|
|
||||||
is_company: z.boolean(),
|
is_company: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
trade_name: z.string(),
|
trade_name: z.string(),
|
||||||
tin: z.string(),
|
tin: z.string(),
|
||||||
|
|
||||||
street: z.string(),
|
street: z.string(),
|
||||||
|
street2: z.string(),
|
||||||
city: z.string(),
|
city: z.string(),
|
||||||
state: z.string(),
|
state: z.string(),
|
||||||
postal_code: z.string(),
|
postal_code: z.string(),
|
||||||
@ -23,9 +25,9 @@ export const GetCustomerByIdResponseSchema = z.object({
|
|||||||
|
|
||||||
legal_record: z.string(),
|
legal_record: z.string(),
|
||||||
|
|
||||||
default_tax: z.number(),
|
default_taxes: z.array(z.string()),
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
lang_code: z.string(),
|
language_code: z.string(),
|
||||||
currency_code: z.string(),
|
currency_code: z.string(),
|
||||||
|
|
||||||
metadata: MetadataSchema.optional(),
|
metadata: MetadataSchema.optional(),
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
export * from "./customer-creation.result.dto";
|
export * from "./create-customer.result.dto";
|
||||||
export * from "./customer-list.response.dto";
|
export * from "./customer-list.response.dto";
|
||||||
export * from "./get-customer-by-id.response.dto";
|
export * from "./get-customer-by-id.response.dto";
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { useDataSource, useQueryKey } from "@erp/core/hooks";
|
import { useDataSource, useQueryKey } from "@erp/core/hooks";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { CreateCustomerRequestDTO } from "../../common/dto";
|
import { UpdateCustomerRequestDTO } from "../../common/dto";
|
||||||
|
|
||||||
export const useCreateCustomerMutation = () => {
|
export const useCreateCustomerMutation = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const dataSource = useDataSource();
|
const dataSource = useDataSource();
|
||||||
const keys = useQueryKey();
|
const keys = useQueryKey();
|
||||||
|
|
||||||
return useMutation<CreateCustomerRequestDTO, Error, Partial<CreateCustomerRequestDTO>>({
|
return useMutation<UpdateCustomerRequestDTO, Error, Partial<UpdateCustomerRequestDTO>>({
|
||||||
mutationFn: (data) => {
|
mutationFn: (data) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
return dataSource.createOne("customers", data);
|
return dataSource.createOne("customers", data);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
import { CreateCustomerRequestSchema } from "../../../common";
|
import { UpdateCustomerRequestSchema } from "../../../common";
|
||||||
|
|
||||||
export const CustomerDataFormSchema = CreateCustomerRequestSchema;
|
export const CustomerDataFormSchema = UpdateCustomerRequestSchema;
|
||||||
export type CustomerData = z.infer<typeof CustomerDataFormSchema>;
|
export type CustomerData = z.infer<typeof CustomerDataFormSchema>;
|
||||||
|
|||||||
1
packages/rdx-ddd/src/helpers/index.ts
Normal file
1
packages/rdx-ddd/src/helpers/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./normalizers";
|
||||||
35
packages/rdx-ddd/src/helpers/normalizers.ts
Normal file
35
packages/rdx-ddd/src/helpers/normalizers.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// application/shared/normalizers.ts
|
||||||
|
// Normalizadores y adaptadores DTO -> Maybe/VO
|
||||||
|
|
||||||
|
import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
/** any | null | undefined -> Maybe<T> usando fábrica VO */
|
||||||
|
export function maybeFromNullableVO<T>(
|
||||||
|
input: any,
|
||||||
|
voFactory: (raw: any) => Result<T>
|
||||||
|
): Result<Maybe<T>> {
|
||||||
|
if (isNullishOrEmpty(input)) return Result.ok(Maybe.none<T>());
|
||||||
|
const vo = voFactory(input);
|
||||||
|
return vo.isSuccess ? Result.ok(Maybe.some(vo.data)) : Result.fail(vo.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** string | null | undefined -> Maybe<string> (trim, vacío => None) */
|
||||||
|
export function maybeFromNullableString(input?: string | null): Maybe<string> {
|
||||||
|
if (isNullishOrEmpty(input)) return Maybe.none<string>();
|
||||||
|
const t = (input as string).trim();
|
||||||
|
return t ? Maybe.some(t) : Maybe.none<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Maybe<T> -> null para transporte */
|
||||||
|
export function toNullable<T>(m: Maybe<T>, map?: (t: T) => any): any | null {
|
||||||
|
if (m.isNone()) return null;
|
||||||
|
const v = m.unwrap() as T;
|
||||||
|
return map ? String(map(v)) : String(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Maybe<T> -> "" para transporte */
|
||||||
|
export function toEmptyString<T>(m: Maybe<T>, map?: (t: T) => string): string {
|
||||||
|
if (m.isNone()) return "";
|
||||||
|
const v = m.unwrap() as T;
|
||||||
|
return map ? map(v) : String(v);
|
||||||
|
}
|
||||||
@ -2,4 +2,5 @@ export * from "./aggregate-root";
|
|||||||
export * from "./aggregate-root-repository.interface";
|
export * from "./aggregate-root-repository.interface";
|
||||||
export * from "./domain-entity";
|
export * from "./domain-entity";
|
||||||
export * from "./events/domain-event.interface";
|
export * from "./events/domain-event.interface";
|
||||||
|
export * from "./helpers";
|
||||||
export * from "./value-objects";
|
export * from "./value-objects";
|
||||||
|
|||||||
@ -15,12 +15,6 @@ describe("EmailAddress Value Object", () => {
|
|||||||
expect(result.error.message).toBe("Invalid email format");
|
expect(result.error.message).toBe("Invalid email format");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should allow null email", () => {
|
|
||||||
const result = EmailAddress.createNullable();
|
|
||||||
expect(result.isSuccess).toBe(true);
|
|
||||||
expect(result.data.getOrUndefined()).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return an error for empty string", () => {
|
it("should return an error for empty string", () => {
|
||||||
const result = EmailAddress.create("");
|
const result = EmailAddress.create("");
|
||||||
|
|
||||||
@ -45,13 +39,6 @@ describe("EmailAddress Value Object", () => {
|
|||||||
expect(email1.data.equals(email2.data)).toBe(false);
|
expect(email1.data.equals(email2.data)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should detect empty email correctly", () => {
|
|
||||||
const email = EmailAddress.createNullable();
|
|
||||||
|
|
||||||
expect(email.isSuccess).toBe(true);
|
|
||||||
expect(email.data.isSome()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should detect non-empty email correctly", () => {
|
it("should detect non-empty email correctly", () => {
|
||||||
const email = EmailAddress.create("test@example.com");
|
const email = EmailAddress.create("test@example.com");
|
||||||
|
|
||||||
|
|||||||
@ -14,19 +14,6 @@ describe("Name Value Object", () => {
|
|||||||
expect(nameResult.error).toBeInstanceOf(Error);
|
expect(nameResult.error).toBeInstanceOf(Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Debe permitir un Name nullable vacío", () => {
|
|
||||||
const nullableNameResult = Name.createNullable("");
|
|
||||||
expect(nullableNameResult.isSuccess).toBe(true);
|
|
||||||
expect(nullableNameResult.data.isSome()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Debe permitir un Name nullable con un valor válido", () => {
|
|
||||||
const nullableNameResult = Name.createNullable("Alice");
|
|
||||||
expect(nullableNameResult.isSuccess).toBe(true);
|
|
||||||
expect(nullableNameResult.data.isSome()).toBe(true);
|
|
||||||
expect(nullableNameResult.data.getOrUndefined()?.toString()).toBe("Alice");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Debe generar acrónimos correctamente", () => {
|
test("Debe generar acrónimos correctamente", () => {
|
||||||
expect(Name.generateAcronym("John Doe")).toBe("JDXX");
|
expect(Name.generateAcronym("John Doe")).toBe("JDXX");
|
||||||
expect(Name.generateAcronym("Alice Bob Charlie")).toBe("ABCX");
|
expect(Name.generateAcronym("Alice Bob Charlie")).toBe("ABCX");
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { parsePhoneNumberWithError } from "libphonenumber-js";
|
import { parsePhoneNumberWithError } from "libphonenumber-js";
|
||||||
import { Maybe } from "../../helpers/maybe";
|
|
||||||
import { PhoneNumber } from "../phone-number";
|
import { PhoneNumber } from "../phone-number";
|
||||||
|
|
||||||
describe("PhoneNumber", () => {
|
describe("PhoneNumber", () => {
|
||||||
@ -21,18 +20,6 @@ describe("PhoneNumber", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("debe devolver None para valores nulos o vacíos", () => {
|
|
||||||
const result = PhoneNumber.createNullable(nullablePhone);
|
|
||||||
expect(result.isSuccess).toBe(true);
|
|
||||||
expect(result.data).toEqual(Maybe.none());
|
|
||||||
});
|
|
||||||
|
|
||||||
test("debe devolver Some con un número de teléfono válido", () => {
|
|
||||||
const result = PhoneNumber.createNullable(validPhone);
|
|
||||||
expect(result.isSuccess).toBe(true);
|
|
||||||
expect(result.data.isSome()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("debe obtener el valor del número de teléfono", () => {
|
test("debe obtener el valor del número de teléfono", () => {
|
||||||
const result = PhoneNumber.create(validPhone);
|
const result = PhoneNumber.create(validPhone);
|
||||||
expect(result.isSuccess).toBe(true);
|
expect(result.isSuccess).toBe(true);
|
||||||
|
|||||||
@ -24,27 +24,6 @@ describe("PostalAddress Value Object", () => {
|
|||||||
expect(result.error?.message).toBe("Invalid postal code format");
|
expect(result.error?.message).toBe("Invalid postal code format");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("✅ `createNullable` debería devolver Maybe.none si los valores son nulos o vacíos", () => {
|
|
||||||
expect(PostalAddress.createNullable().data.isSome()).toBe(false);
|
|
||||||
expect(
|
|
||||||
PostalAddress.createNullable({
|
|
||||||
street: "",
|
|
||||||
city: "",
|
|
||||||
postalCode: "",
|
|
||||||
state: "",
|
|
||||||
country: "",
|
|
||||||
}).data.isSome()
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("✅ `createNullable` debería devolver Maybe.some si los valores son válidos", () => {
|
|
||||||
const result = PostalAddress.createNullable(validAddress);
|
|
||||||
|
|
||||||
expect(result.isSuccess).toBe(true);
|
|
||||||
expect(result.data.isSome()).toBe(true);
|
|
||||||
expect(result.data.unwrap()).toBeInstanceOf(PostalAddress);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("✅ Métodos getters deberían devolver valores esperados", () => {
|
test("✅ Métodos getters deberían devolver valores esperados", () => {
|
||||||
const address = PostalAddress.create(validAddress).data;
|
const address = PostalAddress.create(validAddress).data;
|
||||||
|
|
||||||
|
|||||||
@ -25,17 +25,4 @@ describe("Slug Value Object", () => {
|
|||||||
expect(slugResult.isSuccess).toBe(false);
|
expect(slugResult.isSuccess).toBe(false);
|
||||||
expect(slugResult.error).toBeInstanceOf(Error);
|
expect(slugResult.error).toBeInstanceOf(Error);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Debe permitir un Slug nullable vacío", () => {
|
|
||||||
const nullableSlugResult = Slug.createNullable("");
|
|
||||||
expect(nullableSlugResult.isSuccess).toBe(true);
|
|
||||||
expect(nullableSlugResult.data.isSome()).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Debe permitir un Slug nullable con un valor válido", () => {
|
|
||||||
const nullableSlugResult = Slug.createNullable("my-slug");
|
|
||||||
expect(nullableSlugResult.isSuccess).toBe(true);
|
|
||||||
expect(nullableSlugResult.data.isSome()).toBe(true);
|
|
||||||
expect(nullableSlugResult.data.getOrUndefined()?.toString()).toBe("my-slug");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,19 +19,6 @@ describe("TINNumber", () => {
|
|||||||
expect(result.error?.message).toBe("TIN must be at most 10 characters long");
|
expect(result.error?.message).toBe("TIN must be at most 10 characters long");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("debería devolver None cuando el valor es nulo o vacío en createNullable", () => {
|
|
||||||
const result = TINNumber.createNullable("");
|
|
||||||
expect(result.isSuccess).toBe(true);
|
|
||||||
expect(result.data.isNone()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("debería devolver Some cuando el valor es válido en createNullable", () => {
|
|
||||||
const result = TINNumber.createNullable("6789");
|
|
||||||
expect(result.isSuccess).toBe(true);
|
|
||||||
expect(result.data.isSome()).toBe(true);
|
|
||||||
expect(result.data.unwrap()?.toString()).toBe("6789");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("debería devolver el valor correcto en toString()", () => {
|
it("debería devolver el valor correcto en toString()", () => {
|
||||||
const result = TINNumber.create("ABC123");
|
const result = TINNumber.create("ABC123");
|
||||||
expect(result.isSuccess).toBe(true);
|
expect(result.isSuccess).toBe(true);
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
interface ITestValueProps {
|
interface TestValueProps {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestValueObject extends ValueObject<ITestValueProps> {
|
class TestValueObject extends ValueObject<TestValueProps> {
|
||||||
constructor(value: string) {
|
constructor(value: string) {
|
||||||
super({ value });
|
super({ value });
|
||||||
}
|
}
|
||||||
|
|||||||
38
packages/rdx-ddd/src/value-objects/city.ts
Normal file
38
packages/rdx-ddd/src/value-objects/city.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
|
interface CityProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class City extends ValueObject<CityProps> {
|
||||||
|
private static readonly MAX_LENGTH = 255;
|
||||||
|
|
||||||
|
protected static validate(value: string) {
|
||||||
|
const schema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(City.MAX_LENGTH, {
|
||||||
|
message: `City must be at most ${City.MAX_LENGTH} characters long`,
|
||||||
|
});
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(value: string) {
|
||||||
|
const valueIsValid = City.validate(value);
|
||||||
|
|
||||||
|
if (!valueIsValid.success) {
|
||||||
|
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||||
|
}
|
||||||
|
return Result.ok(new City({ value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive() {
|
||||||
|
return this.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
38
packages/rdx-ddd/src/value-objects/country.ts
Normal file
38
packages/rdx-ddd/src/value-objects/country.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
|
interface CountryProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Country extends ValueObject<CountryProps> {
|
||||||
|
private static readonly MAX_LENGTH = 255;
|
||||||
|
|
||||||
|
protected static validate(value: string) {
|
||||||
|
const schema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(Country.MAX_LENGTH, {
|
||||||
|
message: `Country must be at most ${Country.MAX_LENGTH} characters long`,
|
||||||
|
});
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(value: string) {
|
||||||
|
const valueIsValid = Country.validate(value);
|
||||||
|
|
||||||
|
if (!valueIsValid.success) {
|
||||||
|
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||||
|
}
|
||||||
|
return Result.ok(new Country({ value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive() {
|
||||||
|
return this.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
48
packages/rdx-ddd/src/value-objects/currency-code.ts
Normal file
48
packages/rdx-ddd/src/value-objects/currency-code.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
|
interface CurrencyCodeProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ISO 4217 Currency Codes
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class CurrencyCode extends ValueObject<CurrencyCodeProps> {
|
||||||
|
private static readonly MIN_LENGTH = 3;
|
||||||
|
private static readonly MAX_LENGTH = 3;
|
||||||
|
|
||||||
|
protected static validate(value: string) {
|
||||||
|
const schema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.uppercase()
|
||||||
|
.min(CurrencyCode.MIN_LENGTH, {
|
||||||
|
message: `CurrencyCode must be at least ${CurrencyCode.MIN_LENGTH} characters long`,
|
||||||
|
})
|
||||||
|
.max(CurrencyCode.MAX_LENGTH, {
|
||||||
|
message: `CurrencyCode must be at most ${CurrencyCode.MAX_LENGTH} characters long`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(value: string): Result<CurrencyCode, Error> {
|
||||||
|
const valueIsValid = CurrencyCode.validate(value);
|
||||||
|
|
||||||
|
if (!valueIsValid.success) {
|
||||||
|
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||||
|
}
|
||||||
|
return Result.ok(new CurrencyCode({ value: valueIsValid.data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
@ -17,16 +17,8 @@ export class EmailAddress extends ValueObject<EmailAddressProps> {
|
|||||||
return Result.ok(new EmailAddress({ value: valueIsValid.data }));
|
return Result.ok(new EmailAddress({ value: valueIsValid.data }));
|
||||||
}
|
}
|
||||||
|
|
||||||
static createNullable(value?: string): Result<Maybe<EmailAddress>, Error> {
|
|
||||||
if (!value || value.trim() === "") {
|
|
||||||
return Result.ok(Maybe.none<EmailAddress>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return EmailAddress.create(value).map((value) => Maybe.some(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static validate(value: string) {
|
private static validate(value: string) {
|
||||||
const schema = z.string().email({ message: "Invalid email format" });
|
const schema = z.email({ message: "Invalid email format" });
|
||||||
return schema.safeParse(value);
|
return schema.safeParse(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,22 @@
|
|||||||
|
export * from "./city";
|
||||||
|
export * from "./country";
|
||||||
|
export * from "./currency-code";
|
||||||
export * from "./email-address";
|
export * from "./email-address";
|
||||||
|
export * from "./language-code";
|
||||||
export * from "./money-value";
|
export * from "./money-value";
|
||||||
export * from "./name";
|
export * from "./name";
|
||||||
export * from "./percentage";
|
export * from "./percentage";
|
||||||
export * from "./phone-number";
|
export * from "./phone-number";
|
||||||
export * from "./postal-address";
|
export * from "./postal-address";
|
||||||
|
export * from "./postal-code";
|
||||||
|
export * from "./province";
|
||||||
export * from "./quantity";
|
export * from "./quantity";
|
||||||
export * from "./slug";
|
export * from "./slug";
|
||||||
|
export * from "./street";
|
||||||
|
export * from "./tax-code";
|
||||||
|
export * from "./text-value";
|
||||||
export * from "./tin-number";
|
export * from "./tin-number";
|
||||||
export * from "./unique-id";
|
export * from "./unique-id";
|
||||||
|
export * from "./url-address";
|
||||||
export * from "./utc-date";
|
export * from "./utc-date";
|
||||||
export * from "./value-object";
|
export * from "./value-object";
|
||||||
|
|||||||
48
packages/rdx-ddd/src/value-objects/language-code.ts
Normal file
48
packages/rdx-ddd/src/value-objects/language-code.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
|
interface LanguageCodeProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ISO 639-1 (2 letras)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class LanguageCode extends ValueObject<LanguageCodeProps> {
|
||||||
|
private static readonly MIN_LENGTH = 2;
|
||||||
|
private static readonly MAX_LENGTH = 2;
|
||||||
|
|
||||||
|
protected static validate(value: string) {
|
||||||
|
const schema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.lowercase()
|
||||||
|
.min(LanguageCode.MIN_LENGTH, {
|
||||||
|
message: `LanguageCode must be at least ${LanguageCode.MIN_LENGTH} characters long`,
|
||||||
|
})
|
||||||
|
.max(LanguageCode.MAX_LENGTH, {
|
||||||
|
message: `LanguageCode must be at most ${LanguageCode.MAX_LENGTH} characters long`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(value: string): Result<LanguageCode, Error> {
|
||||||
|
const valueIsValid = LanguageCode.validate(value);
|
||||||
|
|
||||||
|
if (!valueIsValid.success) {
|
||||||
|
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||||
|
}
|
||||||
|
return Result.ok(new LanguageCode({ value: valueIsValid.data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,7 +18,7 @@ export type RoundingMode =
|
|||||||
| "HALF_AWAY_FROM_ZERO"
|
| "HALF_AWAY_FROM_ZERO"
|
||||||
| "DOWN";
|
| "DOWN";
|
||||||
|
|
||||||
export interface IMoneyValueProps {
|
export interface MoneyValueProps {
|
||||||
amount: number;
|
amount: number;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
currency_code?: string;
|
currency_code?: string;
|
||||||
@ -29,7 +29,7 @@ export interface IMoneyValue {
|
|||||||
scale: number;
|
scale: number;
|
||||||
currency: Dinero.Currency;
|
currency: Dinero.Currency;
|
||||||
|
|
||||||
getValue(): IMoneyValueProps;
|
getValue(): MoneyValueProps;
|
||||||
convertScale(newScale: number): MoneyValue;
|
convertScale(newScale: number): MoneyValue;
|
||||||
add(addend: MoneyValue): MoneyValue;
|
add(addend: MoneyValue): MoneyValue;
|
||||||
subtract(subtrahend: MoneyValue): MoneyValue;
|
subtract(subtrahend: MoneyValue): MoneyValue;
|
||||||
@ -47,13 +47,13 @@ export interface IMoneyValue {
|
|||||||
format(locale: string): string;
|
format(locale: string): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyValue {
|
export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyValue {
|
||||||
private readonly dinero: Dinero;
|
private readonly dinero: Dinero;
|
||||||
|
|
||||||
static DEFAULT_SCALE = DEFAULT_SCALE;
|
static DEFAULT_SCALE = DEFAULT_SCALE;
|
||||||
static DEFAULT_CURRENCY_CODE = DEFAULT_CURRENCY_CODE;
|
static DEFAULT_CURRENCY_CODE = DEFAULT_CURRENCY_CODE;
|
||||||
|
|
||||||
static create({ amount, currency_code, scale }: IMoneyValueProps) {
|
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||||
const props = {
|
const props = {
|
||||||
amount: Number(amount),
|
amount: Number(amount),
|
||||||
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
||||||
@ -62,7 +62,7 @@ export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyV
|
|||||||
return Result.ok(new MoneyValue(props));
|
return Result.ok(new MoneyValue(props));
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: IMoneyValueProps) {
|
constructor(props: MoneyValueProps) {
|
||||||
super(props);
|
super(props);
|
||||||
const { amount, scale, currency_code } = props;
|
const { amount, scale, currency_code } = props;
|
||||||
this.dinero = Object.freeze(
|
this.dinero = Object.freeze(
|
||||||
@ -86,7 +86,7 @@ export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyV
|
|||||||
return this.dinero.getPrecision();
|
return this.dinero.getPrecision();
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue(): IMoneyValueProps {
|
getValue(): MoneyValueProps {
|
||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
interface INameProps {
|
interface NameProps {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Name extends ValueObject<INameProps> {
|
export class Name extends ValueObject<NameProps> {
|
||||||
private static readonly MAX_LENGTH = 255;
|
private static readonly MAX_LENGTH = 255;
|
||||||
|
|
||||||
protected static validate(value: string) {
|
protected static validate(value: string) {
|
||||||
@ -26,14 +26,6 @@ export class Name extends ValueObject<INameProps> {
|
|||||||
return Result.ok(new Name({ value }));
|
return Result.ok(new Name({ value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
static createNullable(value?: string): Result<Maybe<Name>, Error> {
|
|
||||||
if (!value || value.trim() === "") {
|
|
||||||
return Result.ok(Maybe.none<Name>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Name.create(value).map((value) => Maybe.some(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
static generateAcronym(name: string): string {
|
static generateAcronym(name: string): string {
|
||||||
const words = name.split(" ").map((word) => word[0].toUpperCase());
|
const words = name.split(" ").map((word) => word[0].toUpperCase());
|
||||||
let acronym = words.join("");
|
let acronym = words.join("");
|
||||||
|
|||||||
@ -10,21 +10,12 @@ const DEFAULT_MAX_VALUE = 100;
|
|||||||
const DEFAULT_MIN_SCALE = 0;
|
const DEFAULT_MIN_SCALE = 0;
|
||||||
const DEFAULT_MAX_SCALE = 2;
|
const DEFAULT_MAX_SCALE = 2;
|
||||||
|
|
||||||
export interface IPercentageProps {
|
export interface PercentageProps {
|
||||||
amount: number;
|
amount: number;
|
||||||
scale: number;
|
scale: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPercentage {
|
export class Percentage extends ValueObject<PercentageProps> {
|
||||||
amount: number;
|
|
||||||
scale: number;
|
|
||||||
|
|
||||||
getValue(): IPercentageProps;
|
|
||||||
toNumber(): number;
|
|
||||||
toString(): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Percentage extends ValueObject<IPercentageProps> implements IPercentage {
|
|
||||||
static DEFAULT_SCALE = DEFAULT_SCALE;
|
static DEFAULT_SCALE = DEFAULT_SCALE;
|
||||||
static MIN_VALUE = DEFAULT_MIN_VALUE;
|
static MIN_VALUE = DEFAULT_MIN_VALUE;
|
||||||
static MAX_VALUE = DEFAULT_MAX_VALUE;
|
static MAX_VALUE = DEFAULT_MAX_VALUE;
|
||||||
@ -32,7 +23,7 @@ export class Percentage extends ValueObject<IPercentageProps> implements IPercen
|
|||||||
static MIN_SCALE = DEFAULT_MIN_SCALE;
|
static MIN_SCALE = DEFAULT_MIN_SCALE;
|
||||||
static MAX_SCALE = DEFAULT_MAX_SCALE;
|
static MAX_SCALE = DEFAULT_MAX_SCALE;
|
||||||
|
|
||||||
protected static validate(values: IPercentageProps) {
|
protected static validate(values: PercentageProps) {
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
amount: z.number().int().min(Percentage.MIN_VALUE, "La cantidad no puede ser negativa."),
|
amount: z.number().int().min(Percentage.MIN_VALUE, "La cantidad no puede ser negativa."),
|
||||||
scale: z
|
scale: z
|
||||||
@ -75,7 +66,7 @@ export class Percentage extends ValueObject<IPercentageProps> implements IPercen
|
|||||||
return this.props.scale;
|
return this.props.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue(): IPercentageProps {
|
getValue(): PercentageProps {
|
||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,62 +1,45 @@
|
|||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
import * as z from "zod/v4";
|
import { City } from "./city";
|
||||||
|
import { Country } from "./country";
|
||||||
|
import { PostalCode } from "./postal-code";
|
||||||
|
import { Province } from "./province";
|
||||||
|
import { Street } from "./street";
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
// 📌 Validaciones usando `zod`
|
export interface PostalAddressProps {
|
||||||
const postalCodeSchema = z
|
street: Maybe<Street>;
|
||||||
.string()
|
street2: Maybe<Street>;
|
||||||
.min(4, "Invalid postal code format")
|
city: Maybe<City>;
|
||||||
.max(10, "Invalid postal code format")
|
postalCode: Maybe<PostalCode>;
|
||||||
.regex(/^\d{4,10}$/, {
|
province: Maybe<Province>;
|
||||||
message: "Invalid postal code format",
|
country: Maybe<Country>;
|
||||||
});
|
|
||||||
|
|
||||||
const streetSchema = z.string().max(255).default("");
|
|
||||||
const street2Schema = z.string().default("");
|
|
||||||
const citySchema = z.string().max(50).default("");
|
|
||||||
const stateSchema = z.string().max(50).default("");
|
|
||||||
const countrySchema = z.string().max(56).default("");
|
|
||||||
|
|
||||||
interface IPostalAddressProps {
|
|
||||||
street: string;
|
|
||||||
street2?: string;
|
|
||||||
city: string;
|
|
||||||
postalCode: string;
|
|
||||||
state: string;
|
|
||||||
country: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostalAddress extends ValueObject<IPostalAddressProps> {
|
export interface PostalAddressSnapshot {
|
||||||
protected static validate(values: IPostalAddressProps) {
|
street: string | null;
|
||||||
return z
|
street2: string | null;
|
||||||
.object({
|
city: string | null;
|
||||||
street: streetSchema,
|
postalCode: string | null;
|
||||||
street2: street2Schema,
|
province: string | null;
|
||||||
city: citySchema,
|
country: string | null;
|
||||||
postalCode: postalCodeSchema,
|
}
|
||||||
state: stateSchema,
|
|
||||||
country: countrySchema,
|
export type PostalAddressPatchProps = Partial<PostalAddressProps>;
|
||||||
})
|
|
||||||
.safeParse(values);
|
export class PostalAddress extends ValueObject<PostalAddressProps> {
|
||||||
|
protected static validate(values: PostalAddressProps) {
|
||||||
|
return Result.ok(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(values: IPostalAddressProps): Result<PostalAddress, Error> {
|
static create(values: PostalAddressProps): Result<PostalAddress, Error> {
|
||||||
const valueIsValid = PostalAddress.validate(values);
|
const valueIsValid = PostalAddress.validate(values);
|
||||||
|
|
||||||
if (!valueIsValid.success) {
|
if (valueIsValid.isFailure) {
|
||||||
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
return Result.fail(valueIsValid.error);
|
||||||
}
|
}
|
||||||
return Result.ok(new PostalAddress(values));
|
return Result.ok(new PostalAddress(values));
|
||||||
}
|
}
|
||||||
|
|
||||||
static createNullable(values?: IPostalAddressProps): Result<Maybe<PostalAddress>, Error> {
|
|
||||||
if (!values || Object.values(values).every((value) => value.trim() === "")) {
|
|
||||||
return Result.ok(Maybe.none<PostalAddress>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return PostalAddress.create(values).map((value) => Maybe.some(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
static update(
|
static update(
|
||||||
oldAddress: PostalAddress,
|
oldAddress: PostalAddress,
|
||||||
data: Partial<PostalAddress>
|
data: Partial<PostalAddress>
|
||||||
@ -66,37 +49,37 @@ export class PostalAddress extends ValueObject<IPostalAddressProps> {
|
|||||||
street2: data.street2 ?? oldAddress.street2,
|
street2: data.street2 ?? oldAddress.street2,
|
||||||
city: data.city ?? oldAddress.city,
|
city: data.city ?? oldAddress.city,
|
||||||
postalCode: data.postalCode ?? oldAddress.postalCode,
|
postalCode: data.postalCode ?? oldAddress.postalCode,
|
||||||
state: data.state ?? oldAddress.state,
|
province: data.province ?? oldAddress.province,
|
||||||
country: data.country ?? oldAddress.country,
|
country: data.country ?? oldAddress.country,
|
||||||
// biome-ignore lint/complexity/noThisInStatic: <explanation>
|
// biome-ignore lint/complexity/noThisInStatic: <explanation>
|
||||||
}).getOrElse(this);
|
}).getOrElse(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
get street(): string {
|
get street(): Maybe<Street> {
|
||||||
return this.props.street;
|
return this.props.street;
|
||||||
}
|
}
|
||||||
|
|
||||||
get street2(): string {
|
get street2(): Maybe<Street> {
|
||||||
return this.props.street2 ?? "";
|
return this.props.street2;
|
||||||
}
|
}
|
||||||
|
|
||||||
get city(): string {
|
get city(): Maybe<City> {
|
||||||
return this.props.city;
|
return this.props.city;
|
||||||
}
|
}
|
||||||
|
|
||||||
get postalCode(): string {
|
get postalCode(): Maybe<PostalCode> {
|
||||||
return this.props.postalCode;
|
return this.props.postalCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
get state(): string {
|
get province(): Maybe<Province> {
|
||||||
return this.props.state;
|
return this.props.province;
|
||||||
}
|
}
|
||||||
|
|
||||||
get country(): string {
|
get country(): Maybe<Country> {
|
||||||
return this.props.country;
|
return this.props.country;
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue(): IPostalAddressProps {
|
getValue(): PostalAddressProps {
|
||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +87,20 @@ export class PostalAddress extends ValueObject<IPostalAddressProps> {
|
|||||||
return this.getValue();
|
return this.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toFormat(): string {
|
||||||
return `${this.props.street}, ${this.props.street2}, ${this.props.city}, ${this.props.postalCode}, ${this.props.state}, ${this.props.country}`;
|
return `${this.props.street}, ${this.props.street2}, ${this.props.city}, ${this.props.postalCode}, ${this.props.province}, ${this.props.country}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toSnapshot(): PostalAddressSnapshot {
|
||||||
|
return {
|
||||||
|
street: this.props.street.isSome() ? this.props.street.unwrap()!.toString() : null,
|
||||||
|
street2: this.props.street2.isSome() ? this.props.street2.unwrap()!.toString() : null,
|
||||||
|
city: this.props.city.isSome() ? this.props.city.unwrap()!.toString() : null,
|
||||||
|
postalCode: this.props.postalCode.isSome()
|
||||||
|
? this.props.postalCode.unwrap()!.toString()
|
||||||
|
: null,
|
||||||
|
province: this.props.province.isSome() ? this.props.province.unwrap()!.toString() : null,
|
||||||
|
country: this.props.country.isSome() ? this.props.country.unwrap()!.toString() : null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
packages/rdx-ddd/src/value-objects/postal-code.ts
Normal file
46
packages/rdx-ddd/src/value-objects/postal-code.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
|
interface PostalCodeProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PostalCode extends ValueObject<PostalCodeProps> {
|
||||||
|
private static readonly MIN_LENGTH = 5;
|
||||||
|
private static readonly MAX_LENGTH = 5;
|
||||||
|
|
||||||
|
protected static validate(value: string) {
|
||||||
|
const schema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(/^[0-9]+$/, {
|
||||||
|
message: "PostalCode must contain only numbers",
|
||||||
|
})
|
||||||
|
.min(PostalCode.MIN_LENGTH, {
|
||||||
|
message: `PostalCode must be at least ${PostalCode.MIN_LENGTH} characters long`,
|
||||||
|
})
|
||||||
|
.max(PostalCode.MAX_LENGTH, {
|
||||||
|
message: `PostalCode must be at most ${PostalCode.MAX_LENGTH} characters long`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(value: string): Result<PostalCode, Error> {
|
||||||
|
const valueIsValid = PostalCode.validate(value);
|
||||||
|
|
||||||
|
if (!valueIsValid.success) {
|
||||||
|
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||||
|
}
|
||||||
|
return Result.ok(new PostalCode({ value: valueIsValid.data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
packages/rdx-ddd/src/value-objects/province.ts
Normal file
38
packages/rdx-ddd/src/value-objects/province.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
|
interface ProvinceProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Province extends ValueObject<ProvinceProps> {
|
||||||
|
private static readonly MAX_LENGTH = 255;
|
||||||
|
|
||||||
|
protected static validate(value: string) {
|
||||||
|
const schema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(Province.MAX_LENGTH, {
|
||||||
|
message: `Province must be at most ${Province.MAX_LENGTH} characters long`,
|
||||||
|
});
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(value: string) {
|
||||||
|
const valueIsValid = Province.validate(value);
|
||||||
|
|
||||||
|
if (!valueIsValid.success) {
|
||||||
|
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||||
|
}
|
||||||
|
return Result.ok(new Province({ value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive() {
|
||||||
|
return this.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,31 +6,13 @@ const DEFAULT_SCALE = 2;
|
|||||||
const DEFAULT_MIN_SCALE = 0;
|
const DEFAULT_MIN_SCALE = 0;
|
||||||
const DEFAULT_MAX_SCALE = 2;
|
const DEFAULT_MAX_SCALE = 2;
|
||||||
|
|
||||||
export interface IQuantityProps {
|
export interface QuantityProps {
|
||||||
amount: number;
|
amount: number;
|
||||||
scale: number;
|
scale: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IQuantity {
|
export class Quantity extends ValueObject<QuantityProps> {
|
||||||
amount: number;
|
protected static validate(values: QuantityProps) {
|
||||||
scale: number;
|
|
||||||
|
|
||||||
getValue(): IQuantityProps;
|
|
||||||
toNumber(): number;
|
|
||||||
toString(): string;
|
|
||||||
|
|
||||||
isZero(): boolean;
|
|
||||||
isPositive(): boolean;
|
|
||||||
isNegative(): boolean;
|
|
||||||
|
|
||||||
increment(anotherQuantity?: Quantity): Result<Quantity, Error>;
|
|
||||||
decrement(anotherQuantity?: Quantity): Result<Quantity, Error>;
|
|
||||||
hasSameScale(otherQuantity: Quantity): boolean;
|
|
||||||
convertScale(newScale: number): Result<Quantity, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Quantity extends ValueObject<IQuantityProps> implements IQuantity {
|
|
||||||
protected static validate(values: IQuantityProps) {
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
amount: z.number().int(),
|
amount: z.number().int(),
|
||||||
scale: z.number().int().min(Quantity.MIN_SCALE).max(Quantity.MAX_SCALE),
|
scale: z.number().int().min(Quantity.MIN_SCALE).max(Quantity.MAX_SCALE),
|
||||||
@ -43,7 +25,7 @@ export class Quantity extends ValueObject<IQuantityProps> implements IQuantity {
|
|||||||
static MIN_SCALE = DEFAULT_MIN_SCALE;
|
static MIN_SCALE = DEFAULT_MIN_SCALE;
|
||||||
static MAX_SCALE = DEFAULT_MAX_SCALE;
|
static MAX_SCALE = DEFAULT_MAX_SCALE;
|
||||||
|
|
||||||
static create({ amount, scale }: IQuantityProps) {
|
static create({ amount, scale }: QuantityProps) {
|
||||||
const props = {
|
const props = {
|
||||||
amount: Number(amount),
|
amount: Number(amount),
|
||||||
scale: scale ?? Quantity.DEFAULT_SCALE,
|
scale: scale ?? Quantity.DEFAULT_SCALE,
|
||||||
@ -51,9 +33,9 @@ export class Quantity extends ValueObject<IQuantityProps> implements IQuantity {
|
|||||||
const checkProps = Quantity.validate(props);
|
const checkProps = Quantity.validate(props);
|
||||||
|
|
||||||
if (!checkProps.success) {
|
if (!checkProps.success) {
|
||||||
return Result.fail(new Error(checkProps.error.errors[0].message));
|
return Result.fail(new Error(checkProps.error.issues[0].message));
|
||||||
}
|
}
|
||||||
return Result.ok(new Quantity({ ...(checkProps.data as IQuantityProps) }));
|
return Result.ok(new Quantity({ ...(checkProps.data as QuantityProps) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
get amount(): number {
|
get amount(): number {
|
||||||
@ -64,7 +46,7 @@ export class Quantity extends ValueObject<IQuantityProps> implements IQuantity {
|
|||||||
return this.props.scale;
|
return this.props.scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
getValue(): IQuantityProps {
|
getValue(): QuantityProps {
|
||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
@ -32,14 +32,6 @@ export class Slug extends ValueObject<SlugProps> {
|
|||||||
return Result.ok(new Slug({ value: valueIsValid.data! }));
|
return Result.ok(new Slug({ value: valueIsValid.data! }));
|
||||||
}
|
}
|
||||||
|
|
||||||
static createNullable(value?: string): Result<Maybe<Slug>, Error> {
|
|
||||||
if (!value || value.trim() === "") {
|
|
||||||
return Result.ok(Maybe.none<Slug>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Slug.create(value).map((value: Slug) => Maybe.some(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue(): string {
|
getValue(): string {
|
||||||
return this.props.value;
|
return this.props.value;
|
||||||
}
|
}
|
||||||
|
|||||||
38
packages/rdx-ddd/src/value-objects/street.ts
Normal file
38
packages/rdx-ddd/src/value-objects/street.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
|
interface StreetProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Street extends ValueObject<StreetProps> {
|
||||||
|
private static readonly MAX_LENGTH = 255;
|
||||||
|
|
||||||
|
protected static validate(value: string) {
|
||||||
|
const schema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.max(Street.MAX_LENGTH, {
|
||||||
|
message: `Street must be at most ${Street.MAX_LENGTH} characters long`,
|
||||||
|
});
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(value: string) {
|
||||||
|
const valueIsValid = Street.validate(value);
|
||||||
|
|
||||||
|
if (!valueIsValid.success) {
|
||||||
|
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||||
|
}
|
||||||
|
return Result.ok(new Street({ value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive() {
|
||||||
|
return this.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
46
packages/rdx-ddd/src/value-objects/tax-code.ts
Normal file
46
packages/rdx-ddd/src/value-objects/tax-code.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
|
interface TaxCodeProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TaxCode extends ValueObject<TaxCodeProps> {
|
||||||
|
protected static readonly MIN_LENGTH = 1;
|
||||||
|
protected static readonly MAX_LENGTH = 10;
|
||||||
|
|
||||||
|
protected static validate(value: string) {
|
||||||
|
const schema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.regex(/^[a-z0-9]+([_-][a-z0-9]+)*$/, {
|
||||||
|
message: "TaxCode must contain only lowercase letters, numbers, and underscores",
|
||||||
|
})
|
||||||
|
.min(TaxCode.MIN_LENGTH, {
|
||||||
|
message: `TaxCode must be at least ${TaxCode.MIN_LENGTH} characters long`,
|
||||||
|
})
|
||||||
|
.max(TaxCode.MAX_LENGTH, {
|
||||||
|
message: `TaxCode must be at most ${TaxCode.MAX_LENGTH} characters long`,
|
||||||
|
});
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(value: string) {
|
||||||
|
const valueIsValid = TaxCode.validate(value);
|
||||||
|
|
||||||
|
if (!valueIsValid.success) {
|
||||||
|
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||||
|
}
|
||||||
|
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
||||||
|
return Result.ok(new TaxCode({ value: valueIsValid.data! }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive(): string {
|
||||||
|
return this.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
39
packages/rdx-ddd/src/value-objects/text-value.ts
Normal file
39
packages/rdx-ddd/src/value-objects/text-value.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
|
interface TextValueProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TextValue extends ValueObject<TextValueProps> {
|
||||||
|
private static readonly MAX_LENGTH = 4096;
|
||||||
|
|
||||||
|
protected static validate(value: string) {
|
||||||
|
const schema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.nonempty({ message: "Text must not be empty" })
|
||||||
|
.max(TextValue.MAX_LENGTH, {
|
||||||
|
message: `Text must be at most ${TextValue.MAX_LENGTH} characters long`,
|
||||||
|
});
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(value: string) {
|
||||||
|
const valueIsValid = TextValue.validate(value);
|
||||||
|
|
||||||
|
if (!valueIsValid.success) {
|
||||||
|
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||||
|
}
|
||||||
|
return Result.ok(new TextValue({ value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive(): string {
|
||||||
|
return this.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
@ -33,14 +33,6 @@ export class TINNumber extends ValueObject<TINNumberProps> {
|
|||||||
return Result.ok(new TINNumber({ value: valueIsValid.data }));
|
return Result.ok(new TINNumber({ value: valueIsValid.data }));
|
||||||
}
|
}
|
||||||
|
|
||||||
static createNullable(value?: string): Result<Maybe<TINNumber>, Error> {
|
|
||||||
if (!value || value.trim() === "") {
|
|
||||||
return Result.ok(Maybe.none<TINNumber>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return TINNumber.create(value).map((value) => Maybe.some(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue(): string {
|
getValue(): string {
|
||||||
return this.props.value;
|
return this.props.value;
|
||||||
}
|
}
|
||||||
|
|||||||
32
packages/rdx-ddd/src/value-objects/url-address.ts
Normal file
32
packages/rdx-ddd/src/value-objects/url-address.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
|
interface URLAddressProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class URLAddress extends ValueObject<URLAddressProps> {
|
||||||
|
static create(value: string): Result<URLAddress, Error> {
|
||||||
|
const valueIsValid = URLAddress.validate(value);
|
||||||
|
|
||||||
|
if (!valueIsValid.success) {
|
||||||
|
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(new URLAddress({ value: valueIsValid.data }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static validate(value: string) {
|
||||||
|
const schema = z.url({ message: "Invalid URL format" });
|
||||||
|
return schema.safeParse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(): string {
|
||||||
|
return this.props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
toPrimitive() {
|
||||||
|
return this.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,14 +2,14 @@ import { Result } from "@repo/rdx-utils";
|
|||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
import { ValueObject } from "./value-object";
|
import { ValueObject } from "./value-object";
|
||||||
|
|
||||||
interface IUtcDateProps {
|
interface UtcDateProps {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UtcDate extends ValueObject<IUtcDateProps> {
|
export class UtcDate extends ValueObject<UtcDateProps> {
|
||||||
private readonly date!: Date;
|
private readonly date!: Date;
|
||||||
|
|
||||||
private constructor(props: IUtcDateProps) {
|
private constructor(props: UtcDateProps) {
|
||||||
super(props);
|
super(props);
|
||||||
const { value: dateString } = props;
|
const { value: dateString } = props;
|
||||||
this.date = Object.freeze(new Date(dateString));
|
this.date = Object.freeze(new Date(dateString));
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
export * from "./collection";
|
export * from "./collection";
|
||||||
export * from "./id-utils";
|
export * from "./id-utils";
|
||||||
export * from "./maybe";
|
export * from "./maybe";
|
||||||
|
export * from "./patch-field";
|
||||||
export * from "./result";
|
export * from "./result";
|
||||||
export * from "./result-collection";
|
export * from "./result-collection";
|
||||||
export * from "./rule-validator";
|
export * from "./rule-validator";
|
||||||
|
|||||||
@ -43,4 +43,8 @@ export class Maybe<T> {
|
|||||||
map<U>(fn: (value: T) => U): Maybe<U> {
|
map<U>(fn: (value: T) => U): Maybe<U> {
|
||||||
return this.isSome() ? Maybe.some(fn(this.value as T)) : Maybe.none();
|
return this.isSome() ? Maybe.some(fn(this.value as T)) : Maybe.none();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match<U>(someFn: (value: T) => U, noneFn: () => U): U {
|
||||||
|
return this.isSome() ? someFn(this.value as T) : noneFn();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
packages/rdx-utils/src/helpers/patch-field.ts
Normal file
38
packages/rdx-utils/src/helpers/patch-field.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { isNullishOrEmpty } from "./utils";
|
||||||
|
|
||||||
|
// Tri-estado para PATCH: unset | set(Some) | set(None)
|
||||||
|
export class PatchField<T> {
|
||||||
|
private constructor(
|
||||||
|
private readonly _isSet: boolean,
|
||||||
|
private readonly _value?: T | null
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static unset<T>(): PatchField<T> {
|
||||||
|
return new PatchField<T>(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static set<T>(value: T | null): PatchField<T> {
|
||||||
|
return new PatchField<T>(true, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSet(): boolean {
|
||||||
|
return this._isSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Devuelve el valor crudo (puede ser null) si isSet=true */
|
||||||
|
get value(): T | null | undefined {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Ejecuta una función solo si isSet=true */
|
||||||
|
ifSet(fn: (v: T | null) => void): void {
|
||||||
|
if (this._isSet) fn(this._value ?? null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toPatchField<T>(value: T | null | undefined): PatchField<T> {
|
||||||
|
if (value === undefined) return PatchField.unset<T>();
|
||||||
|
// "" => null
|
||||||
|
if (isNullishOrEmpty(value)) return PatchField.set<T>(null);
|
||||||
|
return PatchField.set<T>(value as T);
|
||||||
|
}
|
||||||
@ -1,3 +1,9 @@
|
|||||||
|
export function isNullishOrEmpty(input: unknown): boolean {
|
||||||
|
return (
|
||||||
|
input === null || input === undefined || (typeof input === "string" && input.trim() === "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Función genérica para asegurar valores básicos
|
// Función genérica para asegurar valores básicos
|
||||||
function ensure<T>(value: T | undefined | null, defaultValue: T): T {
|
function ensure<T>(value: T | undefined | null, defaultValue: T): T {
|
||||||
return value ?? defaultValue;
|
return value ?? defaultValue;
|
||||||
|
|||||||
332
pnpm-lock.yaml
332
pnpm-lock.yaml
@ -170,7 +170,7 @@ importers:
|
|||||||
version: 29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3))
|
version: 29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3))
|
||||||
ts-jest:
|
ts-jest:
|
||||||
specifier: ^29.2.5
|
specifier: ^29.2.5
|
||||||
version: 29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(esbuild@0.25.5)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3)
|
version: 29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3)
|
||||||
tsconfig-paths:
|
tsconfig-paths:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
@ -623,6 +623,9 @@ importers:
|
|||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.8.3
|
specifier: ^5.8.3
|
||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
|
vitest:
|
||||||
|
specifier: ^3.2.4
|
||||||
|
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)
|
||||||
|
|
||||||
packages/rdx-criteria:
|
packages/rdx-criteria:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -2870,6 +2873,9 @@ packages:
|
|||||||
'@types/body-parser@1.19.5':
|
'@types/body-parser@1.19.5':
|
||||||
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
||||||
|
|
||||||
|
'@types/chai@5.2.2':
|
||||||
|
resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
|
||||||
|
|
||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||||
|
|
||||||
@ -2906,6 +2912,9 @@ packages:
|
|||||||
'@types/debug@4.1.12':
|
'@types/debug@4.1.12':
|
||||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||||
|
|
||||||
|
'@types/deep-eql@4.0.2':
|
||||||
|
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||||
|
|
||||||
'@types/dinero.js@1.9.4':
|
'@types/dinero.js@1.9.4':
|
||||||
resolution: {integrity: sha512-mtJnan4ajy9MqvoJGVXu0tC9EAAzFjeoKc3d+8AW+H/Od9+8IiC59ymjrZF+JdTToyDvkLReacTsc50Z8eYr6Q==}
|
resolution: {integrity: sha512-mtJnan4ajy9MqvoJGVXu0tC9EAAzFjeoKc3d+8AW+H/Od9+8IiC59ymjrZF+JdTToyDvkLReacTsc50Z8eYr6Q==}
|
||||||
|
|
||||||
@ -3047,6 +3056,35 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0
|
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0
|
||||||
|
|
||||||
|
'@vitest/expect@3.2.4':
|
||||||
|
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
||||||
|
|
||||||
|
'@vitest/mocker@3.2.4':
|
||||||
|
resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
|
||||||
|
peerDependencies:
|
||||||
|
msw: ^2.4.9
|
||||||
|
vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
msw:
|
||||||
|
optional: true
|
||||||
|
vite:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@vitest/pretty-format@3.2.4':
|
||||||
|
resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
|
||||||
|
|
||||||
|
'@vitest/runner@3.2.4':
|
||||||
|
resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
|
||||||
|
|
||||||
|
'@vitest/snapshot@3.2.4':
|
||||||
|
resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
|
||||||
|
|
||||||
|
'@vitest/spy@3.2.4':
|
||||||
|
resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
|
||||||
|
|
||||||
|
'@vitest/utils@3.2.4':
|
||||||
|
resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
|
||||||
|
|
||||||
abbrev@1.1.1:
|
abbrev@1.1.1:
|
||||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||||
|
|
||||||
@ -3153,6 +3191,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
assertion-error@2.0.1:
|
||||||
|
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
ast-types@0.13.4:
|
ast-types@0.13.4:
|
||||||
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
|
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -3307,6 +3349,10 @@ packages:
|
|||||||
caniuse-lite@1.0.30001720:
|
caniuse-lite@1.0.30001720:
|
||||||
resolution: {integrity: sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==}
|
resolution: {integrity: sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==}
|
||||||
|
|
||||||
|
chai@5.3.3:
|
||||||
|
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
chalk@2.4.2:
|
chalk@2.4.2:
|
||||||
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -3329,6 +3375,10 @@ packages:
|
|||||||
chardet@0.7.0:
|
chardet@0.7.0:
|
||||||
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
|
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
|
||||||
|
|
||||||
|
check-error@2.1.1:
|
||||||
|
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
||||||
|
engines: {node: '>= 16'}
|
||||||
|
|
||||||
chokidar@4.0.3:
|
chokidar@4.0.3:
|
||||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||||
engines: {node: '>= 14.16.0'}
|
engines: {node: '>= 14.16.0'}
|
||||||
@ -3623,6 +3673,10 @@ packages:
|
|||||||
babel-plugin-macros:
|
babel-plugin-macros:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
deep-eql@5.0.2:
|
||||||
|
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
deep-extend@0.6.0:
|
deep-extend@0.6.0:
|
||||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||||
engines: {node: '>=4.0.0'}
|
engines: {node: '>=4.0.0'}
|
||||||
@ -3806,6 +3860,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-module-lexer@1.7.0:
|
||||||
|
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||||
|
|
||||||
es-object-atoms@1.1.1:
|
es-object-atoms@1.1.1:
|
||||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -3864,6 +3921,9 @@ packages:
|
|||||||
estree-walker@2.0.2:
|
estree-walker@2.0.2:
|
||||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
|
|
||||||
|
estree-walker@3.0.3:
|
||||||
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
|
|
||||||
esutils@2.0.3:
|
esutils@2.0.3:
|
||||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -3883,6 +3943,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
|
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
expect-type@1.2.2:
|
||||||
|
resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
expect@29.7.0:
|
expect@29.7.0:
|
||||||
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
|
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
|
||||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||||
@ -4528,6 +4592,9 @@ packages:
|
|||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
|
js-tokens@9.0.1:
|
||||||
|
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
||||||
|
|
||||||
js-yaml@3.14.1:
|
js-yaml@3.14.1:
|
||||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -4732,6 +4799,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
loupe@3.2.1:
|
||||||
|
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
|
||||||
|
|
||||||
lower-case-first@1.0.2:
|
lower-case-first@1.0.2:
|
||||||
resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==}
|
resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==}
|
||||||
|
|
||||||
@ -5161,6 +5231,13 @@ packages:
|
|||||||
pathe@0.2.0:
|
pathe@0.2.0:
|
||||||
resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
|
resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==}
|
||||||
|
|
||||||
|
pathe@2.0.3:
|
||||||
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
|
pathval@2.0.1:
|
||||||
|
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
|
||||||
|
engines: {node: '>= 14.16'}
|
||||||
|
|
||||||
pause@0.0.1:
|
pause@0.0.1:
|
||||||
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
|
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
|
||||||
|
|
||||||
@ -5689,6 +5766,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
siginfo@2.0.0:
|
||||||
|
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||||
|
|
||||||
signal-exit@3.0.7:
|
signal-exit@3.0.7:
|
||||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||||
|
|
||||||
@ -5770,10 +5850,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
stackback@0.0.2:
|
||||||
|
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||||
|
|
||||||
statuses@2.0.1:
|
statuses@2.0.1:
|
||||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
std-env@3.9.0:
|
||||||
|
resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
|
||||||
|
|
||||||
string-hash@1.1.3:
|
string-hash@1.1.3:
|
||||||
resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==}
|
resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==}
|
||||||
|
|
||||||
@ -5820,6 +5906,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
strip-literal@3.0.0:
|
||||||
|
resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==}
|
||||||
|
|
||||||
styled-components@6.1.19:
|
styled-components@6.1.19:
|
||||||
resolution: {integrity: sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==}
|
resolution: {integrity: sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
@ -5907,6 +5996,9 @@ packages:
|
|||||||
tiny-invariant@1.3.3:
|
tiny-invariant@1.3.3:
|
||||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||||
|
|
||||||
|
tinybench@2.9.0:
|
||||||
|
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||||
|
|
||||||
tinycolor2@1.6.0:
|
tinycolor2@1.6.0:
|
||||||
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||||
|
|
||||||
@ -5920,6 +6012,18 @@ packages:
|
|||||||
tinygradient@1.1.5:
|
tinygradient@1.1.5:
|
||||||
resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==}
|
resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==}
|
||||||
|
|
||||||
|
tinypool@1.1.1:
|
||||||
|
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
|
||||||
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
|
|
||||||
|
tinyrainbow@2.0.0:
|
||||||
|
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
tinyspy@4.0.3:
|
||||||
|
resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
title-case@2.1.1:
|
title-case@2.1.1:
|
||||||
resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==}
|
resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==}
|
||||||
|
|
||||||
@ -6230,6 +6334,11 @@ packages:
|
|||||||
victory-vendor@36.9.2:
|
victory-vendor@36.9.2:
|
||||||
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
|
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
|
||||||
|
|
||||||
|
vite-node@3.2.4:
|
||||||
|
resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
|
||||||
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
vite-plugin-html@3.2.2:
|
vite-plugin-html@3.2.2:
|
||||||
resolution: {integrity: sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==}
|
resolution: {integrity: sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -6283,6 +6392,34 @@ packages:
|
|||||||
yaml:
|
yaml:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
vitest@3.2.4:
|
||||||
|
resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
|
||||||
|
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@edge-runtime/vm': '*'
|
||||||
|
'@types/debug': ^4.1.12
|
||||||
|
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
|
||||||
|
'@vitest/browser': 3.2.4
|
||||||
|
'@vitest/ui': 3.2.4
|
||||||
|
happy-dom: '*'
|
||||||
|
jsdom: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@edge-runtime/vm':
|
||||||
|
optional: true
|
||||||
|
'@types/debug':
|
||||||
|
optional: true
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
'@vitest/browser':
|
||||||
|
optional: true
|
||||||
|
'@vitest/ui':
|
||||||
|
optional: true
|
||||||
|
happy-dom:
|
||||||
|
optional: true
|
||||||
|
jsdom:
|
||||||
|
optional: true
|
||||||
|
|
||||||
void-elements@3.1.0:
|
void-elements@3.1.0:
|
||||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -6310,6 +6447,11 @@ packages:
|
|||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
why-is-node-running@2.3.0:
|
||||||
|
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
wide-align@1.1.5:
|
wide-align@1.1.5:
|
||||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||||
|
|
||||||
@ -8335,6 +8477,10 @@ snapshots:
|
|||||||
'@types/connect': 3.4.38
|
'@types/connect': 3.4.38
|
||||||
'@types/node': 24.0.3
|
'@types/node': 24.0.3
|
||||||
|
|
||||||
|
'@types/chai@5.2.2':
|
||||||
|
dependencies:
|
||||||
|
'@types/deep-eql': 4.0.2
|
||||||
|
|
||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.0.3
|
'@types/node': 24.0.3
|
||||||
@ -8371,6 +8517,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/ms': 2.1.0
|
'@types/ms': 2.1.0
|
||||||
|
|
||||||
|
'@types/deep-eql@4.0.2': {}
|
||||||
|
|
||||||
'@types/dinero.js@1.9.4': {}
|
'@types/dinero.js@1.9.4': {}
|
||||||
|
|
||||||
'@types/estree@1.0.7': {}
|
'@types/estree@1.0.7': {}
|
||||||
@ -8548,6 +8696,48 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@vitest/expect@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/chai': 5.2.2
|
||||||
|
'@vitest/spy': 3.2.4
|
||||||
|
'@vitest/utils': 3.2.4
|
||||||
|
chai: 5.3.3
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
|
'@vitest/mocker@3.2.4(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4))':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/spy': 3.2.4
|
||||||
|
estree-walker: 3.0.3
|
||||||
|
magic-string: 0.30.17
|
||||||
|
optionalDependencies:
|
||||||
|
vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)
|
||||||
|
|
||||||
|
'@vitest/pretty-format@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
|
'@vitest/runner@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/utils': 3.2.4
|
||||||
|
pathe: 2.0.3
|
||||||
|
strip-literal: 3.0.0
|
||||||
|
|
||||||
|
'@vitest/snapshot@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 3.2.4
|
||||||
|
magic-string: 0.30.17
|
||||||
|
pathe: 2.0.3
|
||||||
|
|
||||||
|
'@vitest/spy@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
tinyspy: 4.0.3
|
||||||
|
|
||||||
|
'@vitest/utils@3.2.4':
|
||||||
|
dependencies:
|
||||||
|
'@vitest/pretty-format': 3.2.4
|
||||||
|
loupe: 3.2.1
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
|
||||||
abbrev@1.1.1: {}
|
abbrev@1.1.1: {}
|
||||||
|
|
||||||
accepts@1.3.8:
|
accepts@1.3.8:
|
||||||
@ -8639,6 +8829,8 @@ snapshots:
|
|||||||
|
|
||||||
array-union@2.1.0: {}
|
array-union@2.1.0: {}
|
||||||
|
|
||||||
|
assertion-error@2.0.1: {}
|
||||||
|
|
||||||
ast-types@0.13.4:
|
ast-types@0.13.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
@ -8843,6 +9035,14 @@ snapshots:
|
|||||||
|
|
||||||
caniuse-lite@1.0.30001720: {}
|
caniuse-lite@1.0.30001720: {}
|
||||||
|
|
||||||
|
chai@5.3.3:
|
||||||
|
dependencies:
|
||||||
|
assertion-error: 2.0.1
|
||||||
|
check-error: 2.1.1
|
||||||
|
deep-eql: 5.0.2
|
||||||
|
loupe: 3.2.1
|
||||||
|
pathval: 2.0.1
|
||||||
|
|
||||||
chalk@2.4.2:
|
chalk@2.4.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 3.2.1
|
ansi-styles: 3.2.1
|
||||||
@ -8884,6 +9084,8 @@ snapshots:
|
|||||||
|
|
||||||
chardet@0.7.0: {}
|
chardet@0.7.0: {}
|
||||||
|
|
||||||
|
check-error@2.1.1: {}
|
||||||
|
|
||||||
chokidar@4.0.3:
|
chokidar@4.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
readdirp: 4.1.2
|
readdirp: 4.1.2
|
||||||
@ -9140,6 +9342,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
babel-plugin-macros: 3.1.0
|
babel-plugin-macros: 3.1.0
|
||||||
|
|
||||||
|
deep-eql@5.0.2: {}
|
||||||
|
|
||||||
deep-extend@0.6.0: {}
|
deep-extend@0.6.0: {}
|
||||||
|
|
||||||
deepmerge@4.3.1: {}
|
deepmerge@4.3.1: {}
|
||||||
@ -9297,6 +9501,8 @@ snapshots:
|
|||||||
|
|
||||||
es-errors@1.3.0: {}
|
es-errors@1.3.0: {}
|
||||||
|
|
||||||
|
es-module-lexer@1.7.0: {}
|
||||||
|
|
||||||
es-object-atoms@1.1.1:
|
es-object-atoms@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
@ -9371,6 +9577,10 @@ snapshots:
|
|||||||
|
|
||||||
estree-walker@2.0.2: {}
|
estree-walker@2.0.2: {}
|
||||||
|
|
||||||
|
estree-walker@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.7
|
||||||
|
|
||||||
esutils@2.0.3: {}
|
esutils@2.0.3: {}
|
||||||
|
|
||||||
etag@1.8.1: {}
|
etag@1.8.1: {}
|
||||||
@ -9391,6 +9601,8 @@ snapshots:
|
|||||||
|
|
||||||
exit@0.1.2: {}
|
exit@0.1.2: {}
|
||||||
|
|
||||||
|
expect-type@1.2.2: {}
|
||||||
|
|
||||||
expect@29.7.0:
|
expect@29.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/expect-utils': 29.7.0
|
'@jest/expect-utils': 29.7.0
|
||||||
@ -10230,7 +10442,7 @@ snapshots:
|
|||||||
jest-util@29.7.0:
|
jest-util@29.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
'@types/node': 24.0.3
|
'@types/node': 22.15.32
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
ci-info: 3.9.0
|
ci-info: 3.9.0
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
@ -10289,6 +10501,8 @@ snapshots:
|
|||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
|
js-tokens@9.0.1: {}
|
||||||
|
|
||||||
js-yaml@3.14.1:
|
js-yaml@3.14.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 1.0.10
|
argparse: 1.0.10
|
||||||
@ -10469,6 +10683,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
|
loupe@3.2.1: {}
|
||||||
|
|
||||||
lower-case-first@1.0.2:
|
lower-case-first@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
lower-case: 1.1.4
|
lower-case: 1.1.4
|
||||||
@ -10883,6 +11099,10 @@ snapshots:
|
|||||||
|
|
||||||
pathe@0.2.0: {}
|
pathe@0.2.0: {}
|
||||||
|
|
||||||
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
|
pathval@2.0.1: {}
|
||||||
|
|
||||||
pause@0.0.1: {}
|
pause@0.0.1: {}
|
||||||
|
|
||||||
pg-connection-string@2.9.0: {}
|
pg-connection-string@2.9.0: {}
|
||||||
@ -11406,6 +11626,8 @@ snapshots:
|
|||||||
side-channel-map: 1.0.1
|
side-channel-map: 1.0.1
|
||||||
side-channel-weakmap: 1.0.2
|
side-channel-weakmap: 1.0.2
|
||||||
|
|
||||||
|
siginfo@2.0.0: {}
|
||||||
|
|
||||||
signal-exit@3.0.7: {}
|
signal-exit@3.0.7: {}
|
||||||
|
|
||||||
signal-exit@4.1.0: {}
|
signal-exit@4.1.0: {}
|
||||||
@ -11476,8 +11698,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp: 2.0.0
|
escape-string-regexp: 2.0.0
|
||||||
|
|
||||||
|
stackback@0.0.2: {}
|
||||||
|
|
||||||
statuses@2.0.1: {}
|
statuses@2.0.1: {}
|
||||||
|
|
||||||
|
std-env@3.9.0: {}
|
||||||
|
|
||||||
string-hash@1.1.3: {}
|
string-hash@1.1.3: {}
|
||||||
|
|
||||||
string-length@4.0.2:
|
string-length@4.0.2:
|
||||||
@ -11519,6 +11745,10 @@ snapshots:
|
|||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
strip-json-comments@3.1.1: {}
|
||||||
|
|
||||||
|
strip-literal@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
js-tokens: 9.0.1
|
||||||
|
|
||||||
styled-components@6.1.19(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
styled-components@6.1.19(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emotion/is-prop-valid': 1.2.2
|
'@emotion/is-prop-valid': 1.2.2
|
||||||
@ -11629,6 +11859,8 @@ snapshots:
|
|||||||
|
|
||||||
tiny-invariant@1.3.3: {}
|
tiny-invariant@1.3.3: {}
|
||||||
|
|
||||||
|
tinybench@2.9.0: {}
|
||||||
|
|
||||||
tinycolor2@1.6.0: {}
|
tinycolor2@1.6.0: {}
|
||||||
|
|
||||||
tinyexec@0.3.2: {}
|
tinyexec@0.3.2: {}
|
||||||
@ -11643,6 +11875,12 @@ snapshots:
|
|||||||
'@types/tinycolor2': 1.4.6
|
'@types/tinycolor2': 1.4.6
|
||||||
tinycolor2: 1.6.0
|
tinycolor2: 1.6.0
|
||||||
|
|
||||||
|
tinypool@1.1.1: {}
|
||||||
|
|
||||||
|
tinyrainbow@2.0.0: {}
|
||||||
|
|
||||||
|
tinyspy@4.0.3: {}
|
||||||
|
|
||||||
title-case@2.1.1:
|
title-case@2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
no-case: 2.3.2
|
no-case: 2.3.2
|
||||||
@ -11674,7 +11912,7 @@ snapshots:
|
|||||||
|
|
||||||
ts-interface-checker@0.1.13: {}
|
ts-interface-checker@0.1.13: {}
|
||||||
|
|
||||||
ts-jest@29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(esbuild@0.25.5)(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3):
|
ts-jest@29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)))(typescript@5.8.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
bs-logger: 0.2.6
|
bs-logger: 0.2.6
|
||||||
ejs: 3.1.10
|
ejs: 3.1.10
|
||||||
@ -11692,7 +11930,6 @@ snapshots:
|
|||||||
'@jest/transform': 29.7.0
|
'@jest/transform': 29.7.0
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
babel-jest: 29.7.0(@babel/core@7.27.4)
|
babel-jest: 29.7.0(@babel/core@7.27.4)
|
||||||
esbuild: 0.25.5
|
|
||||||
jest-util: 29.7.0
|
jest-util: 29.7.0
|
||||||
|
|
||||||
ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3):
|
ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3):
|
||||||
@ -11957,6 +12194,27 @@ snapshots:
|
|||||||
d3-time: 3.1.0
|
d3-time: 3.1.0
|
||||||
d3-timer: 3.0.1
|
d3-timer: 3.0.1
|
||||||
|
|
||||||
|
vite-node@3.2.4(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4):
|
||||||
|
dependencies:
|
||||||
|
cac: 6.7.14
|
||||||
|
debug: 4.4.1
|
||||||
|
es-module-lexer: 1.7.0
|
||||||
|
pathe: 2.0.3
|
||||||
|
vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/node'
|
||||||
|
- jiti
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- sass
|
||||||
|
- sass-embedded
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
- tsx
|
||||||
|
- yaml
|
||||||
|
|
||||||
vite-plugin-html@3.2.2(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)):
|
vite-plugin-html@3.2.2(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rollup/pluginutils': 4.2.1
|
'@rollup/pluginutils': 4.2.1
|
||||||
@ -12003,6 +12261,67 @@ snapshots:
|
|||||||
terser: 5.40.0
|
terser: 5.40.0
|
||||||
tsx: 4.19.4
|
tsx: 4.19.4
|
||||||
|
|
||||||
|
vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4):
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.25.5
|
||||||
|
fdir: 6.4.5(picomatch@4.0.2)
|
||||||
|
picomatch: 4.0.2
|
||||||
|
postcss: 8.5.6
|
||||||
|
rollup: 4.41.1
|
||||||
|
tinyglobby: 0.2.14
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/node': 24.0.3
|
||||||
|
fsevents: 2.3.3
|
||||||
|
jiti: 2.4.2
|
||||||
|
less: 4.3.0
|
||||||
|
lightningcss: 1.30.1
|
||||||
|
sass: 1.89.0
|
||||||
|
stylus: 0.62.0
|
||||||
|
terser: 5.40.0
|
||||||
|
tsx: 4.19.4
|
||||||
|
|
||||||
|
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4):
|
||||||
|
dependencies:
|
||||||
|
'@types/chai': 5.2.2
|
||||||
|
'@vitest/expect': 3.2.4
|
||||||
|
'@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4))
|
||||||
|
'@vitest/pretty-format': 3.2.4
|
||||||
|
'@vitest/runner': 3.2.4
|
||||||
|
'@vitest/snapshot': 3.2.4
|
||||||
|
'@vitest/spy': 3.2.4
|
||||||
|
'@vitest/utils': 3.2.4
|
||||||
|
chai: 5.3.3
|
||||||
|
debug: 4.4.1
|
||||||
|
expect-type: 1.2.2
|
||||||
|
magic-string: 0.30.17
|
||||||
|
pathe: 2.0.3
|
||||||
|
picomatch: 4.0.2
|
||||||
|
std-env: 3.9.0
|
||||||
|
tinybench: 2.9.0
|
||||||
|
tinyexec: 0.3.2
|
||||||
|
tinyglobby: 0.2.14
|
||||||
|
tinypool: 1.1.1
|
||||||
|
tinyrainbow: 2.0.0
|
||||||
|
vite: 6.3.5(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)
|
||||||
|
vite-node: 3.2.4(@types/node@24.0.3)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.89.0)(stylus@0.62.0)(terser@5.40.0)(tsx@4.19.4)
|
||||||
|
why-is-node-running: 2.3.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/debug': 4.1.12
|
||||||
|
'@types/node': 24.0.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- jiti
|
||||||
|
- less
|
||||||
|
- lightningcss
|
||||||
|
- msw
|
||||||
|
- sass
|
||||||
|
- sass-embedded
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
|
- supports-color
|
||||||
|
- terser
|
||||||
|
- tsx
|
||||||
|
- yaml
|
||||||
|
|
||||||
void-elements@3.1.0: {}
|
void-elements@3.1.0: {}
|
||||||
|
|
||||||
walker@1.0.8:
|
walker@1.0.8:
|
||||||
@ -12032,6 +12351,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
|
|
||||||
|
why-is-node-running@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
siginfo: 2.0.0
|
||||||
|
stackback: 0.0.2
|
||||||
|
|
||||||
wide-align@1.1.5:
|
wide-align@1.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user