2026-03-25 09:34:17 +00:00
|
|
|
import type { TaxCatalogProvider } from "@erp/core";
|
|
|
|
|
import { Tax } from "@erp/core/api";
|
2026-03-16 17:45:45 +00:00
|
|
|
import { ValueObject } from "@repo/rdx-ddd";
|
2026-03-25 09:34:17 +00:00
|
|
|
import { Maybe, Result } from "@repo/rdx-utils";
|
2026-03-16 17:45:45 +00:00
|
|
|
|
|
|
|
|
export type CustomerTaxesProps = {
|
|
|
|
|
iva: Maybe<Tax>; // si existe
|
|
|
|
|
rec: Maybe<Tax>; // si existe
|
|
|
|
|
retention: Maybe<Tax>; // si existe
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export interface ICustomerItemTaxes {
|
|
|
|
|
iva: Maybe<Tax>; // si existe
|
|
|
|
|
rec: Maybe<Tax>; // si existe
|
|
|
|
|
retention: Maybe<Tax>; // si existe
|
|
|
|
|
|
|
|
|
|
toKey(): string; // Clave para representar un trío.
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
export class CustomerTaxes extends ValueObject<CustomerTaxesProps> implements ICustomerItemTaxes {
|
2026-03-16 17:45:45 +00:00
|
|
|
static create(props: CustomerTaxesProps) {
|
|
|
|
|
return Result.ok(new CustomerTaxes(props));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
/**
|
|
|
|
|
* Reconstruye una instancia de CustomerTaxes a partir de una clave serializada.
|
|
|
|
|
* Este método es la operación inversa de toKey().
|
|
|
|
|
*
|
|
|
|
|
* @param key Clave en formato "ivaCode;recCode;retentionCode"
|
|
|
|
|
* Donde cada código puede ser "#" si el impuesto no existe
|
|
|
|
|
* Ejemplo: "iva_21;rec_02;#" o "#;#;#"
|
|
|
|
|
* @param provider Proveedor del catálogo de impuestos (ej: SpainTaxCatalogProvider)
|
|
|
|
|
* @returns Result<CustomerTaxes> Éxito con la instancia creada o fallo con mensaje de error
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* const key = "iva_21;rec_02;ret_1500";
|
|
|
|
|
* const result = CustomerTaxes.fromKey(key, SpainTaxCatalogProvider());
|
|
|
|
|
* if (result.isOk()) {
|
|
|
|
|
* const customerTaxes = result.value;
|
|
|
|
|
* }
|
|
|
|
|
*/
|
|
|
|
|
static fromKey(key: string, provider: TaxCatalogProvider): Result<CustomerTaxes, Error> {
|
|
|
|
|
// Validar que la clave no esté vacía
|
|
|
|
|
if (!key || typeof key !== "string") {
|
|
|
|
|
return Result.fail(new Error("La clave debe ser una cadena no vacía."));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Dividir la clave por punto y coma para obtener los tres códigos
|
|
|
|
|
const codes = key.split(";");
|
|
|
|
|
|
|
|
|
|
// Validar que la clave tiene exactamente 3 partes (IVA, REC, Retención)
|
|
|
|
|
if (codes.length !== 3) {
|
|
|
|
|
return Result.fail(
|
|
|
|
|
new Error(
|
|
|
|
|
`Formato de clave inválido. Se esperaban 3 códigos separados por ";", se recibieron ${codes.length}.`
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [ivaCode, recCode, retentionCode] = codes;
|
|
|
|
|
|
|
|
|
|
// Función auxiliar para resolver un código a un impuesto (Maybe<Tax>)
|
|
|
|
|
// Si el código es "#", retorna Maybe.none(), si no, busca en el catálogo
|
|
|
|
|
const resolveTaxFromCode = (code: string): Result<Maybe<Tax>> => {
|
|
|
|
|
const trimmedCode = code.trim();
|
|
|
|
|
|
|
|
|
|
// Si el código es "#", significa que no existe este tipo de impuesto
|
|
|
|
|
if (trimmedCode === "#") {
|
|
|
|
|
return Result.ok(Maybe.none<Tax>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Si el código no es "#", buscamos en el catálogo usando Tax.createFromCode
|
|
|
|
|
const taxResult = Tax.createFromCode(trimmedCode, provider);
|
|
|
|
|
|
|
|
|
|
// Si hay un error creando el impuesto, propagamos el error
|
|
|
|
|
if (taxResult.isFailure) {
|
|
|
|
|
return Result.fail(taxResult.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Si se creó exitosamente, lo envolvemos en Maybe.some()
|
|
|
|
|
return Result.ok(Maybe.some(taxResult.data));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Resolver el IVA desde su código
|
|
|
|
|
const ivaResult = resolveTaxFromCode(ivaCode);
|
|
|
|
|
if (ivaResult.isFailure) {
|
|
|
|
|
return Result.fail(
|
|
|
|
|
new Error(`Error al resolver IVA desde código "${ivaCode}": ${ivaResult.error.message}`)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resolver el REC desde su código
|
|
|
|
|
const recResult = resolveTaxFromCode(recCode);
|
|
|
|
|
if (recResult.isFailure) {
|
|
|
|
|
return Result.fail(
|
|
|
|
|
new Error(`Error al resolver REC desde código "${recCode}": ${recResult.error.message}`)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resolver la Retención desde su código
|
|
|
|
|
const retentionResult = resolveTaxFromCode(retentionCode);
|
|
|
|
|
if (retentionResult.isFailure) {
|
|
|
|
|
return Result.fail(
|
|
|
|
|
new Error(
|
|
|
|
|
`Error al resolver Retención desde código "${retentionCode}": ${retentionResult.error.message}`
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Crear la instancia de CustomerTaxes con los impuestos resueltos
|
|
|
|
|
return Result.ok(
|
|
|
|
|
new CustomerTaxes({
|
|
|
|
|
iva: ivaResult.data,
|
|
|
|
|
rec: recResult.data,
|
|
|
|
|
retention: retentionResult.data,
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Genera una clave única que representa la combinación de impuestos.
|
|
|
|
|
* Extrae el código de cada impuesto o usa "#" si no existe.
|
|
|
|
|
* @returns {string} Clave en formato: "ivaCode;recCode;retentionCode"
|
|
|
|
|
*/
|
2026-03-16 17:45:45 +00:00
|
|
|
toKey(): string {
|
2026-03-25 09:34:17 +00:00
|
|
|
// Extrae el código del IVA, o "#" si no existe
|
2026-03-16 17:45:45 +00:00
|
|
|
const ivaCode = this.props.iva.match(
|
|
|
|
|
(iva) => iva.code,
|
|
|
|
|
() => "#"
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
// Extrae el código de la retención de cliente (REC), o "#" si no existe
|
2026-03-16 17:45:45 +00:00
|
|
|
const recCode = this.props.rec.match(
|
|
|
|
|
(rec) => rec.code,
|
|
|
|
|
() => "#"
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
// Extrae el código de la retención, o "#" si no existe
|
2026-03-16 17:45:45 +00:00
|
|
|
const retentionCode = this.props.retention.match(
|
|
|
|
|
(retention) => retention.code,
|
|
|
|
|
() => "#"
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-25 09:34:17 +00:00
|
|
|
// Retorna la clave combinada separada por punto y coma
|
2026-03-16 17:45:45 +00:00
|
|
|
return `${ivaCode};${recCode};${retentionCode}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get iva(): Maybe<Tax> {
|
|
|
|
|
return this.props.iva;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get rec(): Maybe<Tax> {
|
|
|
|
|
return this.props.rec;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get retention(): Maybe<Tax> {
|
|
|
|
|
return this.props.retention;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getProps() {
|
|
|
|
|
return this.props;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toPrimitive() {
|
|
|
|
|
return this.getProps();
|
|
|
|
|
}
|
|
|
|
|
}
|