Facturas de cliente y clientes
This commit is contained in:
parent
4a36097290
commit
ad66580d85
@ -34,6 +34,9 @@ export abstract class ExpressController {
|
||||
} satisfies ApiErrorContext;
|
||||
|
||||
const body = toProblemJson(apiError, ctx);
|
||||
|
||||
console.trace(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 { CustomerInvoiceCustomer, CustomerInvoiceItem, CustomerInvoiceItems } from "../entities";
|
||||
import {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { IMoneyValueProps, MoneyValue } from "@repo/rdx-ddd";
|
||||
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||
|
||||
export class CustomerInvoiceItemSubtotalPrice extends MoneyValue {
|
||||
public static DEFAULT_SCALE = 4;
|
||||
|
||||
static create({ amount, currency_code, scale }: IMoneyValueProps) {
|
||||
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||
const props = {
|
||||
amount: Number(amount),
|
||||
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 {
|
||||
public static DEFAULT_SCALE = 4;
|
||||
|
||||
static create({ amount, currency_code, scale }: IMoneyValueProps) {
|
||||
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||
const props = {
|
||||
amount: Number(amount),
|
||||
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 {
|
||||
public static DEFAULT_SCALE = 4;
|
||||
|
||||
static create({ amount, currency_code, scale }: IMoneyValueProps) {
|
||||
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||
const props = {
|
||||
amount: Number(amount),
|
||||
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
||||
|
||||
@ -24,7 +24,8 @@
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-i18next": "^8.1.0",
|
||||
"typescript": "^5.8.3"
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ag-grid-community/locale": "34.0.0",
|
||||
|
||||
@ -1,35 +1,43 @@
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
import { CustomerCreationResponseDTO } from "../../../../common";
|
||||
import { Customer } from "../../../domain";
|
||||
|
||||
export class CreateCustomersAssembler {
|
||||
public toDTO(customer: Customer): CustomerCreationResponseDTO {
|
||||
const address = customer.address.toPrimitive();
|
||||
|
||||
return {
|
||||
id: customer.id.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(),
|
||||
phone: customer.phone.toPrimitive(),
|
||||
fax: customer.fax.toPrimitive(),
|
||||
website: customer.website,
|
||||
reference: toEmptyString(customer.reference, (value) => value.toPrimitive()),
|
||||
|
||||
default_tax: customer.defaultTax,
|
||||
legal_record: customer.legalRecord,
|
||||
lang_code: customer.langCode,
|
||||
currency_code: customer.currencyCode,
|
||||
is_company: String(customer.isCompany),
|
||||
name: customer.name.toPrimitive(),
|
||||
|
||||
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",
|
||||
|
||||
street: customer.address.street,
|
||||
street2: customer.address.street2,
|
||||
city: customer.address.city,
|
||||
state: customer.address.state,
|
||||
postal_code: customer.address.postalCode,
|
||||
country: customer.address.country,
|
||||
language_code: customer.languageCode.toPrimitive(),
|
||||
currency_code: customer.currencyCode.toPrimitive(),
|
||||
|
||||
metadata: {
|
||||
entity: "customer",
|
||||
|
||||
@ -3,9 +3,9 @@ import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Transaction } from "sequelize";
|
||||
import { CreateCustomerRequestDTO } from "../../../common";
|
||||
import { ICustomerService } from "../../domain";
|
||||
import { mapDTOToCustomerProps } from "../../helpers";
|
||||
import { CustomerService } from "../../domain";
|
||||
import { CreateCustomersAssembler } from "./assembler";
|
||||
import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props";
|
||||
|
||||
type CreateCustomerUseCaseInput = {
|
||||
dto: CreateCustomerRequestDTO;
|
||||
@ -13,7 +13,7 @@ type CreateCustomerUseCaseInput = {
|
||||
|
||||
export class CreateCustomerUseCase {
|
||||
constructor(
|
||||
private readonly service: ICustomerService,
|
||||
private readonly service: CustomerService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
private readonly assembler: CreateCustomersAssembler
|
||||
) {}
|
||||
@ -22,7 +22,7 @@ export class CreateCustomerUseCase {
|
||||
const { dto } = params;
|
||||
|
||||
// 1) Mapear DTO → props de dominio
|
||||
const dtoResult = mapDTOToCustomerProps(dto);
|
||||
const dtoResult = mapDTOToCreateCustomerProps(dto);
|
||||
if (dtoResult.isFailure) {
|
||||
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 { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { ICustomerService } from "../../domain";
|
||||
import { CustomerService } from "../../domain";
|
||||
|
||||
type DeleteCustomerUseCaseInput = {
|
||||
companyId: UniqueID;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export class DeleteCustomerUseCase {
|
||||
constructor(
|
||||
private readonly service: ICustomerService,
|
||||
private readonly service: CustomerService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
) {}
|
||||
|
||||
public execute(dto: DeleteCustomerByIdQueryDTO) {
|
||||
const idOrError = UniqueID.create(dto.id);
|
||||
public execute(params: DeleteCustomerUseCaseInput) {
|
||||
const { companyId, id } = params;
|
||||
|
||||
const idOrError = UniqueID.create(id);
|
||||
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const id = idOrError.data;
|
||||
const validId = idOrError.data;
|
||||
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
const existsCheck = await this.service.existsByIdInCompany(id, transaction);
|
||||
const existsCheck = await this.service.existsByIdInCompany(companyId, validId, transaction);
|
||||
|
||||
if (existsCheck.isFailure) {
|
||||
return Result.fail(existsCheck.error);
|
||||
}
|
||||
|
||||
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) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
|
||||
@ -1,63 +1,43 @@
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
import { GetCustomerByIdResponseDTO } from "../../../../common/dto";
|
||||
import { Customer } from "../../../domain";
|
||||
|
||||
export class GetCustomerAssembler {
|
||||
toDTO(customer: Customer): GetCustomerByIdResponseDTO {
|
||||
const address = customer.address.toPrimitive();
|
||||
|
||||
return {
|
||||
id: customer.id.toPrimitive(),
|
||||
reference: customer.reference,
|
||||
company_id: customer.companyId.toPrimitive(),
|
||||
|
||||
is_company: customer.isCompany,
|
||||
name: customer.name,
|
||||
trade_name: customer.tradeName ?? "",
|
||||
tin: customer.tin.toPrimitive(),
|
||||
reference: toEmptyString(customer.reference, (value) => value.toPrimitive()),
|
||||
|
||||
metadata: {
|
||||
entity: "customer",
|
||||
//updated_at: customer.updatedAt.toDateString(),
|
||||
//created_at: customer.createdAt.toDateString(),
|
||||
},
|
||||
is_company: String(customer.isCompany),
|
||||
name: customer.name.toPrimitive(),
|
||||
|
||||
//subtotal: customer.calculateSubtotal().toPrimitive(),
|
||||
trade_name: toEmptyString(customer.tradeName, (value) => value.toPrimitive()),
|
||||
|
||||
//total: customer.calculateTotal().toPrimitive(),
|
||||
tin: toEmptyString(customer.tin, (value) => value.toPrimitive()),
|
||||
|
||||
/*items:
|
||||
customer.items.size() > 0
|
||||
? customer.items.map((item: CustomerItem) => ({
|
||||
description: item.description.toString(),
|
||||
quantity: item.quantity.toPrimitive(),
|
||||
unit_measure: "",
|
||||
unit_price: item.unitPrice.toPrimitive(),
|
||||
subtotal: item.calculateSubtotal().toPrimitive(),
|
||||
//tax_amount: item.calculateTaxAmount().toPrimitive(),
|
||||
total: item.calculateTotal().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()),
|
||||
|
||||
//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),
|
||||
items: customerItemAssembler(customer.items, context),
|
||||
legal_record: toEmptyString(customer.legalRecord, (value) => value.toPrimitive()),
|
||||
|
||||
payment_term: {
|
||||
payment_type: "",
|
||||
due_date: "",
|
||||
},
|
||||
default_taxes: customer.defaultTaxes.map((item) => item.toPrimitive()),
|
||||
|
||||
due_amount: {
|
||||
currency: customer.currency.toString(),
|
||||
precision: 2,
|
||||
amount: 0,
|
||||
},
|
||||
|
||||
custom_fields: [],
|
||||
|
||||
metadata: {
|
||||
create_time: "",
|
||||
last_updated_time: "",
|
||||
delete_time: "",
|
||||
},*/
|
||||
status: customer.isActive ? "active" : "inactive",
|
||||
language_code: customer.languageCode.toPrimitive(),
|
||||
currency_code: customer.currencyCode.toPrimitive(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,23 +1,24 @@
|
||||
import { ITransactionManager } from "@erp/core/api";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { ICustomerService } from "../../domain";
|
||||
import { CustomerService } from "../../domain";
|
||||
import { GetCustomerAssembler } from "./assembler";
|
||||
|
||||
type GetCustomerUseCaseInput = {
|
||||
tenantId: string;
|
||||
companyId: UniqueID;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export class GetCustomerUseCase {
|
||||
constructor(
|
||||
private readonly service: ICustomerService,
|
||||
private readonly service: CustomerService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
private readonly assembler: GetCustomerAssembler
|
||||
) {}
|
||||
|
||||
public execute(params: GetCustomerUseCaseInput) {
|
||||
const { id, tenantId: companyId } = params;
|
||||
console.log(params);
|
||||
const { id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(id);
|
||||
|
||||
@ -25,24 +26,22 @@ export class GetCustomerUseCase {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const companyIdOrError = UniqueID.create(companyId);
|
||||
|
||||
if (companyIdOrError.isFailure) {
|
||||
return Result.fail(companyIdOrError.error);
|
||||
}
|
||||
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
const customerOrError = await this.service.getCustomerByIdInCompany(
|
||||
companyIdOrError.data,
|
||||
companyId,
|
||||
idOrError.data,
|
||||
transaction
|
||||
);
|
||||
|
||||
console.log(customerOrError);
|
||||
|
||||
if (customerOrError.isFailure) {
|
||||
return Result.fail(customerOrError.error);
|
||||
}
|
||||
|
||||
const getDTO = this.assembler.toDTO(customerOrError.data);
|
||||
console.log(getDTO);
|
||||
return Result.ok(getDTO);
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
|
||||
@ -2,4 +2,4 @@ export * from "./create-customer";
|
||||
export * from "./delete-customer";
|
||||
export * from "./get-customer";
|
||||
export * from "./list-customers";
|
||||
//export * from "./update-customer";
|
||||
export * from "./update-customer";
|
||||
|
||||
@ -10,30 +10,72 @@ export class ListCustomersAssembler {
|
||||
|
||||
return {
|
||||
id: customer.id.toPrimitive(),
|
||||
reference: customer.reference,
|
||||
company_id: customer.companyId.toPrimitive(),
|
||||
|
||||
reference: customer.reference.match(
|
||||
(value) => value.toPrimitive(),
|
||||
() => ""
|
||||
),
|
||||
|
||||
is_company: customer.isCompany,
|
||||
name: customer.name,
|
||||
trade_name: customer.tradeName ?? "",
|
||||
tin: customer.tin.toPrimitive(),
|
||||
name: customer.name.toPrimitive(),
|
||||
trade_name: customer.tradeName.match(
|
||||
(value) => value.toPrimitive(),
|
||||
() => ""
|
||||
),
|
||||
tin: customer.tin.match(
|
||||
(value) => value.toPrimitive(),
|
||||
() => ""
|
||||
),
|
||||
|
||||
street: address.street,
|
||||
city: address.city,
|
||||
state: address.state,
|
||||
postal_code: address.postalCode,
|
||||
country: address.country,
|
||||
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.toPrimitive(),
|
||||
phone: customer.phone.toPrimitive(),
|
||||
fax: customer.fax.toPrimitive(),
|
||||
website: customer.website ?? "",
|
||||
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,
|
||||
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",
|
||||
lang_code: customer.langCode,
|
||||
currency_code: customer.currencyCode,
|
||||
language_code: customer.languageCode.toPrimitive(),
|
||||
currency_code: customer.currencyCode.toPrimitive(),
|
||||
|
||||
metadata: {
|
||||
entity: "customer",
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { ITransactionManager } from "@erp/core/api";
|
||||
import { Criteria } from "@repo/rdx-criteria/server";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Transaction } from "sequelize";
|
||||
import { CustomerListResponsetDTO } from "../../../common/dto";
|
||||
import { ICustomerService } from "../../domain";
|
||||
import { CustomerService } from "../../domain";
|
||||
import { ListCustomersAssembler } from "./assembler";
|
||||
|
||||
type ListCustomersUseCaseInput = {
|
||||
@ -13,7 +14,7 @@ type ListCustomersUseCaseInput = {
|
||||
|
||||
export class ListCustomersUseCase {
|
||||
constructor(
|
||||
private readonly customerService: ICustomerService,
|
||||
private readonly customerService: CustomerService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
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";
|
||||
|
||||
@ -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 "@/core/common/infrastructure/database";
|
||||
import { ITransactionManager } from "@erp/core/api";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { IUpdateCustomerRequestDTO } from "../../common/dto";
|
||||
import { Customer, ICustomerService } from "../domain";
|
||||
import { UpdateCustomerRequestDTO } from "../../../common";
|
||||
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(
|
||||
private readonly customerService: ICustomerService,
|
||||
private readonly transactionManager: ITransactionManager
|
||||
private readonly service: CustomerService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
private readonly assembler: UpdateCustomerAssembler
|
||||
) {}
|
||||
|
||||
public execute(
|
||||
customerID: UniqueID,
|
||||
dto: Partial<IUpdateCustomerRequestDTO>
|
||||
): Promise<Result<Customer, Error>> {
|
||||
public execute(params: UpdateCustomerUseCaseInput) {
|
||||
const { companyId, id, dto } = params;
|
||||
|
||||
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 Result.fail(new Error("No implementado"));
|
||||
/*
|
||||
try {
|
||||
const validOrErrors = this.validateCustomerData(dto);
|
||||
if (validOrErrors.isFailure) {
|
||||
return Result.fail(validOrErrors.error);
|
||||
const updatedCustomer = await this.service.updateCustomerByIdInCompany(
|
||||
companyId,
|
||||
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
|
||||
return await this.customerService.updateCustomerById(customerID, data, transaction);
|
||||
const getDTO = this.assembler.toDTO(savedCustomer.data);
|
||||
return Result.ok(getDTO);
|
||||
} catch (error: unknown) {
|
||||
logger.error(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 {
|
||||
AggregateRoot,
|
||||
CurrencyCode,
|
||||
EmailAddress,
|
||||
LanguageCode,
|
||||
Name,
|
||||
PhoneNumber,
|
||||
PostalAddress,
|
||||
PostalAddressPatchProps,
|
||||
PostalAddressSnapshot,
|
||||
TINNumber,
|
||||
TaxCode,
|
||||
TextValue,
|
||||
URLAddress,
|
||||
UniqueID,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||
import { CustomerStatus } from "../value-objects";
|
||||
|
||||
export interface CustomerProps {
|
||||
companyId: UniqueID;
|
||||
status: CustomerStatus;
|
||||
reference: string;
|
||||
reference: Maybe<Name>;
|
||||
|
||||
isCompany: boolean;
|
||||
name: string;
|
||||
tradeName: string;
|
||||
tin: TINNumber;
|
||||
name: Name;
|
||||
tradeName: Maybe<Name>;
|
||||
tin: Maybe<TINNumber>;
|
||||
|
||||
address: PostalAddress;
|
||||
|
||||
email: EmailAddress;
|
||||
phone: PhoneNumber;
|
||||
fax: PhoneNumber;
|
||||
website: string;
|
||||
email: Maybe<EmailAddress>;
|
||||
phone: Maybe<PhoneNumber>;
|
||||
fax: Maybe<PhoneNumber>;
|
||||
website: Maybe<URLAddress>;
|
||||
|
||||
legalRecord: string;
|
||||
defaultTax: string[];
|
||||
legalRecord: Maybe<TextValue>;
|
||||
defaultTaxes: Collection<TaxCode>;
|
||||
|
||||
langCode: string;
|
||||
currencyCode: string;
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
}
|
||||
|
||||
export interface ICustomer {
|
||||
id: UniqueID;
|
||||
companyId: UniqueID;
|
||||
reference: string;
|
||||
export interface CustomerSnapshot {
|
||||
id: string;
|
||||
companyId: 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;
|
||||
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> {
|
||||
const contact = new Customer(props, id);
|
||||
|
||||
@ -75,76 +86,122 @@ export class Customer extends AggregateRoot<CustomerProps> implements ICustomer
|
||||
return Result.ok(contact);
|
||||
}
|
||||
|
||||
update(partial: Partial<Omit<CustomerProps, "companyId">>): Result<Customer, Error> {
|
||||
const updatedCustomer = new Customer({ ...this.props, ...partial }, this.id);
|
||||
public update(partial: CustomerPatchProps): Result<Customer, Error> {
|
||||
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);
|
||||
}
|
||||
|
||||
get companyId(): UniqueID {
|
||||
return this.props.companyId;
|
||||
public toSnapshot(): CustomerSnapshot {
|
||||
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() {
|
||||
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 {
|
||||
public get isIndividual(): boolean {
|
||||
return !this.props.isCompany;
|
||||
}
|
||||
|
||||
get isCompany(): boolean {
|
||||
public get isCompany(): boolean {
|
||||
return this.props.isCompany;
|
||||
}
|
||||
|
||||
get isActive(): boolean {
|
||||
public get isActive(): boolean {
|
||||
return this.props.status.isActive();
|
||||
}
|
||||
|
||||
public get companyId(): UniqueID {
|
||||
return this.props.companyId;
|
||||
}
|
||||
|
||||
public get reference(): Maybe<Name> {
|
||||
return this.props.reference;
|
||||
}
|
||||
|
||||
public get name(): Name {
|
||||
return this.props.name;
|
||||
}
|
||||
|
||||
public get tradeName(): Maybe<Name> {
|
||||
return this.props.tradeName;
|
||||
}
|
||||
|
||||
public get tin(): Maybe<TINNumber> {
|
||||
return this.props.tin;
|
||||
}
|
||||
|
||||
public get address(): PostalAddress {
|
||||
return this.props.address;
|
||||
}
|
||||
|
||||
public get 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 { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Customer, CustomerProps } from "../aggregates";
|
||||
import { Customer, CustomerPatchProps, CustomerProps } from "../aggregates";
|
||||
import { ICustomerRepository } from "../repositories";
|
||||
import { ICustomerService } from "./customer-service.interface";
|
||||
|
||||
export class CustomerService implements ICustomerService {
|
||||
export class CustomerService {
|
||||
constructor(private readonly repository: ICustomerRepository) {}
|
||||
|
||||
/**
|
||||
@ -87,6 +86,7 @@ export class CustomerService implements ICustomerService {
|
||||
|
||||
/**
|
||||
* 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 customerId - Identificador del cliente a actualizar.
|
||||
@ -97,9 +97,9 @@ export class CustomerService implements ICustomerService {
|
||||
async updateCustomerByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
customerId: UniqueID,
|
||||
partial: Partial<Omit<CustomerProps, "companyId">>,
|
||||
partial: CustomerPatchProps,
|
||||
transaction?: any
|
||||
): Promise<Result<Customer>> {
|
||||
): Promise<Result<Customer, Error>> {
|
||||
const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction);
|
||||
|
||||
if (customerResult.isFailure) {
|
||||
@ -113,7 +113,7 @@ export class CustomerService implements ICustomerService {
|
||||
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";
|
||||
|
||||
@ -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,
|
||||
ListCustomersAssembler,
|
||||
ListCustomersUseCase,
|
||||
UpdateCustomerAssembler,
|
||||
UpdateCustomerUseCase,
|
||||
} from "../application";
|
||||
import { CustomerService, ICustomerService } from "../domain";
|
||||
import { CustomerService } from "../domain";
|
||||
import { CustomerMapper } from "./mappers";
|
||||
import { CustomerRepository } from "./sequelize";
|
||||
|
||||
@ -18,18 +20,18 @@ type CustomerDeps = {
|
||||
transactionManager: SequelizeTransactionManager;
|
||||
repo: CustomerRepository;
|
||||
mapper: CustomerMapper;
|
||||
service: ICustomerService;
|
||||
service: CustomerService;
|
||||
assemblers: {
|
||||
list: ListCustomersAssembler;
|
||||
get: GetCustomerAssembler;
|
||||
create: CreateCustomersAssembler;
|
||||
//update: UpdateCustomerAssembler;
|
||||
update: UpdateCustomerAssembler;
|
||||
};
|
||||
build: {
|
||||
list: () => ListCustomersUseCase;
|
||||
get: () => GetCustomerUseCase;
|
||||
create: () => CreateCustomerUseCase;
|
||||
//update: () => UpdateCustomerUseCase;
|
||||
update: () => UpdateCustomerUseCase;
|
||||
delete: () => DeleteCustomerUseCase;
|
||||
};
|
||||
presenters: {
|
||||
@ -39,7 +41,7 @@ type CustomerDeps = {
|
||||
|
||||
let _repo: CustomerRepository | null = null;
|
||||
let _mapper: CustomerMapper | null = null;
|
||||
let _service: ICustomerService | null = null;
|
||||
let _service: CustomerService | null = null;
|
||||
let _assemblers: CustomerDeps["assemblers"] | null = null;
|
||||
|
||||
export function getCustomerDependencies(params: ModuleParams): CustomerDeps {
|
||||
@ -55,7 +57,7 @@ export function getCustomerDependencies(params: ModuleParams): CustomerDeps {
|
||||
list: new ListCustomersAssembler(), // transforma domain → ListDTO
|
||||
get: new GetCustomerAssembler(), // transforma domain → DetailDTO
|
||||
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),
|
||||
get: () => new GetCustomerUseCase(_service!, transactionManager!, _assemblers!.get),
|
||||
create: () => new CreateCustomerUseCase(_service!, transactionManager!, _assemblers!.create),
|
||||
/*update: () =>
|
||||
new UpdateCustomerUseCase(_service!, transactionManager!, _assemblers!.update),*/
|
||||
update: () => new UpdateCustomerUseCase(_service!, transactionManager!, _assemblers!.update),
|
||||
delete: () => new DeleteCustomerUseCase(_service!, transactionManager!),
|
||||
},
|
||||
presenters: {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||
import { CreateCustomerRequestDTO } from "../../../../common/dto";
|
||||
import { UpdateCustomerRequestDTO } from "../../../../common/dto";
|
||||
import { CreateCustomerUseCase } from "../../../application";
|
||||
|
||||
export class CreateCustomerController extends ExpressController {
|
||||
@ -11,7 +11,7 @@ export class CreateCustomerController extends ExpressController {
|
||||
|
||||
protected async executeImpl() {
|
||||
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)
|
||||
dto.company_id = companyId.toString();
|
||||
|
||||
@ -9,10 +9,10 @@ export class DeleteCustomerController extends ExpressController {
|
||||
}
|
||||
|
||||
async executeImpl(): Promise<any> {
|
||||
const tenantId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const { id } = this.req.params;
|
||||
|
||||
const result = await this.useCase.execute({ id, tenantId });
|
||||
const result = await this.useCase.execute({ id, companyId });
|
||||
|
||||
return result.match(
|
||||
(data) => this.ok(data),
|
||||
|
||||
@ -9,10 +9,12 @@ export class GetCustomerController extends ExpressController {
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const tenantId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
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(
|
||||
(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 { Sequelize } from "sequelize";
|
||||
import {
|
||||
CreateCustomerRequestSchema,
|
||||
CustomerListRequestSchema,
|
||||
DeleteCustomerByIdRequestSchema,
|
||||
GetCustomerByIdRequestSchema,
|
||||
UpdateCustomerRequestSchema,
|
||||
} from "../../../common/dto";
|
||||
import { getCustomerDependencies } from "../dependencies";
|
||||
import {
|
||||
@ -64,7 +64,7 @@ export const customersRouter = (params: ModuleParams) => {
|
||||
"/",
|
||||
//checkTabContext,
|
||||
|
||||
validateRequest(CreateCustomerRequestSchema),
|
||||
validateRequest(UpdateCustomerRequestSchema),
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const useCase = deps.build.create();
|
||||
const controller = new CreateCustomerController(useCase);
|
||||
|
||||
@ -6,8 +6,27 @@ import {
|
||||
ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
} from "@erp/core/api";
|
||||
import { EmailAddress, PhoneNumber, PostalAddress, TINNumber, UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
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 { CustomerCreationAttributes, CustomerModel } from "../sequelize";
|
||||
|
||||
@ -28,41 +47,140 @@ export class CustomerMapper
|
||||
"company_id",
|
||||
errors
|
||||
);
|
||||
|
||||
const isCompany = source.is_company;
|
||||
const status = extractOrPushError(CustomerStatus.create(source.status), "status", errors);
|
||||
const reference = source.reference?.trim() === "" ? undefined : source.reference;
|
||||
|
||||
const isCompany = source.is_company ?? true;
|
||||
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",
|
||||
const reference = extractOrPushError(
|
||||
maybeFromNullableVO(source.reference, (value) => Name.create(value)),
|
||||
"reference",
|
||||
errors
|
||||
);
|
||||
|
||||
const emailAddress = extractOrPushError(EmailAddress.create(source.email), "email", 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 name = extractOrPushError(Name.create(source.name), "name", errors);
|
||||
|
||||
const legalRecord = source.legal_record?.trim() === "" ? undefined : source.legal_record;
|
||||
const langCode = source.lang_code?.trim() === "" ? undefined : source.lang_code;
|
||||
const currencyCode = source.currency_code?.trim() === "" ? undefined : source.currency_code;
|
||||
const tradeName = extractOrPushError(
|
||||
maybeFromNullableVO(source.trade_name, (value) => Name.create(value)),
|
||||
"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) {
|
||||
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!,
|
||||
};
|
||||
|
||||
console.log(postalAddressProps);
|
||||
|
||||
const postalAddress = extractOrPushError(
|
||||
PostalAddress.create(postalAddressProps),
|
||||
"address",
|
||||
errors
|
||||
);
|
||||
|
||||
const customerProps: CustomerProps = {
|
||||
companyId: companyId!,
|
||||
status: status!,
|
||||
@ -73,7 +191,7 @@ export class CustomerMapper
|
||||
tradeName: tradeName!,
|
||||
tin: tinNumber!,
|
||||
|
||||
address: address!,
|
||||
address: postalAddress!,
|
||||
|
||||
email: emailAddress!,
|
||||
phone: phoneNumber!,
|
||||
@ -81,8 +199,8 @@ export class CustomerMapper
|
||||
website: website!,
|
||||
|
||||
legalRecord: legalRecord!,
|
||||
defaultTax: [],
|
||||
langCode: langCode!,
|
||||
defaultTaxes: defaultTaxes!,
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
};
|
||||
|
||||
@ -96,30 +214,38 @@ export class CustomerMapper
|
||||
return {
|
||||
id: source.id.toPrimitive(),
|
||||
company_id: source.companyId.toPrimitive(),
|
||||
reference: source.reference,
|
||||
|
||||
reference: source.reference.match(
|
||||
(value) => value.toPrimitive(),
|
||||
() => ""
|
||||
),
|
||||
|
||||
is_company: source.isCompany,
|
||||
name: source.name,
|
||||
trade_name: source.tradeName,
|
||||
tin: source.tin.toPrimitive(),
|
||||
name: source.name.toPrimitive(),
|
||||
trade_name: toNullable(source.tradeName, (trade_name) => trade_name.toPrimitive()),
|
||||
tin: toNullable(source.tin, (tin) => tin.toPrimitive()),
|
||||
|
||||
email: source.email.toPrimitive(),
|
||||
phone: source.phone.toPrimitive(),
|
||||
fax: source.fax.toPrimitive(),
|
||||
website: source.website,
|
||||
street: toNullable(source.address.street, (street) => street.toPrimitive()),
|
||||
street2: toNullable(source.address.street2, (street2) => street2.toPrimitive()),
|
||||
city: toNullable(source.address.city, (city) => city.toPrimitive()),
|
||||
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(),
|
||||
legal_record: source.legalRecord,
|
||||
lang_code: source.langCode,
|
||||
currency_code: source.currencyCode,
|
||||
email: toNullable(source.email, (email) => email.toPrimitive()),
|
||||
phone: toNullable(source.phone, (phone) => phone.toPrimitive()),
|
||||
fax: toNullable(source.fax, (fax) => fax.toPrimitive()),
|
||||
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",
|
||||
|
||||
street: source.address.street,
|
||||
street2: source.address.street2,
|
||||
city: source.address.city,
|
||||
state: source.address.state,
|
||||
postal_code: source.address.postalCode,
|
||||
country: source.address.country,
|
||||
language_code: source.languageCode.toPrimitive(),
|
||||
currency_code: source.currencyCode.toPrimitive(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export class CustomerModel extends Model<
|
||||
declare street: string;
|
||||
declare street2: string;
|
||||
declare city: string;
|
||||
declare state: string;
|
||||
declare province: string;
|
||||
declare postal_code: string;
|
||||
declare country: string;
|
||||
|
||||
@ -34,9 +34,9 @@ export class CustomerModel extends Model<
|
||||
|
||||
declare legal_record: string;
|
||||
|
||||
declare default_tax: string;
|
||||
declare default_taxes: string;
|
||||
declare status: string;
|
||||
declare lang_code: string;
|
||||
declare language_code: string;
|
||||
declare currency_code: string;
|
||||
|
||||
static associate(database: Sequelize) {}
|
||||
@ -95,7 +95,7 @@ export default (database: Sequelize) => {
|
||||
allowNull: false,
|
||||
defaultValue: "",
|
||||
},
|
||||
state: {
|
||||
province: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: "",
|
||||
@ -143,13 +143,13 @@ export default (database: Sequelize) => {
|
||||
defaultValue: "",
|
||||
},
|
||||
|
||||
default_tax: {
|
||||
default_taxes: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: "",
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
lang_code: {
|
||||
language_code: {
|
||||
type: DataTypes.STRING(2),
|
||||
allowNull: false,
|
||||
defaultValue: "es",
|
||||
@ -180,6 +180,7 @@ export default (database: Sequelize) => {
|
||||
|
||||
indexes: [
|
||||
{ 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 },
|
||||
],
|
||||
|
||||
|
||||
@ -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 { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
@ -89,10 +89,12 @@ export class CustomerRepository
|
||||
});
|
||||
|
||||
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) {
|
||||
return Result.fail(translateSequelizeError(error));
|
||||
}
|
||||
|
||||
@ -5,14 +5,15 @@ export const CreateCustomerRequestSchema = z.object({
|
||||
company_id: z.uuid(),
|
||||
reference: z.string().default(""),
|
||||
|
||||
is_company: z.boolean().default(true),
|
||||
is_company: z.boolean().default(false),
|
||||
name: z.string().default(""),
|
||||
trade_name: z.string().default(""),
|
||||
tin: z.string().default(""),
|
||||
|
||||
street: z.string().default(""),
|
||||
street2: z.string().default(""),
|
||||
city: z.string().default(""),
|
||||
state: z.string().default(""),
|
||||
province: z.string().default(""),
|
||||
postal_code: z.string().default(""),
|
||||
country: z.string().default(""),
|
||||
|
||||
@ -23,9 +24,9 @@ export const CreateCustomerRequestSchema = z.object({
|
||||
|
||||
legal_record: z.string().default(""),
|
||||
|
||||
default_tax: z.array(z.string()).default([]),
|
||||
default_taxes: z.array(z.string()).default([]),
|
||||
status: z.string().default("active"),
|
||||
lang_code: z.string().default("es"),
|
||||
language_code: z.string().default("es"),
|
||||
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 * as z from "zod/v4";
|
||||
|
||||
export const CustomerCreationResponseSchema = z.object({
|
||||
export const CreateCustomerResponseSchema = z.object({
|
||||
id: z.uuid(),
|
||||
company_id: z.uuid(),
|
||||
reference: z.string(),
|
||||
|
||||
is_company: z.boolean(),
|
||||
is_company: z.string(),
|
||||
name: z.string(),
|
||||
trade_name: z.string(),
|
||||
tin: z.string(),
|
||||
@ -25,12 +25,12 @@ export const CustomerCreationResponseSchema = z.object({
|
||||
|
||||
legal_record: z.string(),
|
||||
|
||||
default_tax: z.array(z.string()),
|
||||
default_taxes: z.array(z.string()),
|
||||
status: z.string(),
|
||||
lang_code: z.string(),
|
||||
language_code: z.string(),
|
||||
currency_code: z.string(),
|
||||
|
||||
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(),
|
||||
|
||||
default_tax: z.number(),
|
||||
default_taxes: z.array(z.string()),
|
||||
status: z.string(),
|
||||
lang_code: z.string(),
|
||||
language_code: z.string(),
|
||||
currency_code: z.string(),
|
||||
|
||||
metadata: MetadataSchema.optional(),
|
||||
|
||||
@ -3,14 +3,16 @@ import * as z from "zod/v4";
|
||||
|
||||
export const GetCustomerByIdResponseSchema = z.object({
|
||||
id: z.uuid(),
|
||||
company_id: z.uuid(),
|
||||
reference: z.string(),
|
||||
|
||||
is_company: z.boolean(),
|
||||
is_company: z.string(),
|
||||
name: z.string(),
|
||||
trade_name: z.string(),
|
||||
tin: z.string(),
|
||||
|
||||
street: z.string(),
|
||||
street2: z.string(),
|
||||
city: z.string(),
|
||||
state: z.string(),
|
||||
postal_code: z.string(),
|
||||
@ -23,9 +25,9 @@ export const GetCustomerByIdResponseSchema = z.object({
|
||||
|
||||
legal_record: z.string(),
|
||||
|
||||
default_tax: z.number(),
|
||||
default_taxes: z.array(z.string()),
|
||||
status: z.string(),
|
||||
lang_code: z.string(),
|
||||
language_code: z.string(),
|
||||
currency_code: z.string(),
|
||||
|
||||
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 "./get-customer-by-id.response.dto";
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { useDataSource, useQueryKey } from "@erp/core/hooks";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { CreateCustomerRequestDTO } from "../../common/dto";
|
||||
import { UpdateCustomerRequestDTO } from "../../common/dto";
|
||||
|
||||
export const useCreateCustomerMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
const keys = useQueryKey();
|
||||
|
||||
return useMutation<CreateCustomerRequestDTO, Error, Partial<CreateCustomerRequestDTO>>({
|
||||
return useMutation<UpdateCustomerRequestDTO, Error, Partial<UpdateCustomerRequestDTO>>({
|
||||
mutationFn: (data) => {
|
||||
console.log(data);
|
||||
return dataSource.createOne("customers", data);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
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>;
|
||||
|
||||
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 "./domain-entity";
|
||||
export * from "./events/domain-event.interface";
|
||||
export * from "./helpers";
|
||||
export * from "./value-objects";
|
||||
|
||||
@ -15,12 +15,6 @@ describe("EmailAddress Value Object", () => {
|
||||
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", () => {
|
||||
const result = EmailAddress.create("");
|
||||
|
||||
@ -45,13 +39,6 @@ describe("EmailAddress Value Object", () => {
|
||||
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", () => {
|
||||
const email = EmailAddress.create("test@example.com");
|
||||
|
||||
|
||||
@ -14,19 +14,6 @@ describe("Name Value Object", () => {
|
||||
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", () => {
|
||||
expect(Name.generateAcronym("John Doe")).toBe("JDXX");
|
||||
expect(Name.generateAcronym("Alice Bob Charlie")).toBe("ABCX");
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { parsePhoneNumberWithError } from "libphonenumber-js";
|
||||
import { Maybe } from "../../helpers/maybe";
|
||||
import { PhoneNumber } from "../phone-number";
|
||||
|
||||
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", () => {
|
||||
const result = PhoneNumber.create(validPhone);
|
||||
expect(result.isSuccess).toBe(true);
|
||||
|
||||
@ -24,27 +24,6 @@ describe("PostalAddress Value Object", () => {
|
||||
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", () => {
|
||||
const address = PostalAddress.create(validAddress).data;
|
||||
|
||||
|
||||
@ -25,17 +25,4 @@ describe("Slug Value Object", () => {
|
||||
expect(slugResult.isSuccess).toBe(false);
|
||||
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");
|
||||
});
|
||||
|
||||
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()", () => {
|
||||
const result = TINNumber.create("ABC123");
|
||||
expect(result.isSuccess).toBe(true);
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { ValueObject } from "./value-object";
|
||||
|
||||
interface ITestValueProps {
|
||||
interface TestValueProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
class TestValueObject extends ValueObject<ITestValueProps> {
|
||||
class TestValueObject extends ValueObject<TestValueProps> {
|
||||
constructor(value: string) {
|
||||
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 { ValueObject } from "./value-object";
|
||||
|
||||
@ -17,16 +17,8 @@ export class EmailAddress extends ValueObject<EmailAddressProps> {
|
||||
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) {
|
||||
const schema = z.string().email({ message: "Invalid email format" });
|
||||
const schema = z.email({ message: "Invalid email format" });
|
||||
return schema.safeParse(value);
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +1,22 @@
|
||||
export * from "./city";
|
||||
export * from "./country";
|
||||
export * from "./currency-code";
|
||||
export * from "./email-address";
|
||||
export * from "./language-code";
|
||||
export * from "./money-value";
|
||||
export * from "./name";
|
||||
export * from "./percentage";
|
||||
export * from "./phone-number";
|
||||
export * from "./postal-address";
|
||||
export * from "./postal-code";
|
||||
export * from "./province";
|
||||
export * from "./quantity";
|
||||
export * from "./slug";
|
||||
export * from "./street";
|
||||
export * from "./tax-code";
|
||||
export * from "./text-value";
|
||||
export * from "./tin-number";
|
||||
export * from "./unique-id";
|
||||
export * from "./url-address";
|
||||
export * from "./utc-date";
|
||||
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"
|
||||
| "DOWN";
|
||||
|
||||
export interface IMoneyValueProps {
|
||||
export interface MoneyValueProps {
|
||||
amount: number;
|
||||
scale?: number;
|
||||
currency_code?: string;
|
||||
@ -29,7 +29,7 @@ export interface IMoneyValue {
|
||||
scale: number;
|
||||
currency: Dinero.Currency;
|
||||
|
||||
getValue(): IMoneyValueProps;
|
||||
getValue(): MoneyValueProps;
|
||||
convertScale(newScale: number): MoneyValue;
|
||||
add(addend: MoneyValue): MoneyValue;
|
||||
subtract(subtrahend: MoneyValue): MoneyValue;
|
||||
@ -47,13 +47,13 @@ export interface IMoneyValue {
|
||||
format(locale: string): string;
|
||||
}
|
||||
|
||||
export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyValue {
|
||||
export class MoneyValue extends ValueObject<MoneyValueProps> implements IMoneyValue {
|
||||
private readonly dinero: Dinero;
|
||||
|
||||
static DEFAULT_SCALE = DEFAULT_SCALE;
|
||||
static DEFAULT_CURRENCY_CODE = DEFAULT_CURRENCY_CODE;
|
||||
|
||||
static create({ amount, currency_code, scale }: IMoneyValueProps) {
|
||||
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||
const props = {
|
||||
amount: Number(amount),
|
||||
scale: scale ?? MoneyValue.DEFAULT_SCALE,
|
||||
@ -62,7 +62,7 @@ export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyV
|
||||
return Result.ok(new MoneyValue(props));
|
||||
}
|
||||
|
||||
constructor(props: IMoneyValueProps) {
|
||||
constructor(props: MoneyValueProps) {
|
||||
super(props);
|
||||
const { amount, scale, currency_code } = props;
|
||||
this.dinero = Object.freeze(
|
||||
@ -86,7 +86,7 @@ export class MoneyValue extends ValueObject<IMoneyValueProps> implements IMoneyV
|
||||
return this.dinero.getPrecision();
|
||||
}
|
||||
|
||||
getValue(): IMoneyValueProps {
|
||||
getValue(): MoneyValueProps {
|
||||
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 { ValueObject } from "./value-object";
|
||||
|
||||
interface INameProps {
|
||||
interface NameProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class Name extends ValueObject<INameProps> {
|
||||
export class Name extends ValueObject<NameProps> {
|
||||
private static readonly MAX_LENGTH = 255;
|
||||
|
||||
protected static validate(value: string) {
|
||||
@ -26,14 +26,6 @@ export class Name extends ValueObject<INameProps> {
|
||||
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 {
|
||||
const words = name.split(" ").map((word) => word[0].toUpperCase());
|
||||
let acronym = words.join("");
|
||||
|
||||
@ -10,21 +10,12 @@ const DEFAULT_MAX_VALUE = 100;
|
||||
const DEFAULT_MIN_SCALE = 0;
|
||||
const DEFAULT_MAX_SCALE = 2;
|
||||
|
||||
export interface IPercentageProps {
|
||||
export interface PercentageProps {
|
||||
amount: number;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
interface IPercentage {
|
||||
amount: number;
|
||||
scale: number;
|
||||
|
||||
getValue(): IPercentageProps;
|
||||
toNumber(): number;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export class Percentage extends ValueObject<IPercentageProps> implements IPercentage {
|
||||
export class Percentage extends ValueObject<PercentageProps> {
|
||||
static DEFAULT_SCALE = DEFAULT_SCALE;
|
||||
static MIN_VALUE = DEFAULT_MIN_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 MAX_SCALE = DEFAULT_MAX_SCALE;
|
||||
|
||||
protected static validate(values: IPercentageProps) {
|
||||
protected static validate(values: PercentageProps) {
|
||||
const schema = z.object({
|
||||
amount: z.number().int().min(Percentage.MIN_VALUE, "La cantidad no puede ser negativa."),
|
||||
scale: z
|
||||
@ -75,7 +66,7 @@ export class Percentage extends ValueObject<IPercentageProps> implements IPercen
|
||||
return this.props.scale;
|
||||
}
|
||||
|
||||
getValue(): IPercentageProps {
|
||||
getValue(): PercentageProps {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
|
||||
@ -1,62 +1,45 @@
|
||||
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";
|
||||
|
||||
// 📌 Validaciones usando `zod`
|
||||
const postalCodeSchema = z
|
||||
.string()
|
||||
.min(4, "Invalid postal code format")
|
||||
.max(10, "Invalid postal code format")
|
||||
.regex(/^\d{4,10}$/, {
|
||||
message: "Invalid postal code format",
|
||||
});
|
||||
|
||||
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 interface PostalAddressProps {
|
||||
street: Maybe<Street>;
|
||||
street2: Maybe<Street>;
|
||||
city: Maybe<City>;
|
||||
postalCode: Maybe<PostalCode>;
|
||||
province: Maybe<Province>;
|
||||
country: Maybe<Country>;
|
||||
}
|
||||
|
||||
export class PostalAddress extends ValueObject<IPostalAddressProps> {
|
||||
protected static validate(values: IPostalAddressProps) {
|
||||
return z
|
||||
.object({
|
||||
street: streetSchema,
|
||||
street2: street2Schema,
|
||||
city: citySchema,
|
||||
postalCode: postalCodeSchema,
|
||||
state: stateSchema,
|
||||
country: countrySchema,
|
||||
})
|
||||
.safeParse(values);
|
||||
export interface PostalAddressSnapshot {
|
||||
street: string | null;
|
||||
street2: string | null;
|
||||
city: string | null;
|
||||
postalCode: string | null;
|
||||
province: string | null;
|
||||
country: string | null;
|
||||
}
|
||||
|
||||
export type PostalAddressPatchProps = Partial<PostalAddressProps>;
|
||||
|
||||
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);
|
||||
|
||||
if (!valueIsValid.success) {
|
||||
return Result.fail(new Error(valueIsValid.error.issues[0].message));
|
||||
if (valueIsValid.isFailure) {
|
||||
return Result.fail(valueIsValid.error);
|
||||
}
|
||||
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(
|
||||
oldAddress: PostalAddress,
|
||||
data: Partial<PostalAddress>
|
||||
@ -66,37 +49,37 @@ export class PostalAddress extends ValueObject<IPostalAddressProps> {
|
||||
street2: data.street2 ?? oldAddress.street2,
|
||||
city: data.city ?? oldAddress.city,
|
||||
postalCode: data.postalCode ?? oldAddress.postalCode,
|
||||
state: data.state ?? oldAddress.state,
|
||||
province: data.province ?? oldAddress.province,
|
||||
country: data.country ?? oldAddress.country,
|
||||
// biome-ignore lint/complexity/noThisInStatic: <explanation>
|
||||
}).getOrElse(this);
|
||||
}
|
||||
|
||||
get street(): string {
|
||||
get street(): Maybe<Street> {
|
||||
return this.props.street;
|
||||
}
|
||||
|
||||
get street2(): string {
|
||||
return this.props.street2 ?? "";
|
||||
get street2(): Maybe<Street> {
|
||||
return this.props.street2;
|
||||
}
|
||||
|
||||
get city(): string {
|
||||
get city(): Maybe<City> {
|
||||
return this.props.city;
|
||||
}
|
||||
|
||||
get postalCode(): string {
|
||||
get postalCode(): Maybe<PostalCode> {
|
||||
return this.props.postalCode;
|
||||
}
|
||||
|
||||
get state(): string {
|
||||
return this.props.state;
|
||||
get province(): Maybe<Province> {
|
||||
return this.props.province;
|
||||
}
|
||||
|
||||
get country(): string {
|
||||
get country(): Maybe<Country> {
|
||||
return this.props.country;
|
||||
}
|
||||
|
||||
getValue(): IPostalAddressProps {
|
||||
getValue(): PostalAddressProps {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
@ -104,7 +87,20 @@ export class PostalAddress extends ValueObject<IPostalAddressProps> {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `${this.props.street}, ${this.props.street2}, ${this.props.city}, ${this.props.postalCode}, ${this.props.state}, ${this.props.country}`;
|
||||
toFormat(): string {
|
||||
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_MAX_SCALE = 2;
|
||||
|
||||
export interface IQuantityProps {
|
||||
export interface QuantityProps {
|
||||
amount: number;
|
||||
scale: number;
|
||||
}
|
||||
|
||||
interface IQuantity {
|
||||
amount: number;
|
||||
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) {
|
||||
export class Quantity extends ValueObject<QuantityProps> {
|
||||
protected static validate(values: QuantityProps) {
|
||||
const schema = z.object({
|
||||
amount: z.number().int(),
|
||||
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 MAX_SCALE = DEFAULT_MAX_SCALE;
|
||||
|
||||
static create({ amount, scale }: IQuantityProps) {
|
||||
static create({ amount, scale }: QuantityProps) {
|
||||
const props = {
|
||||
amount: Number(amount),
|
||||
scale: scale ?? Quantity.DEFAULT_SCALE,
|
||||
@ -51,9 +33,9 @@ export class Quantity extends ValueObject<IQuantityProps> implements IQuantity {
|
||||
const checkProps = Quantity.validate(props);
|
||||
|
||||
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 {
|
||||
@ -64,7 +46,7 @@ export class Quantity extends ValueObject<IQuantityProps> implements IQuantity {
|
||||
return this.props.scale;
|
||||
}
|
||||
|
||||
getValue(): IQuantityProps {
|
||||
getValue(): QuantityProps {
|
||||
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 { ValueObject } from "./value-object";
|
||||
|
||||
@ -32,14 +32,6 @@ export class Slug extends ValueObject<SlugProps> {
|
||||
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 {
|
||||
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 { ValueObject } from "./value-object";
|
||||
|
||||
@ -33,14 +33,6 @@ export class TINNumber extends ValueObject<TINNumberProps> {
|
||||
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 {
|
||||
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 { ValueObject } from "./value-object";
|
||||
|
||||
interface IUtcDateProps {
|
||||
interface UtcDateProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class UtcDate extends ValueObject<IUtcDateProps> {
|
||||
export class UtcDate extends ValueObject<UtcDateProps> {
|
||||
private readonly date!: Date;
|
||||
|
||||
private constructor(props: IUtcDateProps) {
|
||||
private constructor(props: UtcDateProps) {
|
||||
super(props);
|
||||
const { value: dateString } = props;
|
||||
this.date = Object.freeze(new Date(dateString));
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
export * from "./collection";
|
||||
export * from "./id-utils";
|
||||
export * from "./maybe";
|
||||
export * from "./patch-field";
|
||||
export * from "./result";
|
||||
export * from "./result-collection";
|
||||
export * from "./rule-validator";
|
||||
|
||||
@ -43,4 +43,8 @@ export class Maybe<T> {
|
||||
map<U>(fn: (value: T) => U): Maybe<U> {
|
||||
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
|
||||
function ensure<T>(value: T | undefined | null, defaultValue: T): T {
|
||||
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))
|
||||
ts-jest:
|
||||
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:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@ -623,6 +623,9 @@ importers:
|
||||
typescript:
|
||||
specifier: ^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:
|
||||
dependencies:
|
||||
@ -2870,6 +2873,9 @@ packages:
|
||||
'@types/body-parser@1.19.5':
|
||||
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
||||
|
||||
'@types/chai@5.2.2':
|
||||
resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||
|
||||
@ -2906,6 +2912,9 @@ packages:
|
||||
'@types/debug@4.1.12':
|
||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||
|
||||
'@types/deep-eql@4.0.2':
|
||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||
|
||||
'@types/dinero.js@1.9.4':
|
||||
resolution: {integrity: sha512-mtJnan4ajy9MqvoJGVXu0tC9EAAzFjeoKc3d+8AW+H/Od9+8IiC59ymjrZF+JdTToyDvkLReacTsc50Z8eYr6Q==}
|
||||
|
||||
@ -3047,6 +3056,35 @@ packages:
|
||||
peerDependencies:
|
||||
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:
|
||||
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
|
||||
|
||||
@ -3153,6 +3191,10 @@ packages:
|
||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
assertion-error@2.0.1:
|
||||
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
ast-types@0.13.4:
|
||||
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
|
||||
engines: {node: '>=4'}
|
||||
@ -3307,6 +3349,10 @@ packages:
|
||||
caniuse-lite@1.0.30001720:
|
||||
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:
|
||||
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
||||
engines: {node: '>=4'}
|
||||
@ -3329,6 +3375,10 @@ packages:
|
||||
chardet@0.7.0:
|
||||
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
|
||||
|
||||
check-error@2.1.1:
|
||||
resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
@ -3623,6 +3673,10 @@ packages:
|
||||
babel-plugin-macros:
|
||||
optional: true
|
||||
|
||||
deep-eql@5.0.2:
|
||||
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
deep-extend@0.6.0:
|
||||
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
@ -3806,6 +3860,9 @@ packages:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-module-lexer@1.7.0:
|
||||
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -3864,6 +3921,9 @@ packages:
|
||||
estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
|
||||
esutils@2.0.3:
|
||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -3883,6 +3943,10 @@ packages:
|
||||
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
|
||||
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:
|
||||
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@ -4528,6 +4592,9 @@ packages:
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
js-tokens@9.0.1:
|
||||
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||
hasBin: true
|
||||
@ -4732,6 +4799,9 @@ packages:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
loupe@3.2.1:
|
||||
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
|
||||
|
||||
lower-case-first@1.0.2:
|
||||
resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==}
|
||||
|
||||
@ -5161,6 +5231,13 @@ packages:
|
||||
pathe@0.2.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
|
||||
|
||||
@ -5689,6 +5766,9 @@ packages:
|
||||
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
siginfo@2.0.0:
|
||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||
|
||||
signal-exit@3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
|
||||
@ -5770,10 +5850,16 @@ packages:
|
||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
statuses@2.0.1:
|
||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
std-env@3.9.0:
|
||||
resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
|
||||
|
||||
string-hash@1.1.3:
|
||||
resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==}
|
||||
|
||||
@ -5820,6 +5906,9 @@ packages:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strip-literal@3.0.0:
|
||||
resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==}
|
||||
|
||||
styled-components@6.1.19:
|
||||
resolution: {integrity: sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==}
|
||||
engines: {node: '>= 16'}
|
||||
@ -5907,6 +5996,9 @@ packages:
|
||||
tiny-invariant@1.3.3:
|
||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
tinycolor2@1.6.0:
|
||||
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||
|
||||
@ -5920,6 +6012,18 @@ packages:
|
||||
tinygradient@1.1.5:
|
||||
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:
|
||||
resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==}
|
||||
|
||||
@ -6230,6 +6334,11 @@ packages:
|
||||
victory-vendor@36.9.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==}
|
||||
peerDependencies:
|
||||
@ -6283,6 +6392,34 @@ packages:
|
||||
yaml:
|
||||
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:
|
||||
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -6310,6 +6447,11 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
why-is-node-running@2.3.0:
|
||||
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
wide-align@1.1.5:
|
||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||
|
||||
@ -8335,6 +8477,10 @@ snapshots:
|
||||
'@types/connect': 3.4.38
|
||||
'@types/node': 24.0.3
|
||||
|
||||
'@types/chai@5.2.2':
|
||||
dependencies:
|
||||
'@types/deep-eql': 4.0.2
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
dependencies:
|
||||
'@types/node': 24.0.3
|
||||
@ -8371,6 +8517,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/ms': 2.1.0
|
||||
|
||||
'@types/deep-eql@4.0.2': {}
|
||||
|
||||
'@types/dinero.js@1.9.4': {}
|
||||
|
||||
'@types/estree@1.0.7': {}
|
||||
@ -8548,6 +8696,48 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- 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: {}
|
||||
|
||||
accepts@1.3.8:
|
||||
@ -8639,6 +8829,8 @@ snapshots:
|
||||
|
||||
array-union@2.1.0: {}
|
||||
|
||||
assertion-error@2.0.1: {}
|
||||
|
||||
ast-types@0.13.4:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@ -8843,6 +9035,14 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
ansi-styles: 3.2.1
|
||||
@ -8884,6 +9084,8 @@ snapshots:
|
||||
|
||||
chardet@0.7.0: {}
|
||||
|
||||
check-error@2.1.1: {}
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
@ -9140,6 +9342,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
babel-plugin-macros: 3.1.0
|
||||
|
||||
deep-eql@5.0.2: {}
|
||||
|
||||
deep-extend@0.6.0: {}
|
||||
|
||||
deepmerge@4.3.1: {}
|
||||
@ -9297,6 +9501,8 @@ snapshots:
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-module-lexer@1.7.0: {}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@ -9371,6 +9577,10 @@ snapshots:
|
||||
|
||||
estree-walker@2.0.2: {}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.7
|
||||
|
||||
esutils@2.0.3: {}
|
||||
|
||||
etag@1.8.1: {}
|
||||
@ -9391,6 +9601,8 @@ snapshots:
|
||||
|
||||
exit@0.1.2: {}
|
||||
|
||||
expect-type@1.2.2: {}
|
||||
|
||||
expect@29.7.0:
|
||||
dependencies:
|
||||
'@jest/expect-utils': 29.7.0
|
||||
@ -10230,7 +10442,7 @@ snapshots:
|
||||
jest-util@29.7.0:
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 24.0.3
|
||||
'@types/node': 22.15.32
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.9.0
|
||||
graceful-fs: 4.2.11
|
||||
@ -10289,6 +10501,8 @@ snapshots:
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
|
||||
js-yaml@3.14.1:
|
||||
dependencies:
|
||||
argparse: 1.0.10
|
||||
@ -10469,6 +10683,8 @@ snapshots:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
loupe@3.2.1: {}
|
||||
|
||||
lower-case-first@1.0.2:
|
||||
dependencies:
|
||||
lower-case: 1.1.4
|
||||
@ -10883,6 +11099,10 @@ snapshots:
|
||||
|
||||
pathe@0.2.0: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
pathval@2.0.1: {}
|
||||
|
||||
pause@0.0.1: {}
|
||||
|
||||
pg-connection-string@2.9.0: {}
|
||||
@ -11406,6 +11626,8 @@ snapshots:
|
||||
side-channel-map: 1.0.1
|
||||
side-channel-weakmap: 1.0.2
|
||||
|
||||
siginfo@2.0.0: {}
|
||||
|
||||
signal-exit@3.0.7: {}
|
||||
|
||||
signal-exit@4.1.0: {}
|
||||
@ -11476,8 +11698,12 @@ snapshots:
|
||||
dependencies:
|
||||
escape-string-regexp: 2.0.0
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
statuses@2.0.1: {}
|
||||
|
||||
std-env@3.9.0: {}
|
||||
|
||||
string-hash@1.1.3: {}
|
||||
|
||||
string-length@4.0.2:
|
||||
@ -11519,6 +11745,10 @@ snapshots:
|
||||
|
||||
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):
|
||||
dependencies:
|
||||
'@emotion/is-prop-valid': 1.2.2
|
||||
@ -11629,6 +11859,8 @@ snapshots:
|
||||
|
||||
tiny-invariant@1.3.3: {}
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinycolor2@1.6.0: {}
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
@ -11643,6 +11875,12 @@ snapshots:
|
||||
'@types/tinycolor2': 1.4.6
|
||||
tinycolor2: 1.6.0
|
||||
|
||||
tinypool@1.1.1: {}
|
||||
|
||||
tinyrainbow@2.0.0: {}
|
||||
|
||||
tinyspy@4.0.3: {}
|
||||
|
||||
title-case@2.1.1:
|
||||
dependencies:
|
||||
no-case: 2.3.2
|
||||
@ -11674,7 +11912,7 @@ snapshots:
|
||||
|
||||
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:
|
||||
bs-logger: 0.2.6
|
||||
ejs: 3.1.10
|
||||
@ -11692,7 +11930,6 @@ snapshots:
|
||||
'@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
|
||||
|
||||
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-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)):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
@ -12003,6 +12261,67 @@ snapshots:
|
||||
terser: 5.40.0
|
||||
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: {}
|
||||
|
||||
walker@1.0.8:
|
||||
@ -12032,6 +12351,11 @@ snapshots:
|
||||
dependencies:
|
||||
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:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
|
||||
Loading…
Reference in New Issue
Block a user