.
This commit is contained in:
parent
3a61d726f8
commit
5d59598106
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -5,6 +5,7 @@
|
|||||||
"cweijan.vscode-mysql-client2",
|
"cweijan.vscode-mysql-client2",
|
||||||
"ms-vscode.vscode-json",
|
"ms-vscode.vscode-json",
|
||||||
"formulahendry.auto-rename-tag",
|
"formulahendry.auto-rename-tag",
|
||||||
"cweijan.dbclient-jdbc"
|
"cweijan.dbclient-jdbc",
|
||||||
|
"pkief.material-icon-theme"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
33
.vscode/settings.json
vendored
33
.vscode/settings.json
vendored
@ -1,20 +1,19 @@
|
|||||||
{
|
{
|
||||||
"files.associations": {
|
// Font Family
|
||||||
"tsconfig.json": "jsonc",
|
"editor.fontFamily": "'Fira Code', 'Cascadia Code', 'Consolas'",
|
||||||
"typescript-config/*.json": "jsonc",
|
|
||||||
"*.css": "tailwindcss"
|
// Enable Font Ligatures
|
||||||
},
|
"editor.fontLigatures": true,
|
||||||
|
|
||||||
// Javascript and TypeScript settings
|
// Javascript and TypeScript settings
|
||||||
"javascript.suggest.enabled": true,
|
"js/ts.suggest.enabled": true,
|
||||||
"javascript.suggest.autoImports": true,
|
"js/ts.suggest.autoImports": true,
|
||||||
"javascript.preferences.importModuleSpecifier": "shortest",
|
"js/ts.preferences.importModuleSpecifier": "shortest",
|
||||||
|
|
||||||
"typescript.suggest.enabled": true,
|
"js/ts.suggest.completeFunctionCalls": true,
|
||||||
"typescript.suggest.completeFunctionCalls": true,
|
"js/ts.suggest.includeAutomaticOptionalChainCompletions": true,
|
||||||
"typescript.suggest.includeAutomaticOptionalChainCompletions": true,
|
"js/ts.suggestionActions.enabled": true,
|
||||||
"typescript.suggestionActions.enabled": true,
|
"js/ts.autoClosingTags.enabled": true,
|
||||||
"typescript.autoClosingTags": true,
|
|
||||||
|
|
||||||
"editor.quickSuggestions": {
|
"editor.quickSuggestions": {
|
||||||
"strings": "on"
|
"strings": "on"
|
||||||
@ -54,11 +53,5 @@
|
|||||||
// other vscode settings
|
// other vscode settings
|
||||||
"[sql]": {
|
"[sql]": {
|
||||||
"editor.defaultFormatter": "cweijan.vscode-mysql-client2"
|
"editor.defaultFormatter": "cweijan.vscode-mysql-client2"
|
||||||
}, // <- your root font size here
|
} // <- your root font size here
|
||||||
|
|
||||||
"invisibleAiChartDetector.watermark.includeSpaceFamily": true,
|
|
||||||
"invisibleAiChartDetector.watermark.includeUnicodeCf": true,
|
|
||||||
"invisibleAiChartDetector.doubleBlankThreshold": 2,
|
|
||||||
"invisibleAiChartDetector.replace.format": "unicode",
|
|
||||||
"invisibleAiChartDetector.clean.replaceSpaceLikesToAscii": true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -154,7 +154,12 @@ async function setupModule(name: string, params: ModuleParams, stack: string[])
|
|||||||
function makeGetService(moduleName: string, pkg: IModuleServer) {
|
function makeGetService(moduleName: string, pkg: IModuleServer) {
|
||||||
return <T>(serviceName: string): T => {
|
return <T>(serviceName: string): T => {
|
||||||
const [serviceModule] = serviceName.split(":");
|
const [serviceModule] = serviceName.split(":");
|
||||||
|
|
||||||
|
// No registrar dependencias para modulos
|
||||||
|
// que usan sus propios servicios.
|
||||||
|
if (serviceModule !== "self") {
|
||||||
trackDependencyUse(moduleName, serviceModule);
|
trackDependencyUse(moduleName, serviceModule);
|
||||||
|
}
|
||||||
|
|
||||||
// IMPORTANTE: devolver el valor
|
// IMPORTANTE: devolver el valor
|
||||||
return getServiceScoped<T>(moduleName, pkg.dependencies ?? [], serviceName);
|
return getServiceScoped<T>(moduleName, pkg.dependencies ?? [], serviceName);
|
||||||
@ -213,6 +218,8 @@ function validateModuleDependencies() {
|
|||||||
const declared = new Set(pkg.dependencies ?? []);
|
const declared = new Set(pkg.dependencies ?? []);
|
||||||
const used = usedDependenciesByModule.get(moduleName) ?? new Set<string>();
|
const used = usedDependenciesByModule.get(moduleName) ?? new Set<string>();
|
||||||
|
|
||||||
|
console.log(declared, used);
|
||||||
|
|
||||||
// ❌ usadas pero no declaradas
|
// ❌ usadas pero no declaradas
|
||||||
const undeclaredUsed = [...used].filter((d) => !declared.has(d));
|
const undeclaredUsed = [...used].filter((d) => !declared.has(d));
|
||||||
|
|
||||||
|
|||||||
@ -12,15 +12,25 @@ export function registerService(name: string, api: any) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Recupera un servicio registrado bajo un "scope".
|
* Recupera un servicio registrado bajo un "scope".
|
||||||
|
*
|
||||||
* getService("customers:repository")
|
* getService("customers:repository")
|
||||||
* Debe declarar: dependencies: ["customers"]
|
* Debe declarar: dependencies: ["customers"]
|
||||||
|
*
|
||||||
|
* El "scope" puede ser "self" para recuperar
|
||||||
|
* los servicios propios registrados.
|
||||||
|
*
|
||||||
|
* getService("self:repository")
|
||||||
*/
|
*/
|
||||||
export function getServiceScoped<T = any>(
|
export function getServiceScoped<T = any>(
|
||||||
requesterModule: string,
|
requesterModule: string,
|
||||||
allowedDeps: readonly string[],
|
allowedDeps: readonly string[],
|
||||||
name: string
|
name: string
|
||||||
): T {
|
): T {
|
||||||
const [serviceModule] = name.split(":");
|
const [serviceModule, ...key] = name.split(":");
|
||||||
|
|
||||||
|
if (serviceModule === "self") {
|
||||||
|
return getService<T>(`${requesterModule}:${key.join(":")}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!allowedDeps.includes(serviceModule)) {
|
if (!allowedDeps.includes(serviceModule)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@ -1,50 +0,0 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* @param obj - El objeto a evaluar.
|
|
||||||
* @template T - El tipo del objeto.
|
|
||||||
* @description Verifica si un objeto no tiene campos con valor undefined.
|
|
||||||
*
|
|
||||||
* Esta función recorre los valores del objeto y devuelve true si todos los valores son diferentes de undefined.
|
|
||||||
* Si al menos un valor es undefined, devuelve false.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const obj = { a: 1, b: 'test', c: null };
|
|
||||||
* console.log(hasNoUndefinedFields(obj)); // true
|
|
||||||
*
|
|
||||||
* const objWithUndefined = { a: 1, b: undefined, c: null };
|
|
||||||
* console.log(hasNoUndefinedFields(objWithUndefined)); // false
|
|
||||||
*
|
|
||||||
* @template T - El tipo del objeto.
|
|
||||||
* @param obj - El objeto a evaluar.
|
|
||||||
* @returns true si el objeto no tiene campos undefined, false en caso contrario.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function hasNoUndefinedFields<T extends Record<string, unknown>>(
|
|
||||||
obj: T
|
|
||||||
): obj is { [K in keyof T]-?: Exclude<T[K], undefined> } {
|
|
||||||
return Object.values(obj).every((value) => value !== undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @description Verifica si un objeto tiene campos con valor undefined.
|
|
||||||
* Esta función es el complemento de `hasNoUndefinedFields`.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const obj = { a: 1, b: 'test', c: null };
|
|
||||||
* console.log(hasUndefinedFields(obj)); // false
|
|
||||||
*
|
|
||||||
* const objWithUndefined = { a: 1, b: undefined, c: null };
|
|
||||||
* console.log(hasUndefinedFields(objWithUndefined)); // true
|
|
||||||
*
|
|
||||||
* @template T - El tipo del objeto.
|
|
||||||
* @param obj - El objeto a evaluar.
|
|
||||||
* @returns true si el objeto tiene al menos un campo undefined, false en caso contrario.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function hasUndefinedFields<T extends Record<string, unknown>>(
|
|
||||||
obj: T
|
|
||||||
): obj is { [K in keyof T]-?: Exclude<T[K], undefined> } {
|
|
||||||
return !hasNoUndefinedFields(obj);
|
|
||||||
}
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
import {
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { CreateCustomerInvoiceRequestDTO } from "../../../common";
|
|
||||||
import {
|
|
||||||
IssuedInvoiceItem,
|
|
||||||
type IssuedInvoiceItemProps,
|
|
||||||
ItemAmount,
|
|
||||||
ItemDescription,
|
|
||||||
ItemDiscountPercentage,
|
|
||||||
ItemQuantity,
|
|
||||||
} from "../../domain";
|
|
||||||
|
|
||||||
import { hasNoUndefinedFields } from "./has-no-undefined-fields";
|
|
||||||
|
|
||||||
export function mapDTOToCustomerInvoiceItemsProps(
|
|
||||||
dtoItems: Pick<CreateCustomerInvoiceRequestDTO, "items">["items"]
|
|
||||||
): Result<IssuedInvoiceItem[], ValidationErrorCollection> {
|
|
||||||
const errors: ValidationErrorDetail[] = [];
|
|
||||||
const items: IssuedInvoiceItem[] = [];
|
|
||||||
|
|
||||||
dtoItems.forEach((item, index) => {
|
|
||||||
const path = (field: string) => `items[${index}].${field}`;
|
|
||||||
|
|
||||||
const description = extractOrPushError(
|
|
||||||
maybeFromNullableResult(item.description, (value) => ItemDescription.create(value)),
|
|
||||||
path("description"),
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const quantity = extractOrPushError(
|
|
||||||
maybeFromNullableResult(item.quantity, (value) => ItemQuantity.create({ value })),
|
|
||||||
path("quantity"),
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const unitAmount = extractOrPushError(
|
|
||||||
maybeFromNullableResult(item.unit_amount, (value) => ItemAmount.create({ value })),
|
|
||||||
path("unit_amount"),
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const discountPercentage = extractOrPushError(
|
|
||||||
maybeFromNullableResult(item.discount_percentage, (value) =>
|
|
||||||
ItemDiscountPercentage.create({ value })
|
|
||||||
),
|
|
||||||
path("discount_percentage"),
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
if (errors.length === 0) {
|
|
||||||
const itemProps: IssuedInvoiceItemProps = {
|
|
||||||
description: description,
|
|
||||||
quantity: quantity,
|
|
||||||
unitAmount: unitAmount,
|
|
||||||
itemDiscountPercentage: discountPercentage,
|
|
||||||
//currencyCode,
|
|
||||||
//languageCode,
|
|
||||||
//taxes:
|
|
||||||
};
|
|
||||||
|
|
||||||
if (hasNoUndefinedFields(itemProps)) {
|
|
||||||
// Validar y crear el item de factura
|
|
||||||
const itemOrError = IssuedInvoiceItem.create(itemProps);
|
|
||||||
|
|
||||||
if (itemOrError.isSuccess) {
|
|
||||||
items.push(itemOrError.data);
|
|
||||||
} else {
|
|
||||||
errors.push({ path: `items[${index}]`, message: itemOrError.error.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(new ValidationErrorCollection("Invoice items dto mapping failed", errors));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Result.ok(items);
|
|
||||||
}
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
import {
|
|
||||||
CurrencyCode,
|
|
||||||
UniqueID,
|
|
||||||
UtcDate,
|
|
||||||
ValidationErrorCollection,
|
|
||||||
type ValidationErrorDetail,
|
|
||||||
extractOrPushError,
|
|
||||||
maybeFromNullableResult,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { CreateCustomerInvoiceRequestDTO } from "../../../common";
|
|
||||||
import {
|
|
||||||
type IProformaCreateProps,
|
|
||||||
InvoiceNumber,
|
|
||||||
InvoiceSerie,
|
|
||||||
InvoiceStatus,
|
|
||||||
} from "../../domain";
|
|
||||||
|
|
||||||
import { mapDTOToCustomerInvoiceItemsProps } from "./map-dto-to-customer-invoice-items-props";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convierte el DTO a las props validadas (CustomerInvoiceProps).
|
|
||||||
* No construye directamente el agregado.
|
|
||||||
*
|
|
||||||
* @param dto - DTO con los datos de la factura de cliente
|
|
||||||
* @returns
|
|
||||||
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function mapDTOToCustomerInvoiceProps(dto: CreateCustomerInvoiceRequestDTO) {
|
|
||||||
const errors: ValidationErrorDetail[] = [];
|
|
||||||
|
|
||||||
const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
|
||||||
|
|
||||||
const invoiceNumber = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.invoice_number, (value) => InvoiceNumber.create(value)),
|
|
||||||
"invoice_number",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
const invoiceSeries = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.series, (value) => InvoiceSerie.create(value)),
|
|
||||||
"invoice_series",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
const invoiceDate = extractOrPushError(
|
|
||||||
UtcDate.createFromISO(dto.invoice_date),
|
|
||||||
"invoice_date",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
const operationDate = extractOrPushError(
|
|
||||||
maybeFromNullableResult(dto.operation_date, (value) => UtcDate.createFromISO(value)),
|
|
||||||
"operation_date",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const currencyCode = extractOrPushError(
|
|
||||||
CurrencyCode.create(dto.currency_code),
|
|
||||||
"currency",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
// 🔄 Validar y construir los items de factura con helper especializado
|
|
||||||
const itemsResult = mapDTOToCustomerInvoiceItemsProps(dto.items);
|
|
||||||
if (itemsResult.isFailure) {
|
|
||||||
return Result.fail(itemsResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
return Result.fail(new ValidationErrorCollection("Invoice dto mapping failed", errors));
|
|
||||||
}
|
|
||||||
|
|
||||||
const invoiceProps: IProformaCreateProps = {
|
|
||||||
invoiceNumber: invoiceNumber!,
|
|
||||||
series: invoiceSeries!,
|
|
||||||
invoiceDate: invoiceDate!,
|
|
||||||
operationDate: operationDate!,
|
|
||||||
status: InvoiceStatus.fromDraft(),
|
|
||||||
currencyCode: currencyCode!,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Result.ok({ id: invoiceId!, props: invoiceProps });
|
|
||||||
|
|
||||||
/*if (hasNoUndefinedFields(invoiceProps)) {
|
|
||||||
const invoiceOrError = CustomerInvoice.create(invoiceProps, invoiceId);
|
|
||||||
if (invoiceOrError.isFailure) {
|
|
||||||
return Result.fail(invoiceOrError.error);
|
|
||||||
}
|
|
||||||
return Result.ok(invoiceOrError.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection([
|
|
||||||
{ path: "", message: "Error building from DTO: Some fields are undefined" },
|
|
||||||
])
|
|
||||||
);*/
|
|
||||||
}
|
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
export * from "./issued-invoice-creator.di";
|
||||||
export * from "./issued-invoice-finder.di";
|
export * from "./issued-invoice-finder.di";
|
||||||
export * from "./issued-invoice-snapshot-builders.di";
|
export * from "./issued-invoice-snapshot-builders.di";
|
||||||
export * from "./issued-invoice-use-cases.di";
|
export * from "./issued-invoice-use-cases.di";
|
||||||
|
export * from "./proforma-to-issued-invoice-props-converter.di";
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
import type { IIssuedInvoiceRepository } from "../repositories";
|
||||||
|
import {
|
||||||
|
type IIssuedInvoiceCreator,
|
||||||
|
type IIssuedInvoiceNumberGenerator,
|
||||||
|
IssuedInvoiceCreator,
|
||||||
|
} from "../services";
|
||||||
|
|
||||||
|
export function buildIssuedInvoiceCreator(params: {
|
||||||
|
numberService: IIssuedInvoiceNumberGenerator;
|
||||||
|
repository: IIssuedInvoiceRepository;
|
||||||
|
}): IIssuedInvoiceCreator {
|
||||||
|
const { numberService, repository } = params;
|
||||||
|
|
||||||
|
return new IssuedInvoiceCreator({
|
||||||
|
repository,
|
||||||
|
numberService,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import {
|
||||||
|
type IProformaToIssuedInvoiceConverter,
|
||||||
|
ProformaToIssuedInvoiceConverter,
|
||||||
|
} from "../services";
|
||||||
|
|
||||||
|
export function buildProformaToIssuedInvoicePropsConverter(): IProformaToIssuedInvoiceConverter {
|
||||||
|
return new ProformaToIssuedInvoiceConverter();
|
||||||
|
}
|
||||||
@ -1,5 +1,8 @@
|
|||||||
|
export * from "./issued-invoice-creator";
|
||||||
export * from "./issued-invoice-document-generator.interface";
|
export * from "./issued-invoice-document-generator.interface";
|
||||||
export * from "./issued-invoice-document-metadata-factory";
|
export * from "./issued-invoice-document-metadata-factory";
|
||||||
export * from "./issued-invoice-document-properties-factory";
|
export * from "./issued-invoice-document-properties-factory";
|
||||||
export * from "./issued-invoice-finder";
|
export * from "./issued-invoice-finder";
|
||||||
export * from "./proforma-to-issued-invoice-materializer";
|
export * from "./issued-invoice-number-generator.interface";
|
||||||
|
export * from "./issued-invoice-public-services.interface";
|
||||||
|
export * from "./proforma-to-issued-invoice-props-converter";
|
||||||
|
|||||||
@ -0,0 +1,64 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import { type IIssuedInvoiceCreateProps, IssuedInvoice } from "../../../domain";
|
||||||
|
import type { IIssuedInvoiceRepository } from "../repositories";
|
||||||
|
|
||||||
|
import type { IIssuedInvoiceNumberGenerator } from "./issued-invoice-number-generator.interface";
|
||||||
|
|
||||||
|
export interface IIssuedInvoiceCreatorParams {
|
||||||
|
companyId: UniqueID;
|
||||||
|
id: UniqueID;
|
||||||
|
props: Omit<IIssuedInvoiceCreateProps, "invoiceNumber">;
|
||||||
|
transaction: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IIssuedInvoiceCreator = {
|
||||||
|
create(params: IIssuedInvoiceCreatorParams): Promise<Result<IssuedInvoice, Error>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type IssuedInvoiceCreatorDeps = {
|
||||||
|
numberService: IIssuedInvoiceNumberGenerator;
|
||||||
|
repository: IIssuedInvoiceRepository;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class IssuedInvoiceCreator implements IIssuedInvoiceCreator {
|
||||||
|
private readonly numberService: IIssuedInvoiceNumberGenerator;
|
||||||
|
private readonly repository: IIssuedInvoiceRepository;
|
||||||
|
|
||||||
|
constructor(deps: IssuedInvoiceCreatorDeps) {
|
||||||
|
this.numberService = deps.numberService;
|
||||||
|
this.repository = deps.repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(params: IIssuedInvoiceCreatorParams): Promise<Result<IssuedInvoice, Error>> {
|
||||||
|
const { companyId, id, props, transaction } = params;
|
||||||
|
|
||||||
|
// 1. Obtener siguiente número
|
||||||
|
const { series } = props;
|
||||||
|
const numberResult = await this.numberService.getNextForCompany(companyId, series, transaction);
|
||||||
|
|
||||||
|
if (numberResult.isFailure) {
|
||||||
|
return Result.fail(numberResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoiceNumber = numberResult.data;
|
||||||
|
|
||||||
|
const invoiceResult = IssuedInvoice.create({ ...props, invoiceNumber, companyId }, id);
|
||||||
|
|
||||||
|
if (invoiceResult.isFailure) {
|
||||||
|
return Result.fail(invoiceResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoice = invoiceResult.data;
|
||||||
|
|
||||||
|
// 3. Persistir
|
||||||
|
const saveResult = await this.repository.create(invoice, transaction);
|
||||||
|
|
||||||
|
if (saveResult.isFailure) {
|
||||||
|
return Result.fail(saveResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(invoice);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { DocumentGenerationService } from "@erp/core/api";
|
import type { DocumentGenerationService } from "@erp/core/api";
|
||||||
|
|
||||||
import type { IssuedInvoiceReportSnapshot } from "../application-models";
|
import type { IIssuedInvoiceReportSnapshot } from "../snapshot-builders/report";
|
||||||
|
|
||||||
export interface IssuedInvoiceDocumentGeneratorService
|
export interface IssuedInvoiceDocumentGeneratorService
|
||||||
extends DocumentGenerationService<IssuedInvoiceReportSnapshot> {}
|
extends DocumentGenerationService<IIssuedInvoiceReportSnapshot> {}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { IDocumentMetadata, IDocumentMetadataFactory } from "@erp/core/api";
|
import type { IDocumentMetadata, IDocumentMetadataFactory } from "@erp/core/api";
|
||||||
|
|
||||||
import type { IssuedInvoiceReportSnapshot } from "../application-models";
|
import type { IIssuedInvoiceReportSnapshot } from "../snapshot-builders";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construye los metadatos del documento PDF de una factura emitida.
|
* Construye los metadatos del documento PDF de una factura emitida.
|
||||||
@ -10,9 +10,9 @@ import type { IssuedInvoiceReportSnapshot } from "../application-models";
|
|||||||
* - Sin IO
|
* - Sin IO
|
||||||
*/
|
*/
|
||||||
export class IssuedInvoiceDocumentMetadataFactory
|
export class IssuedInvoiceDocumentMetadataFactory
|
||||||
implements IDocumentMetadataFactory<IssuedInvoiceReportSnapshot>
|
implements IDocumentMetadataFactory<IIssuedInvoiceReportSnapshot>
|
||||||
{
|
{
|
||||||
build(snapshot: IssuedInvoiceReportSnapshot): IDocumentMetadata {
|
build(snapshot: IIssuedInvoiceReportSnapshot): IDocumentMetadata {
|
||||||
if (!snapshot.id) {
|
if (!snapshot.id) {
|
||||||
throw new Error("IssuedInvoiceReportSnapshot.id is required");
|
throw new Error("IssuedInvoiceReportSnapshot.id is required");
|
||||||
}
|
}
|
||||||
@ -33,12 +33,12 @@ export class IssuedInvoiceDocumentMetadataFactory
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildFilename(snapshot: IssuedInvoiceReportSnapshot): string {
|
private buildFilename(snapshot: IIssuedInvoiceReportSnapshot): string {
|
||||||
// Ejemplo: factura-F2024-000123-FULANITO.pdf
|
// Ejemplo: factura-F2024-000123-FULANITO.pdf
|
||||||
return `factura-${snapshot.series}${snapshot.invoice_number}-${snapshot.recipient.name}.pdf`;
|
return `factura-${snapshot.series}${snapshot.invoice_number}-${snapshot.recipient.name}.pdf`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildCacheKey(snapshot: IssuedInvoiceReportSnapshot): string {
|
private buildCacheKey(snapshot: IIssuedInvoiceReportSnapshot): string {
|
||||||
// Versionado explícito para invalidaciones futuras
|
// Versionado explícito para invalidaciones futuras
|
||||||
return [
|
return [
|
||||||
"issued-invoice",
|
"issued-invoice",
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import type { Collection, Result } from "@repo/rdx-utils";
|
import type { Collection, Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { IssuedInvoice } from "../../../domain";
|
import type { IssuedInvoice } from "../../../domain";
|
||||||
import type { IssuedInvoiceSummary } from "../models";
|
import type { IssuedInvoiceSummary } from "../models";
|
||||||
@ -11,19 +10,19 @@ export interface IIssuedInvoiceFinder {
|
|||||||
findIssuedInvoiceById(
|
findIssuedInvoiceById(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<IssuedInvoice, Error>>;
|
): Promise<Result<IssuedInvoice, Error>>;
|
||||||
|
|
||||||
issuedInvoiceExists(
|
issuedInvoiceExists(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>>;
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
findIssuedInvoicesByCriteria(
|
findIssuedInvoicesByCriteria(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<Collection<IssuedInvoiceSummary>, Error>>;
|
): Promise<Result<Collection<IssuedInvoiceSummary>, Error>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +32,7 @@ export class IssuedInvoiceFinder implements IIssuedInvoiceFinder {
|
|||||||
async findIssuedInvoiceById(
|
async findIssuedInvoiceById(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<IssuedInvoice, Error>> {
|
): Promise<Result<IssuedInvoice, Error>> {
|
||||||
return this.repository.getByIdInCompany(companyId, invoiceId, transaction);
|
return this.repository.getByIdInCompany(companyId, invoiceId, transaction);
|
||||||
}
|
}
|
||||||
@ -41,7 +40,7 @@ export class IssuedInvoiceFinder implements IIssuedInvoiceFinder {
|
|||||||
async issuedInvoiceExists(
|
async issuedInvoiceExists(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
return this.repository.existsByIdInCompany(companyId, invoiceId, transaction);
|
return this.repository.existsByIdInCompany(companyId, invoiceId, transaction);
|
||||||
}
|
}
|
||||||
@ -49,7 +48,7 @@ export class IssuedInvoiceFinder implements IIssuedInvoiceFinder {
|
|||||||
async findIssuedInvoicesByCriteria(
|
async findIssuedInvoicesByCriteria(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<Collection<IssuedInvoiceSummary>, Error>> {
|
): Promise<Result<Collection<IssuedInvoiceSummary>, Error>> {
|
||||||
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
|
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { InvoiceNumber, InvoiceSerie } from "../../../domain";
|
||||||
|
|
||||||
|
export interface IIssuedInvoiceNumberGenerator {
|
||||||
|
/**
|
||||||
|
* Devuelve el siguiente número de factura disponible para una empresa dentro de una "serie" de factura.
|
||||||
|
*
|
||||||
|
* @param companyId - Identificador de la empresa
|
||||||
|
* @param serie - Serie por la que buscar la última factura
|
||||||
|
* @param transaction - Transacción activa
|
||||||
|
*/
|
||||||
|
getNextForCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
series: Maybe<InvoiceSerie>,
|
||||||
|
transaction: unknown
|
||||||
|
): Promise<Result<InvoiceNumber, Error>>;
|
||||||
|
}
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import type { Maybe, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { ICustomerInvoiceNumberGenerator, InvoiceNumber, InvoiceSerie } from "../../../domain";
|
|
||||||
|
|
||||||
export interface IIssuedInvoiceNumberService {
|
|
||||||
/**
|
|
||||||
* Devuelve el siguiente número disponible para una factura emitida.
|
|
||||||
*/
|
|
||||||
nextIssuedInvoiceNumber(
|
|
||||||
companyId: UniqueID,
|
|
||||||
series: Maybe<InvoiceSerie>,
|
|
||||||
transaction: unknown
|
|
||||||
): Promise<Result<InvoiceNumber, Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IssuedInvoiceNumberService implements IIssuedInvoiceNumberService {
|
|
||||||
constructor(private readonly numberGenerator: ICustomerInvoiceNumberGenerator) {}
|
|
||||||
|
|
||||||
async nextIssuedInvoiceNumber(
|
|
||||||
companyId: UniqueID,
|
|
||||||
series: Maybe<InvoiceSerie>,
|
|
||||||
transaction: unknown
|
|
||||||
): Promise<Result<InvoiceNumber, Error>> {
|
|
||||||
return this.numberGenerator.nextForCompany(companyId, series, transaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { IIssuedInvoiceCreatorParams } from ".";
|
||||||
|
|
||||||
|
import type { IssuedInvoice } from "../../../domain";
|
||||||
|
|
||||||
|
export interface IIssuedInvoiceServicesContext {
|
||||||
|
transaction: unknown;
|
||||||
|
companyId: UniqueID;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IIssuedInvoicePublicServices {
|
||||||
|
createIssuedInvoice: (
|
||||||
|
id: UniqueID,
|
||||||
|
props: IIssuedInvoiceCreatorParams["props"],
|
||||||
|
context: IIssuedInvoiceServicesContext
|
||||||
|
) => Promise<Result<IssuedInvoice, Error>>;
|
||||||
|
}
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { CustomerInvoice } from "../../../domain/aggregates";
|
|
||||||
import type { ICustomerInvoiceRepository } from "../../../domain/repositories";
|
|
||||||
|
|
||||||
export type IIssuedInvoiceWriteService = {};
|
|
||||||
|
|
||||||
export class IssuedInvoiceWriteService implements IIssuedInvoiceWriteService {
|
|
||||||
constructor(private readonly repository: ICustomerInvoiceRepository) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emite (crea) una factura definitiva a partir de una factura ya preparada.
|
|
||||||
*
|
|
||||||
* Asume que:
|
|
||||||
* - el número ya ha sido asignado
|
|
||||||
* - el estado es correcto
|
|
||||||
*/
|
|
||||||
async createIssuedInvoice(
|
|
||||||
companyId: UniqueID,
|
|
||||||
invoice: CustomerInvoice,
|
|
||||||
transaction: Transaction
|
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
|
||||||
const result = await this.repository.create(invoice, transaction);
|
|
||||||
if (result.isFailure) {
|
|
||||||
return Result.fail(result.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
import type { IIssuedInvoiceProps, Proforma } from "../../../domain";
|
|
||||||
|
|
||||||
export interface IProformaToIssuedInvoiceMaterializer {
|
|
||||||
materialize(proforma: Proforma, issuedInvoiceId: UniqueID): Result<IIssuedInvoiceProps, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ProformaToIssuedInvoiceMaterializer implements IProformaToIssuedInvoiceMaterializer {
|
|
||||||
public materialize(
|
|
||||||
proforma: Proforma,
|
|
||||||
issuedInvoiceId: UniqueID
|
|
||||||
): Result<IIssuedInvoiceProps, Error> {
|
|
||||||
const amounts = proforma.calculateAllAmounts();
|
|
||||||
const taxGroups = proforma.getTaxes();
|
|
||||||
|
|
||||||
const issuedItems = proforma.items.map((item) => ({
|
|
||||||
description: item.description,
|
|
||||||
quantity: item.quantity,
|
|
||||||
unitPrice: item.unitAmount,
|
|
||||||
taxableAmount: item.getTaxableAmount(),
|
|
||||||
taxesAmount: item.getTaxesAmount(),
|
|
||||||
totalAmount: item.getTotalAmount(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const issuedTaxes = taxGroups.map((group) => ({
|
|
||||||
ivaCode: group.iva.code,
|
|
||||||
ivaPercentage: group.iva.percentage,
|
|
||||||
ivaAmount: group.calculateAmounts().ivaAmount,
|
|
||||||
recCode: group.rec?.code,
|
|
||||||
recPercentage: group.rec?.percentage,
|
|
||||||
recAmount: group.calculateAmounts().recAmount,
|
|
||||||
retentionCode: group.retention?.code,
|
|
||||||
retentionPercentage: group.retention?.percentage,
|
|
||||||
retentionAmount: group.calculateAmounts().retentionAmount,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return Result.ok({
|
|
||||||
companyId: proforma.companyId,
|
|
||||||
invoiceNumber: proforma.invoiceNumber,
|
|
||||||
invoiceDate: proforma.invoiceDate,
|
|
||||||
customerId: proforma.customerId,
|
|
||||||
languageCode: proforma.languageCode,
|
|
||||||
currencyCode: proforma.currencyCode,
|
|
||||||
paymentMethod: proforma.paymentMethod,
|
|
||||||
discountPercentage: proforma.globalDiscountPercentage,
|
|
||||||
|
|
||||||
items: new Collection(issuedItems),
|
|
||||||
taxes: new Collection(issuedTaxes),
|
|
||||||
subtotalAmount: amounts.subtotalAmount,
|
|
||||||
taxableAmount: amounts.taxableAmount,
|
|
||||||
totalAmount: amounts.totalAmount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,149 @@
|
|||||||
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import {
|
||||||
|
type IIssuedInvoiceCreateProps,
|
||||||
|
InvoiceStatus,
|
||||||
|
IssuedInvoiceItem,
|
||||||
|
IssuedInvoiceTax,
|
||||||
|
IssuedInvoiceTaxes,
|
||||||
|
type Proforma,
|
||||||
|
} from "../../../domain";
|
||||||
|
|
||||||
|
export interface IProformaToIssuedInvoiceConverter {
|
||||||
|
toCreateProps(proforma: Proforma): Result<IIssuedInvoiceCreateProps, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProformaToIssuedInvoiceConverter implements IProformaToIssuedInvoiceConverter {
|
||||||
|
public toCreateProps(proforma: Proforma): Result<IIssuedInvoiceCreateProps, Error> {
|
||||||
|
const proformaTotals = proforma.totals();
|
||||||
|
const taxTotals = proforma.taxes();
|
||||||
|
|
||||||
|
const issuedItems: IssuedInvoiceItem[] = [];
|
||||||
|
|
||||||
|
for (const item of proforma.items.getAll()) {
|
||||||
|
const itemTotals = item.totals();
|
||||||
|
|
||||||
|
const itemOrResult = IssuedInvoiceItem.create({
|
||||||
|
description: item.description,
|
||||||
|
quantity: item.quantity,
|
||||||
|
unitAmount: item.unitAmount,
|
||||||
|
currencyCode: proforma.currencyCode,
|
||||||
|
languageCode: proforma.languageCode,
|
||||||
|
|
||||||
|
itemDiscountPercentage: item.itemDiscountPercentage,
|
||||||
|
itemDiscountAmount: itemTotals.itemDiscountAmount,
|
||||||
|
|
||||||
|
globalDiscountPercentage: item.globalDiscountPercentage,
|
||||||
|
globalDiscountAmount: itemTotals.globalDiscountAmount,
|
||||||
|
|
||||||
|
totalDiscountAmount: itemTotals.totalDiscountAmount,
|
||||||
|
|
||||||
|
ivaCode: item.ivaCode(),
|
||||||
|
ivaPercentage: item.ivaPercentage(),
|
||||||
|
ivaAmount: itemTotals.ivaAmount,
|
||||||
|
|
||||||
|
recCode: item.recCode(),
|
||||||
|
recPercentage: item.recPercentage(),
|
||||||
|
recAmount: itemTotals.recAmount,
|
||||||
|
|
||||||
|
retentionCode: item.retentionCode(),
|
||||||
|
retentionPercentage: item.retentionPercentage(),
|
||||||
|
retentionAmount: itemTotals.recAmount,
|
||||||
|
|
||||||
|
subtotalAmount: itemTotals.subtotalAmount,
|
||||||
|
taxableAmount: itemTotals.taxableAmount,
|
||||||
|
taxesAmount: itemTotals.taxesAmount,
|
||||||
|
totalAmount: itemTotals.totalAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemOrResult.isFailure) {
|
||||||
|
return Result.fail(itemOrResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
issuedItems.push(itemOrResult.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const issuedTaxes: IssuedInvoiceTax[] = [];
|
||||||
|
for (const tax of taxTotals.getAll()) {
|
||||||
|
if (tax.ivaCode.isNone()) {
|
||||||
|
return Result.fail(new Error("IVA code is required"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const taxOrResult = IssuedInvoiceTax.create({
|
||||||
|
taxableAmount: tax.taxableAmount,
|
||||||
|
ivaCode: tax.ivaCode.unwrap(),
|
||||||
|
ivaPercentage: tax.ivaPercentage.unwrap(),
|
||||||
|
ivaAmount: tax.ivaAmount,
|
||||||
|
|
||||||
|
recCode: tax.recCode,
|
||||||
|
recPercentage: tax.recPercentage,
|
||||||
|
recAmount: tax.recAmount,
|
||||||
|
|
||||||
|
retentionCode: tax.retentionCode,
|
||||||
|
retentionAmount: tax.retentionAmount,
|
||||||
|
retentionPercentage: tax.retentionPercentage,
|
||||||
|
|
||||||
|
taxesAmount: tax.taxesAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (taxOrResult.isFailure) {
|
||||||
|
return Result.fail(taxOrResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
issuedTaxes.push(taxOrResult.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const issuedInvoiceProps: IIssuedInvoiceCreateProps = {
|
||||||
|
companyId: proforma.companyId,
|
||||||
|
status: InvoiceStatus.issued(),
|
||||||
|
|
||||||
|
series: proforma.series,
|
||||||
|
proformaId: proforma.id,
|
||||||
|
|
||||||
|
invoiceNumber: proforma.invoiceNumber,
|
||||||
|
|
||||||
|
invoiceDate: proforma.invoiceDate,
|
||||||
|
operationDate: proforma.operationDate,
|
||||||
|
|
||||||
|
description: proforma.description,
|
||||||
|
|
||||||
|
languageCode: proforma.languageCode,
|
||||||
|
currencyCode: proforma.currencyCode,
|
||||||
|
notes: proforma.notes,
|
||||||
|
reference: proforma.reference,
|
||||||
|
|
||||||
|
paymentMethod: proforma.paymentMethod,
|
||||||
|
|
||||||
|
customerId: proforma.customerId,
|
||||||
|
recipient: proforma.recipient,
|
||||||
|
|
||||||
|
items: issuedItems,
|
||||||
|
|
||||||
|
taxes: IssuedInvoiceTaxes.create({
|
||||||
|
currencyCode: proforma.currencyCode,
|
||||||
|
languageCode: proforma.languageCode,
|
||||||
|
taxes: issuedTaxes,
|
||||||
|
}),
|
||||||
|
|
||||||
|
subtotalAmount: proformaTotals.subtotalAmount,
|
||||||
|
|
||||||
|
itemsDiscountAmount: proformaTotals.itemsDiscountAmount,
|
||||||
|
globalDiscountPercentage: proforma.globalDiscountPercentage,
|
||||||
|
globalDiscountAmount: proformaTotals.globalDiscountAmount,
|
||||||
|
totalDiscountAmount: proformaTotals.totalDiscountAmount,
|
||||||
|
|
||||||
|
taxableAmount: proformaTotals.taxableAmount,
|
||||||
|
|
||||||
|
ivaAmount: proformaTotals.ivaAmount,
|
||||||
|
recAmount: proformaTotals.recAmount,
|
||||||
|
retentionAmount: proformaTotals.retentionAmount,
|
||||||
|
|
||||||
|
taxesAmount: proformaTotals.taxesAmount,
|
||||||
|
totalAmount: proformaTotals.totalAmount,
|
||||||
|
|
||||||
|
verifactu: Maybe.none(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return Result.ok(issuedInvoiceProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
export * from "./full";
|
export * from "./full";
|
||||||
|
export * from "./report";
|
||||||
export * from "./summary";
|
export * from "./summary";
|
||||||
|
|||||||
@ -27,10 +27,10 @@ export class IssuedInvoiceReportItemSnapshotBuilder
|
|||||||
quantity: QuantityDTOHelper.format(item.quantity, locale, { minimumFractionDigits: 0 }),
|
quantity: QuantityDTOHelper.format(item.quantity, locale, { minimumFractionDigits: 0 }),
|
||||||
unit_amount: MoneyDTOHelper.format(item.unit_amount, locale, moneyOptions),
|
unit_amount: MoneyDTOHelper.format(item.unit_amount, locale, moneyOptions),
|
||||||
subtotal_amount: MoneyDTOHelper.format(item.subtotal_amount, locale, moneyOptions),
|
subtotal_amount: MoneyDTOHelper.format(item.subtotal_amount, locale, moneyOptions),
|
||||||
discount_percentage: PercentageDTOHelper.format(item.discount_percentage, locale, {
|
discount_percentage: PercentageDTOHelper.format(item.item_discount_percentage, locale, {
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
}),
|
}),
|
||||||
discount_amount: MoneyDTOHelper.format(item.discount_amount, locale, moneyOptions),
|
discount_amount: MoneyDTOHelper.format(item.item_discount_amount, locale, moneyOptions),
|
||||||
taxable_amount: MoneyDTOHelper.format(item.taxable_amount, locale, moneyOptions),
|
taxable_amount: MoneyDTOHelper.format(item.taxable_amount, locale, moneyOptions),
|
||||||
taxes_amount: MoneyDTOHelper.format(item.taxes_amount, locale, moneyOptions),
|
taxes_amount: MoneyDTOHelper.format(item.taxes_amount, locale, moneyOptions),
|
||||||
total_amount: MoneyDTOHelper.format(item.total_amount, locale, moneyOptions),
|
total_amount: MoneyDTOHelper.format(item.total_amount, locale, moneyOptions),
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import type { ITransactionManager } from "@erp/core/api";
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { IIssuedInvoiceFinder } from "../services";
|
import type { IIssuedInvoiceFinder } from "../services";
|
||||||
import type { IIssuedInvoiceSummarySnapshotBuilder } from "../snapshot-builders";
|
import type { IIssuedInvoiceSummarySnapshotBuilder } from "../snapshot-builders";
|
||||||
@ -22,7 +21,7 @@ export class ListIssuedInvoicesUseCase {
|
|||||||
public execute(params: ListIssuedInvoicesUseCaseInput) {
|
public execute(params: ListIssuedInvoicesUseCaseInput) {
|
||||||
const { criteria, companyId } = params;
|
const { criteria, companyId } = params;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
return this.transactionManager.complete(async (transaction: unknown) => {
|
||||||
try {
|
try {
|
||||||
const result = await this.finder.findIssuedInvoicesByCriteria(
|
const result = await this.finder.findIssuedInvoicesByCriteria(
|
||||||
companyId,
|
companyId,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export * from "./proforma-creator.di";
|
export * from "./proforma-creator.di";
|
||||||
export * from "./proforma-finder.di";
|
export * from "./proforma-finder.di";
|
||||||
export * from "./proforma-input-mappers.di";
|
export * from "./proforma-input-mappers.di";
|
||||||
|
export * from "./proforma-issuer.di";
|
||||||
export * from "./proforma-snapshot-builders.di";
|
export * from "./proforma-snapshot-builders.di";
|
||||||
export * from "./proforma-use-cases.di";
|
export * from "./proforma-use-cases.di";
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
import type { IProformaToIssuedInvoiceConverter } from "../../issued-invoices";
|
||||||
|
import type { IProformaRepository } from "../repositories";
|
||||||
|
import { type IProformaIssuer, ProformaIssuer } from "../services";
|
||||||
|
|
||||||
|
export const buildProformaIssuer = (params: {
|
||||||
|
proformaConverter: IProformaToIssuedInvoiceConverter;
|
||||||
|
repository: IProformaRepository;
|
||||||
|
}): IProformaIssuer => {
|
||||||
|
const { proformaConverter, repository } = params;
|
||||||
|
|
||||||
|
return new ProformaIssuer({
|
||||||
|
proformaConverter,
|
||||||
|
repository,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,9 +1,11 @@
|
|||||||
import type { ITransactionManager } from "@erp/core/api";
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
|
|
||||||
|
import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
||||||
import type { ICreateProformaInputMapper } from "../mappers";
|
import type { ICreateProformaInputMapper } from "../mappers";
|
||||||
import type {
|
import type {
|
||||||
IProformaCreator,
|
IProformaCreator,
|
||||||
IProformaFinder,
|
IProformaFinder,
|
||||||
|
IProformaIssuer,
|
||||||
ProformaDocumentGeneratorService,
|
ProformaDocumentGeneratorService,
|
||||||
} from "../services";
|
} from "../services";
|
||||||
import type {
|
import type {
|
||||||
@ -11,9 +13,13 @@ import type {
|
|||||||
IProformaSummarySnapshotBuilder,
|
IProformaSummarySnapshotBuilder,
|
||||||
} from "../snapshot-builders";
|
} from "../snapshot-builders";
|
||||||
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders/full";
|
import type { IProformaFullSnapshotBuilder } from "../snapshot-builders/full";
|
||||||
import { GetProformaByIdUseCase, ListProformasUseCase, ReportProformaUseCase } from "../use-cases";
|
import {
|
||||||
import { CreateProformaUseCase } from "../use-cases/create-proforma";
|
CreateProformaUseCase,
|
||||||
import { IssueProformaUseCase } from "../use-cases/issue-proforma.use-case";
|
GetProformaByIdUseCase,
|
||||||
|
IssueProformaUseCase,
|
||||||
|
ListProformasUseCase,
|
||||||
|
ReportProformaUseCase,
|
||||||
|
} from "../use-cases";
|
||||||
|
|
||||||
export function buildGetProformaByIdUseCase(deps: {
|
export function buildGetProformaByIdUseCase(deps: {
|
||||||
finder: IProformaFinder;
|
finder: IProformaFinder;
|
||||||
@ -65,8 +71,26 @@ export function buildCreateProformaUseCase(deps: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildIssueProformaUseCase(deps: { finder: IProformaFinder }) {
|
export function buildIssueProformaUseCase(deps: {
|
||||||
return new IssueProformaUseCase(deps.finder);
|
publicServices: {
|
||||||
|
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
||||||
|
};
|
||||||
|
finder: IProformaFinder;
|
||||||
|
issuer: IProformaIssuer;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
finder,
|
||||||
|
issuer,
|
||||||
|
transactionManager,
|
||||||
|
publicServices: { issuedInvoiceServices },
|
||||||
|
} = deps;
|
||||||
|
return new IssueProformaUseCase({
|
||||||
|
issuedInvoiceServices,
|
||||||
|
finder,
|
||||||
|
issuer,
|
||||||
|
transactionManager,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*export function buildUpdateProformaUseCase(deps: {
|
/*export function buildUpdateProformaUseCase(deps: {
|
||||||
|
|||||||
@ -69,7 +69,7 @@ export class CreateProformaInputMapper /*implements ICreateProformaInputMapper*/
|
|||||||
const { companyId } = params;
|
const { companyId } = params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const defaultStatus = InvoiceStatus.fromDraft();
|
const defaultStatus = InvoiceStatus.draft();
|
||||||
|
|
||||||
const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
||||||
|
|
||||||
|
|||||||
@ -5,3 +5,4 @@ export * from "./proforma-document-properties-factory";
|
|||||||
export * from "./proforma-finder";
|
export * from "./proforma-finder";
|
||||||
export * from "./proforma-issuer";
|
export * from "./proforma-issuer";
|
||||||
export * from "./proforma-number-generator.interface";
|
export * from "./proforma-number-generator.interface";
|
||||||
|
export * from "./proforma-public-services.interface";
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import { type IProformaCreateProps, Proforma } from "../../../domain";
|
import { type IProformaCreateProps, Proforma } from "../../../domain";
|
||||||
import type { IProformaRepository } from "../repositories";
|
import type { IProformaRepository } from "../repositories";
|
||||||
@ -11,7 +10,7 @@ export interface IProformaCreatorParams {
|
|||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
id: UniqueID;
|
id: UniqueID;
|
||||||
props: Omit<IProformaCreateProps, "invoiceNumber">;
|
props: Omit<IProformaCreateProps, "invoiceNumber">;
|
||||||
transaction: Transaction;
|
transaction: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProformaCreator {
|
export interface IProformaCreator {
|
||||||
@ -32,12 +31,7 @@ export class ProformaCreator implements IProformaCreator {
|
|||||||
this.repository = deps.repository;
|
this.repository = deps.repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(params: {
|
async create(params: IProformaCreatorParams): Promise<Result<Proforma, Error>> {
|
||||||
companyId: UniqueID;
|
|
||||||
id: UniqueID;
|
|
||||||
props: IProformaCreateProps;
|
|
||||||
transaction: Transaction;
|
|
||||||
}): Promise<Result<Proforma, Error>> {
|
|
||||||
const { companyId, id, props, transaction } = params;
|
const { companyId, id, props, transaction } = params;
|
||||||
|
|
||||||
// 1. Obtener siguiente número
|
// 1. Obtener siguiente número
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import type { Collection, Result } from "@repo/rdx-utils";
|
import type { Collection, Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { Proforma } from "../../../domain";
|
import type { Proforma } from "../../../domain";
|
||||||
import type { ProformaSummary } from "../models";
|
import type { ProformaSummary } from "../models";
|
||||||
@ -11,19 +10,19 @@ export interface IProformaFinder {
|
|||||||
findProformaById(
|
findProformaById(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<Proforma, Error>>;
|
): Promise<Result<Proforma, Error>>;
|
||||||
|
|
||||||
proformaExists(
|
proformaExists(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>>;
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
findProformasByCriteria(
|
findProformasByCriteria(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<Collection<ProformaSummary>, Error>>;
|
): Promise<Result<Collection<ProformaSummary>, Error>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +32,7 @@ export class ProformaFinder implements IProformaFinder {
|
|||||||
async findProformaById(
|
async findProformaById(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<Proforma, Error>> {
|
): Promise<Result<Proforma, Error>> {
|
||||||
return this.repository.getByIdInCompany(companyId, proformaId, transaction);
|
return this.repository.getByIdInCompany(companyId, proformaId, transaction);
|
||||||
}
|
}
|
||||||
@ -41,7 +40,7 @@ export class ProformaFinder implements IProformaFinder {
|
|||||||
async proformaExists(
|
async proformaExists(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
return this.repository.existsByIdInCompany(companyId, proformaId, transaction);
|
return this.repository.existsByIdInCompany(companyId, proformaId, transaction);
|
||||||
}
|
}
|
||||||
@ -49,7 +48,7 @@ export class ProformaFinder implements IProformaFinder {
|
|||||||
async findProformasByCriteria(
|
async findProformasByCriteria(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<Collection<ProformaSummary>, Error>> {
|
): Promise<Result<Collection<ProformaSummary>, Error>> {
|
||||||
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
|
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,44 +1,65 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { IProformaToIssuedInvoiceMaterializer } from "../../issued-invoices";
|
import type { IIssuedInvoiceCreateProps, Proforma } from "../../../domain";
|
||||||
|
import type { IProformaToIssuedInvoiceConverter } from "../../issued-invoices";
|
||||||
|
import type { IProformaRepository } from "../repositories";
|
||||||
|
|
||||||
|
export interface IProformaIssuerParams {
|
||||||
|
companyId: UniqueID;
|
||||||
|
proforma: Proforma;
|
||||||
|
issuedInvoiceId: UniqueID;
|
||||||
|
transaction: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProformaIssuer {
|
||||||
|
issueProforma(params: IProformaIssuerParams): Promise<Result<IIssuedInvoiceCreateProps, Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProformaIssuerDeps = {
|
||||||
|
proformaConverter: IProformaToIssuedInvoiceConverter;
|
||||||
|
repository: IProformaRepository;
|
||||||
|
};
|
||||||
|
|
||||||
export class ProformaIssuer implements IProformaIssuer {
|
export class ProformaIssuer implements IProformaIssuer {
|
||||||
private readonly proformaRepository: IProformaRepository;
|
private readonly proformaConverter: IProformaToIssuedInvoiceConverter;
|
||||||
private readonly issuedInvoiceFactory: IIssuedInvoiceFactory;
|
private readonly repository: IProformaRepository;
|
||||||
private readonly issuedInvoiceRepository: IIssuedInvoiceRepository;
|
|
||||||
private readonly materializer: IProformaToIssuedInvoiceMaterializer;
|
|
||||||
|
|
||||||
constructor(deps: ProformaIssuerDeps) {
|
constructor(deps: ProformaIssuerDeps) {
|
||||||
this.proformaRepository = deps.proformaRepository;
|
this.proformaConverter = deps.proformaConverter;
|
||||||
this.issuedInvoiceFactory = deps.issuedInvoiceFactory;
|
this.repository = deps.repository;
|
||||||
this.issuedInvoiceRepository = deps.issuedInvoiceRepository;
|
|
||||||
this.materializer = deps.materializer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async issue(
|
public async issueProforma(
|
||||||
proforma: Proforma,
|
params: IProformaIssuerParams
|
||||||
issuedInvoiceId: UniqueID,
|
): Promise<Result<IIssuedInvoiceCreateProps, Error>> {
|
||||||
transaction: Transaction
|
const { proforma, companyId, transaction } = params;
|
||||||
): Promise<Result<Proforma, Error>> {
|
|
||||||
|
// Cambiamos el estado de la proforma a 'issued'
|
||||||
const issueResult = proforma.issue();
|
const issueResult = proforma.issue();
|
||||||
if (issueResult.isFailure) return Result.fail(issueResult.error);
|
if (issueResult.isFailure) {
|
||||||
|
return Result.fail(issueResult.error);
|
||||||
const propsResult = this.materializer.materialize(proforma, issuedInvoiceId);
|
|
||||||
|
|
||||||
if (propsResult.isFailure) return Result.fail(propsResult.error);
|
|
||||||
|
|
||||||
const invoiceResult = this.issuedInvoiceFactory.create(propsResult.data, issuedInvoiceId);
|
|
||||||
|
|
||||||
if (invoiceResult.isFailure) {
|
|
||||||
return Result.fail(invoiceResult.error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.issuedInvoiceRepository.save(proforma.companyId, invoiceResult.data, transaction);
|
// Persistir
|
||||||
|
const updateStatusResult = await this.repository.updateStatusByIdInCompany(
|
||||||
|
companyId,
|
||||||
|
proforma.id,
|
||||||
|
proforma.status,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
|
|
||||||
await this.proformaRepository.save(proforma.companyId, proforma, transaction);
|
if (updateStatusResult.isFailure) {
|
||||||
|
return Result.fail(updateStatusResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
return Result.ok(proforma);
|
// Generamos las propiedades de la factura a partir de la proforma
|
||||||
|
const propsResult = this.proformaConverter.toCreateProps(proforma);
|
||||||
|
|
||||||
|
if (propsResult.isFailure) {
|
||||||
|
return Result.fail(propsResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(propsResult.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,10 +7,10 @@ import type { Maybe, Result } from "@repo/rdx-utils";
|
|||||||
*/
|
*/
|
||||||
export interface IProformaNumberGenerator {
|
export interface IProformaNumberGenerator {
|
||||||
/**
|
/**
|
||||||
* Devuelve el siguiente número de factura disponible para una empresa dentro de una "serie" de factura.
|
* Devuelve el siguiente número de proforma disponible para una empresa dentro de una "serie" de proforma.
|
||||||
*
|
*
|
||||||
* @param companyId - Identificador de la empresa
|
* @param companyId - Identificador de la empresa
|
||||||
* @param serie - Serie por la que buscar la última factura
|
* @param serie - Serie por la que buscar la última proforma
|
||||||
* @param transaction - Transacción activa
|
* @param transaction - Transacción activa
|
||||||
*/
|
*/
|
||||||
getNextForCompany(
|
getNextForCompany(
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { Proforma } from "../../../domain/proformas";
|
||||||
|
import type { IProformaFullSnapshot } from "../snapshot-builders";
|
||||||
|
|
||||||
|
import type { IProformaCreatorParams } from "./proforma-creator";
|
||||||
|
|
||||||
|
export interface IProformaServicesContext {
|
||||||
|
transaction: unknown;
|
||||||
|
companyId: UniqueID;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProformaPublicServices {
|
||||||
|
createProforma: (
|
||||||
|
id: UniqueID,
|
||||||
|
props: IProformaCreatorParams["props"],
|
||||||
|
context: IProformaServicesContext
|
||||||
|
) => Promise<Result<Proforma, Error>>;
|
||||||
|
|
||||||
|
listProformas: (filters: unknown, context: unknown) => null;
|
||||||
|
|
||||||
|
getProformaById: (
|
||||||
|
id: UniqueID,
|
||||||
|
context: IProformaServicesContext
|
||||||
|
) => Promise<Result<Proforma, Error>>;
|
||||||
|
|
||||||
|
getProformaSnapshotById: (
|
||||||
|
id: UniqueID,
|
||||||
|
context: IProformaServicesContext
|
||||||
|
) => Promise<Result<IProformaFullSnapshot, Error>>;
|
||||||
|
|
||||||
|
generateProformaReport: (id: unknown, options: unknown, context: unknown) => null;
|
||||||
|
}
|
||||||
@ -1,134 +0,0 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import {
|
|
||||||
CustomerInvoiceIsProformaSpecification,
|
|
||||||
type InvoiceStatus,
|
|
||||||
ProformaCannotBeDeletedError,
|
|
||||||
StatusInvoiceIsDraftSpecification,
|
|
||||||
} from "../../../domain";
|
|
||||||
import type {
|
|
||||||
CustomerInvoice,
|
|
||||||
CustomerInvoicePatchProps,
|
|
||||||
CustomerInvoiceProps,
|
|
||||||
} from "../../../domain/aggregates";
|
|
||||||
import type { ICustomerInvoiceRepository } from "../../../domain/repositories";
|
|
||||||
import type { IProformaFactory } from "../../services/proforma-factory";
|
|
||||||
|
|
||||||
export type IIssuedInvoiceWriteService = {};
|
|
||||||
|
|
||||||
export class IssuedInvoiceWriteService implements IIssuedInvoiceWriteService {
|
|
||||||
constructor(
|
|
||||||
private readonly repository: ICustomerInvoiceRepository,
|
|
||||||
private readonly proformaFactory: IProformaFactory
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Crea y persiste una nueva proforma para una empresa.
|
|
||||||
*/
|
|
||||||
async createProforma(
|
|
||||||
companyId: UniqueID,
|
|
||||||
props: Omit<CustomerInvoiceProps, "companyId">,
|
|
||||||
transaction: Transaction
|
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
|
||||||
const invoiceResult = this.proformaFactory.createProforma(companyId, props);
|
|
||||||
if (invoiceResult.isFailure) {
|
|
||||||
return Result.fail(invoiceResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.repository.create(invoiceResult.data, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actualiza una proforma existente.
|
|
||||||
*/
|
|
||||||
async updateProforma(
|
|
||||||
companyId: UniqueID,
|
|
||||||
proforma: CustomerInvoice,
|
|
||||||
transaction: Transaction
|
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
|
||||||
return this.repository.update(proforma, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aplica cambios parciales a una proforma existente.
|
|
||||||
* No persiste automáticamente.
|
|
||||||
*/
|
|
||||||
async patchProforma(
|
|
||||||
companyId: UniqueID,
|
|
||||||
proformaId: UniqueID,
|
|
||||||
changes: CustomerInvoicePatchProps,
|
|
||||||
transaction: Transaction
|
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
|
||||||
const proformaResult = await this.repository.getProformaByIdInCompany(
|
|
||||||
companyId,
|
|
||||||
proformaId,
|
|
||||||
transaction,
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (proformaResult.isFailure) {
|
|
||||||
return Result.fail(proformaResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = proformaResult.data.update(changes);
|
|
||||||
if (updated.isFailure) {
|
|
||||||
return Result.fail(updated.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(updated.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Elimina (baja lógica) una proforma.
|
|
||||||
*/
|
|
||||||
async deleteProforma(
|
|
||||||
companyId: UniqueID,
|
|
||||||
proformaId: UniqueID,
|
|
||||||
transaction: Transaction
|
|
||||||
): Promise<Result<boolean, Error>> {
|
|
||||||
const proformaResult = await this.repository.getProformaByIdInCompany(
|
|
||||||
companyId,
|
|
||||||
proformaId,
|
|
||||||
transaction,
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
if (proformaResult.isFailure) {
|
|
||||||
return Result.fail(proformaResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const proforma = proformaResult.data;
|
|
||||||
|
|
||||||
const isProforma = new CustomerInvoiceIsProformaSpecification();
|
|
||||||
if (!(await isProforma.isSatisfiedBy(proforma))) {
|
|
||||||
return Result.fail(new ProformaCannotBeDeletedError(proformaId.toString(), "not a proforma"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDraft = new StatusInvoiceIsDraftSpecification();
|
|
||||||
if (!(await isDraft.isSatisfiedBy(proforma))) {
|
|
||||||
return Result.fail(
|
|
||||||
new ProformaCannotBeDeletedError(proformaId.toString(), "status is not 'draft'")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.repository.deleteProformaByIdInCompany(companyId, proformaId, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actualiza el estado de una proforma.
|
|
||||||
*/
|
|
||||||
async updateProformaStatus(
|
|
||||||
companyId: UniqueID,
|
|
||||||
proformaId: UniqueID,
|
|
||||||
newStatus: InvoiceStatus,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<boolean, Error>> {
|
|
||||||
return this.repository.updateProformaStatusByIdInCompany(
|
|
||||||
companyId,
|
|
||||||
proformaId,
|
|
||||||
newStatus,
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -9,8 +9,8 @@ export interface IProformaItemFullSnapshot {
|
|||||||
|
|
||||||
subtotal_amount: { value: string; scale: string; currency_code: string };
|
subtotal_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|
||||||
discount_percentage: { value: string; scale: string };
|
item_discount_percentage: { value: string; scale: string };
|
||||||
discount_amount: { value: string; scale: string; currency_code: string };
|
item_discount_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|
||||||
global_discount_percentage: { value: string; scale: string };
|
global_discount_percentage: { value: string; scale: string };
|
||||||
global_discount_amount: { value: string; scale: string; currency_code: string };
|
global_discount_amount: { value: string; scale: string; currency_code: string };
|
||||||
|
|||||||
@ -32,8 +32,10 @@ export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnaps
|
|||||||
? allAmounts.subtotalAmount.toObjectString()
|
? allAmounts.subtotalAmount.toObjectString()
|
||||||
: ItemAmount.EMPTY_MONEY_OBJECT,
|
: ItemAmount.EMPTY_MONEY_OBJECT,
|
||||||
|
|
||||||
discount_percentage: maybeToEmptyPercentageObjectString(proformaItem.itemDiscountPercentage),
|
item_discount_percentage: maybeToEmptyPercentageObjectString(
|
||||||
discount_amount: isValued
|
proformaItem.itemDiscountPercentage
|
||||||
|
),
|
||||||
|
item_discount_amount: isValued
|
||||||
? allAmounts.itemDiscountAmount.toObjectString()
|
? allAmounts.itemDiscountAmount.toObjectString()
|
||||||
: ItemAmount.EMPTY_MONEY_OBJECT,
|
: ItemAmount.EMPTY_MONEY_OBJECT,
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
|
||||||
import type { ISnapshotBuilder, ISnapshotBuilderParams } from "@erp/core/api";
|
import type { ISnapshotBuilder, ISnapshotBuilderParams } from "@erp/core/api";
|
||||||
|
|
||||||
import type { ProformaFullSnapshot, ProformaReportItemSnapshot } from "../../application-models";
|
import type { IProformaFullSnapshot } from "../full";
|
||||||
|
|
||||||
|
import type { ProformaReportItemSnapshot } from "./proforma-report-item-snapshot.interface";
|
||||||
|
|
||||||
export interface IProformaItemReportSnapshotBuilder
|
export interface IProformaItemReportSnapshotBuilder
|
||||||
extends ISnapshotBuilder<ProformaFullSnapshot["items"], ProformaReportItemSnapshot[]> {}
|
extends ISnapshotBuilder<IProformaFullSnapshot["items"], ProformaReportItemSnapshot[]> {}
|
||||||
|
|
||||||
export class ProformaItemReportSnapshotBuilder implements IProformaItemReportSnapshotBuilder {
|
export class ProformaItemReportSnapshotBuilder implements IProformaItemReportSnapshotBuilder {
|
||||||
toOutput(
|
toOutput(
|
||||||
items: ProformaFullSnapshot["items"],
|
items: IProformaFullSnapshot["items"],
|
||||||
params?: ISnapshotBuilderParams
|
params?: ISnapshotBuilderParams
|
||||||
): ProformaReportItemSnapshot[] {
|
): ProformaReportItemSnapshot[] {
|
||||||
const locale = params?.locale as string;
|
const locale = params?.locale as string;
|
||||||
|
|||||||
@ -1,30 +1,29 @@
|
|||||||
import { DateHelper, MoneyDTOHelper, PercentageDTOHelper } from "@erp/core";
|
import { DateHelper, MoneyDTOHelper, PercentageDTOHelper } from "@erp/core";
|
||||||
import type { ISnapshotBuilder, ISnapshotBuilderParams } from "@erp/core/api";
|
import type { ISnapshotBuilder, ISnapshotBuilderParams } from "@erp/core/api";
|
||||||
|
|
||||||
import type {
|
import type { IProformaFullSnapshot } from "../full";
|
||||||
ProformaFullSnapshot,
|
|
||||||
ProformaReportItemSnapshot,
|
import type { ProformaReportItemSnapshot } from "./proforma-report-item-snapshot.interface";
|
||||||
ProformaReportSnapshot,
|
import type { ProformaReportSnapshot } from "./proforma-report-snapshot.interface";
|
||||||
ProformaReportTaxSnapshot,
|
import type { ProformaReportTaxSnapshot } from "./proforma-report-tax-snapshot.interface";
|
||||||
} from "../../application-models";
|
|
||||||
|
|
||||||
export interface IProformaReportSnapshotBuilder
|
export interface IProformaReportSnapshotBuilder
|
||||||
extends ISnapshotBuilder<ProformaFullSnapshot, ProformaReportSnapshot> {}
|
extends ISnapshotBuilder<IProformaFullSnapshot, ProformaReportSnapshot> {}
|
||||||
|
|
||||||
export class ProformaReportSnapshotBuilder implements IProformaReportSnapshotBuilder {
|
export class ProformaReportSnapshotBuilder implements IProformaReportSnapshotBuilder {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly itemsBuilder: ISnapshotBuilder<
|
private readonly itemsBuilder: ISnapshotBuilder<
|
||||||
ProformaFullSnapshot["items"],
|
IProformaFullSnapshot["items"],
|
||||||
ProformaReportItemSnapshot[]
|
ProformaReportItemSnapshot[]
|
||||||
>,
|
>,
|
||||||
private readonly taxesBuilder: ISnapshotBuilder<
|
private readonly taxesBuilder: ISnapshotBuilder<
|
||||||
ProformaFullSnapshot["taxes"],
|
IProformaFullSnapshot["taxes"],
|
||||||
ProformaReportTaxSnapshot[]
|
ProformaReportTaxSnapshot[]
|
||||||
>
|
>
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
toOutput(
|
toOutput(
|
||||||
snapshot: ProformaFullSnapshot,
|
snapshot: IProformaFullSnapshot,
|
||||||
params?: ISnapshotBuilderParams
|
params?: ISnapshotBuilderParams
|
||||||
): ProformaReportSnapshot {
|
): ProformaReportSnapshot {
|
||||||
const locale = params?.locale as string;
|
const locale = params?.locale as string;
|
||||||
@ -61,17 +60,37 @@ export class ProformaReportSnapshotBuilder implements IProformaReportSnapshotBui
|
|||||||
taxes: this.taxesBuilder.toOutput(snapshot.taxes, { locale }),
|
taxes: this.taxesBuilder.toOutput(snapshot.taxes, { locale }),
|
||||||
|
|
||||||
subtotal_amount: MoneyDTOHelper.format(snapshot.subtotal_amount, locale, moneyOptions),
|
subtotal_amount: MoneyDTOHelper.format(snapshot.subtotal_amount, locale, moneyOptions),
|
||||||
discount_percentage: PercentageDTOHelper.format(snapshot.discount_percentage, locale, {
|
|
||||||
|
items_discount_amount: MoneyDTOHelper.format(
|
||||||
|
snapshot.items_discount_amount,
|
||||||
|
locale,
|
||||||
|
moneyOptions
|
||||||
|
),
|
||||||
|
global_discount_percentage: PercentageDTOHelper.format(
|
||||||
|
snapshot.global_discount_percentage,
|
||||||
|
locale,
|
||||||
|
{
|
||||||
hideZeros: true,
|
hideZeros: true,
|
||||||
}),
|
}
|
||||||
discount_amount: MoneyDTOHelper.format(snapshot.discount_amount, locale, moneyOptions),
|
),
|
||||||
|
global_discount_amount: MoneyDTOHelper.format(
|
||||||
|
snapshot.global_discount_amount,
|
||||||
|
locale,
|
||||||
|
moneyOptions
|
||||||
|
),
|
||||||
|
total_discount_amount: MoneyDTOHelper.format(
|
||||||
|
snapshot.total_discount_amount,
|
||||||
|
locale,
|
||||||
|
moneyOptions
|
||||||
|
),
|
||||||
|
|
||||||
taxable_amount: MoneyDTOHelper.format(snapshot.taxable_amount, locale, moneyOptions),
|
taxable_amount: MoneyDTOHelper.format(snapshot.taxable_amount, locale, moneyOptions),
|
||||||
taxes_amount: MoneyDTOHelper.format(snapshot.taxes_amount, locale, moneyOptions),
|
taxes_amount: MoneyDTOHelper.format(snapshot.taxes_amount, locale, moneyOptions),
|
||||||
total_amount: MoneyDTOHelper.format(snapshot.total_amount, locale, moneyOptions),
|
total_amount: MoneyDTOHelper.format(snapshot.total_amount, locale, moneyOptions),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatAddress(recipient: ProformaFullSnapshot["recipient"]): string {
|
private formatAddress(recipient: IProformaFullSnapshot["recipient"]): string {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
|
||||||
if (recipient.street) lines.push(recipient.street);
|
if (recipient.street) lines.push(recipient.street);
|
||||||
|
|||||||
@ -27,8 +27,14 @@ export interface ProformaReportSnapshot {
|
|||||||
taxes: ProformaReportTaxSnapshot[];
|
taxes: ProformaReportTaxSnapshot[];
|
||||||
|
|
||||||
subtotal_amount: string;
|
subtotal_amount: string;
|
||||||
discount_percentage: string;
|
|
||||||
discount_amount: string;
|
items_discount_amount: string;
|
||||||
|
|
||||||
|
global_discount_percentage: string;
|
||||||
|
global_discount_amount: string;
|
||||||
|
|
||||||
|
total_discount_amount: string;
|
||||||
|
|
||||||
taxable_amount: string;
|
taxable_amount: string;
|
||||||
taxes_amount: string;
|
taxes_amount: string;
|
||||||
total_amount: string;
|
total_amount: string;
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { ITransactionManager } from "@erp/core/api";
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { CreateProformaRequestDTO } from "../../../../../common";
|
import type { CreateProformaRequestDTO } from "../../../../../common";
|
||||||
import type { ICreateProformaInputMapper } from "../../mappers";
|
import type { ICreateProformaInputMapper } from "../../mappers";
|
||||||
@ -44,7 +43,7 @@ export class CreateProformaUseCase {
|
|||||||
|
|
||||||
const { props, id } = mappedPropsResult.data;
|
const { props, id } = mappedPropsResult.data;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
return this.transactionManager.complete(async (transaction: unknown) => {
|
||||||
try {
|
try {
|
||||||
const createResult = await this.creator.create({ companyId, id, props, transaction });
|
const createResult = await this.creator.create({ companyId, id, props, transaction });
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,23 @@
|
|||||||
import type { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
import { UniqueID, UtcDate } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import type { IIssuedInvoicePublicServices } from "../../issued-invoices";
|
||||||
IssueCustomerInvoiceDomainService,
|
import type { IProformaFinder, IProformaIssuer } from "../services";
|
||||||
ProformaCustomerInvoiceDomainService,
|
|
||||||
} from "../../../domain";
|
|
||||||
import type { CustomerInvoiceApplicationService } from "../../services";
|
|
||||||
|
|
||||||
type IssueProformaUseCaseInput = {
|
type IssueProformaUseCaseInput = {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
proforma_id: string;
|
proforma_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type IssueProformaUseCaseDeps = {
|
||||||
|
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
||||||
|
finder: IProformaFinder;
|
||||||
|
issuer: IProformaIssuer;
|
||||||
|
transactionManager: ITransactionManager;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Caso de uso: Conversión de una proforma a factura definitiva.
|
* Caso de uso: Conversión de una issuedinvoice a factura definitiva.
|
||||||
*
|
*
|
||||||
* - Recupera la proforma
|
* - Recupera la proforma
|
||||||
* - Valida su estado ("approved")
|
* - Valida su estado ("approved")
|
||||||
@ -23,29 +26,30 @@ type IssueProformaUseCaseInput = {
|
|||||||
* - Persiste ambas dentro de la misma transacción
|
* - Persiste ambas dentro de la misma transacción
|
||||||
*/
|
*/
|
||||||
export class IssueProformaUseCase {
|
export class IssueProformaUseCase {
|
||||||
private readonly issueDomainService: IssueCustomerInvoiceDomainService;
|
private readonly issuedInvoiceServices: IIssuedInvoicePublicServices;
|
||||||
private readonly proformaDomainService: ProformaCustomerInvoiceDomainService;
|
private readonly finder: IProformaFinder;
|
||||||
|
private readonly issuer: IProformaIssuer;
|
||||||
|
private readonly transactionManager: ITransactionManager;
|
||||||
|
|
||||||
constructor(
|
constructor(deps: IssueProformaUseCaseDeps) {
|
||||||
private readonly service: CustomerInvoiceApplicationService,
|
this.issuedInvoiceServices = deps.issuedInvoiceServices;
|
||||||
private readonly transactionManager: ITransactionManager,
|
this.finder = deps.finder;
|
||||||
private readonly presenterRegistry: IPresenterRegistry
|
this.issuer = deps.issuer;
|
||||||
) {
|
this.transactionManager = deps.transactionManager;
|
||||||
this.issueDomainService = new IssueCustomerInvoiceDomainService();
|
|
||||||
this.proformaDomainService = new ProformaCustomerInvoiceDomainService();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public execute(params: IssueProformaUseCaseInput) {
|
public execute(params: IssueProformaUseCaseInput) {
|
||||||
const { proforma_id, companyId } = params;
|
const { proforma_id, companyId } = params;
|
||||||
const idOrError = UniqueID.create(proforma_id);
|
|
||||||
if (idOrError.isFailure) return Result.fail(idOrError.error);
|
|
||||||
|
|
||||||
const proformaId = idOrError.data;
|
const proformaIdOrError = UniqueID.create(proforma_id);
|
||||||
|
if (proformaIdOrError.isFailure) return Result.fail(proformaIdOrError.error);
|
||||||
|
|
||||||
|
const proformaId = proformaIdOrError.data;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
/** 1. Recuperamos la proforma */
|
// 1. Recuperamos la issuedinvoice
|
||||||
const proformaResult = await this.service.getProformaByIdInCompany(
|
const proformaResult = await this.finder.findProformaById(
|
||||||
companyId,
|
companyId,
|
||||||
proformaId,
|
proformaId,
|
||||||
transaction
|
transaction
|
||||||
@ -54,49 +58,39 @@ export class IssueProformaUseCase {
|
|||||||
if (proformaResult.isFailure) return Result.fail(proformaResult.error);
|
if (proformaResult.isFailure) return Result.fail(proformaResult.error);
|
||||||
const proforma = proformaResult.data;
|
const proforma = proformaResult.data;
|
||||||
|
|
||||||
/** 2. Generar nueva factura */
|
// 2. Generamos la factura definitiva y la guardamos
|
||||||
const nextNumberResult = await this.service.getNextIssuedInvoiceNumber(
|
const issuedInvoiceId = UniqueID.generateNewID();
|
||||||
|
const createPropsOrError = await this.issuer.issueProforma({
|
||||||
companyId,
|
companyId,
|
||||||
proforma.series,
|
issuedInvoiceId,
|
||||||
transaction
|
proforma,
|
||||||
);
|
transaction,
|
||||||
|
|
||||||
if (nextNumberResult.isFailure) return Result.fail(nextNumberResult.error);
|
|
||||||
|
|
||||||
/** 4. Crear factura definitiva (dominio) */
|
|
||||||
const issuedInvoiceOrError = await this.issueDomainService.issueFromProforma(proforma, {
|
|
||||||
issueNumber: nextNumberResult.data,
|
|
||||||
issueDate: UtcDate.today(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (issuedInvoiceOrError.isFailure) return Result.fail(issuedInvoiceOrError.error);
|
if (createPropsOrError.isFailure) {
|
||||||
|
return Result.fail(createPropsOrError.error);
|
||||||
|
}
|
||||||
|
|
||||||
/** 5. Guardar la nueva factura */
|
const createProps = createPropsOrError.data;
|
||||||
const saveInvoiceResult = await this.service.createIssuedInvoiceInCompany(
|
|
||||||
|
// Creamos y guardamos en persistencia la factura definitiva
|
||||||
|
const invoiceResult = await this.issuedInvoiceServices.createIssuedInvoice(
|
||||||
|
issuedInvoiceId,
|
||||||
|
createProps,
|
||||||
|
{
|
||||||
companyId,
|
companyId,
|
||||||
issuedInvoiceOrError.data,
|
transaction,
|
||||||
transaction
|
}
|
||||||
);
|
|
||||||
if (saveInvoiceResult.isFailure) return Result.fail(saveInvoiceResult.error);
|
|
||||||
|
|
||||||
/** 6. Actualizar la proforma */
|
|
||||||
const closedProformaResult = await this.proformaDomainService.markAsIssued(proforma);
|
|
||||||
if (closedProformaResult.isFailure) return Result.fail(closedProformaResult.error);
|
|
||||||
const closedProforma = closedProformaResult.data;
|
|
||||||
|
|
||||||
/** 7. Guardar la proforma */
|
|
||||||
await this.service.updateProformaStatusByIdInCompany(
|
|
||||||
companyId,
|
|
||||||
proformaId,
|
|
||||||
closedProforma.status,
|
|
||||||
transaction
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const invoice = saveInvoiceResult.data;
|
if (invoiceResult.isFailure) {
|
||||||
|
return Result.fail(invoiceResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
const dto = {
|
const dto = {
|
||||||
proforma_id: proforma.id.toString(),
|
issuedinvoice_id: issuedInvoiceId.toString(),
|
||||||
invoice_id: invoice.id.toString(),
|
proforma_id: proformaId.toString(),
|
||||||
customer_id: invoice.customerId.toString(),
|
customer_id: proforma.customerId.toString(),
|
||||||
};
|
};
|
||||||
return Result.ok(dto);
|
return Result.ok(dto);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import type { ITransactionManager } from "@erp/core/api";
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { IProformaFinder } from "../services";
|
import type { IProformaFinder } from "../services";
|
||||||
import type { IProformaSummarySnapshotBuilder } from "../snapshot-builders";
|
import type { IProformaSummarySnapshotBuilder } from "../snapshot-builders";
|
||||||
@ -22,7 +21,7 @@ export class ListProformasUseCase {
|
|||||||
public execute(params: ListProformasUseCaseInput) {
|
public execute(params: ListProformasUseCaseInput) {
|
||||||
const { criteria, companyId } = params;
|
const { criteria, companyId } = params;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
return this.transactionManager.complete(async (transaction: unknown) => {
|
||||||
try {
|
try {
|
||||||
const result = await this.finder.findProformasByCriteria(companyId, criteria, transaction);
|
const result = await this.finder.findProformasByCriteria(companyId, criteria, transaction);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
|
import type { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { UpdateProformaByIdRequestDTO } from "../../../../../common";
|
import type { UpdateProformaByIdRequestDTO } from "../../../../../common";
|
||||||
import type { ProformaPatchProps } from "../../../../domain";
|
import type { ProformaPatchProps } from "../../../../domain";
|
||||||
@ -43,7 +42,7 @@ export class UpdateProformaUseCase {
|
|||||||
|
|
||||||
const patchProps: ProformaPatchProps = patchPropsResult.data;
|
const patchProps: ProformaPatchProps = patchPropsResult.data;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
return this.transactionManager.complete(async (transaction: unknown) => {
|
||||||
try {
|
try {
|
||||||
const updatedInvoice = await this.service.patchProformaByIdInCompany(
|
const updatedInvoice = await this.service.patchProformaByIdInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { type Collection, Maybe, Result } from "@repo/rdx-utils";
|
import { type Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CustomerInvoiceIsProformaSpecification,
|
CustomerInvoiceIsProformaSpecification,
|
||||||
@ -35,7 +34,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
*/
|
*/
|
||||||
async getNextProformaNumber(
|
async getNextProformaNumber(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
transaction: Transaction
|
transaction: unknown
|
||||||
): Promise<Result<InvoiceNumber, Error>> {
|
): Promise<Result<InvoiceNumber, Error>> {
|
||||||
return await this.numberGenerator.nextForCompany(companyId, Maybe.none(), transaction);
|
return await this.numberGenerator.nextForCompany(companyId, Maybe.none(), transaction);
|
||||||
}
|
}
|
||||||
@ -51,7 +50,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async getNextIssuedInvoiceNumber(
|
async getNextIssuedInvoiceNumber(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
series: Maybe<InvoiceSerie>,
|
series: Maybe<InvoiceSerie>,
|
||||||
transaction: Transaction
|
transaction: unknown
|
||||||
): Promise<Result<InvoiceNumber, Error>> {
|
): Promise<Result<InvoiceNumber, Error>> {
|
||||||
return await this.numberGenerator.nextForCompany(companyId, series, transaction);
|
return await this.numberGenerator.nextForCompany(companyId, series, transaction);
|
||||||
}
|
}
|
||||||
@ -83,7 +82,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async createIssuedInvoiceInCompany(
|
async createIssuedInvoiceInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoice: CustomerInvoice,
|
invoice: CustomerInvoice,
|
||||||
transaction: Transaction
|
transaction: unknown
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
const result = await this.repository.create(invoice, transaction);
|
const result = await this.repository.create(invoice, transaction);
|
||||||
if (result.isFailure) {
|
if (result.isFailure) {
|
||||||
@ -104,7 +103,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async createProformaInCompany(
|
async createProformaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proforma: CustomerInvoice,
|
proforma: CustomerInvoice,
|
||||||
transaction: Transaction
|
transaction: unknown
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
const result = await this.repository.create(proforma, transaction);
|
const result = await this.repository.create(proforma, transaction);
|
||||||
if (result.isFailure) {
|
if (result.isFailure) {
|
||||||
@ -125,7 +124,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async updateProformaInCompany(
|
async updateProformaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proforma: CustomerInvoice,
|
proforma: CustomerInvoice,
|
||||||
transaction: Transaction
|
transaction: unknown
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
const result = await this.repository.update(proforma, transaction);
|
const result = await this.repository.update(proforma, transaction);
|
||||||
if (result.isFailure) {
|
if (result.isFailure) {
|
||||||
@ -148,7 +147,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async existsProformaByIdInCompany(
|
async existsProformaByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
return this.repository.existsByIdInCompany(companyId, proformaId, transaction, {
|
return this.repository.existsByIdInCompany(companyId, proformaId, transaction, {
|
||||||
is_proforma: true,
|
is_proforma: true,
|
||||||
@ -168,7 +167,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async existsIssuedInvoiceByIdInCompany(
|
async existsIssuedInvoiceByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
return this.repository.existsByIdInCompany(companyId, invoiceId, transaction, {
|
return this.repository.existsByIdInCompany(companyId, invoiceId, transaction, {
|
||||||
is_proforma: false,
|
is_proforma: false,
|
||||||
@ -186,7 +185,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async findProformasByCriteriaInCompany(
|
async findProformasByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>> {
|
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>> {
|
||||||
return this.repository.findProformasByCriteriaInCompany(companyId, criteria, transaction, {});
|
return this.repository.findProformasByCriteriaInCompany(companyId, criteria, transaction, {});
|
||||||
}
|
}
|
||||||
@ -202,7 +201,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async findIssuedInvoiceByCriteriaInCompany(
|
async findIssuedInvoiceByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>> {
|
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>> {
|
||||||
return this.repository.findIssuedInvoicesByCriteriaInCompany(
|
return this.repository.findIssuedInvoicesByCriteriaInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
@ -222,7 +221,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async getIssuedInvoiceByIdInCompany(
|
async getIssuedInvoiceByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<CustomerInvoice>> {
|
): Promise<Result<CustomerInvoice>> {
|
||||||
return await this.repository.getIssuedInvoiceByIdInCompany(
|
return await this.repository.getIssuedInvoiceByIdInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
@ -242,7 +241,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async getProformaByIdInCompany(
|
async getProformaByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<CustomerInvoice>> {
|
): Promise<Result<CustomerInvoice>> {
|
||||||
return await this.repository.getProformaByIdInCompany(companyId, proformaId, transaction, {});
|
return await this.repository.getProformaByIdInCompany(companyId, proformaId, transaction, {});
|
||||||
}
|
}
|
||||||
@ -261,7 +260,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
changes: CustomerInvoicePatchProps,
|
changes: CustomerInvoicePatchProps,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
const proformaResult = await this.getProformaByIdInCompany(companyId, proformaId, transaction);
|
const proformaResult = await this.getProformaByIdInCompany(companyId, proformaId, transaction);
|
||||||
|
|
||||||
@ -289,7 +288,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
async deleteProformaByIdInCompany(
|
async deleteProformaByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
// 1) Buscar la proforma
|
// 1) Buscar la proforma
|
||||||
const proformaResult = await this.getProformaByIdInCompany(companyId, proformaId, transaction);
|
const proformaResult = await this.getProformaByIdInCompany(companyId, proformaId, transaction);
|
||||||
@ -329,7 +328,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proformaId: UniqueID,
|
proformaId: UniqueID,
|
||||||
newStatus: InvoiceStatus,
|
newStatus: InvoiceStatus,
|
||||||
transaction?: Transaction
|
transaction?: unknown
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
return this.repository.updateProformaStatusByIdInCompany(
|
return this.repository.updateProformaStatusByIdInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
|
|||||||
@ -32,9 +32,11 @@ export class InvoiceAmount extends MoneyValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure fluent operations keep the subclass type
|
// Ensure fluent operations keep the subclass type
|
||||||
convertScale(newScale: number) {
|
roundUsingScale(intermediateScale: number) {
|
||||||
const mv = super.convertScale(newScale);
|
const scaled = super.convertScale(intermediateScale);
|
||||||
const p = mv.toPrimitive();
|
const normalized = scaled.convertScale(InvoiceAmount.DEFAULT_SCALE);
|
||||||
|
const p = normalized.toPrimitive();
|
||||||
|
|
||||||
return new InvoiceAmount({
|
return new InvoiceAmount({
|
||||||
value: p.value,
|
value: p.value,
|
||||||
currency_code: p.currency_code,
|
currency_code: p.currency_code,
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export enum INVOICE_STATUS {
|
|||||||
const INVOICE_TRANSITIONS: Record<string, string[]> = {
|
const INVOICE_TRANSITIONS: Record<string, string[]> = {
|
||||||
draft: [INVOICE_STATUS.SENT],
|
draft: [INVOICE_STATUS.SENT],
|
||||||
sent: [INVOICE_STATUS.APPROVED, INVOICE_STATUS.REJECTED],
|
sent: [INVOICE_STATUS.APPROVED, INVOICE_STATUS.REJECTED],
|
||||||
approved: [INVOICE_STATUS.ISSUED, INVOICE_STATUS.DRAFT],
|
approved: [INVOICE_STATUS.ISSUED, INVOICE_STATUS.REJECTED, INVOICE_STATUS.DRAFT],
|
||||||
rejected: [INVOICE_STATUS.DRAFT],
|
rejected: [INVOICE_STATUS.DRAFT],
|
||||||
issued: [],
|
issued: [],
|
||||||
};
|
};
|
||||||
@ -39,34 +39,34 @@ export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
|
|||||||
|
|
||||||
return Result.ok(
|
return Result.ok(
|
||||||
value === "rejected"
|
value === "rejected"
|
||||||
? InvoiceStatus.fromRejected()
|
? InvoiceStatus.rejected()
|
||||||
: value === "sent"
|
: value === "sent"
|
||||||
? InvoiceStatus.fromSent()
|
? InvoiceStatus.sent()
|
||||||
: value === "issued"
|
: value === "issued"
|
||||||
? InvoiceStatus.fromIssued()
|
? InvoiceStatus.issued()
|
||||||
: value === "approved"
|
: value === "approved"
|
||||||
? InvoiceStatus.fromApproved()
|
? InvoiceStatus.approved()
|
||||||
: InvoiceStatus.fromDraft()
|
: InvoiceStatus.draft()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromDraft(): InvoiceStatus {
|
public static draft(): InvoiceStatus {
|
||||||
return new InvoiceStatus({ value: INVOICE_STATUS.DRAFT });
|
return new InvoiceStatus({ value: INVOICE_STATUS.DRAFT });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromIssued(): InvoiceStatus {
|
public static issued(): InvoiceStatus {
|
||||||
return new InvoiceStatus({ value: INVOICE_STATUS.ISSUED });
|
return new InvoiceStatus({ value: INVOICE_STATUS.ISSUED });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromSent(): InvoiceStatus {
|
public static sent(): InvoiceStatus {
|
||||||
return new InvoiceStatus({ value: INVOICE_STATUS.SENT });
|
return new InvoiceStatus({ value: INVOICE_STATUS.SENT });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromApproved(): InvoiceStatus {
|
public static approved(): InvoiceStatus {
|
||||||
return new InvoiceStatus({ value: INVOICE_STATUS.APPROVED });
|
return new InvoiceStatus({ value: INVOICE_STATUS.APPROVED });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromRejected(): InvoiceStatus {
|
public static rejected(): InvoiceStatus {
|
||||||
return new InvoiceStatus({ value: INVOICE_STATUS.REJECTED });
|
return new InvoiceStatus({ value: INVOICE_STATUS.REJECTED });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,9 +32,11 @@ export class ItemAmount extends MoneyValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure fluent operations keep the subclass type
|
// Ensure fluent operations keep the subclass type
|
||||||
convertScale(newScale: number) {
|
roundUsingScale(intermediateScale: number) {
|
||||||
const mv = super.convertScale(newScale);
|
const scaled = super.convertScale(intermediateScale);
|
||||||
const p = mv.toPrimitive();
|
const normalized = scaled.convertScale(ItemAmount.DEFAULT_SCALE);
|
||||||
|
const p = normalized.toPrimitive();
|
||||||
|
|
||||||
return new ItemAmount({
|
return new ItemAmount({
|
||||||
value: p.value,
|
value: p.value,
|
||||||
currency_code: p.currency_code,
|
currency_code: p.currency_code,
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
|
||||||
import type { Result } from "@repo/rdx-utils";
|
|
||||||
|
|
||||||
type ItemDiscountPercentageProps = Pick<PercentageProps, "value">;
|
|
||||||
|
|
||||||
export class ItemDiscountPercentage extends Percentage {
|
|
||||||
static DEFAULT_SCALE = 2;
|
|
||||||
|
|
||||||
static create({ value }: ItemDiscountPercentageProps): Result<Percentage> {
|
|
||||||
return Percentage.create({
|
|
||||||
value,
|
|
||||||
scale: ItemDiscountPercentage.DEFAULT_SCALE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static zero() {
|
|
||||||
return ItemDiscountPercentage.create({ value: 0 }).data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -19,13 +19,20 @@ import type {
|
|||||||
InvoiceSerie,
|
InvoiceSerie,
|
||||||
InvoiceStatus,
|
InvoiceStatus,
|
||||||
} from "../../common";
|
} from "../../common";
|
||||||
import { IssuedInvoiceItems, type IssuedInvoiceTaxes, type VerifactuRecord } from "../entities";
|
import {
|
||||||
|
type IIssuedInvoiceItemCreateProps,
|
||||||
|
IssuedInvoiceItem,
|
||||||
|
IssuedInvoiceItems,
|
||||||
|
type IssuedInvoiceTaxes,
|
||||||
|
type VerifactuRecord,
|
||||||
|
} from "../entities";
|
||||||
|
import { IssuedInvoiceItemMismatch } from "../errors";
|
||||||
|
|
||||||
export interface IIssuedInvoiceProps {
|
export interface IIssuedInvoiceCreateProps {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
status: InvoiceStatus;
|
status: InvoiceStatus;
|
||||||
|
|
||||||
proformaId: Maybe<UniqueID>; // <- proforma padre en caso de issue
|
proformaId: UniqueID; // <- id de la proforma padre en caso de issue
|
||||||
|
|
||||||
series: Maybe<InvoiceSerie>;
|
series: Maybe<InvoiceSerie>;
|
||||||
invoiceNumber: InvoiceNumber;
|
invoiceNumber: InvoiceNumber;
|
||||||
@ -45,7 +52,7 @@ export interface IIssuedInvoiceProps {
|
|||||||
|
|
||||||
paymentMethod: Maybe<InvoicePaymentMethod>;
|
paymentMethod: Maybe<InvoicePaymentMethod>;
|
||||||
|
|
||||||
items: IssuedInvoiceItems;
|
items: IIssuedInvoiceItemCreateProps[];
|
||||||
taxes: IssuedInvoiceTaxes;
|
taxes: IssuedInvoiceTaxes;
|
||||||
|
|
||||||
subtotalAmount: InvoiceAmount;
|
subtotalAmount: InvoiceAmount;
|
||||||
@ -67,21 +74,28 @@ export interface IIssuedInvoiceProps {
|
|||||||
verifactu: Maybe<VerifactuRecord>;
|
verifactu: Maybe<VerifactuRecord>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IssuedInvoice extends AggregateRoot<IIssuedInvoiceProps> {
|
export interface IIssuedInvoice {
|
||||||
private _items!: IssuedInvoiceItems;
|
companyId: UniqueID;
|
||||||
|
}
|
||||||
|
|
||||||
protected constructor(props: IIssuedInvoiceProps, id?: UniqueID) {
|
export type InternalIssuedInvoiceProps = Omit<IIssuedInvoiceCreateProps, "items">;
|
||||||
|
|
||||||
|
export class IssuedInvoice
|
||||||
|
extends AggregateRoot<InternalIssuedInvoiceProps>
|
||||||
|
implements IIssuedInvoice
|
||||||
|
{
|
||||||
|
private readonly _items!: IssuedInvoiceItems;
|
||||||
|
|
||||||
|
protected constructor(
|
||||||
|
props: InternalIssuedInvoiceProps,
|
||||||
|
items: IssuedInvoiceItems,
|
||||||
|
id?: UniqueID
|
||||||
|
) {
|
||||||
super(props, id);
|
super(props, id);
|
||||||
this._items =
|
this._items = items;
|
||||||
props.items ||
|
|
||||||
IssuedInvoiceItems.create({
|
|
||||||
languageCode: props.languageCode,
|
|
||||||
currencyCode: props.currencyCode,
|
|
||||||
globalDiscountPercentage: props.globalDiscountPercentage,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(props: IIssuedInvoiceProps, id?: UniqueID): Result<IssuedInvoice, Error> {
|
static create(props: IIssuedInvoiceCreateProps, id?: UniqueID): Result<IssuedInvoice, Error> {
|
||||||
if (!props.recipient) {
|
if (!props.recipient) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new DomainValidationError(
|
new DomainValidationError(
|
||||||
@ -92,7 +106,21 @@ export class IssuedInvoice extends AggregateRoot<IIssuedInvoiceProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const issuedInvoice = new IssuedInvoice(props, id);
|
const internalItems = IssuedInvoiceItems.create({
|
||||||
|
items: [],
|
||||||
|
languageCode: props.languageCode,
|
||||||
|
currencyCode: props.currencyCode,
|
||||||
|
globalDiscountPercentage: props.globalDiscountPercentage,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { items, ...internalProps } = props;
|
||||||
|
const issuedInvoice = new IssuedInvoice(internalProps, internalItems, id);
|
||||||
|
|
||||||
|
const initializeResult = issuedInvoice.initializeItems(items);
|
||||||
|
|
||||||
|
if (initializeResult.isFailure) {
|
||||||
|
return Result.fail(initializeResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
// Reglas de negocio / validaciones
|
// Reglas de negocio / validaciones
|
||||||
// ...
|
// ...
|
||||||
@ -104,6 +132,32 @@ export class IssuedInvoice extends AggregateRoot<IIssuedInvoiceProps> {
|
|||||||
return Result.ok(issuedInvoice);
|
return Result.ok(issuedInvoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rehidratación desde persistencia
|
||||||
|
static rehydrate(
|
||||||
|
props: InternalIssuedInvoiceProps,
|
||||||
|
items: IssuedInvoiceItems,
|
||||||
|
id: UniqueID
|
||||||
|
): IssuedInvoice {
|
||||||
|
return new IssuedInvoice(props, items, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeItems(itemsProps: IIssuedInvoiceItemCreateProps[]): Result<void, Error> {
|
||||||
|
for (const [index, itemProps] of itemsProps.entries()) {
|
||||||
|
const itemResult = IssuedInvoiceItem.create(itemProps);
|
||||||
|
|
||||||
|
if (itemResult.isFailure) {
|
||||||
|
return Result.fail(itemResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const added = this._items.add(itemResult.data);
|
||||||
|
|
||||||
|
if (!added) {
|
||||||
|
return Result.fail(new IssuedInvoiceItemMismatch(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result.ok();
|
||||||
|
}
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
|
|
||||||
public get companyId(): UniqueID {
|
public get companyId(): UniqueID {
|
||||||
@ -114,7 +168,7 @@ export class IssuedInvoice extends AggregateRoot<IIssuedInvoiceProps> {
|
|||||||
return this.props.customerId;
|
return this.props.customerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get proformaId(): Maybe<UniqueID> {
|
public get proformaId(): UniqueID {
|
||||||
return this.props.proformaId;
|
return this.props.proformaId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,8 +284,4 @@ export class IssuedInvoice extends AggregateRoot<IIssuedInvoiceProps> {
|
|||||||
public get hasPaymentMethod() {
|
public get hasPaymentMethod() {
|
||||||
return this.paymentMethod.isSome();
|
return this.paymentMethod.isSome();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getProps(): IIssuedInvoiceProps {
|
|
||||||
return this.props;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import type { ItemAmount, ItemDescription, ItemQuantity } from "../../../common"
|
|||||||
* Todos los importes están previamente calculados y congelados.
|
* Todos los importes están previamente calculados y congelados.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type IssuedInvoiceItemProps = {
|
export interface IIssuedInvoiceItemCreateProps {
|
||||||
description: Maybe<ItemDescription>;
|
description: Maybe<ItemDescription>;
|
||||||
|
|
||||||
quantity: Maybe<ItemQuantity>;
|
quantity: Maybe<ItemQuantity>;
|
||||||
@ -49,18 +49,55 @@ export type IssuedInvoiceItemProps = {
|
|||||||
|
|
||||||
languageCode: LanguageCode;
|
languageCode: LanguageCode;
|
||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
};
|
|
||||||
|
|
||||||
export interface IIssuedInvoiceItem extends IssuedInvoiceItemProps {
|
|
||||||
isValued(): boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IIssuedInvoiceItem {
|
||||||
|
isValued(): boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado"
|
||||||
|
|
||||||
|
description: Maybe<ItemDescription>;
|
||||||
|
|
||||||
|
quantity: Maybe<ItemQuantity>;
|
||||||
|
unitAmount: Maybe<ItemAmount>;
|
||||||
|
|
||||||
|
subtotalAmount: ItemAmount;
|
||||||
|
|
||||||
|
itemDiscountPercentage: Maybe<DiscountPercentage>;
|
||||||
|
itemDiscountAmount: ItemAmount;
|
||||||
|
|
||||||
|
globalDiscountPercentage: DiscountPercentage;
|
||||||
|
globalDiscountAmount: ItemAmount;
|
||||||
|
|
||||||
|
totalDiscountAmount: ItemAmount;
|
||||||
|
|
||||||
|
taxableAmount: ItemAmount;
|
||||||
|
|
||||||
|
ivaCode: Maybe<string>;
|
||||||
|
ivaPercentage: Maybe<DiscountPercentage>;
|
||||||
|
ivaAmount: ItemAmount;
|
||||||
|
|
||||||
|
recCode: Maybe<string>;
|
||||||
|
recPercentage: Maybe<DiscountPercentage>;
|
||||||
|
recAmount: ItemAmount;
|
||||||
|
|
||||||
|
retentionCode: Maybe<string>;
|
||||||
|
retentionPercentage: Maybe<DiscountPercentage>;
|
||||||
|
retentionAmount: ItemAmount;
|
||||||
|
|
||||||
|
taxesAmount: ItemAmount;
|
||||||
|
totalAmount: ItemAmount;
|
||||||
|
|
||||||
|
languageCode: LanguageCode;
|
||||||
|
currencyCode: CurrencyCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternalIssuedInvoiceItemProps = IIssuedInvoiceItemCreateProps;
|
||||||
|
|
||||||
export class IssuedInvoiceItem
|
export class IssuedInvoiceItem
|
||||||
extends DomainEntity<IssuedInvoiceItemProps>
|
extends DomainEntity<InternalIssuedInvoiceItemProps>
|
||||||
implements IIssuedInvoiceItem
|
implements IIssuedInvoiceItem
|
||||||
{
|
{
|
||||||
public static create(
|
public static create(
|
||||||
props: IssuedInvoiceItemProps,
|
props: IIssuedInvoiceItemCreateProps,
|
||||||
id?: UniqueID
|
id?: UniqueID
|
||||||
): Result<IssuedInvoiceItem, Error> {
|
): Result<IssuedInvoiceItem, Error> {
|
||||||
const item = new IssuedInvoiceItem(props, id);
|
const item = new IssuedInvoiceItem(props, id);
|
||||||
@ -72,7 +109,11 @@ export class IssuedInvoiceItem
|
|||||||
return Result.ok(item);
|
return Result.ok(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected constructor(props: IssuedInvoiceItemProps, id?: UniqueID) {
|
static rehydrate(props: InternalIssuedInvoiceItemProps, id: UniqueID): IssuedInvoiceItem {
|
||||||
|
return new IssuedInvoiceItem(props, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected constructor(props: InternalIssuedInvoiceItemProps, id?: UniqueID) {
|
||||||
super(props, id);
|
super(props, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +204,7 @@ export class IssuedInvoiceItem
|
|||||||
return this.props.totalAmount;
|
return this.props.totalAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProps(): IssuedInvoiceItemProps {
|
getProps(): IIssuedInvoiceItemCreateProps {
|
||||||
return this.props;
|
return this.props;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./issued-invoice-item-not-valid-error";
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { DomainError } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error de dominio que indica que al añadir un nuevo item a la lista
|
||||||
|
* de detalles, este no tiene el mismo "currencyCode" y "languageCode"
|
||||||
|
* que la colección de items.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export class IssuedInvoiceItemMismatch extends DomainError {
|
||||||
|
/**
|
||||||
|
* Crea una instancia del error con el identificador del item.
|
||||||
|
*
|
||||||
|
* @param position - Posición del item
|
||||||
|
* @param options - Opciones nativas de Error (puedes pasar `cause`).
|
||||||
|
*/
|
||||||
|
constructor(position: number, options?: ErrorOptions) {
|
||||||
|
super(
|
||||||
|
`Error. IssuedInvoice item with position '${position}' rejected due to currency/language mismatch.`,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
this.name = "IssuedInvoiceItemMismatch";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* *Type guard* para `IssuedInvoiceItemNotValid`.
|
||||||
|
*
|
||||||
|
* @param e - Error desconocido
|
||||||
|
* @returns `true` si `e` es `IssuedInvoiceItemNotValid`
|
||||||
|
*/
|
||||||
|
export const isIssuedInvoiceItemMismatch = (e: unknown): e is IssuedInvoiceItemMismatch =>
|
||||||
|
e instanceof IssuedInvoiceItemMismatch;
|
||||||
@ -1,3 +1,4 @@
|
|||||||
export * from "./aggregates";
|
export * from "./aggregates";
|
||||||
export * from "./entities";
|
export * from "./entities";
|
||||||
|
export * from "./errors";
|
||||||
export * from "./value-objects";
|
export * from "./value-objects";
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
type InvoiceNumber,
|
type InvoiceNumber,
|
||||||
type InvoiceRecipient,
|
type InvoiceRecipient,
|
||||||
type InvoiceSerie,
|
type InvoiceSerie,
|
||||||
type InvoiceStatus,
|
InvoiceStatus,
|
||||||
type ItemAmount,
|
type ItemAmount,
|
||||||
} from "../../common/value-objects";
|
} from "../../common/value-objects";
|
||||||
import {
|
import {
|
||||||
@ -27,7 +27,7 @@ import {
|
|||||||
ProformaItems,
|
ProformaItems,
|
||||||
} from "../entities";
|
} from "../entities";
|
||||||
import { ProformaItemMismatch } from "../errors";
|
import { ProformaItemMismatch } from "../errors";
|
||||||
import { type IProformaTaxTotals, ProformaTaxesCalculator } from "../services";
|
import type { IProformaTaxTotals } from "../services";
|
||||||
import { ProformaItemTaxes } from "../value-objects";
|
import { ProformaItemTaxes } from "../value-objects";
|
||||||
|
|
||||||
export interface IProformaCreateProps {
|
export interface IProformaCreateProps {
|
||||||
@ -249,7 +249,7 @@ export class Proforma extends AggregateRoot<InternalProformaProps> implements IP
|
|||||||
}
|
}
|
||||||
|
|
||||||
public issue(): Result<void, Error> {
|
public issue(): Result<void, Error> {
|
||||||
if (!this.props.status.canTransitionTo("ISSUED")) {
|
if (!this.props.status.canTransitionTo("issued")) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new DomainValidationError(
|
new DomainValidationError(
|
||||||
"INVALID_STATE",
|
"INVALID_STATE",
|
||||||
@ -259,8 +259,7 @@ export class Proforma extends AggregateRoot<InternalProformaProps> implements IP
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Falta
|
this.props.status = InvoiceStatus.issued();
|
||||||
//this.props.status = this.props.status.canTransitionTo("ISSUED");
|
|
||||||
return Result.ok();
|
return Result.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +291,7 @@ export class Proforma extends AggregateRoot<InternalProformaProps> implements IP
|
|||||||
}
|
}
|
||||||
|
|
||||||
public taxes(): Collection<IProformaTaxTotals> {
|
public taxes(): Collection<IProformaTaxTotals> {
|
||||||
return new ProformaTaxesCalculator(this.items).calculate();
|
return this.items.taxes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public addItem(props: IProformaItemCreateProps): Result<void, Error> {
|
public addItem(props: IProformaItemCreateProps): Result<void, Error> {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
|||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ProformaItemMismatch } from "../../errors";
|
import { ProformaItemMismatch } from "../../errors";
|
||||||
|
import { type IProformaTaxTotals, ProformaTaxesCalculator } from "../../services";
|
||||||
import { ProformaItemsTotalsCalculator } from "../../services/proforma-items-totals-calculator";
|
import { ProformaItemsTotalsCalculator } from "../../services/proforma-items-totals-calculator";
|
||||||
|
|
||||||
import type { IProformaItem, IProformaItemTotals, ProformaItem } from "./proforma-item.entity";
|
import type { IProformaItem, IProformaItemTotals, ProformaItem } from "./proforma-item.entity";
|
||||||
@ -37,6 +38,7 @@ export interface IProformaItems {
|
|||||||
|
|
||||||
valued(): IProformaItem[]; // Devuelve solo las líneas valoradas.
|
valued(): IProformaItem[]; // Devuelve solo las líneas valoradas.
|
||||||
totals(): IProformaItemTotals;
|
totals(): IProformaItemTotals;
|
||||||
|
taxes(): Collection<IProformaTaxTotals>;
|
||||||
|
|
||||||
readonly globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
readonly globalDiscountPercentage: DiscountPercentage; // % descuento de la cabecera
|
||||||
readonly languageCode: LanguageCode; // Para formateos específicos de idioma
|
readonly languageCode: LanguageCode; // Para formateos específicos de idioma
|
||||||
@ -57,6 +59,7 @@ export class ProformaItems extends Collection<ProformaItem> implements IProforma
|
|||||||
if (this.items.length > 0) {
|
if (this.items.length > 0) {
|
||||||
this.ensureSameContext(this.items);
|
this.ensureSameContext(this.items);
|
||||||
}
|
}
|
||||||
|
4;
|
||||||
}
|
}
|
||||||
|
|
||||||
static create(props: IProformaItemsProps): ProformaItems {
|
static create(props: IProformaItemsProps): ProformaItems {
|
||||||
@ -113,6 +116,10 @@ export class ProformaItems extends Collection<ProformaItem> implements IProforma
|
|||||||
return new ProformaItemsTotalsCalculator(this).calculate();
|
return new ProformaItemsTotalsCalculator(this).calculate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public taxes(): Collection<IProformaTaxTotals> {
|
||||||
|
return new ProformaTaxesCalculator(this).calculate();
|
||||||
|
}
|
||||||
|
|
||||||
private ensureSameContext(items: IProformaItem[]): void {
|
private ensureSameContext(items: IProformaItem[]): void {
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const same =
|
const same =
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ItemAmount } from "../../common";
|
import { ItemAmount } from "../../common";
|
||||||
import type { IProformaItems } from "../entities";
|
import type { ProformaItems } from "../entities";
|
||||||
|
|
||||||
export interface IProformaItemsTotals {
|
export interface IProformaItemsTotals {
|
||||||
subtotalAmount: ItemAmount;
|
subtotalAmount: ItemAmount;
|
||||||
@ -23,7 +23,7 @@ export interface IProformaItemsTotals {
|
|||||||
* La lógica fiscal está en ProformaItem; aquí solo se agregan resultados.
|
* La lógica fiscal está en ProformaItem; aquí solo se agregan resultados.
|
||||||
*/
|
*/
|
||||||
export class ProformaItemsTotalsCalculator {
|
export class ProformaItemsTotalsCalculator {
|
||||||
constructor(private readonly items: IProformaItems) {}
|
constructor(private readonly items: ProformaItems) {}
|
||||||
|
|
||||||
public calculate(): IProformaItemsTotals {
|
public calculate(): IProformaItemsTotals {
|
||||||
const zero = ItemAmount.zero(this.items.currencyCode.code);
|
const zero = ItemAmount.zero(this.items.currencyCode.code);
|
||||||
@ -47,26 +47,28 @@ export class ProformaItemsTotalsCalculator {
|
|||||||
const amounts = item.totals();
|
const amounts = item.totals();
|
||||||
|
|
||||||
// Subtotales
|
// Subtotales
|
||||||
subtotalAmount = subtotalAmount.add(amounts.subtotalAmount);
|
subtotalAmount = subtotalAmount.add(amounts.subtotalAmount.roundUsingScale(2));
|
||||||
|
|
||||||
// Descuentos
|
// Descuentos
|
||||||
itemDiscountAmount = itemDiscountAmount.add(amounts.itemDiscountAmount);
|
itemDiscountAmount = itemDiscountAmount.add(amounts.itemDiscountAmount.roundUsingScale(2));
|
||||||
globalDiscountAmount = globalDiscountAmount.add(amounts.globalDiscountAmount);
|
globalDiscountAmount = globalDiscountAmount.add(
|
||||||
totalDiscountAmount = totalDiscountAmount.add(amounts.totalDiscountAmount);
|
amounts.globalDiscountAmount.roundUsingScale(2)
|
||||||
|
);
|
||||||
|
totalDiscountAmount = totalDiscountAmount.add(amounts.totalDiscountAmount.roundUsingScale(2));
|
||||||
|
|
||||||
// Base imponible
|
// Base imponible
|
||||||
taxableAmount = taxableAmount.add(amounts.taxableAmount);
|
taxableAmount = taxableAmount.add(amounts.taxableAmount.roundUsingScale(2));
|
||||||
|
|
||||||
// Impuestos individuales
|
// Impuestos individuales
|
||||||
ivaAmount = ivaAmount.add(amounts.ivaAmount);
|
ivaAmount = ivaAmount.add(amounts.ivaAmount.roundUsingScale(2));
|
||||||
recAmount = recAmount.add(amounts.recAmount);
|
recAmount = recAmount.add(amounts.recAmount.roundUsingScale(2));
|
||||||
retentionAmount = retentionAmount.add(amounts.retentionAmount);
|
retentionAmount = retentionAmount.add(amounts.retentionAmount.roundUsingScale(2));
|
||||||
|
|
||||||
// Total impuestos del ítem
|
// Total impuestos del ítem
|
||||||
taxesAmount = taxesAmount.add(amounts.taxesAmount);
|
taxesAmount = taxesAmount.add(amounts.taxesAmount.roundUsingScale(2));
|
||||||
|
|
||||||
// Total final del ítem
|
// Total final del ítem
|
||||||
totalAmount = totalAmount.add(amounts.totalAmount);
|
totalAmount = totalAmount.add(amounts.totalAmount.roundUsingScale(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -29,8 +29,8 @@ export class ProformaTaxesCalculator {
|
|||||||
constructor(private readonly items: IProformaItems) {}
|
constructor(private readonly items: IProformaItems) {}
|
||||||
|
|
||||||
public calculate(): Collection<IProformaTaxTotals> {
|
public calculate(): Collection<IProformaTaxTotals> {
|
||||||
const groups = proformaComputeTaxGroups(this.items);
|
const groups = proformaComputeTaxGroups(this.items); // <- devuelve en escala 4
|
||||||
const currencyCode = this.items.currencyCode;
|
//const currencyCode = this.items.currencyCode;
|
||||||
|
|
||||||
const rows = Array.from(groups.values()).map((g) => {
|
const rows = Array.from(groups.values()).map((g) => {
|
||||||
const taxableAmount = this.toInvoiceAmount(g.taxableAmount);
|
const taxableAmount = this.toInvoiceAmount(g.taxableAmount);
|
||||||
|
|||||||
@ -67,7 +67,7 @@ export class IssueCustomerInvoiceDomainService {
|
|||||||
...proformaProps,
|
...proformaProps,
|
||||||
isProforma: false,
|
isProforma: false,
|
||||||
proformaId: Maybe.some(proforma.id),
|
proformaId: Maybe.some(proforma.id),
|
||||||
status: InvoiceStatus.fromIssued(),
|
status: InvoiceStatus.issued(),
|
||||||
invoiceNumber: issueNumber,
|
invoiceNumber: issueNumber,
|
||||||
invoiceDate: issueDate,
|
invoiceDate: issueDate,
|
||||||
description: proformaProps.description.isNone() ? Maybe.some(".") : proformaProps.description,
|
description: proformaProps.description.isNone() ? Maybe.some(".") : proformaProps.description,
|
||||||
|
|||||||
@ -1,21 +1,15 @@
|
|||||||
import type { IModuleServer } from "@erp/core/api";
|
import type { IModuleServer } from "@erp/core/api";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type IssuedInvoicePublicServices,
|
buildIssuedInvoicePublicServices,
|
||||||
type IssuedInvoicesInternalDeps,
|
|
||||||
type ProformaPublicServices,
|
|
||||||
type ProformasInternalDeps,
|
|
||||||
buildIssuedInvoiceServices,
|
|
||||||
buildIssuedInvoicesDependencies,
|
buildIssuedInvoicesDependencies,
|
||||||
buildProformaServices,
|
buildProformaPublicServices,
|
||||||
buildProformasDependencies,
|
buildProformasDependencies,
|
||||||
issuedInvoicesRouter,
|
issuedInvoicesRouter,
|
||||||
models,
|
models,
|
||||||
proformasRouter,
|
proformasRouter,
|
||||||
} from "./infrastructure";
|
} from "./infrastructure";
|
||||||
|
|
||||||
export type { IssuedInvoicePublicServices, ProformaPublicServices };
|
|
||||||
|
|
||||||
export const customerInvoicesAPIModule: IModuleServer = {
|
export const customerInvoicesAPIModule: IModuleServer = {
|
||||||
name: "customer-invoices",
|
name: "customer-invoices",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
@ -36,14 +30,8 @@ export const customerInvoicesAPIModule: IModuleServer = {
|
|||||||
const proformasInternal = buildProformasDependencies(params);
|
const proformasInternal = buildProformasDependencies(params);
|
||||||
|
|
||||||
// 2) Servicios públicos (Application Services)
|
// 2) Servicios públicos (Application Services)
|
||||||
const issuedInvoicesServices: IssuedInvoicePublicServices = buildIssuedInvoiceServices(
|
const issuedInvoicesServices = buildIssuedInvoicePublicServices(params, issuedInvoicesInternal);
|
||||||
params,
|
const proformasServices = buildProformaPublicServices(params, proformasInternal);
|
||||||
issuedInvoicesInternal
|
|
||||||
);
|
|
||||||
const proformasServices: ProformaPublicServices = buildProformaServices(
|
|
||||||
params,
|
|
||||||
proformasInternal
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.info("🚀 CustomerInvoices module dependencies registered", { label: this.name });
|
logger.info("🚀 CustomerInvoices module dependencies registered", { label: this.name });
|
||||||
|
|
||||||
@ -73,22 +61,11 @@ export const customerInvoicesAPIModule: IModuleServer = {
|
|||||||
* - NO construye dominio
|
* - NO construye dominio
|
||||||
*/
|
*/
|
||||||
async start(params) {
|
async start(params) {
|
||||||
const { app, baseRoutePath, logger, getInternal } = params;
|
const { app, baseRoutePath, logger, getInternal, getService } = params;
|
||||||
|
|
||||||
// Recuperamos el dominio interno del módulo
|
|
||||||
const issuedInvoicesInternalDeps = getInternal<IssuedInvoicesInternalDeps>(
|
|
||||||
"customer-invoices",
|
|
||||||
"issuedInvoices"
|
|
||||||
);
|
|
||||||
|
|
||||||
const proformasInternalDeps = getInternal<ProformasInternalDeps>(
|
|
||||||
"customer-invoices",
|
|
||||||
"proformas"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Registro de rutas HTTP
|
// Registro de rutas HTTP
|
||||||
issuedInvoicesRouter(params, issuedInvoicesInternalDeps);
|
issuedInvoicesRouter(params);
|
||||||
proformasRouter(params, proformasInternalDeps);
|
proformasRouter(params);
|
||||||
|
|
||||||
logger.info("🚀 CustomerInvoices module started", {
|
logger.info("🚀 CustomerInvoices module started", {
|
||||||
label: this.name,
|
label: this.name,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import {
|
import {
|
||||||
|
DiscountPercentage,
|
||||||
type ISequelizeDomainMapper,
|
type ISequelizeDomainMapper,
|
||||||
type MapperParamsType,
|
type MapperParamsType,
|
||||||
SequelizeDomainMapper,
|
SequelizeDomainMapper,
|
||||||
@ -18,18 +19,11 @@ import { Result } from "@repo/rdx-utils";
|
|||||||
import {
|
import {
|
||||||
type IProformaCreateProps,
|
type IProformaCreateProps,
|
||||||
IssuedInvoiceItem,
|
IssuedInvoiceItem,
|
||||||
type IssuedInvoiceItemProps,
|
|
||||||
ItemAmount,
|
ItemAmount,
|
||||||
ItemDescription,
|
ItemDescription,
|
||||||
ItemDiscountPercentage,
|
|
||||||
ItemQuantity,
|
ItemQuantity,
|
||||||
ItemTaxGroup,
|
|
||||||
type Proforma,
|
type Proforma,
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
import type {
|
|
||||||
CustomerInvoiceItemCreationAttributes,
|
|
||||||
CustomerInvoiceItemModel,
|
|
||||||
} from "../../../../sequelize";
|
|
||||||
|
|
||||||
export interface ICustomerInvoiceItemDomainMapper
|
export interface ICustomerInvoiceItemDomainMapper
|
||||||
extends ISequelizeDomainMapper<
|
extends ISequelizeDomainMapper<
|
||||||
@ -99,7 +93,7 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
|
|
||||||
const discountPercentage = extractOrPushError(
|
const discountPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(source.discount_percentage_value, (v) =>
|
maybeFromNullableResult(source.discount_percentage_value, (v) =>
|
||||||
ItemDiscountPercentage.create({ value: v })
|
DiscountPercentage.create({ value: v })
|
||||||
),
|
),
|
||||||
`items[${index}].discount_percentage`,
|
`items[${index}].discount_percentage`,
|
||||||
errors
|
errors
|
||||||
@ -107,7 +101,7 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
|
|
||||||
const globalDiscountPercentage = extractOrPushError(
|
const globalDiscountPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(source.global_discount_percentage_value, (v) =>
|
maybeFromNullableResult(source.global_discount_percentage_value, (v) =>
|
||||||
ItemDiscountPercentage.create({ value: v })
|
DiscountPercentage.create({ value: v })
|
||||||
),
|
),
|
||||||
`items[${index}].discount_percentage`,
|
`items[${index}].discount_percentage`,
|
||||||
errors
|
errors
|
||||||
@ -242,7 +236,7 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
),
|
),
|
||||||
discount_percentage_scale:
|
discount_percentage_scale:
|
||||||
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
ItemDiscountPercentage.DEFAULT_SCALE,
|
DiscountPercentage.DEFAULT_SCALE,
|
||||||
|
|
||||||
discount_amount_value: allAmounts.itemDiscountAmount.value,
|
discount_amount_value: allAmounts.itemDiscountAmount.value,
|
||||||
discount_amount_scale: allAmounts.itemDiscountAmount.scale,
|
discount_amount_scale: allAmounts.itemDiscountAmount.scale,
|
||||||
@ -255,7 +249,7 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
|
|
||||||
global_discount_percentage_scale:
|
global_discount_percentage_scale:
|
||||||
maybeToNullable(source.globalDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
maybeToNullable(source.globalDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
ItemDiscountPercentage.DEFAULT_SCALE,
|
DiscountPercentage.DEFAULT_SCALE,
|
||||||
|
|
||||||
global_discount_amount_value: allAmounts.globalDiscountAmount.value,
|
global_discount_amount_value: allAmounts.globalDiscountAmount.value,
|
||||||
global_discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
global_discount_amount_scale: allAmounts.globalDiscountAmount.scale,
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
|
export * from "./issued-invoice-number-generator.di";
|
||||||
export * from "./issued-invoice-public-services";
|
export * from "./issued-invoice-public-services";
|
||||||
export * from "./issued-invoices.di";
|
export * from "./issued-invoices.di";
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
import type { IIssuedInvoiceNumberGenerator } from "../../../application";
|
||||||
|
import { SequelizeIssuedInvoiceNumberGenerator } from "../persistence";
|
||||||
|
|
||||||
|
export const buildIssuedInvoiceNumberGenerator = (): IIssuedInvoiceNumberGenerator =>
|
||||||
|
new SequelizeIssuedInvoiceNumberGenerator();
|
||||||
@ -1,26 +1,27 @@
|
|||||||
import type { SetupParams } from "@erp/core/api";
|
import type { SetupParams } from "@erp/core/api";
|
||||||
import { buildCatalogs, buildTransactionManager } from "@erp/core/api";
|
import { buildCatalogs, buildTransactionManager } from "@erp/core/api";
|
||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type IIssuedInvoiceCreatorParams,
|
||||||
|
type IIssuedInvoicePublicServices,
|
||||||
|
type IIssuedInvoiceServicesContext,
|
||||||
|
buildIssuedInvoiceCreator,
|
||||||
buildIssuedInvoiceFinder,
|
buildIssuedInvoiceFinder,
|
||||||
buildIssuedInvoiceSnapshotBuilders,
|
buildIssuedInvoiceSnapshotBuilders,
|
||||||
} from "../../../application/issued-invoices";
|
} from "../../../application/issued-invoices";
|
||||||
|
|
||||||
import { buildIssuedInvoiceDocumentService } from "./issued-invoice-documents.di";
|
import { buildIssuedInvoiceDocumentService } from "./issued-invoice-documents.di";
|
||||||
|
import { buildIssuedInvoiceNumberGenerator } from "./issued-invoice-number-generator.di";
|
||||||
import { buildIssuedInvoicePersistenceMappers } from "./issued-invoice-persistence-mappers.di";
|
import { buildIssuedInvoicePersistenceMappers } from "./issued-invoice-persistence-mappers.di";
|
||||||
import { buildIssuedInvoiceRepository } from "./issued-invoice-repositories.di";
|
import { buildIssuedInvoiceRepository } from "./issued-invoice-repositories.di";
|
||||||
import type { IssuedInvoicesInternalDeps } from "./issued-invoices.di";
|
import type { IssuedInvoicesInternalDeps } from "./issued-invoices.di";
|
||||||
|
|
||||||
export type IssuedInvoicePublicServices = {
|
export function buildIssuedInvoicePublicServices(
|
||||||
listIssuedInvoices: (filters: unknown, context: unknown) => null;
|
|
||||||
getIssuedInvoiceById: (id: unknown, context: unknown) => null;
|
|
||||||
generateIssuedInvoiceReport: (id: unknown, options: unknown, context: unknown) => null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function buildIssuedInvoiceServices(
|
|
||||||
params: SetupParams,
|
params: SetupParams,
|
||||||
deps: IssuedInvoicesInternalDeps
|
deps: IssuedInvoicesInternalDeps
|
||||||
): IssuedInvoicePublicServices {
|
): IIssuedInvoicePublicServices {
|
||||||
const { database } = params;
|
const { database } = params;
|
||||||
|
|
||||||
// Infrastructure
|
// Infrastructure
|
||||||
@ -29,20 +30,29 @@ export function buildIssuedInvoiceServices(
|
|||||||
const persistenceMappers = buildIssuedInvoicePersistenceMappers(catalogs);
|
const persistenceMappers = buildIssuedInvoicePersistenceMappers(catalogs);
|
||||||
|
|
||||||
const repository = buildIssuedInvoiceRepository({ database, mappers: persistenceMappers });
|
const repository = buildIssuedInvoiceRepository({ database, mappers: persistenceMappers });
|
||||||
|
const numberService = buildIssuedInvoiceNumberGenerator();
|
||||||
|
|
||||||
// Application helpers
|
// Application helpers
|
||||||
|
const creator = buildIssuedInvoiceCreator({ numberService, repository });
|
||||||
const finder = buildIssuedInvoiceFinder(repository);
|
const finder = buildIssuedInvoiceFinder(repository);
|
||||||
const snapshotBuilders = buildIssuedInvoiceSnapshotBuilders();
|
const snapshotBuilders = buildIssuedInvoiceSnapshotBuilders();
|
||||||
const documentGeneratorPipeline = buildIssuedInvoiceDocumentService(params);
|
const documentGeneratorPipeline = buildIssuedInvoiceDocumentService(params);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listIssuedInvoices: (filters, context) => null,
|
createIssuedInvoice: async (
|
||||||
//internal.useCases.listIssuedInvoices().execute(filters, context),
|
id: UniqueID,
|
||||||
|
props: IIssuedInvoiceCreatorParams["props"],
|
||||||
|
context: IIssuedInvoiceServicesContext
|
||||||
|
) => {
|
||||||
|
const { transaction, companyId } = context;
|
||||||
|
|
||||||
getIssuedInvoiceById: (id, context) => null,
|
const createResult = await creator.create({ companyId, id, props, transaction });
|
||||||
//internal.useCases.getIssuedInvoiceById().execute(id, context),
|
|
||||||
|
|
||||||
generateIssuedInvoiceReport: (id, options, context) => null,
|
if (createResult.isFailure) {
|
||||||
//internal.useCases.reportIssuedInvoice().execute(id, options, context),
|
return Result.fail(createResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(createResult.data);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,14 +10,9 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
type CustomerInvoiceIdAlreadyExistsError,
|
type CustomerInvoiceIdAlreadyExistsError,
|
||||||
type EntityIsNotProformaError,
|
type IssuedInvoiceItemMismatch,
|
||||||
type InvalidProformaTransitionError,
|
|
||||||
type ProformaCannotBeConvertedToInvoiceError,
|
|
||||||
isCustomerInvoiceIdAlreadyExistsError,
|
isCustomerInvoiceIdAlreadyExistsError,
|
||||||
isEntityIsNotProformaError,
|
isIssuedInvoiceItemMismatch,
|
||||||
isInvalidProformaTransitionError,
|
|
||||||
isProformaCannotBeConvertedToInvoiceError,
|
|
||||||
isProformaCannotBeDeletedError,
|
|
||||||
} from "../../../domain";
|
} from "../../../domain";
|
||||||
|
|
||||||
// Crea una regla específica (prioridad alta para sobreescribir mensajes)
|
// Crea una regla específica (prioridad alta para sobreescribir mensajes)
|
||||||
@ -31,47 +26,17 @@ const invoiceDuplicateRule: ErrorToApiRule = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const entityIsNotProformaError: ErrorToApiRule = {
|
const issuedinvoiceItemMismatchError: ErrorToApiRule = {
|
||||||
priority: 120,
|
priority: 120,
|
||||||
matches: (e) => isEntityIsNotProformaError(e),
|
matches: (e) => isIssuedInvoiceItemMismatch(e),
|
||||||
build: (e) =>
|
build: (e) =>
|
||||||
new ValidationApiError(
|
new ValidationApiError(
|
||||||
(e as EntityIsNotProformaError).message || "Entity with the provided id is not proforma"
|
(e as IssuedInvoiceItemMismatch).message ||
|
||||||
),
|
"IssuedInvoice item rejected due to currency/language mismatch"
|
||||||
};
|
|
||||||
|
|
||||||
const proformaTransitionRule: ErrorToApiRule = {
|
|
||||||
priority: 120,
|
|
||||||
matches: (e) => isInvalidProformaTransitionError(e),
|
|
||||||
build: (e) =>
|
|
||||||
new ValidationApiError(
|
|
||||||
(e as InvalidProformaTransitionError).message || "Invalid transition for proforma."
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
const proformaConversionRule: ErrorToApiRule = {
|
|
||||||
priority: 120,
|
|
||||||
matches: (e) => isProformaCannotBeConvertedToInvoiceError(e),
|
|
||||||
build: (e) =>
|
|
||||||
new ValidationApiError(
|
|
||||||
(e as ProformaCannotBeConvertedToInvoiceError).message ||
|
|
||||||
"Proforma cannot be converted to an Invoice."
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
const proformaCannotBeDeletedRule: ErrorToApiRule = {
|
|
||||||
priority: 120,
|
|
||||||
matches: (e) => isProformaCannotBeDeletedError(e),
|
|
||||||
build: (e) =>
|
|
||||||
new ValidationApiError(
|
|
||||||
(e as ProformaCannotBeConvertedToInvoiceError).message || "Proforma cannot be deleted."
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cómo aplicarla: crea una nueva instancia del mapper con la regla extra
|
// Cómo aplicarla: crea una nueva instancia del mapper con la regla extra
|
||||||
export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
||||||
.register(invoiceDuplicateRule)
|
.register(invoiceDuplicateRule)
|
||||||
.register(entityIsNotProformaError)
|
.register(issuedinvoiceItemMismatchError);
|
||||||
.register(proformaConversionRule)
|
|
||||||
.register(proformaCannotBeDeletedRule)
|
|
||||||
.register(proformaTransitionRule);
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { mockUser, requireAuthenticated, requireCompanyContext } from "@erp/auth/api";
|
import { mockUser, requireAuthenticated, requireCompanyContext } from "@erp/auth/api";
|
||||||
import { type ModuleParams, type RequestWithAuth, validateRequest } from "@erp/core/api";
|
import { type RequestWithAuth, type StartParams, validateRequest } from "@erp/core/api";
|
||||||
import { type NextFunction, type Request, type Response, Router } from "express";
|
import { type NextFunction, type Request, type Response, Router } from "express";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -16,11 +16,15 @@ import {
|
|||||||
ReportIssuedInvoiceController,
|
ReportIssuedInvoiceController,
|
||||||
} from "./controllers";
|
} from "./controllers";
|
||||||
|
|
||||||
export const issuedInvoicesRouter = (params: ModuleParams, deps: IssuedInvoicesInternalDeps) => {
|
export const issuedInvoicesRouter = (params: StartParams) => {
|
||||||
const { app, config } = params;
|
const { app, config, getService, getInternal } = params;
|
||||||
|
|
||||||
|
const deps = getInternal<IssuedInvoicesInternalDeps>("customer-invoices", "issuedInvoices");
|
||||||
|
|
||||||
const router: Router = Router({ mergeParams: true });
|
const router: Router = Router({ mergeParams: true });
|
||||||
|
|
||||||
|
// ----------------------------------------------
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
|
||||||
// 🔐 Autenticación + Tenancy para TODO el router
|
// 🔐 Autenticación + Tenancy para TODO el router
|
||||||
router.use(
|
router.use(
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export * from "./mappers";
|
export * from "./mappers";
|
||||||
export * from "./repositories";
|
export * from "./repositories";
|
||||||
|
export * from "./services";
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
import { Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type IIssuedInvoiceProps,
|
type InternalIssuedInvoiceProps,
|
||||||
InvoiceAmount,
|
InvoiceAmount,
|
||||||
InvoiceNumber,
|
InvoiceNumber,
|
||||||
InvoicePaymentMethod,
|
InvoicePaymentMethod,
|
||||||
@ -63,8 +63,9 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
||||||
|
|
||||||
|
// Para issued invoices, proforma_id debe estar relleno
|
||||||
const proformaId = extractOrPushError(
|
const proformaId = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.proforma_id, (v) => UniqueID.create(v)),
|
UniqueID.create(String(raw.proforma_id)),
|
||||||
"proforma_id",
|
"proforma_id",
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -346,7 +347,7 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
|
|||||||
currencyCode: attributes.currencyCode!,
|
currencyCode: attributes.currencyCode!,
|
||||||
});
|
});
|
||||||
|
|
||||||
const invoiceProps: IIssuedInvoiceProps = {
|
const invoiceProps: InternalIssuedInvoiceProps = {
|
||||||
companyId: attributes.companyId!,
|
companyId: attributes.companyId!,
|
||||||
|
|
||||||
proformaId: attributes.proformaId!,
|
proformaId: attributes.proformaId!,
|
||||||
@ -383,22 +384,14 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
paymentMethod: attributes.paymentMethod!,
|
paymentMethod: attributes.paymentMethod!,
|
||||||
|
|
||||||
items,
|
|
||||||
taxes,
|
taxes,
|
||||||
verifactu,
|
verifactu,
|
||||||
};
|
};
|
||||||
|
|
||||||
const createResult = IssuedInvoice.create(invoiceProps, attributes.invoiceId);
|
const invoiceId = attributes.invoiceId!;
|
||||||
|
const invoice = IssuedInvoice.rehydrate(invoiceProps, items, invoiceId);
|
||||||
|
|
||||||
if (createResult.isFailure) {
|
return Result.ok(invoice);
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Customer invoice entity creation failed", [
|
|
||||||
{ path: "invoice", message: createResult.error.message },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(createResult.data);
|
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return Result.fail(err as Error);
|
return Result.fail(err as Error);
|
||||||
}
|
}
|
||||||
@ -471,7 +464,7 @@ export class SequelizeIssuedInvoiceDomainMapper extends SequelizeDomainMapper<
|
|||||||
// Flags / estado / serie / número
|
// Flags / estado / serie / número
|
||||||
is_proforma: false,
|
is_proforma: false,
|
||||||
status: source.status.toPrimitive(),
|
status: source.status.toPrimitive(),
|
||||||
proforma_id: maybeToNullable(source.proformaId, (v) => v.toPrimitive()),
|
proforma_id: source.proformaId.toPrimitive(),
|
||||||
|
|
||||||
series: maybeToNullable(source.series, (v) => v.toPrimitive()),
|
series: maybeToNullable(source.series, (v) => v.toPrimitive()),
|
||||||
invoice_number: source.invoiceNumber.toPrimitive(),
|
invoice_number: source.invoiceNumber.toPrimitive(),
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import { type MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
|
import {
|
||||||
|
DiscountPercentage,
|
||||||
|
type MapperParamsType,
|
||||||
|
SequelizeDomainMapper,
|
||||||
|
TaxPercentage,
|
||||||
|
} from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
UniqueID,
|
UniqueID,
|
||||||
ValidationErrorCollection,
|
ValidationErrorCollection,
|
||||||
@ -13,16 +18,13 @@ import {
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DiscountPercentage,
|
type IIssuedInvoiceCreateProps,
|
||||||
type IIssuedInvoiceProps,
|
type IIssuedInvoiceItemCreateProps,
|
||||||
type IssuedInvoice,
|
type IssuedInvoice,
|
||||||
IssuedInvoiceItem,
|
IssuedInvoiceItem,
|
||||||
type IssuedInvoiceItemProps,
|
|
||||||
ItemAmount,
|
ItemAmount,
|
||||||
ItemDescription,
|
ItemDescription,
|
||||||
ItemDiscountPercentage,
|
|
||||||
ItemQuantity,
|
ItemQuantity,
|
||||||
ItemTaxPercentage,
|
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
import type {
|
import type {
|
||||||
CustomerInvoiceItemCreationAttributes,
|
CustomerInvoiceItemCreationAttributes,
|
||||||
@ -34,7 +36,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
CustomerInvoiceItemCreationAttributes,
|
CustomerInvoiceItemCreationAttributes,
|
||||||
IssuedInvoiceItem
|
IssuedInvoiceItem
|
||||||
> {
|
> {
|
||||||
private taxCatalog!: JsonTaxCatalogProvider;
|
private readonly taxCatalog!: JsonTaxCatalogProvider;
|
||||||
|
|
||||||
constructor(params: MapperParamsType) {
|
constructor(params: MapperParamsType) {
|
||||||
super();
|
super();
|
||||||
@ -52,11 +54,11 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
private mapAttributesToDomain(
|
private mapAttributesToDomain(
|
||||||
raw: CustomerInvoiceItemModel,
|
raw: CustomerInvoiceItemModel,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): Partial<IssuedInvoiceItemProps> & { itemId?: UniqueID } {
|
): Partial<IIssuedInvoiceItemCreateProps> & { itemId?: UniqueID } {
|
||||||
const { errors, index, attributes } = params as {
|
const { errors, index, attributes } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
attributes: Partial<IIssuedInvoiceProps>;
|
attributes: Partial<IIssuedInvoiceCreateProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const itemId = extractOrPushError(
|
const itemId = extractOrPushError(
|
||||||
@ -96,7 +98,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
|
|
||||||
const itemDiscountPercentage = extractOrPushError(
|
const itemDiscountPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.item_discount_percentage_value, (v) =>
|
maybeFromNullableResult(raw.item_discount_percentage_value, (v) =>
|
||||||
ItemDiscountPercentage.create({ value: v })
|
DiscountPercentage.create({ value: v })
|
||||||
),
|
),
|
||||||
`items[${index}].item_discount_percentage_value`,
|
`items[${index}].item_discount_percentage_value`,
|
||||||
errors
|
errors
|
||||||
@ -112,9 +114,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
);
|
);
|
||||||
|
|
||||||
const globalDiscountPercentage = extractOrPushError(
|
const globalDiscountPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.global_discount_percentage_value, (v) =>
|
DiscountPercentage.create({ value: raw.global_discount_percentage_value }),
|
||||||
DiscountPercentage.create({ value: v })
|
|
||||||
),
|
|
||||||
`items[${index}].global_discount_percentage_value`,
|
`items[${index}].global_discount_percentage_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -149,9 +149,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
const ivaCode = maybeFromNullableOrEmptyString(raw.iva_code);
|
const ivaCode = maybeFromNullableOrEmptyString(raw.iva_code);
|
||||||
|
|
||||||
const ivaPercentage = extractOrPushError(
|
const ivaPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.iva_percentage_value, (value) =>
|
maybeFromNullableResult(raw.iva_percentage_value, (value) => TaxPercentage.create({ value })),
|
||||||
ItemTaxPercentage.create({ value })
|
|
||||||
),
|
|
||||||
`items[${index}].iva_percentage_value`,
|
`items[${index}].iva_percentage_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -168,9 +166,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
const recCode = maybeFromNullableOrEmptyString(raw.rec_code);
|
const recCode = maybeFromNullableOrEmptyString(raw.rec_code);
|
||||||
|
|
||||||
const recPercentage = extractOrPushError(
|
const recPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.rec_percentage_value, (value) =>
|
maybeFromNullableResult(raw.rec_percentage_value, (value) => TaxPercentage.create({ value })),
|
||||||
ItemTaxPercentage.create({ value })
|
|
||||||
),
|
|
||||||
`items[${index}].rec_percentage_value`,
|
`items[${index}].rec_percentage_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
@ -188,7 +184,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
|
|
||||||
const retentionPercentage = extractOrPushError(
|
const retentionPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.retention_percentage_value, (value) =>
|
maybeFromNullableResult(raw.retention_percentage_value, (value) =>
|
||||||
ItemTaxPercentage.create({ value })
|
TaxPercentage.create({ value })
|
||||||
),
|
),
|
||||||
`items[${index}].retention_percentage_value`,
|
`items[${index}].retention_percentage_value`,
|
||||||
errors
|
errors
|
||||||
@ -263,7 +259,7 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
const { errors, index } = params as {
|
const { errors, index } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
attributes: Partial<IIssuedInvoiceProps>;
|
attributes: Partial<IIssuedInvoiceCreateProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1) Valores escalares (atributos generales)
|
// 1) Valores escalares (atributos generales)
|
||||||
@ -277,7 +273,8 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2) Construcción del elemento de dominio
|
// 2) Construcción del elemento de dominio
|
||||||
const createResult = IssuedInvoiceItem.create(
|
const itemId = attributes.itemId!;
|
||||||
|
const newItem = IssuedInvoiceItem.rehydrate(
|
||||||
{
|
{
|
||||||
description: attributes.description!,
|
description: attributes.description!,
|
||||||
|
|
||||||
@ -314,18 +311,10 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
languageCode: attributes.languageCode!,
|
languageCode: attributes.languageCode!,
|
||||||
currencyCode: attributes.currencyCode!,
|
currencyCode: attributes.currencyCode!,
|
||||||
},
|
},
|
||||||
attributes.itemId
|
itemId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (createResult.isFailure) {
|
return Result.ok(newItem);
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Invoice item entity creation failed", [
|
|
||||||
{ path: `items[${index}]`, message: createResult.error.message },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return createResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public mapToPersistence(
|
public mapToPersistence(
|
||||||
@ -364,18 +353,14 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
|
|||||||
),
|
),
|
||||||
item_discount_percentage_scale:
|
item_discount_percentage_scale:
|
||||||
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
ItemDiscountPercentage.DEFAULT_SCALE,
|
DiscountPercentage.DEFAULT_SCALE,
|
||||||
|
|
||||||
item_discount_amount_value: source.itemDiscountAmount.toPrimitive().value,
|
item_discount_amount_value: source.itemDiscountAmount.toPrimitive().value,
|
||||||
item_discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale,
|
item_discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale,
|
||||||
|
|
||||||
global_discount_percentage_value: maybeToNullable(
|
global_discount_percentage_value: source.globalDiscountPercentage.toPrimitive().value,
|
||||||
source.globalDiscountPercentage,
|
|
||||||
(v) => v.toPrimitive().value
|
|
||||||
),
|
|
||||||
global_discount_percentage_scale:
|
global_discount_percentage_scale:
|
||||||
maybeToNullable(source.globalDiscountPercentage, (v) => v.toPrimitive().scale) ??
|
source.globalDiscountPercentage.toPrimitive().scale ?? DiscountPercentage.DEFAULT_SCALE,
|
||||||
ItemDiscountPercentage.DEFAULT_SCALE,
|
|
||||||
|
|
||||||
global_discount_amount_value: source.globalDiscountAmount.value,
|
global_discount_amount_value: source.globalDiscountAmount.value,
|
||||||
global_discount_amount_scale: source.globalDiscountAmount.scale,
|
global_discount_amount_scale: source.globalDiscountAmount.scale,
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import {
|
|||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type IIssuedInvoiceProps,
|
type IIssuedInvoiceCreateProps,
|
||||||
InvoiceRecipient,
|
InvoiceRecipient,
|
||||||
type IssuedInvoice,
|
type IssuedInvoice,
|
||||||
} from "../../../../../../domain";
|
} from "../../../../../../domain";
|
||||||
@ -33,7 +33,7 @@ export class SequelizeIssuedInvoiceRecipientDomainMapper {
|
|||||||
|
|
||||||
const { errors, attributes } = params as {
|
const { errors, attributes } = params as {
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
attributes: Partial<IIssuedInvoiceProps>;
|
attributes: Partial<IIssuedInvoiceCreateProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _name = source.customer_name!;
|
const _name = source.customer_name!;
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import { type MapperParamsType, SequelizeDomainMapper, TaxPercentage } from "@erp/core/api";
|
import {
|
||||||
|
DiscountPercentage,
|
||||||
|
type MapperParamsType,
|
||||||
|
SequelizeDomainMapper,
|
||||||
|
TaxPercentage,
|
||||||
|
} from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
Percentage,
|
Percentage,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
@ -14,7 +19,7 @@ import {
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type IIssuedInvoiceProps,
|
type IIssuedInvoiceCreateProps,
|
||||||
InvoiceAmount,
|
InvoiceAmount,
|
||||||
type IssuedInvoice,
|
type IssuedInvoice,
|
||||||
IssuedInvoiceTax,
|
IssuedInvoiceTax,
|
||||||
@ -64,7 +69,7 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp
|
|||||||
const { errors, index, attributes } = params as {
|
const { errors, index, attributes } = params as {
|
||||||
index: number;
|
index: number;
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
attributes: Partial<IIssuedInvoiceProps>;
|
attributes: Partial<IIssuedInvoiceCreateProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const taxableAmount = extractOrPushError(
|
const taxableAmount = extractOrPushError(
|
||||||
@ -78,9 +83,10 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp
|
|||||||
|
|
||||||
const ivaCode = raw.iva_code;
|
const ivaCode = raw.iva_code;
|
||||||
|
|
||||||
|
// Una issued invoice debe traer IVA
|
||||||
const ivaPercentage = extractOrPushError(
|
const ivaPercentage = extractOrPushError(
|
||||||
TaxPercentage.create({
|
TaxPercentage.create({
|
||||||
value: raw.iva_percentage_value,
|
value: Number(raw.iva_percentage_value),
|
||||||
}),
|
}),
|
||||||
`taxes[${index}].iva_percentage_value`,
|
`taxes[${index}].iva_percentage_value`,
|
||||||
errors
|
errors
|
||||||
@ -210,7 +216,7 @@ export class SequelizeIssuedInvoiceTaxesDomainMapper extends SequelizeDomainMapp
|
|||||||
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
rec_percentage_value: maybeToNullable(source.recPercentage, (v) => v.toPrimitive().value),
|
||||||
rec_percentage_scale:
|
rec_percentage_scale:
|
||||||
maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ??
|
maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ??
|
||||||
ItemDiscountPercentage.DEFAULT_SCALE,
|
DiscountPercentage.DEFAULT_SCALE,
|
||||||
|
|
||||||
rec_amount_value: source.recAmount.toPrimitive().value,
|
rec_amount_value: source.recAmount.toPrimitive().value,
|
||||||
rec_amount_scale: source.recAmount.toPrimitive().scale ?? ItemAmount.DEFAULT_SCALE,
|
rec_amount_scale: source.recAmount.toPrimitive().scale ?? ItemAmount.DEFAULT_SCALE,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type IIssuedInvoiceProps,
|
type IIssuedInvoiceCreateProps,
|
||||||
type IssuedInvoice,
|
type IssuedInvoice,
|
||||||
VerifactuRecord,
|
VerifactuRecord,
|
||||||
VerifactuRecordEstado,
|
VerifactuRecordEstado,
|
||||||
@ -33,7 +33,7 @@ export class SequelizeIssuedInvoiceVerifactuDomainMapper extends SequelizeDomain
|
|||||||
): Result<Maybe<VerifactuRecord>, Error> {
|
): Result<Maybe<VerifactuRecord>, Error> {
|
||||||
const { errors, attributes } = params as {
|
const { errors, attributes } = params as {
|
||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
attributes: Partial<IIssuedInvoiceProps>;
|
attributes: Partial<IIssuedInvoiceCreateProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!source) {
|
if (!source) {
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./sequelize-issued-invoice-number-generator.service";
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
import { type Transaction, type WhereOptions, literal } from "sequelize";
|
||||||
|
|
||||||
|
import type { IIssuedInvoiceNumberGenerator } from "../../../../../application/issued-invoices";
|
||||||
|
import { InvoiceNumber, type InvoiceSerie } from "../../../../../domain";
|
||||||
|
import { CustomerInvoiceModel } from "../../../../common/persistence";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generador de números de factura
|
||||||
|
*/
|
||||||
|
export class SequelizeIssuedInvoiceNumberGenerator implements IIssuedInvoiceNumberGenerator {
|
||||||
|
public async getNextForCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
series: Maybe<InvoiceSerie>,
|
||||||
|
transaction: Transaction
|
||||||
|
): Promise<Result<InvoiceNumber, Error>> {
|
||||||
|
const where: WhereOptions = {
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
is_proforma: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
series.match(
|
||||||
|
(serieVO) => {
|
||||||
|
where.series = serieVO.toString();
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
where.series = null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const lastInvoice = await CustomerInvoiceModel.findOne({
|
||||||
|
attributes: ["invoice_number"],
|
||||||
|
where,
|
||||||
|
// Orden numérico real: CAST(... AS UNSIGNED)
|
||||||
|
order: [literal("CAST(invoice_number AS UNSIGNED) DESC")],
|
||||||
|
transaction,
|
||||||
|
raw: true,
|
||||||
|
// Bloqueo opcional para evitar carreras si estás dentro de una TX
|
||||||
|
lock: transaction.LOCK.UPDATE, // requiere InnoDB y TX abierta
|
||||||
|
});
|
||||||
|
|
||||||
|
let nextValue = "0001"; // valor inicial por defecto
|
||||||
|
|
||||||
|
if (lastInvoice) {
|
||||||
|
const current = Number(lastInvoice.invoice_number);
|
||||||
|
const next = Number.isFinite(current) && current > 0 ? current + 1 : 1;
|
||||||
|
nextValue = String(next).padStart(4, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
const numberResult = InvoiceNumber.create(nextValue);
|
||||||
|
if (numberResult.isFailure) {
|
||||||
|
return Result.fail(numberResult.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(numberResult.data);
|
||||||
|
} catch (error) {
|
||||||
|
return Result.fail(
|
||||||
|
new Error(
|
||||||
|
`Error generating invoice number for company ${companyId}: ${(error as Error).message}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,13 +23,6 @@ type ProformaServicesContext = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ProformaPublicServices = {
|
export type ProformaPublicServices = {
|
||||||
createProforma: (
|
|
||||||
id: UniqueID,
|
|
||||||
props: IProformaCreatorParams["props"],
|
|
||||||
context: ProformaServicesContext
|
|
||||||
) => Promise<Result<Proforma, Error>>;
|
|
||||||
|
|
||||||
listProformas: (filters: unknown, context: unknown) => null;
|
|
||||||
getProformaById: (
|
getProformaById: (
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
context: ProformaServicesContext
|
context: ProformaServicesContext
|
||||||
@ -40,10 +33,14 @@ export type ProformaPublicServices = {
|
|||||||
context: ProformaServicesContext
|
context: ProformaServicesContext
|
||||||
) => Promise<Result<IProformaFullSnapshot, Error>>;
|
) => Promise<Result<IProformaFullSnapshot, Error>>;
|
||||||
|
|
||||||
generateProformaReport: (id: unknown, options: unknown, context: unknown) => null;
|
createProforma: (
|
||||||
|
id: UniqueID,
|
||||||
|
props: IProformaCreatorParams["props"],
|
||||||
|
context: ProformaServicesContext
|
||||||
|
) => Promise<Result<Proforma, Error>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function buildProformaServices(
|
export function buildProformaPublicServices(
|
||||||
params: SetupParams,
|
params: SetupParams,
|
||||||
deps: ProformasInternalDeps
|
deps: ProformasInternalDeps
|
||||||
): ProformaPublicServices {
|
): ProformaPublicServices {
|
||||||
@ -78,7 +75,6 @@ export function buildProformaServices(
|
|||||||
return Result.ok(createResult.data);
|
return Result.ok(createResult.data);
|
||||||
},
|
},
|
||||||
|
|
||||||
listProformas: (filters, context) => null,
|
|
||||||
//internal.useCases.listProformas().execute(filters, context),
|
//internal.useCases.listProformas().execute(filters, context),
|
||||||
|
|
||||||
getProformaById: async (id: UniqueID, context: ProformaServicesContext) => {
|
getProformaById: async (id: UniqueID, context: ProformaServicesContext) => {
|
||||||
@ -105,7 +101,7 @@ export function buildProformaServices(
|
|||||||
return Result.ok(fullSnapshot);
|
return Result.ok(fullSnapshot);
|
||||||
},
|
},
|
||||||
|
|
||||||
generateProformaReport: (id, options, context) => null,
|
//generateProformaReport: (id, options, context) => null,
|
||||||
//internal.useCases.reportProforma().execute(id, options, context),
|
//internal.useCases.reportProforma().execute(id, options, context),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,15 +3,20 @@ import { type ModuleParams, buildCatalogs, buildTransactionManager } from "@erp/
|
|||||||
import {
|
import {
|
||||||
type CreateProformaUseCase,
|
type CreateProformaUseCase,
|
||||||
type GetProformaByIdUseCase,
|
type GetProformaByIdUseCase,
|
||||||
|
type IIssuedInvoicePublicServices,
|
||||||
|
type IssueProformaUseCase,
|
||||||
type ListProformasUseCase,
|
type ListProformasUseCase,
|
||||||
type ReportProformaUseCase,
|
type ReportProformaUseCase,
|
||||||
buildCreateProformaUseCase,
|
buildCreateProformaUseCase,
|
||||||
buildGetProformaByIdUseCase,
|
buildGetProformaByIdUseCase,
|
||||||
|
buildIssueProformaUseCase,
|
||||||
buildListProformasUseCase,
|
buildListProformasUseCase,
|
||||||
buildProformaCreator,
|
buildProformaCreator,
|
||||||
buildProformaFinder,
|
buildProformaFinder,
|
||||||
buildProformaInputMappers,
|
buildProformaInputMappers,
|
||||||
|
buildProformaIssuer,
|
||||||
buildProformaSnapshotBuilders,
|
buildProformaSnapshotBuilders,
|
||||||
|
buildProformaToIssuedInvoicePropsConverter,
|
||||||
buildReportProformaUseCase,
|
buildReportProformaUseCase,
|
||||||
} from "../../../application";
|
} from "../../../application";
|
||||||
|
|
||||||
@ -26,6 +31,9 @@ export type ProformasInternalDeps = {
|
|||||||
getProformaById: () => GetProformaByIdUseCase;
|
getProformaById: () => GetProformaByIdUseCase;
|
||||||
reportProforma: () => ReportProformaUseCase;
|
reportProforma: () => ReportProformaUseCase;
|
||||||
createProforma: () => CreateProformaUseCase;
|
createProforma: () => CreateProformaUseCase;
|
||||||
|
issueProforma: (publicServices: {
|
||||||
|
issuedInvoiceServices: IIssuedInvoicePublicServices;
|
||||||
|
}) => IssueProformaUseCase;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
updateProforma: () => UpdateProformaUseCase;
|
updateProforma: () => UpdateProformaUseCase;
|
||||||
@ -44,12 +52,18 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
const persistenceMappers = buildProformaPersistenceMappers(catalogs);
|
const persistenceMappers = buildProformaPersistenceMappers(catalogs);
|
||||||
|
|
||||||
const repository = buildProformaRepository({ database, mappers: persistenceMappers });
|
const repository = buildProformaRepository({ database, mappers: persistenceMappers });
|
||||||
const numberService = buildProformaNumberGenerator();
|
const proformaNumberService = buildProformaNumberGenerator();
|
||||||
|
|
||||||
// Application helpers
|
// Application helpers
|
||||||
const inputMappers = buildProformaInputMappers(catalogs);
|
const inputMappers = buildProformaInputMappers(catalogs);
|
||||||
const finder = buildProformaFinder(repository);
|
const finder = buildProformaFinder(repository);
|
||||||
const creator = buildProformaCreator({ numberService, repository });
|
const creator = buildProformaCreator({ numberService: proformaNumberService, repository });
|
||||||
|
const proformaToIssuedInvoiceConverter = buildProformaToIssuedInvoicePropsConverter();
|
||||||
|
|
||||||
|
const issuer = buildProformaIssuer({
|
||||||
|
proformaConverter: proformaToIssuedInvoiceConverter,
|
||||||
|
repository,
|
||||||
|
});
|
||||||
|
|
||||||
const snapshotBuilders = buildProformaSnapshotBuilders();
|
const snapshotBuilders = buildProformaSnapshotBuilders();
|
||||||
const documentGeneratorPipeline = buildProformaDocumentService(params);
|
const documentGeneratorPipeline = buildProformaDocumentService(params);
|
||||||
@ -87,6 +101,14 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter
|
|||||||
fullSnapshotBuilder: snapshotBuilders.full,
|
fullSnapshotBuilder: snapshotBuilders.full,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
issueProforma: (publicServices: { issuedInvoiceServices: IIssuedInvoicePublicServices }) =>
|
||||||
|
buildIssueProformaUseCase({
|
||||||
|
publicServices,
|
||||||
|
finder,
|
||||||
|
issuer,
|
||||||
|
transactionManager,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
//export * from "./create-proforma.controller";
|
//export * from "./create-proforma.controller";
|
||||||
//export * from "./delete-proforma.controller";
|
//export * from "./delete-proforma.controller";
|
||||||
export * from "./get-proforma.controller";
|
export * from "./get-proforma.controller";
|
||||||
//export * from "./issue-proforma.controller";
|
export * from "./issue-proforma.controller";
|
||||||
export * from "./list-proformas.controller";
|
export * from "./list-proformas.controller";
|
||||||
export * from "./report-proforma.controller";
|
export * from "./report-proforma.controller";
|
||||||
//export * from "./update-proforma.controller";
|
//export * from "./update-proforma.controller";
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
requireCompanyContextGuard,
|
requireCompanyContextGuard,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
|
|
||||||
import type { IssueProformaUseCase } from "../../../../application/index.ts";
|
import type { IssueProformaUseCase } from "../../../../application/proformas";
|
||||||
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts";
|
import { proformasApiErrorMapper } from "../proformas-api-error-mapper.ts";
|
||||||
|
|
||||||
export class IssueProformaController extends ExpressController {
|
export class IssueProformaController extends ExpressController {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
|
import { DiscountPercentage } from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
CurrencyCode,
|
CurrencyCode,
|
||||||
DomainError,
|
DomainError,
|
||||||
@ -57,7 +58,7 @@ export class CreateProformaRequestMapper {
|
|||||||
try {
|
try {
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
|
|
||||||
const defaultStatus = InvoiceStatus.fromDraft();
|
const defaultStatus = InvoiceStatus.draft();
|
||||||
|
|
||||||
const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors);
|
const proformaId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors);
|
||||||
|
|
||||||
@ -209,7 +210,7 @@ export class CreateProformaRequestMapper {
|
|||||||
|
|
||||||
const discountPercentage = extractOrPushError(
|
const discountPercentage = extractOrPushError(
|
||||||
maybeFromNullableResult(item.item_discount_percentage, (value) =>
|
maybeFromNullableResult(item.item_discount_percentage, (value) =>
|
||||||
ItemDiscountPercentage.create(value)
|
DiscountPercentage.create(value)
|
||||||
),
|
),
|
||||||
"discount_percentage",
|
"discount_percentage",
|
||||||
this.errors
|
this.errors
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { mockUser, requireAuthenticated, requireCompanyContext } from "@erp/auth/api";
|
import { mockUser, requireAuthenticated, requireCompanyContext } from "@erp/auth/api";
|
||||||
import { type ModuleParams, type RequestWithAuth, validateRequest } from "@erp/core/api";
|
import { type RequestWithAuth, type StartParams, validateRequest } from "@erp/core/api";
|
||||||
import { type NextFunction, type Request, type Response, Router } from "express";
|
import { type NextFunction, type Request, type Response, Router } from "express";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GetProformaController,
|
GetProformaController,
|
||||||
|
IssueProformaController,
|
||||||
ListProformasController,
|
ListProformasController,
|
||||||
type ProformasInternalDeps,
|
type ProformasInternalDeps,
|
||||||
ReportProformaController,
|
ReportProformaController,
|
||||||
@ -12,18 +13,30 @@ import {
|
|||||||
import {
|
import {
|
||||||
CreateProformaRequestSchema,
|
CreateProformaRequestSchema,
|
||||||
GetProformaByIdRequestSchema,
|
GetProformaByIdRequestSchema,
|
||||||
|
IssueProformaByIdParamsRequestSchema,
|
||||||
ListProformasRequestSchema,
|
ListProformasRequestSchema,
|
||||||
ReportProformaByIdParamsRequestSchema,
|
ReportProformaByIdParamsRequestSchema,
|
||||||
ReportProformaByIdQueryRequestSchema,
|
ReportProformaByIdQueryRequestSchema,
|
||||||
} from "../../../../common";
|
} from "../../../../common";
|
||||||
|
import type { IIssuedInvoicePublicServices } from "../../../application";
|
||||||
|
|
||||||
import { CreateProformaController } from "./controllers/create-proforma.controller";
|
import { CreateProformaController } from "./controllers/create-proforma.controller";
|
||||||
|
|
||||||
export const proformasRouter = (params: ModuleParams, deps: ProformasInternalDeps) => {
|
export const proformasRouter = (params: StartParams) => {
|
||||||
const { app, config } = params;
|
const { app, config, getService, getInternal } = params;
|
||||||
|
|
||||||
|
const deps = getInternal<ProformasInternalDeps>("customer-invoices", "proformas");
|
||||||
|
|
||||||
|
const issuedInvoicesServices = getService<IIssuedInvoicePublicServices>("self:issuedInvoices");
|
||||||
|
|
||||||
|
const publicServices = {
|
||||||
|
issuedInvoiceServices: issuedInvoicesServices,
|
||||||
|
};
|
||||||
|
|
||||||
const router: Router = Router({ mergeParams: true });
|
const router: Router = Router({ mergeParams: true });
|
||||||
|
|
||||||
|
// ----------------------------------------------
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "production") {
|
||||||
// 🔐 Autenticación + Tenancy para TODO el router
|
// 🔐 Autenticación + Tenancy para TODO el router
|
||||||
router.use(
|
router.use(
|
||||||
@ -131,18 +144,18 @@ export const proformasRouter = (params: ModuleParams, deps: ProformasInternalDep
|
|||||||
}
|
}
|
||||||
);*/
|
);*/
|
||||||
|
|
||||||
/*router.put(
|
router.put(
|
||||||
"/:proforma_id/issue",
|
"/:proforma_id/issue",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
|
|
||||||
validateRequest(IssueProformaByIdParamsRequestSchema, "params"),
|
validateRequest(IssueProformaByIdParamsRequestSchema, "params"),
|
||||||
|
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.issue_proforma();
|
const useCase = deps.useCases.issueProforma(publicServices);
|
||||||
const controller = new IssuedProformaController(useCase);
|
const controller = new IssueProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
);*/
|
);
|
||||||
|
|
||||||
app.use(`${config.server.apiBasePath}/proformas`, router);
|
app.use(`${config.server.apiBasePath}/proformas`, router);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -58,12 +58,6 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
||||||
|
|
||||||
const proformaId = extractOrPushError(
|
|
||||||
maybeFromNullableResult(raw.proforma_id, (v) => UniqueID.create(v)),
|
|
||||||
"proforma_id",
|
|
||||||
errors
|
|
||||||
);
|
|
||||||
|
|
||||||
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
|
const status = extractOrPushError(InvoiceStatus.create(raw.status), "status", errors);
|
||||||
|
|
||||||
const series = extractOrPushError(
|
const series = extractOrPushError(
|
||||||
@ -160,7 +154,6 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
invoiceId,
|
invoiceId,
|
||||||
companyId,
|
companyId,
|
||||||
customerId,
|
customerId,
|
||||||
proformaId,
|
|
||||||
status,
|
status,
|
||||||
series,
|
series,
|
||||||
invoiceNumber,
|
invoiceNumber,
|
||||||
@ -244,8 +237,8 @@ export class SequelizeProformaDomainMapper extends SequelizeDomainMapper<
|
|||||||
paymentMethod: attributes.paymentMethod!,
|
paymentMethod: attributes.paymentMethod!,
|
||||||
};
|
};
|
||||||
|
|
||||||
const invoiceId = attributes.invoiceId!;
|
const proformaId = attributes.invoiceId!;
|
||||||
const proforma = Proforma.rehydrate(invoiceProps, items, invoiceId);
|
const proforma = Proforma.rehydrate(invoiceProps, items, proformaId);
|
||||||
|
|
||||||
return Result.ok(proforma);
|
return Result.ok(proforma);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
|||||||
@ -75,7 +75,7 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
|
|
||||||
const quantity = extractOrPushError(
|
const quantity = extractOrPushError(
|
||||||
maybeFromNullableResult(raw.quantity_value, (v) => ItemQuantity.create({ value: v })),
|
maybeFromNullableResult(raw.quantity_value, (v) => ItemQuantity.create({ value: v })),
|
||||||
`items[${index}].quantity`,
|
`items[${index}].quantity_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
maybeFromNullableResult(raw.unit_amount_value, (value) =>
|
maybeFromNullableResult(raw.unit_amount_value, (value) =>
|
||||||
ItemAmount.create({ value, currency_code: parent.currencyCode?.code })
|
ItemAmount.create({ value, currency_code: parent.currencyCode?.code })
|
||||||
),
|
),
|
||||||
`items[${index}].unit_amount`,
|
`items[${index}].unit_amount_value`,
|
||||||
errors
|
errors
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -163,26 +163,20 @@ export class SequelizeProformaItemDomainMapper extends SequelizeDomainMapper<
|
|||||||
const itemId = attributes.itemId!;
|
const itemId = attributes.itemId!;
|
||||||
const newItem = ProformaItem.rehydrate(
|
const newItem = ProformaItem.rehydrate(
|
||||||
{
|
{
|
||||||
languageCode: attributes.languageCode!,
|
|
||||||
currencyCode: attributes.currencyCode!,
|
|
||||||
description: attributes.description!,
|
description: attributes.description!,
|
||||||
quantity: attributes.quantity!,
|
quantity: attributes.quantity!,
|
||||||
unitAmount: attributes.unitAmount!,
|
unitAmount: attributes.unitAmount!,
|
||||||
|
|
||||||
itemDiscountPercentage: attributes.itemDiscountPercentage!,
|
itemDiscountPercentage: attributes.itemDiscountPercentage!,
|
||||||
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
globalDiscountPercentage: attributes.globalDiscountPercentage!,
|
||||||
taxes: taxesResult.data,
|
taxes: taxesResult.data,
|
||||||
|
|
||||||
|
languageCode: attributes.languageCode!,
|
||||||
|
currencyCode: attributes.currencyCode!,
|
||||||
},
|
},
|
||||||
itemId
|
itemId
|
||||||
);
|
);
|
||||||
|
|
||||||
/*if (createResult.isFailure) {
|
|
||||||
return Result.fail(
|
|
||||||
new ValidationErrorCollection("Invoice item entity creation failed", [
|
|
||||||
{ path: `items[${index}]`, message: createResult.error.message },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return Result.ok(newItem);
|
return Result.ok(newItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,13 +31,6 @@ export class SequelizeProformaRecipientDomainMapper {
|
|||||||
parent: Partial<IProformaCreateProps>;
|
parent: Partial<IProformaCreateProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* if (!source.current_customer) {
|
|
||||||
errors.push({
|
|
||||||
path: "current_customer",
|
|
||||||
message: "Current customer not included in query (SequelizeProformaRecipientDomainMapper)",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const _name = source.current_customer.name;
|
const _name = source.current_customer.name;
|
||||||
const _tin = source.current_customer.tin;
|
const _tin = source.current_customer.tin;
|
||||||
const _street = source.current_customer.street;
|
const _street = source.current_customer.street;
|
||||||
|
|||||||
@ -89,8 +89,8 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
|
|||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
|
|
||||||
discount_percentage: PercentageSchema,
|
item_discount_percentage: PercentageSchema,
|
||||||
discount_amount: MoneySchema,
|
item_discount_amount: MoneySchema,
|
||||||
|
|
||||||
global_discount_percentage: PercentageSchema,
|
global_discount_percentage: PercentageSchema,
|
||||||
global_discount_amount: MoneySchema,
|
global_discount_amount: MoneySchema,
|
||||||
|
|||||||
@ -83,8 +83,8 @@ export const GetProformaByIdResponseSchema = z.object({
|
|||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
|
|
||||||
discount_percentage: PercentageSchema,
|
item_discount_percentage: PercentageSchema,
|
||||||
discount_amount: MoneySchema,
|
item_discount_amount: MoneySchema,
|
||||||
|
|
||||||
global_discount_percentage: PercentageSchema,
|
global_discount_percentage: PercentageSchema,
|
||||||
global_discount_amount: MoneySchema,
|
global_discount_amount: MoneySchema,
|
||||||
|
|||||||
@ -19,24 +19,24 @@ import * as React from "react";
|
|||||||
import { useTranslation } from "../../../../../i18n";
|
import { useTranslation } from "../../../../../i18n";
|
||||||
import {
|
import {
|
||||||
PROFORMA_STATUS_TRANSITIONS,
|
PROFORMA_STATUS_TRANSITIONS,
|
||||||
|
type ProformaListRow,
|
||||||
type ProformaStatus,
|
type ProformaStatus,
|
||||||
type ProformaSummaryData,
|
} from "../../../../shared";
|
||||||
} from "../../../../types";
|
|
||||||
import { ProformaStatusBadge } from "../../components";
|
import { ProformaStatusBadge } from "../../components";
|
||||||
|
|
||||||
type GridActionHandlers = {
|
type GridActionHandlers = {
|
||||||
onEditClick?: (proforma: ProformaSummaryData) => void;
|
onEditClick?: (proforma: ProformaListRow) => void;
|
||||||
onIssueClick?: (proforma: ProformaSummaryData) => void;
|
onIssueClick?: (proforma: ProformaListRow) => void;
|
||||||
onChangeStatusClick?: (proforma: ProformaSummaryData, nextStatus: string) => void;
|
onChangeStatusClick?: (proforma: ProformaListRow, nextStatus: string) => void;
|
||||||
onDeleteClick?: (proforma: ProformaSummaryData) => void;
|
onDeleteClick?: (proforma: ProformaListRow) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useProformasGridColumns(
|
export function useProformasGridColumns(
|
||||||
actionHandlers: GridActionHandlers = {}
|
actionHandlers: GridActionHandlers = {}
|
||||||
): ColumnDef<ProformaSummaryData, unknown>[] {
|
): ColumnDef<ProformaListRow, unknown>[] {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return React.useMemo<ColumnDef<ProformaSummaryData, unknown>[]>(
|
return React.useMemo<ColumnDef<ProformaListRow, unknown>[]>(
|
||||||
() => [
|
() => [
|
||||||
/*{
|
/*{
|
||||||
id: "select",
|
id: "select",
|
||||||
|
|||||||
@ -3,11 +3,11 @@ import { cn } from "@repo/shadcn-ui/lib/utils";
|
|||||||
|
|
||||||
import { useTranslation } from "../../../../i18n";
|
import { useTranslation } from "../../../../i18n";
|
||||||
import {
|
import {
|
||||||
type ProformaStatus,
|
|
||||||
getProformaStatusButtonVariant,
|
getProformaStatusButtonVariant,
|
||||||
getProformaStatusColor,
|
getProformaStatusColor,
|
||||||
getProformaStatusIcon,
|
getProformaStatusIcon,
|
||||||
} from "../../../types";
|
} from "../../../change-status/helpers";
|
||||||
|
import type { ProformaStatus } from "../../../shared";
|
||||||
|
|
||||||
export type ProformaStatusBadgeProps = {
|
export type ProformaStatusBadgeProps = {
|
||||||
status: string | ProformaStatus; // permitir cualquier valor
|
status: string | ProformaStatus; // permitir cualquier valor
|
||||||
|
|||||||
@ -1,165 +0,0 @@
|
|||||||
// application/customer-application-service.ts
|
|
||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { type Collection, Result } from "@repo/rdx-utils";
|
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Customer,
|
|
||||||
type CustomerPatchProps,
|
|
||||||
type ICustomerCreateProps,
|
|
||||||
type ICustomerRepository,
|
|
||||||
} from "../domain";
|
|
||||||
import type { CustomerListDTO } from "../infrastructure";
|
|
||||||
|
|
||||||
export class CustomerApplicationService {
|
|
||||||
constructor(private readonly repository: ICustomerRepository) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construye un nuevo agregado CustomerInvoice a partir de props validadas.
|
|
||||||
*
|
|
||||||
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
|
||||||
* @param props - Las propiedades ya validadas para crear el cliente.
|
|
||||||
* @param customerId - Identificador UUID del cliente (opcional).
|
|
||||||
* @returns Result<Customer, Error> - El agregado construido o un error si falla la creación.
|
|
||||||
*/
|
|
||||||
buildCustomerInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
props: Omit<ICustomerCreateProps, "companyId">,
|
|
||||||
customerId?: UniqueID
|
|
||||||
): Result<Customer, Error> {
|
|
||||||
return Customer.create({ ...props, companyId }, customerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Guarda un nuevo cliente y devuelve el cliente guardado.
|
|
||||||
*
|
|
||||||
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
|
||||||
* @param customer - El cliente a guardar.
|
|
||||||
* @param transaction - Transacción activa para la operación.
|
|
||||||
* @returns Result<Customer, Error> - El cliente guardado o un error si falla la operación.
|
|
||||||
*/
|
|
||||||
async createCustomerInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
customer: Customer,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<Customer, Error>> {
|
|
||||||
const result = await this.repository.create(customer, transaction);
|
|
||||||
if (result.isFailure) return Result.fail(result.error);
|
|
||||||
|
|
||||||
return this.getCustomerByIdInCompany(companyId, customer.id, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actualiza un cliente existente y devuelve el cliente actualizado.
|
|
||||||
*
|
|
||||||
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
|
||||||
* @param customer - El cliente a guardar.
|
|
||||||
* @param transaction - Transacción activa para la operación.
|
|
||||||
* @returns Result<Customer, Error> - El cliente guardado o un error si falla la operación.
|
|
||||||
*/
|
|
||||||
async updateCustomerInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
customer: Customer,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<Customer, Error>> {
|
|
||||||
const result = await this.repository.update(customer, transaction);
|
|
||||||
if (result.isFailure) return Result.fail(result.error);
|
|
||||||
|
|
||||||
return this.getCustomerByIdInCompany(companyId, customer.id, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actualiza parcialmente un cliente existente con nuevos datos.
|
|
||||||
* Solo en memoria. 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.
|
|
||||||
* @param changes - Subconjunto de props válidas para aplicar.
|
|
||||||
* @param transaction - Transacción activa para la operación.
|
|
||||||
* @returns Result<Customer, Error> - Cliente actualizado o error.
|
|
||||||
*/
|
|
||||||
async patchCustomerByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
customerId: UniqueID,
|
|
||||||
changes: CustomerPatchProps,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<Customer, Error>> {
|
|
||||||
const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction);
|
|
||||||
if (customerResult.isFailure) {
|
|
||||||
return Result.fail(customerResult.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = customerResult.data.update(changes);
|
|
||||||
if (updated.isFailure) {
|
|
||||||
return Result.fail(updated.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(updated.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Elimina (o marca como eliminado) un cliente según su ID.
|
|
||||||
*
|
|
||||||
* @param companyId - Identificador de la empresa a la que pertenece el cliente.
|
|
||||||
* @param customerId - Identificador UUID del cliente.
|
|
||||||
* @param transaction - Transacción activa para la operación.
|
|
||||||
* @returns Result<boolean, Error> - Resultado de la operación.
|
|
||||||
*/
|
|
||||||
async deleteCustomerByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
customerId: UniqueID,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<boolean, Error>> {
|
|
||||||
return this.repository.deleteByIdInCompany(companyId, customerId, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* Comprueba si existe o no en persistencia un cliente con el ID proporcionado
|
|
||||||
*
|
|
||||||
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
|
||||||
* @param customerId - Identificador UUID del cliente
|
|
||||||
* @param transaction - Transacción activa para la operación.
|
|
||||||
* @returns Result<Boolean, Error> - Existe el cliente o no.
|
|
||||||
*/
|
|
||||||
async existsByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
customerId: UniqueID,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<boolean, Error>> {
|
|
||||||
return this.repository.existsByIdInCompany(companyId, customerId, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recupera un cliente por su identificador único.
|
|
||||||
*
|
|
||||||
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
|
||||||
* @param customerId - Identificador UUID del cliente.
|
|
||||||
* @param transaction - Transacción activa para la operación.
|
|
||||||
* @returns Result<Customer, Error> - Cliente encontrado o error.
|
|
||||||
*/
|
|
||||||
async getCustomerByIdInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
customerId: UniqueID,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<Customer, Error>> {
|
|
||||||
return this.repository.getByIdInCompany(companyId, customerId, transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtiene una colección de clientes que cumplen con los filtros definidos en un objeto Criteria.
|
|
||||||
*
|
|
||||||
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
|
||||||
* @param criteria - Objeto con condiciones de filtro, paginación y orden.
|
|
||||||
* @param transaction - Transacción activa para la operación.
|
|
||||||
* @returns Result<Collection<Customer>, Error> - Colección de clientes o error.
|
|
||||||
*/
|
|
||||||
async findCustomerByCriteriaInCompany(
|
|
||||||
companyId: UniqueID,
|
|
||||||
criteria: Criteria,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<Collection<CustomerListDTO>, Error>> {
|
|
||||||
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import { DuplicateEntityError } from "@erp/core/api";
|
import { DuplicateEntityError } from "@erp/core/api";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import { Customer, type ICustomerCreateProps } from "../../domain";
|
import { Customer, type ICustomerCreateProps } from "../../domain";
|
||||||
import type { ICustomerRepository } from "../repositories";
|
import type { ICustomerRepository } from "../repositories";
|
||||||
@ -12,7 +11,7 @@ export interface ICustomerCreator {
|
|||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
id: UniqueID;
|
id: UniqueID;
|
||||||
props: ICustomerCreateProps;
|
props: ICustomerCreateProps;
|
||||||
transaction: Transaction;
|
unknown: unknown;
|
||||||
}): Promise<Result<Customer, Error>>;
|
}): Promise<Result<Customer, Error>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,16 +30,12 @@ export class CustomerCreator implements ICustomerCreator {
|
|||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
id: UniqueID;
|
id: UniqueID;
|
||||||
props: ICustomerCreateProps;
|
props: ICustomerCreateProps;
|
||||||
transaction: Transaction;
|
unknown: unknown;
|
||||||
}): Promise<Result<Customer, Error>> {
|
}): Promise<Result<Customer, Error>> {
|
||||||
const { companyId, id, props, transaction } = params;
|
const { companyId, id, props, unknown } = params;
|
||||||
|
|
||||||
// 1. Verificar unicidad
|
// 1. Verificar unicidad
|
||||||
const spec = new CustomerNotExistsInCompanySpecification(
|
const spec = new CustomerNotExistsInCompanySpecification(this.repository, companyId, unknown);
|
||||||
this.repository,
|
|
||||||
companyId,
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
|
|
||||||
const isNew = await spec.isSatisfiedBy(id);
|
const isNew = await spec.isSatisfiedBy(id);
|
||||||
|
|
||||||
@ -64,7 +59,7 @@ export class CustomerCreator implements ICustomerCreator {
|
|||||||
const newCustomer = createResult.data;
|
const newCustomer = createResult.data;
|
||||||
|
|
||||||
// 3. Persistir agregado
|
// 3. Persistir agregado
|
||||||
const saveResult = await this.repository.create(newCustomer, transaction);
|
const saveResult = await this.repository.create(newCustomer, unknown);
|
||||||
|
|
||||||
if (saveResult.isFailure) {
|
if (saveResult.isFailure) {
|
||||||
return Result.fail(saveResult.error);
|
return Result.fail(saveResult.error);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import type { TINNumber, UniqueID } from "@repo/rdx-ddd";
|
import type { TINNumber, UniqueID } from "@repo/rdx-ddd";
|
||||||
import type { Collection, Result } from "@repo/rdx-utils";
|
import type { Collection, Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { Customer } from "../../domain";
|
import type { Customer } from "../../domain";
|
||||||
import type { CustomerSummary } from "../models";
|
import type { CustomerSummary } from "../models";
|
||||||
@ -11,25 +10,25 @@ export interface ICustomerFinder {
|
|||||||
findCustomerById(
|
findCustomerById(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
customerId: UniqueID,
|
customerId: UniqueID,
|
||||||
transaction?: Transaction
|
unknown?: unknown
|
||||||
): Promise<Result<Customer, Error>>;
|
): Promise<Result<Customer, Error>>;
|
||||||
|
|
||||||
findCustomerByTIN(
|
findCustomerByTIN(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
tin: TINNumber,
|
tin: TINNumber,
|
||||||
transaction?: Transaction
|
unknown?: unknown
|
||||||
): Promise<Result<Customer, Error>>;
|
): Promise<Result<Customer, Error>>;
|
||||||
|
|
||||||
customerExists(
|
customerExists(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
unknown?: unknown
|
||||||
): Promise<Result<boolean, Error>>;
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
findCustomersByCriteria(
|
findCustomersByCriteria(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: Transaction
|
unknown?: unknown
|
||||||
): Promise<Result<Collection<CustomerSummary>, Error>>;
|
): Promise<Result<Collection<CustomerSummary>, Error>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,32 +38,32 @@ export class CustomerFinder implements ICustomerFinder {
|
|||||||
async findCustomerById(
|
async findCustomerById(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
customerId: UniqueID,
|
customerId: UniqueID,
|
||||||
transaction?: Transaction
|
unknown?: unknown
|
||||||
): Promise<Result<Customer, Error>> {
|
): Promise<Result<Customer, Error>> {
|
||||||
return this.repository.getByIdInCompany(companyId, customerId, transaction);
|
return this.repository.getByIdInCompany(companyId, customerId, unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
findCustomerByTIN(
|
findCustomerByTIN(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
tin: TINNumber,
|
tin: TINNumber,
|
||||||
transaction?: Transaction
|
unknown?: unknown
|
||||||
): Promise<Result<Customer, Error>> {
|
): Promise<Result<Customer, Error>> {
|
||||||
return this.repository.getByTINInCompany(companyId, tin, transaction);
|
return this.repository.getByTINInCompany(companyId, tin, unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
async customerExists(
|
async customerExists(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
customerId: UniqueID,
|
customerId: UniqueID,
|
||||||
transaction?: Transaction
|
unknown?: unknown
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
return this.repository.existsByIdInCompany(companyId, customerId, transaction);
|
return this.repository.existsByIdInCompany(companyId, customerId, unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findCustomersByCriteria(
|
async findCustomersByCriteria(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: Transaction
|
unknown?: unknown
|
||||||
): Promise<Result<Collection<CustomerSummary>, Error>> {
|
): Promise<Result<Collection<CustomerSummary>, Error>> {
|
||||||
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
|
return this.repository.findByCriteriaInCompany(companyId, criteria, unknown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
import type { TINNumber, UniqueID } from "@repo/rdx-ddd";
|
||||||
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
import type { Customer, ICustomerCreateProps } from "../../domain";
|
||||||
|
|
||||||
|
export interface ICustomerServicesContext {
|
||||||
|
transaction: unknown;
|
||||||
|
companyId: UniqueID;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICustomerPublicServices {
|
||||||
|
findCustomerByTIN: (
|
||||||
|
tin: TINNumber,
|
||||||
|
context: ICustomerServicesContext
|
||||||
|
) => Promise<Result<Customer, Error>>;
|
||||||
|
|
||||||
|
createCustomer: (
|
||||||
|
id: UniqueID,
|
||||||
|
props: ICustomerCreateProps,
|
||||||
|
context: ICustomerServicesContext
|
||||||
|
) => Promise<Result<Customer, Error>>;
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { Customer, CustomerPatchProps } from "../../domain";
|
import type { Customer, CustomerPatchProps } from "../../domain";
|
||||||
import type { ICustomerRepository } from "../repositories";
|
import type { ICustomerRepository } from "../repositories";
|
||||||
@ -10,7 +9,7 @@ export interface ICustomerUpdater {
|
|||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
id: UniqueID;
|
id: UniqueID;
|
||||||
props: CustomerPatchProps;
|
props: CustomerPatchProps;
|
||||||
transaction: Transaction;
|
transaction: unknown;
|
||||||
}): Promise<Result<Customer, Error>>;
|
}): Promise<Result<Customer, Error>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ export class CustomerUpdater implements ICustomerUpdater {
|
|||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
id: UniqueID;
|
id: UniqueID;
|
||||||
props: CustomerPatchProps;
|
props: CustomerPatchProps;
|
||||||
transaction: Transaction;
|
transaction: unknown;
|
||||||
}): Promise<Result<Customer, Error>> {
|
}): Promise<Result<Customer, Error>> {
|
||||||
const { companyId, id, props, transaction } = params;
|
const { companyId, id, props, transaction } = params;
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from "./customer-creator";
|
export * from "./customer-creator";
|
||||||
export * from "./customer-finder";
|
export * from "./customer-finder";
|
||||||
|
export * from "./customer-public-services.interface";
|
||||||
export * from "./customer-updater";
|
export * from "./customer-updater";
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { CompositeSpecification, type UniqueID } from "@repo/rdx-ddd";
|
import { CompositeSpecification, type UniqueID } from "@repo/rdx-ddd";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { ICustomerRepository } from "../../application";
|
import type { ICustomerRepository } from "../../application";
|
||||||
import { logger } from "../../helpers";
|
import { logger } from "../../helpers";
|
||||||
@ -8,7 +7,7 @@ export class CustomerNotExistsInCompanySpecification extends CompositeSpecificat
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly repository: ICustomerRepository,
|
private readonly repository: ICustomerRepository,
|
||||||
private readonly companyId: UniqueID,
|
private readonly companyId: UniqueID,
|
||||||
private readonly transaction?: Transaction
|
private readonly transaction?: unknown
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { ITransactionManager } from "@erp/core/api";
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { CreateCustomerRequestDTO } from "../../../common";
|
import type { CreateCustomerRequestDTO } from "../../../common";
|
||||||
import type { ICreateCustomerInputMapper } from "../mappers";
|
import type { ICreateCustomerInputMapper } from "../mappers";
|
||||||
@ -43,7 +42,7 @@ export class CreateCustomerUseCase {
|
|||||||
|
|
||||||
const { props, id } = mappedPropsResult.data;
|
const { props, id } = mappedPropsResult.data;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
return this.transactionManager.complete(async (transaction: unknown) => {
|
||||||
try {
|
try {
|
||||||
const createResult = await this.creator.create({ companyId, id, props, transaction });
|
const createResult = await this.creator.create({ companyId, id, props, transaction });
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import type { ITransactionManager } from "@erp/core/api";
|
|||||||
import type { Criteria } from "@repo/rdx-criteria/server";
|
import type { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import type { UniqueID } from "@repo/rdx-ddd";
|
import type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { ICustomerFinder } from "../services";
|
import type { ICustomerFinder } from "../services";
|
||||||
import type { ICustomerSummarySnapshotBuilder } from "../snapshot-builders/summary";
|
import type { ICustomerSummarySnapshotBuilder } from "../snapshot-builders/summary";
|
||||||
@ -22,7 +21,7 @@ export class ListCustomersUseCase {
|
|||||||
public execute(params: ListCustomersUseCaseInput) {
|
public execute(params: ListCustomersUseCaseInput) {
|
||||||
const { criteria, companyId } = params;
|
const { criteria, companyId } = params;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
return this.transactionManager.complete(async (transaction: unknown) => {
|
||||||
try {
|
try {
|
||||||
const result = await this.finder.findCustomersByCriteria(companyId, criteria, transaction);
|
const result = await this.finder.findCustomersByCriteria(companyId, criteria, transaction);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import type { ITransactionManager } from "@erp/core/api";
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import type { UpdateCustomerByIdRequestDTO } from "../../../../common/dto";
|
import type { UpdateCustomerByIdRequestDTO } from "../../../../common/dto";
|
||||||
import type { CustomerPatchProps } from "../../../domain";
|
import type { CustomerPatchProps } from "../../../domain";
|
||||||
@ -52,7 +51,7 @@ export class UpdateCustomerUseCase {
|
|||||||
|
|
||||||
const patchProps: CustomerPatchProps = patchPropsResult.data;
|
const patchProps: CustomerPatchProps = patchPropsResult.data;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
return this.transactionManager.complete(async (transaction: unknown) => {
|
||||||
try {
|
try {
|
||||||
const updateResult = await this.updater.update({
|
const updateResult = await this.updater.update({
|
||||||
companyId,
|
companyId,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import type { IModuleServer } from "@erp/core/api";
|
|||||||
import { type CustomerPublicServices, customersRouter, models } from "./infrastructure";
|
import { type CustomerPublicServices, customersRouter, models } from "./infrastructure";
|
||||||
import {
|
import {
|
||||||
type CustomersInternalDeps,
|
type CustomersInternalDeps,
|
||||||
buildCustomerServices,
|
buildCustomerPublicServices,
|
||||||
buildCustomersDependencies,
|
buildCustomersDependencies,
|
||||||
} from "./infrastructure/di";
|
} from "./infrastructure/di";
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export const customersAPIModule: IModuleServer = {
|
|||||||
const internal = buildCustomersDependencies(params);
|
const internal = buildCustomersDependencies(params);
|
||||||
|
|
||||||
// 2) Servicios públicos (Application Services)
|
// 2) Servicios públicos (Application Services)
|
||||||
const customersServices: CustomerPublicServices = buildCustomerServices(params, internal);
|
const customersServices: CustomerPublicServices = buildCustomerPublicServices(params, internal);
|
||||||
|
|
||||||
logger.info("🚀 Customers module dependencies registered", {
|
logger.info("🚀 Customers module dependencies registered", {
|
||||||
label: this.name,
|
label: this.name,
|
||||||
|
|||||||
@ -1,38 +1,23 @@
|
|||||||
import { type SetupParams, buildCatalogs } from "@erp/core/api";
|
import { type SetupParams, buildCatalogs } from "@erp/core/api";
|
||||||
import type { TINNumber, UniqueID } from "@repo/rdx-ddd";
|
import type { TINNumber, UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
|
||||||
|
|
||||||
import { buildCustomerCreator, buildCustomerFinder } from "../../application";
|
import {
|
||||||
import type { Customer, ICustomerCreateProps } from "../../domain";
|
type ICustomerPublicServices,
|
||||||
|
type ICustomerServicesContext,
|
||||||
|
buildCustomerCreator,
|
||||||
|
buildCustomerFinder,
|
||||||
|
} from "../../application";
|
||||||
|
import type { ICustomerCreateProps } from "../../domain";
|
||||||
|
|
||||||
import { buildCustomerPersistenceMappers } from "./customer-persistence-mappers.di";
|
import { buildCustomerPersistenceMappers } from "./customer-persistence-mappers.di";
|
||||||
import { buildCustomerRepository } from "./customer-repositories.di";
|
import { buildCustomerRepository } from "./customer-repositories.di";
|
||||||
import type { CustomersInternalDeps } from "./customers.di";
|
import type { CustomersInternalDeps } from "./customers.di";
|
||||||
|
|
||||||
type CustomerServicesContext = {
|
export function buildCustomerPublicServices(
|
||||||
transaction: Transaction;
|
|
||||||
companyId: UniqueID;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CustomerPublicServices = {
|
|
||||||
//listCustomers: (filters: unknown, context: unknown) => null;
|
|
||||||
findCustomerByTIN: (
|
|
||||||
tin: TINNumber,
|
|
||||||
context: CustomerServicesContext
|
|
||||||
) => Promise<Result<Customer, Error>>;
|
|
||||||
createCustomer: (
|
|
||||||
id: UniqueID,
|
|
||||||
props: ICustomerCreateProps,
|
|
||||||
context: CustomerServicesContext
|
|
||||||
) => Promise<Result<Customer, Error>>;
|
|
||||||
//generateCustomerReport: (id: unknown, options: unknown, context: unknown) => null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function buildCustomerServices(
|
|
||||||
params: SetupParams,
|
params: SetupParams,
|
||||||
deps: CustomersInternalDeps
|
deps: CustomersInternalDeps
|
||||||
): CustomerPublicServices {
|
): ICustomerPublicServices {
|
||||||
const { database } = params;
|
const { database } = params;
|
||||||
const catalogs = buildCatalogs();
|
const catalogs = buildCatalogs();
|
||||||
|
|
||||||
@ -44,7 +29,7 @@ export function buildCustomerServices(
|
|||||||
const creator = buildCustomerCreator({ repository });
|
const creator = buildCustomerCreator({ repository });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
findCustomerByTIN: async (tin: TINNumber, context: CustomerServicesContext) => {
|
findCustomerByTIN: async (tin: TINNumber, context: ICustomerServicesContext) => {
|
||||||
const { companyId, transaction } = context;
|
const { companyId, transaction } = context;
|
||||||
|
|
||||||
const customerResult = await finder.findCustomerByTIN(companyId, tin, transaction);
|
const customerResult = await finder.findCustomerByTIN(companyId, tin, transaction);
|
||||||
@ -59,7 +44,7 @@ export function buildCustomerServices(
|
|||||||
createCustomer: async (
|
createCustomer: async (
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
props: ICustomerCreateProps,
|
props: ICustomerCreateProps,
|
||||||
context: CustomerServicesContext
|
context: ICustomerServicesContext
|
||||||
) => {
|
) => {
|
||||||
const { companyId, transaction } = context;
|
const { companyId, transaction } = context;
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user