Paso de factuges a proforma
This commit is contained in:
parent
4786eb189e
commit
41f30cde9d
@ -3,7 +3,13 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import type { Transaction } from "sequelize";
|
||||
|
||||
import { type IProformaCreatorParams, buildProformaCreator } from "../../../application";
|
||||
import {
|
||||
type IProformaCreatorParams,
|
||||
type IProformaFullSnapshot,
|
||||
buildProformaCreator,
|
||||
buildProformaFinder,
|
||||
buildProformaSnapshotBuilders,
|
||||
} from "../../../application";
|
||||
import type { Proforma } from "../../../domain";
|
||||
|
||||
import { buildProformaNumberGenerator } from "./proforma-number-generator.di";
|
||||
@ -24,7 +30,16 @@ export type ProformaPublicServices = {
|
||||
) => Promise<Result<Proforma, Error>>;
|
||||
|
||||
listProformas: (filters: unknown, context: unknown) => null;
|
||||
getProformaById: (id: unknown, context: unknown) => null;
|
||||
getProformaById: (
|
||||
id: UniqueID,
|
||||
context: ProformaServicesContext
|
||||
) => Promise<Result<Proforma, Error>>;
|
||||
|
||||
getProformaSnapshotById: (
|
||||
id: UniqueID,
|
||||
context: ProformaServicesContext
|
||||
) => Promise<Result<IProformaFullSnapshot, Error>>;
|
||||
|
||||
generateProformaReport: (id: unknown, options: unknown, context: unknown) => null;
|
||||
};
|
||||
|
||||
@ -40,9 +55,11 @@ export function buildProformaServices(
|
||||
|
||||
const repository = buildProformaRepository({ database, mappers: persistenceMappers });
|
||||
const numberService = buildProformaNumberGenerator();
|
||||
const snapshotsBuilder = buildProformaSnapshotBuilders();
|
||||
|
||||
// Application helpers
|
||||
const creator = buildProformaCreator({ numberService, repository });
|
||||
const finder = buildProformaFinder(repository);
|
||||
|
||||
return {
|
||||
createProforma: async (
|
||||
@ -64,8 +81,29 @@ export function buildProformaServices(
|
||||
listProformas: (filters, context) => null,
|
||||
//internal.useCases.listProformas().execute(filters, context),
|
||||
|
||||
getProformaById: (id, context) => null,
|
||||
//internal.useCases.getProformaById().execute(id, context),
|
||||
getProformaById: async (id: UniqueID, context: ProformaServicesContext) => {
|
||||
const { transaction, companyId } = context;
|
||||
const proformaResult = await finder.findProformaById(companyId, id, transaction);
|
||||
|
||||
if (proformaResult.isFailure) {
|
||||
return Result.fail(proformaResult.error);
|
||||
}
|
||||
|
||||
return Result.ok(proformaResult.data);
|
||||
},
|
||||
|
||||
getProformaSnapshotById: async (id: UniqueID, context: ProformaServicesContext) => {
|
||||
const { transaction, companyId } = context;
|
||||
const proformaResult = await finder.findProformaById(companyId, id, transaction);
|
||||
|
||||
if (proformaResult.isFailure) {
|
||||
return Result.fail(proformaResult.error);
|
||||
}
|
||||
|
||||
const fullSnapshot = snapshotsBuilder.full.toOutput(proformaResult.data);
|
||||
|
||||
return Result.ok(fullSnapshot);
|
||||
},
|
||||
|
||||
generateProformaReport: (id, options, context) => null,
|
||||
//internal.useCases.reportProforma().execute(id, options, context),
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
} from "@repo/rdx-ddd";
|
||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { CustomerStatus, CustomerTaxesProps } from "../value-objects";
|
||||
import { type CustomerStatus, CustomerTaxes } from "../value-objects";
|
||||
|
||||
export interface ICustomerCreateProps {
|
||||
companyId: UniqueID;
|
||||
@ -43,7 +43,7 @@ export interface ICustomerCreateProps {
|
||||
|
||||
legalRecord: Maybe<TextValue>;
|
||||
|
||||
defaultTaxes: CustomerTaxesProps;
|
||||
defaultTaxes: CustomerTaxes;
|
||||
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
@ -86,19 +86,20 @@ export interface ICustomer {
|
||||
readonly website: Maybe<URLAddress>;
|
||||
readonly legalRecord: Maybe<TextValue>;
|
||||
|
||||
readonly defaultTaxes: CustomerTaxesProps;
|
||||
readonly defaultTaxes: CustomerTaxes;
|
||||
|
||||
readonly languageCode: LanguageCode;
|
||||
readonly currencyCode: CurrencyCode;
|
||||
}
|
||||
|
||||
type CustomerInternalProps = Omit<ICustomerCreateProps, "address"> & {
|
||||
type CustomerInternalProps = Omit<ICustomerCreateProps, "address" | "defaultTaxes"> & {
|
||||
readonly address: PostalAddress;
|
||||
readonly defaultTaxes: CustomerTaxes;
|
||||
};
|
||||
|
||||
export class Customer extends AggregateRoot<CustomerInternalProps> implements ICustomer {
|
||||
static create(props: ICustomerCreateProps, id?: UniqueID): Result<Customer, Error> {
|
||||
const { address, ...internalProps } = props;
|
||||
const { address, defaultTaxes, ...internalProps } = props;
|
||||
|
||||
const postalAddressResult = PostalAddress.create(address);
|
||||
|
||||
@ -106,9 +107,15 @@ export class Customer extends AggregateRoot<CustomerInternalProps> implements IC
|
||||
return Result.fail(postalAddressResult.error);
|
||||
}
|
||||
|
||||
const taxes = CustomerTaxes.create(defaultTaxes);
|
||||
if (taxes.isFailure) {
|
||||
return Result.fail(taxes.error);
|
||||
}
|
||||
|
||||
const contact = new Customer(
|
||||
{
|
||||
...internalProps,
|
||||
defaultTaxes: taxes.data,
|
||||
address: postalAddressResult.data,
|
||||
},
|
||||
id
|
||||
@ -220,7 +227,7 @@ export class Customer extends AggregateRoot<CustomerInternalProps> implements IC
|
||||
return this.props.legalRecord;
|
||||
}
|
||||
|
||||
public get defaultTaxes(): CustomerTaxesProps {
|
||||
public get defaultTaxes(): CustomerTaxes {
|
||||
return this.props.defaultTaxes;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { Tax } from "@erp/core/api";
|
||||
import type { TaxCatalogProvider } from "@erp/core";
|
||||
import { Tax } from "@erp/core/api";
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
export type CustomerTaxesProps = {
|
||||
iva: Maybe<Tax>; // si existe
|
||||
@ -16,30 +17,131 @@ export interface ICustomerItemTaxes {
|
||||
toKey(): string; // Clave para representar un trío.
|
||||
}
|
||||
|
||||
export class CustomerTaxes
|
||||
extends ValueObject<CustomerTaxesProps>
|
||||
implements ICustomerItemTaxes
|
||||
{
|
||||
export class CustomerTaxes extends ValueObject<CustomerTaxesProps> implements ICustomerItemTaxes {
|
||||
static create(props: CustomerTaxesProps) {
|
||||
return Result.ok(new CustomerTaxes(props));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"
|
||||
*/
|
||||
toKey(): string {
|
||||
// Extrae el código del IVA, o "#" si no existe
|
||||
const ivaCode = this.props.iva.match(
|
||||
(iva) => iva.code,
|
||||
() => "#"
|
||||
);
|
||||
|
||||
// Extrae el código de la retención de cliente (REC), o "#" si no existe
|
||||
const recCode = this.props.rec.match(
|
||||
(rec) => rec.code,
|
||||
() => "#"
|
||||
);
|
||||
|
||||
// Extrae el código de la retención, o "#" si no existe
|
||||
const retentionCode = this.props.retention.match(
|
||||
(retention) => retention.code,
|
||||
() => "#"
|
||||
);
|
||||
|
||||
// Retorna la clave combinada separada por punto y coma
|
||||
return `${ivaCode};${recCode};${retentionCode}`;
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { ICatalogs } from "@erp/core/api";
|
||||
|
||||
import { SequelizeCustomerDomainMapper, SequelizeCustomerSummaryMapper } from "../mappers";
|
||||
|
||||
export interface ICustomerPersistenceMappers {
|
||||
@ -7,9 +9,13 @@ export interface ICustomerPersistenceMappers {
|
||||
//createMapper: CreateCustomerInputMapper;
|
||||
}
|
||||
|
||||
export const buildCustomerPersistenceMappers = (): ICustomerPersistenceMappers => {
|
||||
export const buildCustomerPersistenceMappers = (
|
||||
catalogs: ICatalogs
|
||||
): ICustomerPersistenceMappers => {
|
||||
const { taxCatalog } = catalogs;
|
||||
|
||||
// Mappers para el repositorio
|
||||
const domainMapper = new SequelizeCustomerDomainMapper();
|
||||
const domainMapper = new SequelizeCustomerDomainMapper({ taxCatalog });
|
||||
const summaryMapper = new SequelizeCustomerSummaryMapper();
|
||||
|
||||
// Mappers el DTO a las props validadas (CustomerProps) y luego construir agregado
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { SetupParams } from "@erp/core/api";
|
||||
import { type SetupParams, buildCatalogs } from "@erp/core/api";
|
||||
import type { TINNumber, UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import type { Transaction } from "sequelize";
|
||||
@ -34,9 +34,10 @@ export function buildCustomerServices(
|
||||
deps: CustomersInternalDeps
|
||||
): CustomerPublicServices {
|
||||
const { database } = params;
|
||||
const catalogs = buildCatalogs();
|
||||
|
||||
// Infrastructure
|
||||
const persistenceMappers = buildCustomerPersistenceMappers();
|
||||
const persistenceMappers = buildCustomerPersistenceMappers(catalogs);
|
||||
const repository = buildCustomerRepository({ database, mappers: persistenceMappers });
|
||||
|
||||
const finder = buildCustomerFinder({ repository });
|
||||
|
||||
@ -42,7 +42,7 @@ export function buildCustomersDependencies(params: ModuleParams): CustomersInter
|
||||
// Infrastructure
|
||||
const transactionManager = buildTransactionManager(database);
|
||||
const catalogs = buildCatalogs();
|
||||
const persistenceMappers = buildCustomerPersistenceMappers();
|
||||
const persistenceMappers = buildCustomerPersistenceMappers(catalogs);
|
||||
|
||||
const repository = buildCustomerRepository({ database, mappers: persistenceMappers });
|
||||
//const numberService = buildCustomerNumberGenerator();
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { TaxCatalogProvider } from "@erp/core";
|
||||
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
||||
import {
|
||||
City,
|
||||
@ -12,7 +13,6 @@ import {
|
||||
Province,
|
||||
Street,
|
||||
TINNumber,
|
||||
type TaxCode,
|
||||
TextValue,
|
||||
URLAddress,
|
||||
UniqueID,
|
||||
@ -22,9 +22,14 @@ import {
|
||||
maybeFromNullableResult,
|
||||
maybeToNullable,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
import { Customer, CustomerStatus, type ICustomerCreateProps } from "../../../domain";
|
||||
import {
|
||||
Customer,
|
||||
CustomerStatus,
|
||||
CustomerTaxes,
|
||||
type ICustomerCreateProps,
|
||||
} from "../../../domain";
|
||||
import type { CustomerCreationAttributes, CustomerModel } from "../../sequelize";
|
||||
|
||||
export class SequelizeCustomerDomainMapper extends SequelizeDomainMapper<
|
||||
@ -32,6 +37,13 @@ export class SequelizeCustomerDomainMapper extends SequelizeDomainMapper<
|
||||
CustomerCreationAttributes,
|
||||
Customer
|
||||
> {
|
||||
private readonly taxCatalog: TaxCatalogProvider;
|
||||
|
||||
constructor(params: { taxCatalog: TaxCatalogProvider }) {
|
||||
super();
|
||||
this.taxCatalog = params.taxCatalog;
|
||||
}
|
||||
|
||||
public mapToDomain(source: CustomerModel, params?: MapperParamsType): Result<Customer, Error> {
|
||||
try {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
@ -167,16 +179,11 @@ export class SequelizeCustomerDomainMapper extends SequelizeDomainMapper<
|
||||
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!);
|
||||
}
|
||||
});
|
||||
}*/
|
||||
const defaultTaxes = extractOrPushError(
|
||||
CustomerTaxes.fromKey(source.default_taxes, this.taxCatalog),
|
||||
"default_taxes",
|
||||
errors
|
||||
);
|
||||
|
||||
// Now, create the PostalAddress VO
|
||||
const postalAddressProps = {
|
||||
@ -256,7 +263,7 @@ export class SequelizeCustomerDomainMapper extends SequelizeDomainMapper<
|
||||
website: maybeToNullable(source.website, (website) => website.toPrimitive()),
|
||||
|
||||
legal_record: maybeToNullable(source.legalRecord, (legalRecord) => legalRecord.toPrimitive()),
|
||||
default_taxes: source.defaultTaxes.map((taxItem) => taxItem.toPrimitive()).join(", "),
|
||||
default_taxes: source.defaultTaxes.toKey(),
|
||||
|
||||
status: source.isActive ? "active" : "inactive",
|
||||
language_code: source.languageCode.toPrimitive(),
|
||||
|
||||
@ -51,7 +51,7 @@ export class CustomerModel extends Model<
|
||||
|
||||
declare legal_record: CreationOptional<string | null>;
|
||||
|
||||
declare default_taxes: CreationOptional<string | null>;
|
||||
declare default_taxes: string;
|
||||
declare status: string;
|
||||
declare language_code: CreationOptional<string>;
|
||||
declare currency_code: CreationOptional<string>;
|
||||
|
||||
@ -2,7 +2,6 @@ import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||
import { DiscountPercentage, Tax } from "@erp/core/api";
|
||||
import {
|
||||
type IProformaItemCreateProps,
|
||||
InvoicePaymentMethod,
|
||||
InvoiceSerie,
|
||||
ItemAmount,
|
||||
ItemDescription,
|
||||
@ -45,6 +44,10 @@ export interface IProformaFromFactuGESProps {
|
||||
tin: TINNumber;
|
||||
};
|
||||
|
||||
paymentLookup: {
|
||||
factuges_id: string;
|
||||
};
|
||||
|
||||
customerDraft: {
|
||||
//reference: Maybe<Name>;
|
||||
|
||||
@ -88,11 +91,14 @@ export interface IProformaFromFactuGESProps {
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
paymentMethod: Maybe<InvoicePaymentMethod>;
|
||||
|
||||
items: IProformaItemCreateProps[];
|
||||
globalDiscountPercentage: DiscountPercentage;
|
||||
};
|
||||
|
||||
paymentDraft: {
|
||||
factuges_id: string;
|
||||
description: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICreateProformaFromFactugesInputMapper {
|
||||
@ -132,14 +138,24 @@ export class CreateProformaFromFactugesInputMapper
|
||||
errors,
|
||||
});
|
||||
|
||||
const paymentProps = this.mapPaymentProps(dto, {
|
||||
companyId,
|
||||
currencyCode,
|
||||
errors,
|
||||
});
|
||||
|
||||
this.throwIfValidationErrors(errors);
|
||||
|
||||
return Result.ok({
|
||||
customerLookup: {
|
||||
tin: customerProps.tin,
|
||||
},
|
||||
paymentLookup: {
|
||||
factuges_id: paymentProps.factuges_id,
|
||||
},
|
||||
customerDraft: customerProps,
|
||||
proformaDraft: proformaProps,
|
||||
paymentDraft: paymentProps,
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
const error = isValidationErrorCollection(err)
|
||||
@ -149,7 +165,24 @@ export class CreateProformaFromFactugesInputMapper
|
||||
}
|
||||
}
|
||||
|
||||
public mapProformaProps(
|
||||
private mapPaymentProps(
|
||||
dto: CreateProformaFromFactugesRequestDTO,
|
||||
params: {
|
||||
companyId: UniqueID;
|
||||
currencyCode: CurrencyCode;
|
||||
errors: ValidationErrorDetail[];
|
||||
}
|
||||
): IProformaFromFactuGESProps["paymentDraft"] {
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
const { companyId } = params;
|
||||
|
||||
return {
|
||||
factuges_id: String(dto.payment_method_id),
|
||||
description: String(dto.payment_method_description),
|
||||
};
|
||||
}
|
||||
|
||||
private mapProformaProps(
|
||||
dto: CreateProformaFromFactugesRequestDTO,
|
||||
params: {
|
||||
companyId: UniqueID;
|
||||
@ -208,14 +241,6 @@ export class CreateProformaFromFactugesInputMapper
|
||||
errors
|
||||
);
|
||||
|
||||
const paymentMethod = extractOrPushError(
|
||||
maybeFromNullableResult(dto.payment_method, (value) =>
|
||||
InvoicePaymentMethod.create({ paymentDescription: value })
|
||||
),
|
||||
"payment_method",
|
||||
errors
|
||||
);
|
||||
|
||||
const globalDiscountPercentage = extractOrPushError(
|
||||
DiscountPercentage.create({ value: Number(dto.global_discount_percentage_value) }),
|
||||
"global_discount_percentage_value",
|
||||
@ -257,7 +282,6 @@ export class CreateProformaFromFactugesInputMapper
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
|
||||
paymentMethod: paymentMethod!,
|
||||
globalDiscountPercentage: globalDiscountPercentage!,
|
||||
|
||||
items: itemsProps, // ← IProformaItemProps[]
|
||||
@ -443,10 +467,10 @@ export class CreateProformaFromFactugesInputMapper
|
||||
);
|
||||
|
||||
const discountPercentage = extractOrPushError(
|
||||
maybeFromNullableResult(item.discount_percentage_value, (value) =>
|
||||
maybeFromNullableResult(item.item_discount_percentage_value, (value) =>
|
||||
DiscountPercentage.create({ value: Number(value) })
|
||||
),
|
||||
`items[${index}].discount_percentage_value`,
|
||||
`items[${index}].item_discount_percentage_value`,
|
||||
params.errors
|
||||
);
|
||||
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||
import { type ITransactionManager, Tax, isEntityNotFoundError } from "@erp/core/api";
|
||||
import type { ProformaPublicServices } from "@erp/customer-invoices/api";
|
||||
import { type InvoiceRecipient, InvoiceStatus } from "@erp/customer-invoices/api/domain";
|
||||
import {
|
||||
InvoicePaymentMethod,
|
||||
type InvoiceRecipient,
|
||||
InvoiceStatus,
|
||||
} from "@erp/customer-invoices/api/domain";
|
||||
import type { CustomerPublicServices } from "@erp/customers/api";
|
||||
import {
|
||||
type Customer,
|
||||
@ -11,7 +15,6 @@ import {
|
||||
} from "@erp/customers/api/domain";
|
||||
import { type Name, type PhoneNumber, type TextValue, UniqueID } from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import type { IProformaCreatorParams } from "node_modules/@erp/customer-invoices/src/api/application";
|
||||
import type { Transaction } from "sequelize";
|
||||
|
||||
import type { CreateProformaFromFactugesRequestDTO } from "../../../common";
|
||||
@ -20,6 +23,14 @@ import type {
|
||||
IProformaFromFactuGESProps,
|
||||
} from "../mappers";
|
||||
|
||||
import paymentsCatalog from "./payments.json";
|
||||
|
||||
type FakePaymentMethod = {
|
||||
id: UniqueID;
|
||||
description: string;
|
||||
factuges_id: string;
|
||||
};
|
||||
|
||||
type CreateProformaFromFactugesUseCaseInput = {
|
||||
companyId: UniqueID;
|
||||
dto: CreateProformaFromFactugesRequestDTO;
|
||||
@ -33,6 +44,8 @@ type CreateProformaFromFactugesUseCaseDeps = {
|
||||
transactionManager: ITransactionManager;
|
||||
};
|
||||
|
||||
type CreateProformaProps = Parameters<ProformaPublicServices["createProforma"]>["1"];
|
||||
|
||||
export class CreateProformaFromFactugesUseCase {
|
||||
private readonly dtoMapper: ICreateProformaFromFactugesInputMapper;
|
||||
private readonly customerServices: CustomerPublicServices;
|
||||
@ -57,7 +70,8 @@ export class CreateProformaFromFactugesUseCase {
|
||||
return Result.fail(mappedPropsResult.error);
|
||||
}
|
||||
|
||||
const { customerLookup, customerDraft, proformaDraft } = mappedPropsResult.data;
|
||||
const { customerLookup, paymentLookup, customerDraft, proformaDraft, paymentDraft } =
|
||||
mappedPropsResult.data;
|
||||
|
||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
||||
try {
|
||||
@ -71,17 +85,35 @@ export class CreateProformaFromFactugesUseCase {
|
||||
|
||||
const customer = customerResult.data;
|
||||
|
||||
// Crear la proforma para ese cliente
|
||||
const createPropsResult = this.buildProformaCreateProps(proformaDraft, customer.id, {
|
||||
const paymentResult = await this.resolvePayment(paymentLookup, paymentDraft, {
|
||||
companyId,
|
||||
transaction,
|
||||
});
|
||||
if (customerResult.isFailure) {
|
||||
return Result.fail(customerResult.error);
|
||||
}
|
||||
|
||||
const payment = paymentResult.data;
|
||||
|
||||
// Crear la proforma para ese cliente
|
||||
const createPropsResult = this.buildProformaCreateProps({
|
||||
proformaDraft,
|
||||
payment,
|
||||
customerId: customer.id,
|
||||
context: {
|
||||
companyId,
|
||||
transaction,
|
||||
},
|
||||
});
|
||||
|
||||
if (createPropsResult.isFailure) {
|
||||
return Result.fail(createPropsResult.error);
|
||||
}
|
||||
|
||||
const newId = UniqueID.generateNewID();
|
||||
|
||||
const createResult = await this.proformaServices.createProforma(
|
||||
UniqueID.generateNewID(),
|
||||
newId,
|
||||
createPropsResult.data,
|
||||
{
|
||||
companyId,
|
||||
@ -93,38 +125,56 @@ export class CreateProformaFromFactugesUseCase {
|
||||
return Result.fail(createResult.error);
|
||||
}
|
||||
|
||||
const proforma = createResult.data;
|
||||
const readResult = await this.proformaServices.getProformaSnapshotById(
|
||||
createResult.data.id,
|
||||
{
|
||||
companyId,
|
||||
transaction,
|
||||
}
|
||||
);
|
||||
|
||||
const snapshot = {
|
||||
if (readResult.isFailure) {
|
||||
return Result.fail(readResult.error);
|
||||
}
|
||||
|
||||
const snapshot = readResult.data;
|
||||
|
||||
const result = {
|
||||
customer_id: customer.id.toString(),
|
||||
proforma_id: proforma.id.toString(),
|
||||
proforma_id: snapshot.id.toString(),
|
||||
};
|
||||
|
||||
return Result.ok(snapshot);
|
||||
return Result.ok(result);
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private buildProformaCreateProps(
|
||||
proformaDraft: IProformaFromFactuGESProps["proformaDraft"],
|
||||
customerId: UniqueID,
|
||||
private buildProformaCreateProps(deps: {
|
||||
proformaDraft: IProformaFromFactuGESProps["proformaDraft"];
|
||||
customerId: UniqueID;
|
||||
payment: FakePaymentMethod;
|
||||
context: {
|
||||
companyId: UniqueID;
|
||||
transaction: Transaction;
|
||||
}
|
||||
): Result<IProformaCreatorParams["props"], Error> {
|
||||
};
|
||||
}): Result<CreateProformaProps, Error> {
|
||||
const { proformaDraft, payment, customerId, context } = deps;
|
||||
const { companyId } = context;
|
||||
|
||||
const defaultStatus = InvoiceStatus.fromApproved();
|
||||
const recipient = Maybe.none<InvoiceRecipient>();
|
||||
const paymentMethod = Maybe.some(
|
||||
InvoicePaymentMethod.create({ paymentDescription: payment.description }, payment.id).data
|
||||
);
|
||||
|
||||
return Result.ok({
|
||||
...proformaDraft,
|
||||
companyId,
|
||||
customerId,
|
||||
status: defaultStatus,
|
||||
paymentMethod,
|
||||
recipient,
|
||||
});
|
||||
}
|
||||
@ -176,6 +226,33 @@ export class CreateProformaFromFactugesUseCase {
|
||||
});
|
||||
}
|
||||
|
||||
private async resolvePayment(
|
||||
paymentLookup: IProformaFromFactuGESProps["paymentLookup"],
|
||||
paymentDraft: IProformaFromFactuGESProps["paymentDraft"],
|
||||
context: {
|
||||
companyId: UniqueID;
|
||||
transaction: Transaction;
|
||||
}
|
||||
): Promise<Result<FakePaymentMethod, Error>> {
|
||||
const { companyId, transaction } = context;
|
||||
|
||||
const existingPaymentResult = paymentsCatalog.find(
|
||||
(payment) =>
|
||||
payment.factuges_id === paymentLookup.factuges_id &&
|
||||
payment.company_id === companyId.toString()
|
||||
);
|
||||
|
||||
if (existingPaymentResult) {
|
||||
return Result.ok({
|
||||
id: UniqueID.create(existingPaymentResult.id).data,
|
||||
description: existingPaymentResult.description,
|
||||
factuges_id: existingPaymentResult.factuges_id,
|
||||
});
|
||||
}
|
||||
|
||||
return Result.fail(new Error("Forma de pago no existe!!!"));
|
||||
}
|
||||
|
||||
private buildCustomerCreateProps(
|
||||
customerDraft: IProformaFromFactuGESProps["customerDraft"],
|
||||
context: {
|
||||
|
||||
20
modules/factuges/src/api/application/use-cases/payments.json
Normal file
20
modules/factuges/src/api/application/use-cases/payments.json
Normal file
@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"id": "019c2834-a766-7787-a626-fa89cac3a8a1",
|
||||
"company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f",
|
||||
"factuges_id": "6",
|
||||
"description": "TRANSFERENCIA"
|
||||
},
|
||||
{
|
||||
"id": "57ed228f-88bd-431d-b5e6-0ed9cff01684",
|
||||
"company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f",
|
||||
"factuges_id": "14",
|
||||
"description": "DOMICILIACION BANCARIA"
|
||||
},
|
||||
{
|
||||
"id": "336e477f-9260-4cb7-b6fd-76f3b088a395",
|
||||
"company_id": "5e4dc5b3-96b9-4968-9490-14bd032fec5f",
|
||||
"factuges_id": "15",
|
||||
"description": "TRANSFERENCIA BANCARIA"
|
||||
}
|
||||
]
|
||||
@ -43,7 +43,6 @@ export const factugesRouter = (
|
||||
router.post(
|
||||
"/",
|
||||
//checkTabContext,
|
||||
|
||||
validateRequest(CreateProformaFromFactugesRequestSchema, "body"),
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const useCase = deps.useCases.createProforma(publicServices);
|
||||
|
||||
@ -7,16 +7,31 @@ export const CreateProformaItemFromFactugesRequestSchema = z.object({
|
||||
quantity_value: NumericStringSchema.default(""), // Ya viene escalado
|
||||
unit_value: NumericStringSchema.default(""),
|
||||
|
||||
discount_percentage_value: NumericStringSchema.default(""),
|
||||
subtotal_amuount_value: NumericStringSchema.default(""),
|
||||
item_discount_percentage_value: NumericStringSchema.default(""),
|
||||
item_discount_amount_value: NumericStringSchema.default(""),
|
||||
|
||||
global_discount_percentage_value: NumericStringSchema.default(""),
|
||||
global_discount_amount_value: NumericStringSchema.default(""),
|
||||
|
||||
total_discount_amount_value: NumericStringSchema.default(""),
|
||||
taxable_amount_value: NumericStringSchema.default(""),
|
||||
|
||||
total_value: NumericStringSchema.default(""),
|
||||
|
||||
iva_code: z.string().default(""),
|
||||
iva_percentage_value: NumericStringSchema.default(""),
|
||||
iva_amount_value: NumericStringSchema.default(""),
|
||||
|
||||
rec_code: z.string().default(""),
|
||||
rec_percentage_value: NumericStringSchema.default(""),
|
||||
rec_amount_value: NumericStringSchema.default(""),
|
||||
|
||||
retention_code: z.string().default(""),
|
||||
retention_percentage_value: NumericStringSchema.default(""),
|
||||
retention_amount_value: NumericStringSchema.default(""),
|
||||
|
||||
taxes_amount_value: NumericStringSchema.default(""),
|
||||
});
|
||||
|
||||
export type CreateProformaItemFromFactugesRequestDTO = z.infer<
|
||||
@ -24,23 +39,23 @@ export type CreateProformaItemFromFactugesRequestDTO = z.infer<
|
||||
>;
|
||||
|
||||
export const CreateProformaFromFactugesRequestSchema = z.object({
|
||||
//factuges_id: z.string(),
|
||||
|
||||
//id: z.uuid(),
|
||||
factuges_id: z.string(),
|
||||
//company_id: z.uuid(),
|
||||
//is_proforma: z.string().default("1"),
|
||||
|
||||
//status: z.string(),
|
||||
series: z.string(),
|
||||
//invoice_number: z.string(),
|
||||
|
||||
reference: z.string().default(""),
|
||||
description: z.string().default(""),
|
||||
|
||||
invoice_date: z.string(),
|
||||
operation_date: z.string().default(""),
|
||||
|
||||
description: z.string().default(""),
|
||||
notes: z.string().default(""),
|
||||
language_code: z.string().default("es"),
|
||||
|
||||
customer: z.object({
|
||||
//factuges_id: z.string(),
|
||||
is_company: z.string(),
|
||||
name: z.string(),
|
||||
tin: z.string(),
|
||||
@ -51,7 +66,7 @@ export const CreateProformaFromFactugesRequestSchema = z.object({
|
||||
postal_code: z.string(),
|
||||
country: z.string(),
|
||||
|
||||
language_code: z.string(),
|
||||
language_code: z.string().default("es"),
|
||||
|
||||
phone_primary: z.string(),
|
||||
phone_secondary: z.string(),
|
||||
@ -63,9 +78,16 @@ export const CreateProformaFromFactugesRequestSchema = z.object({
|
||||
website: z.string(),
|
||||
}),
|
||||
|
||||
subtotal_amount_value: NumericStringSchema,
|
||||
global_discount_percentage_value: NumericStringSchema,
|
||||
|
||||
payment_method: z.string().default(""),
|
||||
discount_amount_value: NumericStringSchema,
|
||||
taxable_amount_value: NumericStringSchema,
|
||||
taxes_amount_value: NumericStringSchema,
|
||||
total_amount_value: NumericStringSchema,
|
||||
|
||||
payment_method_id: z.string(),
|
||||
payment_method_description: z.string(),
|
||||
|
||||
items: z.array(CreateProformaItemFromFactugesRequestSchema),
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user