Facturas de cliente

This commit is contained in:
David Arranz 2025-09-13 20:45:55 +02:00
parent c7b42fa8ba
commit d7dc148439
82 changed files with 416 additions and 844 deletions

View File

@ -45,17 +45,19 @@ export interface IPresenterRegistry {
/**
* Obtiene un mapper de dominio por clave de proyección.
*/
getPresenter<TSource, TOutput>(key: PresenterKey): IPresenter<TSource, TOutput>;
getPresenter<TSource = unknown, TOutput = unknown>(
key: PresenterKey
): IPresenter<TSource, TOutput>;
/**
* Registra un mapper de dominio bajo una clave de proyección.
*/
registerPresenter<TSource, TOutput>(
registerPresenter<TSource = unknown, TOutput = unknown>(
key: PresenterKey,
presenter: IPresenter<TSource, TOutput>
): this;
registerPresenters(
presenters: Array<{ key: PresenterKey; presenter: IPresenter<any, any> }>
presenters: Array<{ key: PresenterKey; presenter: IPresenter<unknown, unknown> }>
): this;
}

View File

@ -12,6 +12,20 @@ export class InMemoryPresenterRegistry implements IPresenterRegistry {
};
}
/**
* 🔹 Construye la clave única para el registro.
*/
private _buildKey(key: PresenterKey): string {
const { resource, projection, format, version, locale } = key;
return [
resource.toLowerCase(),
projection.toLowerCase(),
format!.toLowerCase(),
version ?? "latest",
locale ?? "default",
].join("::");
}
private _registerPresenter<TSource, TOutput>(
key: PresenterKey,
presenter: IPresenter<TSource, TOutput>
@ -54,7 +68,9 @@ export class InMemoryPresenterRegistry implements IPresenterRegistry {
);
}
throw new ApplicationError(`Error. Presenter ${key.resource} ${key.projection} not registred!`);
throw new ApplicationError(
`Error. Presenter ${key.resource} / ${key.projection} not registred!`
);
}
registerPresenter<TSource, TOutput>(
@ -73,18 +89,4 @@ export class InMemoryPresenterRegistry implements IPresenterRegistry {
presenters.forEach(({ key, presenter }) => this._registerPresenter(key, presenter));
return this;
}
/**
* 🔹 Construye la clave única para el registro.
*/
private _buildKey(key: PresenterKey): string {
const { resource, projection, format, version, locale } = key;
return [
resource.toLowerCase(),
projection.toLowerCase(),
format!.toLowerCase(),
version ?? "latest",
locale ?? "default",
].join("::");
}
}

View File

@ -5,7 +5,9 @@ export type IPresenterParams = {
presenterRegistry: IPresenterRegistry;
} & Record<string, unknown>;
export abstract class Presenter<T, S> implements IPresenter<T, S> {
export abstract class Presenter<TSource = unknown, TOutput = unknown>
implements IPresenter<TSource, TOutput>
{
constructor(protected presenterRegistry: IPresenterRegistry) {}
abstract toOutput(source: T): S;
abstract toOutput(source: TSource): TOutput;
}

View File

@ -61,7 +61,7 @@ export type DomainMapperWithBulk<TPersistence, TDomain> = IDomainMapper<TPersist
* - Responsabilidad: transformar registros de persistencia en DTOs para lectura (listados, resúmenes, informes).
* - No intenta reconstruir agregados ni validar value objects de dominio.
**/
export interface IReadModelMapperWithBulk<TPersistence, TDTO> {
export interface IQueryMapperWithBulk<TPersistence, TDTO> {
/**
* Convierte un registro crudo en un DTO de lectura.
*/

View File

@ -1,38 +1,42 @@
/**
* 🔑 Claves de proyección comunes para seleccionar mappers en lectura.
* Puedes extender con otras cadenas según tus necesidades ("SUMMARY", "EXPORT", etc.).
* 🔑 Claves de proyección comunes para seleccionar mappers de dominio y de consulta.
*/
export type MapperProjectionKey = "FULL" | "LIST" | "REPORT" | (string & {});
export type MapperKey = {
resource: string;
query: "LIST" | "REPORT" | (string & {}); // "SUMMARY", "EXPORT", etc.
};
export type MapperDomainKey = Pick<MapperKey, "resource">;
export type MapperQueryKey = MapperKey;
/**
* 🏗 Registro/Fábrica de mappers (Strategy/Factory)
* - Permite resolver diferentes mappers según la proyección (FULL, SUMMARY, etc.)
* - Permite resolver diferentes mappers según la consulta (FULL, SUMMARY, etc.)
* - Facilita inyección y test (DIP), evitando dependencias duras en implementaciones concretas.
*
* Ejemplo de uso:
* - registry.registerDomainMapper("FULL", customerInvoiceFullMapper);
* - registry.registerReadModelMapper("SUMMARY", customerInvoiceSummaryMapper);
* - registry.registerReadModelMapper("REPORT", customerInvoiceReportMapper);
*/
export interface IMapperRegistry {
/**
* Obtiene un mapper de dominio por clave de proyección.
*/
getDomainMapper<T>(key: MapperProjectionKey): T;
getDomainMapper<T>(key: MapperDomainKey): T;
/**
* Obtiene un mapper de read model por clave de proyección.
*/
getReadModelMapper<T>(key: MapperProjectionKey): T;
getQueryMapper<T>(key: MapperQueryKey): T;
/**
* Registra un mapper de dominio bajo una clave de proyección.
* Registra mapper de dominio bajo una clave de proyección.
*/
registerDomainMapper<T>(key: MapperProjectionKey, mapper: T): void;
registerDomainMapper<T>(key: MapperDomainKey, mapper: T): this;
/**
* Registra un mapper de read model bajo una clave de proyección.
* Registra un mapper de query / read model bajo una clave de proyección.
*/
registerReadModelMapper<T>(key: MapperProjectionKey, mapper: T): void;
registerQueryMapper<T>(key: MapperQueryKey, mapper: T): this;
registerQueryMappers(mappers: Array<{ key: MapperQueryKey; mapper: any }>): this;
}

View File

@ -1,30 +1,78 @@
import { InfrastructureError } from "../errors";
import { IMapperRegistry, MapperProjectionKey } from "./mapper-registry.interface";
import {
IMapperRegistry,
MapperDomainKey,
MapperKey,
MapperQueryKey,
} from "./mapper-registry.interface";
export class InMemoryMapperRegistry implements IMapperRegistry {
private domainMappers: Map<MapperProjectionKey, any> = new Map();
private readModelMappers: Map<MapperProjectionKey, any> = new Map();
private _mappers: Map<string, any> = new Map();
getDomainMapper<T>(key: MapperProjectionKey): T {
if (!this.domainMappers.has(key)) {
throw new InfrastructureError(`Error. Domain model mapper ${key} not registred!`);
private _normalizeKey(key: MapperDomainKey | MapperQueryKey): MapperKey {
const { resource, query } = key as {
resource: string;
query?: string;
};
return {
resource,
query: query ?? "DOMAIN", // 👈 valor por defecto
};
}
/**
* 🔹 Construye la clave única para el registro.
*/
private _buildKey(key: MapperKey): string {
const { resource, query } = key as {
resource: string;
query?: string;
};
return [resource.toLowerCase(), query ?? "DOMAIN"].join("::");
}
private _getMapper<T>(key: MapperKey): T {
const normalizedKey = this._normalizeKey(key);
const exactKey = this._buildKey(normalizedKey);
if (!this._mappers.has(exactKey)) {
throw new InfrastructureError(`Error. Mapper ${normalizedKey.resource} not registred!`);
}
return this.domainMappers.get(key);
return this._mappers.get(exactKey);
}
getReadModelMapper<T>(key: MapperProjectionKey): T {
if (!this.readModelMappers.has(key)) {
throw new InfrastructureError(`Error. Read model mapper ${key} not registred!`);
}
return this.readModelMappers.get(key);
getDomainMapper<T>(key: MapperDomainKey): T {
const normalizedKey = this._normalizeKey({
resource: key.resource,
query: "DOMAIN",
});
return this._getMapper(normalizedKey);
}
registerDomainMapper<T>(key: MapperProjectionKey, mapper: T): void {
this.domainMappers.set(key, mapper);
getQueryMapper<T>(key: MapperQueryKey): T {
const normalizedKey = this._normalizeKey({
resource: key.resource,
query: "DOMAIN",
});
return this._getMapper(normalizedKey);
}
registerReadModelMapper<T>(key: MapperProjectionKey, mapper: T): void {
this.readModelMappers.set(key, mapper);
registerDomainMapper<T>(key: MapperDomainKey, mapper: T) {
const exactKey = this._buildKey(this._normalizeKey(key));
this._mappers.set(exactKey, mapper);
return this;
}
registerQueryMapper<T>(key: MapperQueryKey, mapper: T) {
const exactKey = this._buildKey(this._normalizeKey(key));
this._mappers.set(exactKey, mapper);
return this;
}
registerQueryMappers(mappers: Array<{ key: MapperQueryKey; mapper: any }>): this {
mappers.forEach(({ key, mapper }) => this.registerQueryMapper(key, mapper));
return this;
}
}

View File

@ -1,7 +1,6 @@
import { DomainMapperWithBulk, IReadModelMapperWithBulk } from "../../../domain";
import { DomainMapperWithBulk, IQueryMapperWithBulk } from "../../../domain";
export interface ISequelizeDomainMapper<TModel, TModelAttributes, TEntity>
extends DomainMapperWithBulk<TModel | TModelAttributes, TEntity> {}
export interface ISequelizeReadModelMapper<TModel, TDTO>
extends IReadModelMapperWithBulk<TModel, TDTO> {}
export interface ISequelizeQueryMapper<TModel, TDTO> extends IQueryMapperWithBulk<TModel, TDTO> {}

View File

@ -1,10 +1,10 @@
import { Collection, Result } from "@repo/rdx-utils";
import { Model } from "sequelize";
import { MapperParamsType } from "../../../domain";
import { ISequelizeReadModelMapper } from "./sequelize-mapper.interface";
import { ISequelizeQueryMapper } from "./sequelize-mapper.interface";
export abstract class SequelizeReadModelMapper<TModel extends Model, TEntity>
implements ISequelizeReadModelMapper<TModel, TEntity>
export abstract class SequelizeQueryMapper<TModel extends Model, TEntity>
implements ISequelizeQueryMapper<TModel, TEntity>
{
public abstract mapToDTO(raw: TModel, params?: MapperParamsType): Result<TEntity, Error>;

View File

@ -1,9 +1,18 @@
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
import { IAggregateRootRepository, UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { FindOptions, ModelDefined, Transaction } from "sequelize";
import { FindOptions, ModelDefined, Sequelize, Transaction } from "sequelize";
import { IMapperRegistry } from "../mappers";
export abstract class SequelizeRepository<T> implements IAggregateRootRepository<T> {
protected readonly _database!: Sequelize;
protected readonly _registry!: IMapperRegistry;
constructor(params: { mapperRegistry: IMapperRegistry; database: Sequelize }) {
this._registry = params.mapperRegistry;
this._database = params.database;
}
protected convertCriteria(criteria: Criteria): FindOptions {
return new CriteriaToSequelizeConverter().convert(criteria);
}

View File

@ -1,45 +0,0 @@
import { toEmptyString } from "@repo/rdx-ddd";
import { CreateCustomerInvoiceResponseDTO } from "../../../../common/dto";
import { CustomerInvoice } from "../../../domain";
type CreateCustomerInvoiceItemsByInvoiceIdResponseDTO = CreateCustomerInvoiceResponseDTO["items"];
export class CreateCustomerInvoiceItemsPresenter {
toDTO(invoice: CustomerInvoice): CreateCustomerInvoiceItemsByInvoiceIdResponseDTO {
const { items } = invoice;
return items.map((item, index) => ({
id: item.id.toString(),
position: String(index),
description: toEmptyString(item.description, (value) => value.toPrimitive()),
quantity: item.quantity.match(
(quantity) => {
const { value, scale } = quantity.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
unit_amount: item.unitAmount.match(
(unitAmount) => {
const { value, scale } = unitAmount.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
discount_percentage: item.discountPercentage.match(
(discountPercentage) => {
const { value, scale } = discountPercentage.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
total_amount: {
value: item.totalAmount.toPrimitive().value.toString(),
scale: item.totalAmount.toPrimitive().scale.toString(),
},
}));
}
}

View File

@ -1,69 +0,0 @@
import { UpdateCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
import { toEmptyString } from "@repo/rdx-ddd";
import { CustomerInvoice } from "../../../domain";
import { CreateCustomerInvoiceItemsPresenter } from "./create-customer-invoice-items.presenter";
export class CreateCustomerInvoicePresenter {
private _itemsPresenter!: CreateCustomerInvoiceItemsPresenter;
constructor() {
this._itemsPresenter = new CreateCustomerInvoiceItemsPresenter();
}
public toDTO(invoice: CustomerInvoice): UpdateCustomerInvoiceByIdResponseDTO {
const items = this._itemsPresenter.toDTO(invoice);
return {
id: invoice.id.toPrimitive(),
company_id: invoice.companyId.toPrimitive(),
invoice_number: invoice.invoiceNumber.toString(),
status: invoice.status.toPrimitive(),
series: invoice.series.toString(),
invoice_date: invoice.invoiceDate.toDateString(),
operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()),
notes: toEmptyString(invoice.notes, (value) => value.toPrimitive()),
language_code: invoice.languageCode.toPrimitive(),
currency_code: invoice.currencyCode.toPrimitive(),
subtotal_amount: {
value: invoice.subtotalAmount.value.toString(),
scale: invoice.subtotalAmount.scale.toString(),
},
discount_percentage: {
value: invoice.discountPercentage.value.toString(),
scale: invoice.discountPercentage.scale.toString(),
},
discount_amount: {
value: invoice.discountAmount.value.toString(),
scale: invoice.discountAmount.scale.toString(),
},
taxable_amount: {
value: invoice.taxableAmount.value.toString(),
scale: invoice.taxableAmount.scale.toString(),
},
tax_amount: {
value: invoice.taxAmount.value.toString(),
scale: invoice.taxAmount.scale.toString(),
},
total_amount: {
value: invoice.totalAmount.value.toString(),
scale: invoice.totalAmount.scale.toString(),
},
items,
metadata: {
entity: "customer-invoices",
},
};
}
}

View File

@ -1 +0,0 @@
export * from "./create-customer-invoices.presenter";

View File

@ -1 +0,0 @@
export * from "./delete-customer-invoice.use-case";

View File

@ -1,2 +0,0 @@
export * from "./presenter";
export * from "./get-customer-invoice.use-case";

View File

@ -1,16 +0,0 @@
import { CustomerInvoiceItem } from "#/server/domain";
import { IInvoicingContext } from "#/server/intrastructure";
import { Collection } from "@rdx/core";
export const customerInvoiceItemPresenter = (items: Collection<CustomerInvoiceItem>, context: IInvoicingContext) =>
items.totalCount > 0
? items.items.map((item: CustomerInvoiceItem) => ({
description: item.description.toString(),
quantity: item.quantity.toString(),
unit_measure: "",
unit_price: item.unitPrice.toPrimitive() as IMoney_Response_DTO,
subtotal: item.calculateSubtotal().toPrimitive() as IMoney_Response_DTO,
tax_amount: item.calculateTaxAmount().toPrimitive() as IMoney_Response_DTO,
total: item.calculateTotal().toPrimitive() as IMoney_Response_DTO,
}))
: [];

View File

@ -1,26 +0,0 @@
import { ICustomerInvoiceParticipant } from "@/contexts/invoicing/domain";
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
import { ICreateCustomerInvoice_Participant_Response_DTO } from "@shared/contexts";
import { CustomerInvoiceParticipantAddressPresenter } from "./CustomerInvoiceParticipantAddress.presenter";
export const CustomerInvoiceParticipantPresenter = async (
participant: ICustomerInvoiceParticipant,
context: IInvoicingContext,
): Promise<ICreateCustomerInvoice_Participant_Response_DTO | undefined> => {
return {
id: participant.id.toString(),
tin: participant.tin.toString(),
first_name: participant.firstName.toString(),
last_name: participant.lastName.toString(),
company_name: participant.companyName.toString(),
billing_address: await CustomerInvoiceParticipantAddressPresenter(
participant.billingAddress!,
context,
),
shipping_address: await CustomerInvoiceParticipantAddressPresenter(
participant.shippingAddress!,
context,
),
};
};

View File

@ -1,19 +0,0 @@
import { CustomerInvoiceParticipantAddress } from "@/contexts/invoicing/domain";
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
import { ICreateCustomerInvoice_AddressParticipant_Response_DTO } from "@shared/contexts";
export const CustomerInvoiceParticipantAddressPresenter = async (
address: CustomerInvoiceParticipantAddress,
context: IInvoicingContext,
): Promise<ICreateCustomerInvoice_AddressParticipant_Response_DTO> => {
return {
id: address.id.toString(),
street: address.street.toString(),
city: address.city.toString(),
postal_code: address.postalCode.toString(),
province: address.province.toString(),
country: address.country.toString(),
email: address.email.toString(),
phone: address.phone.toString(),
};
};

View File

@ -1,47 +0,0 @@
import { toEmptyString } from "@repo/rdx-ddd";
import { GetCustomerInvoiceByIdResponseDTO } from "../../../../common/dto";
import { CustomerInvoice } from "../../../domain";
type GetCustomerInvoiceItemsByInvoiceIdResponseDTO = GetCustomerInvoiceByIdResponseDTO["items"];
export class GetCustomerInvoiceItemsPresenter {
toDTO(invoice: CustomerInvoice): GetCustomerInvoiceItemsByInvoiceIdResponseDTO {
const { items } = invoice;
return items.map((item, index) => {
const amounts = item.getAllAmounts();
return {
id: item.id.toString(),
position: String(index),
description: toEmptyString(item.description, (value) => value.toPrimitive()),
quantity: item.quantity.match(
(quantity) => {
const { value, scale } = quantity.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
unit_amount: item.unitAmount.match(
(unitAmount) => {
const { value, scale } = unitAmount.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
discount_percentage: item.discountPercentage.match(
(discountPercentage) => {
const { value, scale } = discountPercentage.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
total_amount: amounts.totalAmount.toPrimitive(),
};
});
}
}

View File

@ -1,76 +0,0 @@
import { IPresenter, IPresenterRegistry } from "@erp/core/api";
import { toEmptyString } from "@repo/rdx-ddd";
import { GetCustomerInvoiceByIdResponseDTO } from "../../../../common/dto";
import { CustomerInvoice } from "../../../domain";
import { GetCustomerInvoiceItemsPresenter } from "./get-invoice-items.presenter";
export class GetCustomerInvoicePresentwer implements IPresenter {
private _itemsPresenter!: GetCustomerInvoiceItemsPresenter;
constructor(private presenterRegistry: IPresenterRegistry) {
this._itemsPresenter = this.presenterRegistry.getPresenter({
resource: "customer-invoice-item",
projection: "FULL",
});
}
toOutput(params: {
invoice: CustomerInvoice;
}): GetCustomerInvoiceByIdResponseDTO {
const { invoice } = params;
const items = this._itemsPresenter.toDTO(invoice);
return {
id: invoice.id.toString(),
company_id: invoice.companyId.toString(),
invoice_number: invoice.invoiceNumber.toString(),
status: invoice.status.toPrimitive(),
series: toEmptyString(invoice.series, (value) => value.toString()),
invoice_date: invoice.invoiceDate.toDateString(),
operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()),
notes: toEmptyString(invoice.notes, (value) => value.toString()),
language_code: invoice.languageCode.toString(),
currency_code: invoice.currencyCode.toString(),
/*subtotal_amount: {
value: invoice.subtotalAmount.value.toString(),
scale: invoice.subtotalAmount.scale.toString(),
},
discount_percentage: {
value: invoice.discountPercentage.value.toString(),
scale: invoice.discountPercentage.scale.toString(),
},
discount_amount: {
value: invoice.discountAmount.value.toString(),
scale: invoice.discountAmount.scale.toString(),
},
taxable_amount: {
value: invoice.taxableAmount.value.toString(),
scale: invoice.taxableAmount.scale.toString(),
},
tax_amount: {
value: invoice.taxAmount.value.toString(),
scale: invoice.taxAmount.scale.toString(),
},
total_amount: {
value: invoice.totalAmount.value.toString(),
scale: invoice.totalAmount.scale.toString(),
},*/
items,
metadata: {
entity: "customer-invoices",
},
};
}
}

View File

@ -1 +0,0 @@
export * from "./get-invoice.presenter";

View File

@ -1,7 +1,2 @@
export * from "./create-customer-invoice";
export * from "./delete-customer-invoice";
export * from "./get-customer-invoice";
export * from "./list-customer-invoices";
export * from "./report-customer-invoice";
//export * from "./update-customer-invoice";
export * from "./presenters";
export * from "./use-cases";

View File

@ -1,2 +0,0 @@
export * from "./presenter";
export * from "./list-customer-invoices.use-case";

View File

@ -1,2 +1,2 @@
export * from "./full-domain";
export * from "./list";
export * from "./domain";
export * from "./queries";

View File

@ -1,74 +0,0 @@
import { Presenter } from "@erp/core/api";
import { CustomerInvoiceListDTO } from "@erp/customer-invoices/api/infrastructure";
import { Criteria } from "@repo/rdx-criteria/server";
import { toEmptyString } from "@repo/rdx-ddd";
import { ArrayElement, Collection } from "@repo/rdx-utils";
import { CustomerInvoiceListResponseDTO } from "../../../../common/dto";
export class CustomerInvoicesListPresenter extends Presenter {
protected _map(invoice: CustomerInvoiceListDTO) {
const recipientDTO = invoice.recipient.toObjectString();
const invoiceDTO: ArrayElement<CustomerInvoiceListResponseDTO["items"]> = {
id: invoice.id.toString(),
company_id: invoice.companyId.toString(),
customer_id: invoice.customerId.toString(),
invoice_number: invoice.invoiceNumber.toString(),
status: invoice.status.toPrimitive(),
series: toEmptyString(invoice.series, (value) => value.toString()),
invoice_date: invoice.invoiceDate.toDateString(),
operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()),
recipient: {
customer_id: invoice.customerId.toString(),
...recipientDTO,
},
language_code: invoice.languageCode.code,
currency_code: invoice.currencyCode.code,
taxes: invoice.taxes,
subtotal_amount: invoice.subtotalAmount.toObjectString(),
discount_amount: invoice.discountAmount.toObjectString(),
taxable_amount: invoice.taxableAmount.toObjectString(),
taxes_amount: invoice.taxesAmount.toObjectString(),
total_amount: invoice.totalAmount.toObjectString(),
metadata: {
entity: "customer-invoice",
},
};
return invoiceDTO;
}
toOutput(params: {
customerInvoices: Collection<CustomerInvoiceListDTO>;
criteria: Criteria;
}): CustomerInvoiceListResponseDTO {
const { customerInvoices, criteria } = params;
const invoices = customerInvoices.map((invoice) => this._map(invoice));
const totalItems = customerInvoices.total();
return {
page: criteria.pageNumber,
per_page: criteria.pageSize,
total_pages: Math.ceil(totalItems / criteria.pageSize),
total_items: totalItems,
items: invoices,
metadata: {
entity: "customer-invoices",
criteria: criteria.toJSON(),
//links: {
// self: `/api/customer-invoices?page=${criteria.pageNumber}&per_page=${criteria.pageSize}`,
// first: `/api/customer-invoices?page=1&per_page=${criteria.pageSize}`,
// last: `/api/customer-invoices?page=${Math.ceil(totalItems / criteria.pageSize)}&per_page=${criteria.pageSize}`,
//},
},
};
}
}

View File

@ -1 +0,0 @@
export * from "./customer-invoices.list.presenter";

View File

@ -1,2 +0,0 @@
export * from "./presenter";
export * from "./update-customer-invoice.use-case";

View File

@ -1 +0,0 @@
export * from "./update-invoice.presenter";

View File

@ -1,46 +0,0 @@
import { toEmptyString } from "@repo/rdx-ddd";
import { UpdateCustomerInvoiceByIdResponseDTO } from "../../../../common/dto";
import { CustomerInvoice } from "../../../domain";
type UpdateCustomerInvoiceItemsByInvoiceIdResponseDTO =
UpdateCustomerInvoiceByIdResponseDTO["items"];
export class UpdateCustomerInvoiceItemsPresenter {
toDTO(invoice: CustomerInvoice): UpdateCustomerInvoiceItemsByInvoiceIdResponseDTO {
const { items } = invoice;
return items.map((item, index) => ({
id: item.id.toString(),
position: String(index),
description: toEmptyString(item.description, (value) => value.toPrimitive()),
quantity: item.quantity.match(
(quantity) => {
const { value, scale } = quantity.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
unit_amount: item.unitAmount.match(
(unitAmount) => {
const { value, scale } = unitAmount.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
discount_percentage: item.discountPercentage.match(
(discountPercentage) => {
const { value, scale } = discountPercentage.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
total_amount: {
value: item.totalAmount.toPrimitive().value.toString(),
scale: item.totalAmount.toPrimitive().scale.toString(),
},
}));
}
}

View File

@ -1,110 +0,0 @@
import { toEmptyString } from "@repo/rdx-ddd";
import { UpdateCustomerInvoiceByIdResponseDTO } from "../../../../common/dto";
import { CustomerInvoice } from "../../../domain";
import { UpdateCustomerInvoiceItemsPresenter } from "./update-invoice-items.presenter";
export class UpdateCustomerInvoicePresenter {
private _itemsPresenter!: UpdateCustomerInvoiceItemsPresenter;
constructor() {
this._itemsPresenter = new UpdateCustomerInvoiceItemsPresenter();
}
public toDTO(invoice: CustomerInvoice): UpdateCustomerInvoiceByIdResponseDTO {
const items = this._itemsPresenter.toDTO(invoice);
return {
id: invoice.id.toPrimitive(),
company_id: invoice.companyId.toPrimitive(),
invoice_number: invoice.invoiceNumber.toString(),
status: invoice.status.toPrimitive(),
series: invoice.series.toString(),
invoice_date: invoice.invoiceDate.toDateString(),
operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()),
notes: toEmptyString(invoice.notes, (value) => value.toPrimitive()),
language_code: invoice.languageCode.toPrimitive(),
currency_code: invoice.currencyCode.toPrimitive(),
subtotal_amount: {
value: invoice.subtotalAmount.value.toString(),
scale: invoice.subtotalAmount.scale.toString(),
},
discount_percentage: {
value: invoice.discountPercentage.value.toString(),
scale: invoice.discountPercentage.scale.toString(),
},
discount_amount: {
value: invoice.discountAmount.value.toString(),
scale: invoice.discountAmount.scale.toString(),
},
taxable_amount: {
value: invoice.taxableAmount.value.toString(),
scale: invoice.taxableAmount.scale.toString(),
},
tax_amount: {
value: invoice.taxAmount.value.toString(),
scale: invoice.taxAmount.scale.toString(),
},
total_amount: {
value: invoice.totalAmount.value.toString(),
scale: invoice.totalAmount.scale.toString(),
},
items,
metadata: {
entity: "customer-invoices",
},
//subtotal: customerInvoice.calculateSubtotal().toPrimitive(),
//total: customerInvoice.calculateTotal().toPrimitive(),
/*items:
customerInvoice.items.size() > 0
? customerInvoice.items.map((item: CustomerInvoiceItem) => ({
description: item.description.toString(),
quantity: item.quantity.toPrimitive(),
unit_measure: "",
unit_price: item.unitPrice.toPrimitive(),
subtotal: item.calculateSubtotal().toPrimitive(),
//tax_amount: item.calculateTaxAmount().toPrimitive(),
total: item.calculateTotal().toPrimitive(),
}))
: [],*/
//sender: {}, //await CustomerInvoiceParticipantPresenter(customerInvoice.senderId, context),
/*recipient: await CustomerInvoiceParticipantPresenter(customerInvoice.recipient, context),
items: customerInvoiceItemPresenter(customerInvoice.items, context),
payment_term: {
payment_type: "",
due_date: "",
},
due_amount: {
currency: customerInvoice.currency.toString(),
precision: 2,
amount: 0,
},
custom_fields: [],
metadata: {
create_time: "",
last_updated_time: "",
delete_time: "",
},*/
};
}
}

View File

@ -3,8 +3,8 @@ import { DuplicateEntityError, ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize";
import { CreateCustomerInvoiceRequestDTO } from "../../../common/dto";
import { CustomerInvoiceService } from "../../domain";
import { CreateCustomerInvoiceRequestDTO } from "../../../../common/dto";
import { CustomerInvoiceService } from "../../../domain";
import { CreateCustomerInvoiceAssembler } from "./assembler";
import { CreateCustomerInvoicePropsMapper } from "./map-dto-to-create-customer-invoice-props";
@ -18,8 +18,7 @@ export class CreateCustomerInvoiceUseCase {
private readonly service: CustomerInvoiceService,
private readonly transactionManager: ITransactionManager,
private readonly assembler: CreateCustomerInvoiceAssembler,
private readonly taxCatalog: JsonTaxCatalogProvider,
private readonly taxCatalog: JsonTaxCatalogProvider
) {}
public execute(params: CreateCustomerInvoiceUseCaseInput) {

View File

@ -1,2 +1 @@
export * from "./presenter";
export * from "./create-customer-invoice.use-case";

View File

@ -20,7 +20,7 @@ import { Result } from "@repo/rdx-utils";
import {
CreateCustomerInvoiceItemRequestDTO,
CreateCustomerInvoiceRequestDTO,
} from "../../../common/dto";
} from "../../../../common/dto";
import {
CustomerInvoiceItem,
CustomerInvoiceItemDescription,
@ -33,7 +33,7 @@ import {
ItemAmount,
ItemDiscount,
ItemQuantity,
} from "../../domain";
} from "../../../domain";
/**
* Convierte el DTO a las props validadas (CustomerProps).
@ -51,7 +51,7 @@ export class CreateCustomerInvoicePropsMapper {
private languageCode?: LanguageCode;
private currencyCode?: CurrencyCode;
constructor(params: {taxCatalog: JsonTaxCatalogProvider}) {
constructor(params: { taxCatalog: JsonTaxCatalogProvider }) {
this.taxCatalog = params.taxCatalog;
this.errors = [];
}
@ -63,8 +63,16 @@ export class CreateCustomerInvoicePropsMapper {
const defaultStatus = CustomerInvoiceStatus.createDraft();
const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", this.errors);
const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_id", this.errors);
const customerId = extractOrPushError(UniqueID.create(dto.customer_id), "customer_id", this.errors);
const companyId = extractOrPushError(
UniqueID.create(dto.company_id),
"company_id",
this.errors
);
const customerId = extractOrPushError(
UniqueID.create(dto.customer_id),
"customer_id",
this.errors
);
const invoiceNumber = extractOrPushError(
CustomerInvoiceNumber.create(dto.invoice_number),
@ -230,4 +238,3 @@ export class CreateCustomerInvoicePropsMapper {
return taxes;
}
}

View File

@ -2,7 +2,7 @@ import { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { CustomerInvoiceService } from "../../domain";
import { CustomerInvoiceFullPresenter } from "../presenters/full-domain";
import { CustomerInvoiceFullPresenter } from "../presenters/domain";
type GetCustomerInvoiceUseCaseInput = {
companyId: UniqueID;

View File

@ -0,0 +1,5 @@
export * from "./create";
export * from "./get-customer-invoice.use-case";
export * from "./list-customer-invoices.use-case";
export * from "./report";
//export * from "./update-customer-invoice.use-case";

View File

@ -5,7 +5,7 @@ import { Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize";
import { CustomerInvoiceListResponseDTO } from "../../../common/dto";
import { CustomerInvoiceService } from "../../domain";
import { CustomerInvoicesListPresenter } from "../presenters/list";
import { ListCustomerInvoicesPresenter } from "../presenters";
type ListCustomerInvoicesUseCaseInput = {
companyId: UniqueID;
@ -26,7 +26,7 @@ export class ListCustomerInvoicesUseCase {
const presenter = this.presenterRegistry.getPresenter({
resource: "customer-invoice",
projection: "LIST",
}) as CustomerInvoicesListPresenter;
}) as ListCustomerInvoicesPresenter;
return this.transactionManager.complete(async (transaction: Transaction) => {
try {

View File

@ -0,0 +1,54 @@
import { EntityNotFoundError, ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { CustomerInvoiceService } from "../../../domain";
type DeleteCustomerInvoiceUseCaseInput = {
companyId: UniqueID;
invoice_id: string;
};
export class DeleteCustomerInvoiceUseCase {
constructor(
private readonly service: CustomerInvoiceService,
private readonly transactionManager: ITransactionManager
) {}
public execute(params: DeleteCustomerInvoiceUseCaseInput) {
const { invoice_id, companyId } = params;
const idOrError = UniqueID.create(invoice_id);
if (idOrError.isFailure) {
return Result.fail(idOrError.error);
}
const invoiceId = idOrError.data;
return this.transactionManager.complete(async (transaction) => {
try {
const existsCheck = await this.service.existsByIdInCompany(
companyId,
invoiceId,
transaction
);
if (existsCheck.isFailure) {
return Result.fail(existsCheck.error);
}
const invoiceExists = existsCheck.data;
if (!invoiceExists) {
return Result.fail(
new EntityNotFoundError("Customer invoice", "id", invoiceId.toString())
);
}
return await this.service.deleteInvoiceByIdInCompany(companyId, invoiceId, transaction);
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -1,7 +1,7 @@
import { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { CustomerInvoiceService } from "../../domain";
import { CustomerInvoiceService } from "../../../domain";
import { CustomerInvoiceReportPDFPresenter } from "./reporter";
type ReportCustomerInvoiceUseCaseInput = {

View File

@ -2,7 +2,7 @@ import { Presenter } from "@erp/core/api";
import * as handlebars from "handlebars";
import { readFileSync } from "node:fs";
import path from "node:path";
import { CustomerInvoice } from "../../../domain";
import { CustomerInvoice } from "../../../../domain";
export class CustomerInvoiceReportHTMLPresenter extends Presenter {
toOutput(customerInvoice: CustomerInvoice): string {

View File

@ -1,7 +1,7 @@
import { Presenter } from "@erp/core/api";
import puppeteer from "puppeteer";
import report from "puppeteer-report";
import { CustomerInvoice } from "../../../domain";
import { CustomerInvoice } from "../../../../domain";
import { CustomerInvoiceReportHTMLPresenter } from "./customer-invoice.report.html";
// https://plnkr.co/edit/lWk6Yd?preview

View File

@ -1,28 +1,31 @@
import { JsonTaxCatalogProvider, spainTaxCatalogProvider } from "@erp/core";
// modules/invoice/infrastructure/invoice-dependencies.factory.ts
import type { IMapperRegistry, IPresenterRegistry, ModuleParams } from "@erp/core/api";
import {
InMemoryMapperRegistry,
InMemoryPresenterRegistry,
SequelizeTransactionManager,
} from "@erp/core/api";
import {
CreateCustomerInvoiceUseCase,
CustomerInvoiceFullPresenter,
CustomerInvoiceItemsFullPresenter,
CustomerInvoiceReportHTMLPresenter,
CustomerInvoiceReportPDFPresenter,
DeleteCustomerInvoiceUseCase,
GetCustomerInvoiceUseCase,
ListCustomerInvoicesPresenter,
ListCustomerInvoicesUseCase,
ReportCustomerInvoiceUseCase,
} from "../application";
import { JsonTaxCatalogProvider, spainTaxCatalogProvider } from "@erp/core";
import { CustomerInvoiceService } from "../domain";
import { CustomerInvoiceFullMapper, CustomerInvoiceListMapper } from "./mappers";
import { CustomerInvoiceDomainMapper, CustomerInvoiceListMapper } from "./mappers";
import { CustomerInvoiceRepository } from "./sequelize";
type InvoiceDeps = {
export type CustomerInvoiceDeps = {
transactionManager: SequelizeTransactionManager;
mapperRegistry: IMapperRegistry;
presenterRegistry: IPresenterRegistry;
@ -36,111 +39,97 @@ type InvoiceDeps = {
get: () => GetCustomerInvoiceUseCase;
create: () => CreateCustomerInvoiceUseCase;
//update: () => UpdateCustomerInvoiceUseCase;
delete: () => DeleteCustomerInvoiceUseCase;
//delete: () => DeleteCustomerInvoiceUseCase;
report: () => ReportCustomerInvoiceUseCase;
};
};
let _presenterRegistry: IPresenterRegistry | null = null;
let _mapperRegistry: IMapperRegistry | null = null;
let _repo: CustomerInvoiceRepository | null = null;
let _service: CustomerInvoiceService | null = null;
let _catalogs: InvoiceDeps["catalogs"] | null = null;
export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
export function buildCustomerInvoiceDependencies(params: ModuleParams): CustomerInvoiceDeps {
const { database } = params;
const transactionManager = new SequelizeTransactionManager(database);
if (!_catalogs) _catalogs = { taxes: spainTaxCatalogProvider };
const catalogs = { taxes: spainTaxCatalogProvider };
if (!_mapperRegistry) {
_mapperRegistry = new InMemoryMapperRegistry();
_mapperRegistry.registerDomainMapper(
"FULL",
new CustomerInvoiceFullMapper({
taxCatalog: _catalogs!.taxes,
})
);
_mapperRegistry.registerReadModelMapper("LIST", new CustomerInvoiceListMapper());
}
if (!_repo) _repo = new CustomerInvoiceRepository({ mapperRegistry: _mapperRegistry, database });
if (!_service) _service = new CustomerInvoiceService(_repo);
if (!_presenterRegistry) {
_presenterRegistry = new InMemoryPresenterRegistry();
_presenterRegistry.registerPresenters([
// Mapper Registry
const mapperRegistry = new InMemoryMapperRegistry();
mapperRegistry
.registerDomainMapper(
{ resource: "customer-invoice" },
new CustomerInvoiceDomainMapper({ taxCatalog: catalogs.taxes })
)
.registerQueryMappers([
{
key: {
resource: "customer-invoice-items",
projection: "FULL",
},
presenter: new CustomerInvoiceItemsFullPresenter(_presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "FULL",
},
presenter: new CustomerInvoiceFullPresenter(_presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "LIST",
},
presenter: new ListCustomerInvoicesPresenter(_presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "REPORT",
format: "HTML",
},
presenter: new CustomerInvoiceReportHTMLPresenter(_presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "REPORT",
format: "PDF",
},
presenter: new CustomerInvoiceReportPDFPresenter(_presenterRegistry),
key: { resource: "customer-invoice", query: "LIST" },
mapper: new CustomerInvoiceListMapper(),
},
]);
}
// Repository & Services
const repo = new CustomerInvoiceRepository({ mapperRegistry, database });
const service = new CustomerInvoiceService(repo);
// Presenter Registry
const presenterRegistry = new InMemoryPresenterRegistry();
presenterRegistry.registerPresenters([
{
key: {
resource: "customer-invoice-items",
projection: "FULL",
},
presenter: new CustomerInvoiceItemsFullPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "FULL",
},
presenter: new CustomerInvoiceFullPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "LIST",
},
presenter: new ListCustomerInvoicesPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "REPORT",
format: "HTML",
},
presenter: new CustomerInvoiceReportHTMLPresenter(presenterRegistry),
},
{
key: {
resource: "customer-invoice",
projection: "REPORT",
format: "PDF",
},
presenter: new CustomerInvoiceReportPDFPresenter(presenterRegistry),
},
]);
return {
transactionManager,
repo: _repo,
mapperRegistry: _mapperRegistry,
presenterRegistry: _presenterRegistry,
service: _service,
catalogs: _catalogs,
repo,
mapperRegistry,
presenterRegistry,
service,
catalogs,
build: {
list: () =>
new ListCustomerInvoicesUseCase(_service!, transactionManager!, _presenterRegistry!),
get: () => new GetCustomerInvoiceUseCase(_service!, transactionManager!, _presenterRegistry!),
list: () => new ListCustomerInvoicesUseCase(service, transactionManager, presenterRegistry),
get: () => new GetCustomerInvoiceUseCase(service, transactionManager, presenterRegistry),
create: () =>
new CreateCustomerInvoiceUseCase(
_service!,
transactionManager!,
_presenterRegistry!,
_catalogs!.taxes
service,
transactionManager,
presenterRegistry,
catalogs.taxes
),
/*update: () =>
new UpdateCustomerInvoiceUseCase(
_service!,
transactionManager!,
_presenterRegistry!,
_catalogs!.taxes
),*/
delete: () => new DeleteCustomerInvoiceUseCase(_service!, transactionManager!),
// update: () => new UpdateCustomerInvoiceUseCase(service, transactionManager),
// delete: () => new DeleteCustomerInvoiceUseCase(service, transactionManager),
report: () =>
new ReportCustomerInvoiceUseCase(_service!, transactionManager!, _presenterRegistry!),
new ReportCustomerInvoiceUseCase(service, transactionManager, presenterRegistry),
},
};
}

View File

@ -5,28 +5,26 @@ import { Sequelize } from "sequelize";
import {
CreateCustomerInvoiceRequestSchema,
CustomerInvoiceListRequestSchema,
DeleteCustomerInvoiceByIdRequestSchema,
GetCustomerInvoiceByIdRequestSchema,
ReportCustomerInvoiceByIdRequestSchema,
} from "../../../common/dto";
import { getInvoiceDependencies } from "../dependencies";
import { buildCustomerInvoiceDependencies } from "../dependencies";
import {
CreateCustomerInvoiceController,
DeleteCustomerInvoiceController,
GetCustomerInvoiceController,
ListCustomerInvoicesController,
ReportCustomerInvoiceController,
} from "./controllers";
export const customerInvoicesRouter = (params: ModuleParams) => {
const { app, database, baseRoutePath, logger } = params as {
const { app, baseRoutePath, logger } = params as {
app: Application;
database: Sequelize;
baseRoutePath: string;
logger: ILogger;
};
const deps = getInvoiceDependencies(params);
const deps = buildCustomerInvoiceDependencies(params);
const router: Router = Router({ mergeParams: true });
@ -94,7 +92,7 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
}
);*/
router.delete(
/*router.delete(
"/:invoice_id",
//checkTabContext,
@ -104,7 +102,7 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
const controller = new DeleteCustomerInvoiceController(useCase);
return controller.execute(req, res, next);
}
);
);*/
router.get(
"/:invoice_id/report",

View File

@ -19,28 +19,28 @@ import {
ItemTaxes,
} from "../../../domain";
import { CustomerInvoiceItemCreationAttributes, CustomerInvoiceItemModel } from "../../sequelize";
import { ItemTaxesMapper } from "./item-taxes.full.mapper";
import { ItemTaxesDomainMapper } from "./item-taxes.mapper";
export interface ICustomerInvoiceItemMapper
export interface ICustomerInvoiceItemDomainMapper
extends ISequelizeDomainMapper<
CustomerInvoiceItemModel,
CustomerInvoiceItemCreationAttributes,
CustomerInvoiceItem
> {}
export class CustomerInvoiceItemMapper
export class CustomerInvoiceItemDomainMapper
extends SequelizeDomainMapper<
CustomerInvoiceItemModel,
CustomerInvoiceItemCreationAttributes,
CustomerInvoiceItem
>
implements ICustomerInvoiceItemMapper
implements ICustomerInvoiceItemDomainMapper
{
private _taxesMapper: ItemTaxesMapper;
private _taxesMapper: ItemTaxesDomainMapper;
constructor(params: MapperParamsType) {
super();
this._taxesMapper = new ItemTaxesMapper(params);
this._taxesMapper = new ItemTaxesDomainMapper(params);
}
private mapAttributesToDomain(source: CustomerInvoiceItemModel, params?: MapperParamsType) {

View File

@ -26,24 +26,24 @@ import {
} from "../../../domain";
import { InvoiceTaxes } from "../../../domain/entities/invoice-taxes";
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
import { CustomerInvoiceItemMapper as CustomerInvoiceItemFullMapper } from "./customer-invoice-item.full.mapper";
import { InvoiceRecipientMapper as InvoiceRecipientFullMapper } from "./invoice-recipient.full.mapper";
import { TaxesMapper as TaxesFullMapper } from "./taxes.full.mapper";
import { CustomerInvoiceItemDomainMapper as CustomerInvoiceItemFullMapper } from "./customer-invoice-item.mapper";
import { InvoiceRecipientDomainMapper as InvoiceRecipientFullMapper } from "./invoice-recipient.mapper";
import { TaxesDomainMapper as TaxesFullMapper } from "./taxes.mapper";
export interface ICustomerInvoiceFullMapper
export interface ICustomerInvoiceDomainMapper
extends ISequelizeDomainMapper<
CustomerInvoiceModel,
CustomerInvoiceCreationAttributes,
CustomerInvoice
> {}
export class CustomerInvoiceFullMapper
export class CustomerInvoiceDomainMapper
extends SequelizeDomainMapper<
CustomerInvoiceModel,
CustomerInvoiceCreationAttributes,
CustomerInvoice
>
implements ICustomerInvoiceFullMapper
implements ICustomerInvoiceDomainMapper
{
private _itemsMapper: CustomerInvoiceItemFullMapper;
private _recipientMapper: InvoiceRecipientFullMapper;

View File

@ -0,0 +1 @@
export * from "./customer-invoice.mapper";

View File

@ -19,7 +19,7 @@ import { Maybe, Result } from "@repo/rdx-utils";
import { CustomerInvoiceProps, InvoiceRecipient } from "../../../domain";
import { CustomerInvoiceModel } from "../../sequelize";
export class InvoiceRecipientMapper {
export class InvoiceRecipientDomainMapper {
public mapToDomain(
source: CustomerInvoiceModel,
params?: MapperParamsType

View File

@ -14,7 +14,7 @@ import {
CustomerInvoiceItemTaxModel,
} from "../../sequelize";
export class ItemTaxesMapper extends SequelizeDomainMapper<
export class ItemTaxesDomainMapper extends SequelizeDomainMapper<
CustomerInvoiceItemTaxModel,
CustomerInvoiceItemCreationAttributes,
ItemTax
@ -62,10 +62,10 @@ export class ItemTaxesMapper extends SequelizeDomainMapper<
return createResult;
}
public mapToPersistence(
/*public mapToPersistence(
source: ItemTax,
params?: MapperParamsType
): CustomerInvoiceItemCreationAttributes {
throw new Error("not implemented");
}
}*/
}

View File

@ -12,7 +12,7 @@ import { CustomerInvoiceProps } from "../../../domain";
import { InvoiceTax } from "../../../domain/entities/invoice-taxes";
import { CustomerInvoiceTaxCreationAttributes, CustomerInvoiceTaxModel } from "../../sequelize";
export class TaxesMapper extends SequelizeDomainMapper<
export class TaxesDomainMapper extends SequelizeDomainMapper<
CustomerInvoiceTaxModel,
CustomerInvoiceTaxCreationAttributes,
InvoiceTax
@ -64,10 +64,10 @@ export class TaxesMapper extends SequelizeDomainMapper<
return createResult;
}
public mapToPersistence(
/*public mapToPersistence(
source: InvoiceTax,
params?: MapperParamsType
): CustomerInvoiceTaxCreationAttributes {
throw new Error("not implemented");
}
}*/
}

View File

@ -1 +0,0 @@
export * from "./customer-invoice.full.mapper";

View File

@ -1,2 +1,2 @@
export * from "./full-domain";
export * from "./list";
export * from "./domain";
export * from "./queries";

View File

@ -1,7 +1,7 @@
import {
ISequelizeReadModelMapper,
ISequelizeQueryMapper,
MapperParamsType,
SequelizeReadModelMapper,
SequelizeQueryMapper,
ValidationErrorCollection,
ValidationErrorDetail,
extractOrPushError,
@ -56,10 +56,10 @@ export type CustomerInvoiceListDTO = {
};
export interface ICustomerInvoiceListMapper
extends ISequelizeReadModelMapper<CustomerInvoiceModel, CustomerInvoiceListDTO> {}
extends ISequelizeQueryMapper<CustomerInvoiceModel, CustomerInvoiceListDTO> {}
export class CustomerInvoiceListMapper
extends SequelizeReadModelMapper<CustomerInvoiceModel, CustomerInvoiceListDTO>
extends SequelizeQueryMapper<CustomerInvoiceModel, CustomerInvoiceListDTO>
implements ICustomerInvoiceListMapper
{
private _recipientMapper: InvoiceRecipientListMapper;

View File

@ -10,9 +10,9 @@ import {
} from "@repo/rdx-ddd";
import {
IReadModelMapperWithBulk,
IQueryMapperWithBulk,
MapperParamsType,
SequelizeReadModelMapper,
SequelizeQueryMapper,
ValidationErrorDetail,
extractOrPushError,
} from "@erp/core/api";
@ -23,10 +23,10 @@ import { CustomerInvoiceModel } from "../../sequelize";
import { CustomerInvoiceListDTO } from "./customer-invoice.list.mapper";
interface IInvoiceRecipientListMapper
extends IReadModelMapperWithBulk<CustomerInvoiceModel, InvoiceRecipient> {}
extends IQueryMapperWithBulk<CustomerInvoiceModel, InvoiceRecipient> {}
export class InvoiceRecipientListMapper
extends SequelizeReadModelMapper<CustomerInvoiceModel, InvoiceRecipient>
extends SequelizeQueryMapper<CustomerInvoiceModel, InvoiceRecipient>
implements IInvoiceRecipientListMapper
{
public mapToDTO(

View File

@ -1,37 +1,23 @@
import {
EntityNotFoundError,
IMapperRegistry,
SequelizeRepository,
translateSequelizeError,
} from "@erp/core/api";
import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } from "@erp/core/api";
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils";
import { Sequelize, Transaction } from "sequelize";
import { Transaction } from "sequelize";
import { CustomerInvoice, ICustomerInvoiceRepository } from "../../domain";
import {
CustomerInvoiceListDTO,
ICustomerInvoiceFullMapper,
ICustomerInvoiceDomainMapper,
ICustomerInvoiceListMapper,
} from "../mappers";
import { CustomerInvoiceItemTaxModel } from "./customer-invoice-item-tax.model";
import { CustomerInvoiceItemModel } from "./customer-invoice-item.model";
import { CustomerInvoiceTaxModel } from "./customer-invoice-tax.model";
import { CustomerInvoiceModel } from "./customer-invoice.model";
import { CustomerInvoiceItemTaxModel } from "./models/customer-invoice-item-tax.model";
import { CustomerInvoiceItemModel } from "./models/customer-invoice-item.model";
import { CustomerInvoiceTaxModel } from "./models/customer-invoice-tax.model";
import { CustomerInvoiceModel } from "./models/customer-invoice.model";
export class CustomerInvoiceRepository
extends SequelizeRepository<CustomerInvoice>
implements ICustomerInvoiceRepository
{
private readonly _database!: Sequelize;
private readonly _registry!: IMapperRegistry;
constructor(params: { mapperRegistry: IMapperRegistry; database: Sequelize }) {
super();
this._registry = params.mapperRegistry;
this._database = params.database;
}
// Listado por tenant con criteria saneada
/* async searchInCompany(criteria: any, companyId: string): Promise<{
rows: InvoiceListRow[];
@ -78,7 +64,9 @@ export class CustomerInvoiceRepository
transaction: Transaction
): Promise<Result<CustomerInvoice, Error>> {
try {
const mapper: ICustomerInvoiceFullMapper = this._registry.getDomainMapper("FULL");
const mapper: ICustomerInvoiceDomainMapper = this._registry.getDomainMapper({
resource: "customer-invoice",
});
const mapperData = mapper.mapToPersistence(invoice);
if (mapperData.isFailure) {
@ -134,7 +122,7 @@ export class CustomerInvoiceRepository
transaction: Transaction
): Promise<Result<CustomerInvoice, Error>> {
try {
const mapper: ICustomerInvoiceFullMapper = this._registry.getDomainMapper("FULL");
const mapper: ICustomerInvoiceDomainMapper = this._registry.getDomainMapper("FULL");
const { CustomerModel } = this._database.models;
const row = await CustomerInvoiceModel.findOne({
@ -194,7 +182,7 @@ export class CustomerInvoiceRepository
transaction: Transaction
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>> {
try {
const mapper: ICustomerInvoiceListMapper = this._registry.getReadModelMapper("LIST");
const mapper: ICustomerInvoiceListMapper = this._registry.getQueryMapper("LIST");
const { CustomerModel } = this._database.models;
const converter = new CriteriaToSequelizeConverter();
const query = converter.convert(criteria);

View File

@ -1,13 +1,10 @@
import customerInvoiceItemTaxesModelInit from "./customer-invoice-item-tax.model";
import customerInvoiceItemModelInit from "./customer-invoice-item.model";
import customerInvoiceTaxesModelInit from "./customer-invoice-tax.model";
import customerInvoiceModelInit from "./customer-invoice.model";
import customerInvoiceItemTaxesModelInit from "./models/customer-invoice-item-tax.model";
import customerInvoiceItemModelInit from "./models/customer-invoice-item.model";
import customerInvoiceTaxesModelInit from "./models/customer-invoice-tax.model";
import customerInvoiceModelInit from "./models/customer-invoice.model";
export * from "./customer-invoice-item-tax.model";
export * from "./customer-invoice-item.model";
export * from "./customer-invoice-tax.model";
export * from "./customer-invoice.model";
export * from "./customer-invoice.repository";
export * from "./models";
// Array de inicializadores para que registerModels() lo use
export const models = [

View File

@ -6,7 +6,7 @@ import {
NonAttribute,
Sequelize,
} from "sequelize";
import { CustomerInvoiceItem } from "../../domain";
import { CustomerInvoiceItem } from "../../../domain";
export type CustomerInvoiceItemTaxCreationAttributes = InferCreationAttributes<
CustomerInvoiceItemTaxModel,

View File

@ -6,7 +6,7 @@ import {
NonAttribute,
Sequelize,
} from "sequelize";
import { CustomerInvoice } from "../../domain";
import { CustomerInvoice } from "../../../domain";
export type CustomerInvoiceTaxCreationAttributes = InferCreationAttributes<
CustomerInvoiceTaxModel,

View File

@ -0,0 +1,4 @@
export * from "./customer-invoice-item-tax.model";
export * from "./customer-invoice-item.model";
export * from "./customer-invoice-tax.model";
export * from "./customer-invoice.model";

View File

@ -1,5 +1,9 @@
import type { IMapperRegistry, IPresenterRegistry, ModuleParams } from "@erp/core/api";
import { InMemoryMapperRegistry, SequelizeTransactionManager } from "@erp/core/api";
import {
InMemoryMapperRegistry,
InMemoryPresenterRegistry,
SequelizeTransactionManager,
} from "@erp/core/api";
import {
CreateCustomerUseCase,
@ -9,10 +13,10 @@ import {
UpdateCustomerUseCase,
} from "../application";
import { CustomerService } from "../domain";
import { CustomerMapper } from "./mappers";
import { CustomerDomainMapper } from "./mappers";
import { CustomerRepository } from "./sequelize";
type CustomerDeps = {
export type CustomerDeps = {
transactionManager: SequelizeTransactionManager;
mapperRegistry: IMapperRegistry;
presenterRegistry: IPresenterRegistry;
@ -27,46 +31,37 @@ type CustomerDeps = {
};
};
const _presenterRegistry: IPresenterRegistry | null = null;
let _mapperRegistry: IMapperRegistry | null = null;
let _repo: CustomerRepository | null = null;
let _service: CustomerService | null = null;
export function getCustomerDependencies(params: ModuleParams): CustomerDeps {
export function buildCustomerDependencies(params: ModuleParams): CustomerDeps {
const { database } = params;
const transactionManager = new SequelizeTransactionManager(database);
if (!_mapperRegistry) {
_mapperRegistry = new InMemoryMapperRegistry();
_mapperRegistry.registerDomainMapper("FULL", new CustomerMapper());
}
// Mapper Registry
const mapperRegistry = new InMemoryMapperRegistry();
mapperRegistry.registerDomainMapper({ resource: "customer" }, new CustomerDomainMapper());
if (!_repo) _repo = new CustomerRepository({ mapperRegistry: _mapperRegistry });
if (!_service) _service = new CustomerService(_repo);
// Repository & Services
const repo = new CustomerRepository({ mapperRegistry: _mapperRegistry, database });
const service = new CustomerService(repo);
/*if (!_presenterRegistry) {
_presenterRegistry = new InMemoryPresenterRegistry();
_presenterRegistry.registerPresenters([
{
key: { resource: "customer", projection: "FULL" },
presenter: new ListCustomersAssembler(),
},
{
key: { resource: "customer", projection: "LIST" },
presenter: new GetCustomerAssembler(),
},
]);
}*/
// Presenter Registry
const presenterRegistry = new InMemoryPresenterRegistry();
presenterRegistry.registerPresenters([
{
key: { resource: "customer", projection: "FULL" },
presenter: new ListCustomersAssembler(),
},
{
key: { resource: "customer", projection: "LIST" },
presenter: new GetCustomerAssembler(),
},
]);
return {
transactionManager,
repo: _repo,
mapperRegistry: _mapperRegistry,
//presenterRegistry: _presenterRegistry,
service: _service,
repo,
mapperRegistry,
presenterRegistry,
service,
build: {
/*list: () => new ListCustomersUseCase(_service!, transactionManager!, presenterRegistry!),
get: () => new GetCustomerUseCase(_service!, transactionManager!, presenterRegistry!),

View File

@ -1,5 +1,5 @@
import {
ISequelizeMapper,
ISequelizeDomainMapper,
MapperParamsType,
SequelizeDomainMapper,
ValidationErrorCollection,
@ -27,15 +27,15 @@ import {
toNullable,
} from "@repo/rdx-ddd";
import { Collection, Result, isNullishOrEmpty } from "@repo/rdx-utils";
import { Customer, CustomerProps, CustomerStatus } from "../../domain";
import { CustomerCreationAttributes, CustomerModel } from "../sequelize";
import { Customer, CustomerProps, CustomerStatus } from "../../../domain";
import { CustomerCreationAttributes, CustomerModel } from "../../sequelize";
export interface ICustomerMapper
extends ISequelizeMapper<CustomerModel, CustomerCreationAttributes, Customer> {}
export interface ICustomerDomainMapper
extends ISequelizeDomainMapper<CustomerModel, CustomerCreationAttributes, Customer> {}
export class CustomerMapper
export class CustomerDomainMapper
extends SequelizeDomainMapper<CustomerModel, CustomerCreationAttributes, Customer>
implements ICustomerMapper
implements ICustomerDomainMapper
{
public mapToDomain(source: CustomerModel, params?: MapperParamsType): Result<Customer, Error> {
try {

View File

@ -0,0 +1 @@
export * from "./customer.full.mapper";

View File

@ -1 +1,2 @@
export * from "./customer.mapper";
export * from "./domain";
export * from "./queries";

View File

@ -1,7 +1,7 @@
import customerModelInit from "./customer.model";
import customerModelInit from "./models/customer.model";
export * from "./customer.model";
export * from "./customer.repository";
export * from "./models";
export * from "./repositories";
// Array de inicializadores para que registerModels() lo use
export const models = [customerModelInit];

View File

@ -0,0 +1 @@
export * from "./customer.model";

View File

@ -3,21 +3,13 @@ import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/serve
import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize";
import { Customer, ICustomerRepository } from "../../domain";
import { ICustomerMapper } from "../mappers/customer.mapper";
import { CustomerModel } from "./customer.model";
import { Customer, ICustomerRepository } from "../../../domain";
import { CustomerModel } from "../models/customer.model";
export class CustomerRepository
extends SequelizeRepository<Customer>
implements ICustomerRepository
{
private readonly mapper!: ICustomerMapper;
constructor(mapper: ICustomerMapper) {
super();
this.mapper = mapper;
}
/**
*
* Guarda un nuevo cliente o actualiza uno existente.
@ -28,9 +20,20 @@ export class CustomerRepository
*/
async save(customer: Customer, transaction: Transaction): Promise<Result<Customer, Error>> {
try {
const data = this.mapper.mapToPersistence(customer);
const mapper: ICustomerDomainMapper = this._registry.getDomainMapper({
resource: "customer",
});
const mapperData = mapper.mapToPersistence(customer);
if (mapperData.isFailure) {
return Result.fail(mapperData.error);
}
const { data } = mapperData;
const [instance] = await CustomerModel.upsert(data, { transaction, returning: true });
const savedCustomer = this.mapper.mapToDomain(instance);
const savedCustomer = mapper.mapToDomain(instance);
return savedCustomer;
} catch (err: unknown) {
return Result.fail(translateSequelizeError(err));
@ -75,6 +78,10 @@ export class CustomerRepository
transaction?: Transaction
): Promise<Result<Customer, Error>> {
try {
const mapper: ICustomerDomainMapper = this._registry.getDomainMapper({
resource: "customer",
});
const row = await CustomerModel.findOne({
where: { id: id.toString(), company_id: companyId.toString() },
transaction,
@ -84,7 +91,7 @@ export class CustomerRepository
return Result.fail(new EntityNotFoundError("Customer", "id", id.toString()));
}
const customer = this.mapper.mapToDomain(row);
const customer = mapper.mapToDomain(row);
return customer;
} catch (error: any) {
return Result.fail(translateSequelizeError(error));
@ -107,6 +114,10 @@ export class CustomerRepository
transaction?: Transaction
): Promise<Result<Collection<Customer>>> {
try {
const mapper: ICustomerDomainMapper = this._registry.getDomainMapper({
resource: "customer",
});
const converter = new CriteriaToSequelizeConverter();
const query = converter.convert(criteria);
@ -120,7 +131,7 @@ export class CustomerRepository
transaction,
});
return this.mapper.mapArrayToDomain(instances);
return mapper.mapArrayToDomain(instances);
} catch (err: unknown) {
return Result.fail(translateSequelizeError(err));
}

View File

@ -0,0 +1 @@
export * from "./customer.repository";