Facturas de cliente
This commit is contained in:
parent
877d98f188
commit
e80cc572f9
@ -1,2 +1,3 @@
|
|||||||
export * from "./errors";
|
export * from "./errors";
|
||||||
|
export * from "./repositories";
|
||||||
export * from "./value-objects";
|
export * from "./value-objects";
|
||||||
|
|||||||
1
modules/core/src/api/domain/repositories/index.ts
Normal file
1
modules/core/src/api/domain/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./repository.interface";
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tipo para los parámetros que reciben los métodos de los mappers
|
||||||
|
* Es un objeto que puede contener cualquier cosa.
|
||||||
|
*/
|
||||||
|
export type MapperParamsType = Record<string, any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🧭 Mapper de Dominio (Persistencia ↔ Dominio/Agregado)
|
||||||
|
* - Responsabilidad: transformar un registro de persistencia en un agregado de dominio y viceversa.
|
||||||
|
* - No debe contener lógica de negocio; sólo construcción/serialización de objetos.
|
||||||
|
*/
|
||||||
|
export interface IDomainMapper<TPersistence, TDomain> {
|
||||||
|
/**
|
||||||
|
* Convierte un registro crudo de persistencia (ORM/row) en un agregado/entidad de dominio.
|
||||||
|
* Debe devolver Result.fail(...) si la construcción del dominio no es posible/consistente.
|
||||||
|
*/
|
||||||
|
mapToDomain(raw: TPersistence, params?: MapperParamsType): Result<TDomain, Error>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convierte un agregado/entidad de dominio en un objeto de persistencia listo para el ORM.
|
||||||
|
* Debe devolver Result.fail(...) si hay incoherencias o datos no serializables.
|
||||||
|
*/
|
||||||
|
mapToPersistence(domain: TDomain, params?: MapperParamsType): Result<TPersistence, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 📦 Extensión opcional para operaciones en lote de mapeo de dominio.
|
||||||
|
* Útil para repos que recuperan múltiples filas y necesitan entregar colecciones de agregados.
|
||||||
|
*/
|
||||||
|
export interface IBulkDomainMapper<TPersistence, TDomain> {
|
||||||
|
/**
|
||||||
|
* Mapea múltiples registros crudos de persistencia a una Collection de agregados de dominio.
|
||||||
|
*/
|
||||||
|
mapToDomainCollection(
|
||||||
|
raws: TPersistence[],
|
||||||
|
totalCount: number,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<Collection<TDomain>, Error>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapea múltiples agregados de dominio a un array de objetos de persistencia.
|
||||||
|
*/
|
||||||
|
mapToPersistenceArray(
|
||||||
|
domains: Collection<TDomain>,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<TPersistence[], Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🧩 Tipo para cuando se desea un mapper de dominio con soporte opcional de operaciones en lote.
|
||||||
|
* Puedes implementar sólo IDomainMapper y añadir IBulkDomainMapper cuando lo necesites.
|
||||||
|
*/
|
||||||
|
export type DomainMapperWithBulk<TPersistence, TDomain> = IDomainMapper<TPersistence, TDomain> &
|
||||||
|
Partial<IBulkDomainMapper<TPersistence, TDomain>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 👓 Mapper de Read Model (Persistencia ↔ DTO/Proyección de Lectura)
|
||||||
|
* - 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> {
|
||||||
|
/**
|
||||||
|
* Convierte un registro crudo en un DTO de lectura.
|
||||||
|
*/
|
||||||
|
mapToDTO(raw: TPersistence, params?: MapperParamsType): Result<TDTO, Error>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convierte múltiples registros crudos en una Collection de DTOs de lectura.
|
||||||
|
*/
|
||||||
|
mapToDTOCollection(
|
||||||
|
raws: TPersistence[],
|
||||||
|
totalCount: number,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<Collection<TDTO>, Error>;
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export * from "./database";
|
export * from "./database";
|
||||||
export * from "./errors";
|
export * from "./errors";
|
||||||
export * from "./express";
|
export * from "./express";
|
||||||
|
export * from "./mappers";
|
||||||
export * from "./sequelize";
|
export * from "./sequelize";
|
||||||
|
|||||||
2
modules/core/src/api/infrastructure/mappers/index.ts
Normal file
2
modules/core/src/api/infrastructure/mappers/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./mapper-registry";
|
||||||
|
export * from "./mapper-registry.interface";
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* 🔑 Claves de proyección comunes para seleccionar mappers en lectura.
|
||||||
|
* Puedes extender con otras cadenas según tus necesidades ("SUMMARY", "EXPORT", etc.).
|
||||||
|
*/
|
||||||
|
export type MapperProjectionKey = "FULL" | "LIST" | "REPORTS" | (string & {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🏗️ Registro/Fábrica de mappers (Strategy/Factory)
|
||||||
|
* - Permite resolver diferentes mappers según la proyección (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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene un mapper de read model por clave de proyección.
|
||||||
|
*/
|
||||||
|
getReadModelMapper<T>(key: MapperProjectionKey): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra un mapper de dominio bajo una clave de proyección.
|
||||||
|
*/
|
||||||
|
registerDomainMapper<T>(key: MapperProjectionKey, mapper: T): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra un mapper de read model bajo una clave de proyección.
|
||||||
|
*/
|
||||||
|
registerReadModelMapper<T>(key: MapperProjectionKey, mapper: T): void;
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { InfrastructureError } from "../errors";
|
||||||
|
import { IMapperRegistry, MapperProjectionKey } from "./mapper-registry.interface";
|
||||||
|
|
||||||
|
export class InMemoryMapperRegistry implements IMapperRegistry {
|
||||||
|
private domainMappers: Map<MapperProjectionKey, any> = new Map();
|
||||||
|
private readModelMappers: Map<MapperProjectionKey, any> = new Map();
|
||||||
|
|
||||||
|
getDomainMapper<T>(key: MapperProjectionKey): T {
|
||||||
|
if (!this.readModelMappers.has(key)) {
|
||||||
|
throw new InfrastructureError(`Error. Domain model mapper ${key} not registred!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.domainMappers.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerDomainMapper<T>(key: MapperProjectionKey, mapper: T): void {
|
||||||
|
this.domainMappers.set(key, mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerReadModelMapper<T>(key: MapperProjectionKey, mapper: T): void {
|
||||||
|
this.readModelMappers.set(key, mapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
|
export * from "./mappers";
|
||||||
export * from "./sequelize-error-translator";
|
export * from "./sequelize-error-translator";
|
||||||
export * from "./sequelize-mapper";
|
|
||||||
export * from "./sequelize-repository";
|
export * from "./sequelize-repository";
|
||||||
export * from "./sequelize-transaction-manager";
|
export * from "./sequelize-transaction-manager";
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./sequelize-domain-mapper";
|
||||||
|
export * from "./sequelize-mapper.interface";
|
||||||
|
export * from "./sequelize-read-model-mapper";
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
import { Collection, Result, ResultCollection } from "@repo/rdx-utils";
|
||||||
|
import { Model } from "sequelize";
|
||||||
|
import { MapperParamsType } from "../../../domain";
|
||||||
|
import { ISequelizeDomainMapper } from "./sequelize-mapper.interface";
|
||||||
|
|
||||||
|
export abstract class SequelizeDomainMapper<TModel extends Model, TModelAttributes, TEntity>
|
||||||
|
implements ISequelizeDomainMapper<TModel, TModelAttributes, TEntity>
|
||||||
|
{
|
||||||
|
public abstract mapToDomain(raw: TModel, params?: MapperParamsType): Result<TEntity, Error>;
|
||||||
|
public abstract mapToPersistence(
|
||||||
|
domain: TEntity,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<TModelAttributes, Error>;
|
||||||
|
|
||||||
|
public mapToDomainCollection(
|
||||||
|
raws: (TModel | TModelAttributes)[],
|
||||||
|
totalCount: number,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<Collection<TEntity>, Error> {
|
||||||
|
const _source = raws ?? [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (_source.length === 0) {
|
||||||
|
return Result.ok(new Collection([], totalCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = _source.map(
|
||||||
|
(value, index) => this.mapToDomain(value as TModel, { index, ...params }).data
|
||||||
|
);
|
||||||
|
return Result.ok(new Collection(items, totalCount));
|
||||||
|
} catch (error) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapToPersistenceArray(
|
||||||
|
domains: Collection<TEntity>,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<TModelAttributes[], Error> {
|
||||||
|
const results = new ResultCollection(
|
||||||
|
domains.map((domain, index) => this.mapToPersistence(domain, { index, ...params }))
|
||||||
|
);
|
||||||
|
if (results.hasSomeFaultyResult()) return results.getFirstFaultyResult();
|
||||||
|
|
||||||
|
return Result.ok(results.objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*protected _safeMap<T>(operation: () => T, key: string): Result<T, Error> {
|
||||||
|
try {
|
||||||
|
return Result.ok(operation());
|
||||||
|
} catch (error: unknown) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _mapsValue(
|
||||||
|
row: TModel,
|
||||||
|
key: string,
|
||||||
|
customMapFn: (value: any, params: MapperParamsType) => Result<any, Error>,
|
||||||
|
params: MapperParamsType = { defaultValue: null }
|
||||||
|
): Result<any, Error> {
|
||||||
|
return customMapFn(row?.dataValues[key] ?? params.defaultValue, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _mapsAssociation(
|
||||||
|
row: TModel,
|
||||||
|
associationName: string,
|
||||||
|
customMapper: DomainMapperWithBulk<any, any>,
|
||||||
|
params: MapperParamsType = {}
|
||||||
|
): Result<any, Error> {
|
||||||
|
if (!customMapper) {
|
||||||
|
Result.fail(Error(`Custom mapper undefined for ${associationName}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { filter, ...otherParams } = params;
|
||||||
|
let associationRows = row?.dataValues[associationName] ?? [];
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
associationRows = Array.isArray(associationRows)
|
||||||
|
? associationRows.filter(filter)
|
||||||
|
: filter(associationRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.isArray(associationRows)
|
||||||
|
? customMapper.mapToDomainCollection(associationRows, associationRows.length, otherParams)
|
||||||
|
: customMapper.mapToDomain(associationRows, otherParams);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { DomainMapperWithBulk, IReadModelMapperWithBulk } from "../../../domain";
|
||||||
|
|
||||||
|
export interface ISequelizeDomainMapper<TModel, TModelAttributes, TEntity>
|
||||||
|
extends DomainMapperWithBulk<TModel | TModelAttributes, TEntity> {}
|
||||||
|
|
||||||
|
export interface ISequelizeReadModelMapper<TModel, TDTO>
|
||||||
|
extends IReadModelMapperWithBulk<TModel, TDTO> {}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
|
import { Model } from "sequelize";
|
||||||
|
import { MapperParamsType } from "../../../domain";
|
||||||
|
import { ISequelizeReadModelMapper } from "./sequelize-mapper.interface";
|
||||||
|
|
||||||
|
export abstract class SequelizeReadModelMapper<TModel extends Model, TEntity>
|
||||||
|
implements ISequelizeReadModelMapper<TModel, TEntity>
|
||||||
|
{
|
||||||
|
public abstract mapToDTO(raw: TModel, params?: MapperParamsType): Result<TEntity, Error>;
|
||||||
|
|
||||||
|
public mapToDTOCollection(
|
||||||
|
raws: TModel[],
|
||||||
|
totalCount: number,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<Collection<TEntity>, Error> {
|
||||||
|
const _source = raws ?? [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (_source.length === 0) {
|
||||||
|
return Result.ok(new Collection([], totalCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = _source.map(
|
||||||
|
(value, index) => this.mapToDTO(value as TModel, { index, ...params }).data
|
||||||
|
);
|
||||||
|
return Result.ok(new Collection(items, totalCount));
|
||||||
|
} catch (error) {
|
||||||
|
return Result.fail(error as Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import { ICustomerInvoiceParticipant } from "@/contexts/invoicing/domain";
|
|
||||||
import { IListCustomerInvoice_Participant_Response_DTO } from "@shared/contexts";
|
|
||||||
import { CustomerInvoiceParticipantAddressAssembler } from "./CustomerInvoiceParticipantAddress.assembler";
|
|
||||||
|
|
||||||
export const CustomerInvoiceParticipantAssembler = (
|
|
||||||
participant: ICustomerInvoiceParticipant,
|
|
||||||
): IListCustomerInvoice_Participant_Response_DTO => {
|
|
||||||
return {
|
|
||||||
participant_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: CustomerInvoiceParticipantAddressAssembler(
|
|
||||||
participant?.billingAddress!,
|
|
||||||
),
|
|
||||||
shipping_address: CustomerInvoiceParticipantAddressAssembler(
|
|
||||||
participant?.shippingAddress!,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
export const CustomerInvoiceParticipantAddressAssembler = (
|
|
||||||
address: CustomerInvoiceParticipantAddress
|
|
||||||
): IListCustomerInvoice_AddressParticipant_Response_DTO => {
|
|
||||||
return {
|
|
||||||
address_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(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
import { Criteria } from "@repo/rdx-criteria/server";
|
|
||||||
import { toEmptyString } from "@repo/rdx-ddd";
|
|
||||||
import { Collection } from "@repo/rdx-utils";
|
|
||||||
import { CustomerInvoiceListResponseDTO } from "../../../../common/dto";
|
|
||||||
import { CustomerInvoice } from "../../../domain";
|
|
||||||
|
|
||||||
export class ListCustomerInvoicesAssembler {
|
|
||||||
toDTO(
|
|
||||||
customerInvoices: Collection<CustomerInvoice>,
|
|
||||||
criteria: Criteria
|
|
||||||
): CustomerInvoiceListResponseDTO {
|
|
||||||
const items = customerInvoices.map((invoice) => {
|
|
||||||
const recipient = invoice.recipient.match(
|
|
||||||
(recipient) => recipient.toString(),
|
|
||||||
() => ({
|
|
||||||
tin: "",
|
|
||||||
name: "",
|
|
||||||
street: "",
|
|
||||||
street2: "",
|
|
||||||
city: "",
|
|
||||||
postal_code: "",
|
|
||||||
province: "",
|
|
||||||
country: "",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
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(),
|
|
||||||
...recipient,
|
|
||||||
},
|
|
||||||
|
|
||||||
items,
|
|
||||||
|
|
||||||
metadata: {
|
|
||||||
entity: "customer-invoice",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const totalItems = customerInvoices.total();
|
|
||||||
|
|
||||||
return {
|
|
||||||
page: criteria.pageNumber,
|
|
||||||
per_page: criteria.pageSize,
|
|
||||||
total_pages: Math.ceil(totalItems / criteria.pageSize),
|
|
||||||
total_items: totalItems,
|
|
||||||
items: items,
|
|
||||||
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}`,
|
|
||||||
//},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +1,16 @@
|
|||||||
|
import { CustomerInvoiceListDTO } from "@erp/customer-invoices/api/infrastructure";
|
||||||
import { Criteria } from "@repo/rdx-criteria/server";
|
import { Criteria } from "@repo/rdx-criteria/server";
|
||||||
import { toEmptyString } from "@repo/rdx-ddd";
|
import { toEmptyString } from "@repo/rdx-ddd";
|
||||||
import { ArrayElement, Collection } from "@repo/rdx-utils";
|
import { ArrayElement, Collection } from "@repo/rdx-utils";
|
||||||
import { CustomerInvoiceListResponseDTO } from "../../../../common/dto";
|
import { CustomerInvoiceListResponseDTO } from "../../../../common/dto";
|
||||||
import { CustomerInvoice } from "../../../domain";
|
|
||||||
|
|
||||||
export class ListCustomerInvoicesAssembler {
|
export class ListCustomerInvoicesAssembler {
|
||||||
toDTO(
|
toDTO(
|
||||||
customerInvoices: Collection<CustomerInvoice>,
|
customerInvoices: Collection<CustomerInvoiceListDTO>,
|
||||||
criteria: Criteria
|
criteria: Criteria
|
||||||
): CustomerInvoiceListResponseDTO {
|
): CustomerInvoiceListResponseDTO {
|
||||||
const invoices = customerInvoices.map((invoice) => {
|
const invoices = customerInvoices.map((invoice) => {
|
||||||
const recipientDTO = invoice.recipient.match(
|
const recipientDTO = invoice.recipient.toObjectString();
|
||||||
(recipient) => recipient.toString(),
|
|
||||||
() => ({
|
|
||||||
tin: "",
|
|
||||||
name: "",
|
|
||||||
street: "",
|
|
||||||
street2: "",
|
|
||||||
city: "",
|
|
||||||
postal_code: "",
|
|
||||||
province: "",
|
|
||||||
country: "",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const allAmounts = invoice.getAllAmounts();
|
|
||||||
|
|
||||||
const invoiceDTO: ArrayElement<CustomerInvoiceListResponseDTO["items"]> = {
|
const invoiceDTO: ArrayElement<CustomerInvoiceListResponseDTO["items"]> = {
|
||||||
id: invoice.id.toString(),
|
id: invoice.id.toString(),
|
||||||
@ -43,16 +29,13 @@ export class ListCustomerInvoicesAssembler {
|
|||||||
...recipientDTO,
|
...recipientDTO,
|
||||||
},
|
},
|
||||||
|
|
||||||
taxes: invoice.taxes
|
taxes: invoice.taxes,
|
||||||
.getAll()
|
|
||||||
.map((taxItem) => taxItem.tax.code)
|
|
||||||
.join(","),
|
|
||||||
|
|
||||||
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
/*subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
|
||||||
discount_amount: allAmounts.discountAmount.toObjectString(),
|
discount_amount: allAmounts.discountAmount.toObjectString(),
|
||||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
total_amount: allAmounts.totalAmount.toObjectString(),*/
|
||||||
|
|
||||||
metadata: {
|
metadata: {
|
||||||
entity: "customer-invoice",
|
entity: "customer-invoice",
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export class InvoiceRecipient extends ValueObject<InvoiceRecipientProps> {
|
|||||||
return this.getProps();
|
return this.getProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toObjectString() {
|
||||||
return {
|
return {
|
||||||
tin: this.tin.toString(),
|
tin: this.tin.toString(),
|
||||||
name: this.name.toString(),
|
name: this.name.toString(),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { JsonTaxCatalogProvider, spainTaxCatalogProvider } from "@erp/core";
|
import { JsonTaxCatalogProvider, spainTaxCatalogProvider } from "@erp/core";
|
||||||
import type { ModuleParams } from "@erp/core/api";
|
import type { IMapperRegistry, ModuleParams } from "@erp/core/api";
|
||||||
import { SequelizeTransactionManager } from "@erp/core/api";
|
import { InMemoryMapperRegistry, SequelizeTransactionManager } from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
CreateCustomerInvoiceAssembler,
|
CreateCustomerInvoiceAssembler,
|
||||||
CreateCustomerInvoiceUseCase,
|
CreateCustomerInvoiceUseCase,
|
||||||
@ -13,13 +13,13 @@ import {
|
|||||||
UpdateCustomerInvoiceUseCase,
|
UpdateCustomerInvoiceUseCase,
|
||||||
} from "../application";
|
} from "../application";
|
||||||
import { CustomerInvoiceService } from "../domain";
|
import { CustomerInvoiceService } from "../domain";
|
||||||
import { CustomerInvoiceMapper } from "./mappers";
|
import { CustomerInvoiceFullMapper, CustomerInvoiceListMapper } from "./mappers";
|
||||||
import { CustomerInvoiceRepository } from "./sequelize";
|
import { CustomerInvoiceRepository } from "./sequelize";
|
||||||
|
|
||||||
type InvoiceDeps = {
|
type InvoiceDeps = {
|
||||||
transactionManager: SequelizeTransactionManager;
|
transactionManager: SequelizeTransactionManager;
|
||||||
|
mapperRegistry: IMapperRegistry;
|
||||||
repo: CustomerInvoiceRepository;
|
repo: CustomerInvoiceRepository;
|
||||||
mapper: CustomerInvoiceMapper;
|
|
||||||
service: CustomerInvoiceService;
|
service: CustomerInvoiceService;
|
||||||
catalogs: {
|
catalogs: {
|
||||||
taxes: JsonTaxCatalogProvider;
|
taxes: JsonTaxCatalogProvider;
|
||||||
@ -43,7 +43,7 @@ type InvoiceDeps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let _repo: CustomerInvoiceRepository | null = null;
|
let _repo: CustomerInvoiceRepository | null = null;
|
||||||
let _mapper: CustomerInvoiceMapper | null = null;
|
let _mapperRegistry: IMapperRegistry | null = null;
|
||||||
let _service: CustomerInvoiceService | null = null;
|
let _service: CustomerInvoiceService | null = null;
|
||||||
let _assemblers: InvoiceDeps["assemblers"] | null = null;
|
let _assemblers: InvoiceDeps["assemblers"] | null = null;
|
||||||
let _catalogs: InvoiceDeps["catalogs"] | null = null;
|
let _catalogs: InvoiceDeps["catalogs"] | null = null;
|
||||||
@ -51,13 +51,19 @@ let _catalogs: InvoiceDeps["catalogs"] | null = null;
|
|||||||
export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
||||||
const { database } = params;
|
const { database } = params;
|
||||||
const transactionManager = new SequelizeTransactionManager(database);
|
const transactionManager = new SequelizeTransactionManager(database);
|
||||||
|
|
||||||
if (!_catalogs) _catalogs = { taxes: spainTaxCatalogProvider };
|
if (!_catalogs) _catalogs = { taxes: spainTaxCatalogProvider };
|
||||||
if (!_mapper)
|
|
||||||
_mapper = new CustomerInvoiceMapper({
|
const fullMapper: CustomerInvoiceFullMapper = new CustomerInvoiceFullMapper({
|
||||||
taxCatalog: _catalogs!.taxes,
|
taxCatalog: _catalogs!.taxes,
|
||||||
});
|
});
|
||||||
if (!_repo) _repo = new CustomerInvoiceRepository({ mapper: _mapper, database });
|
const listMapper = new CustomerInvoiceListMapper();
|
||||||
|
|
||||||
|
if (!_mapperRegistry) {
|
||||||
|
_mapperRegistry = new InMemoryMapperRegistry();
|
||||||
|
_mapperRegistry.registerDomainMapper("FULL", fullMapper);
|
||||||
|
_mapperRegistry.registerReadModelMapper("LIST", listMapper);
|
||||||
|
}
|
||||||
|
if (!_repo) _repo = new CustomerInvoiceRepository({ mapperRegistry: _mapperRegistry, database });
|
||||||
if (!_service) _service = new CustomerInvoiceService(_repo);
|
if (!_service) _service = new CustomerInvoiceService(_repo);
|
||||||
|
|
||||||
if (!_assemblers) {
|
if (!_assemblers) {
|
||||||
@ -72,25 +78,14 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
|||||||
return {
|
return {
|
||||||
transactionManager,
|
transactionManager,
|
||||||
repo: _repo,
|
repo: _repo,
|
||||||
mapper: _mapper,
|
mapperRegistry: _mapperRegistry,
|
||||||
service: _service,
|
service: _service,
|
||||||
assemblers: _assemblers,
|
assemblers: _assemblers,
|
||||||
catalogs: _catalogs,
|
catalogs: _catalogs,
|
||||||
build: {
|
build: {
|
||||||
list: () =>
|
list: () =>
|
||||||
new ListCustomerInvoicesUseCase(
|
new ListCustomerInvoicesUseCase(_service!, transactionManager!, _assemblers!.list),
|
||||||
_service!,
|
get: () => new GetCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.get),
|
||||||
transactionManager!,
|
|
||||||
_assemblers!.list,
|
|
||||||
_catalogs!.taxes
|
|
||||||
),
|
|
||||||
get: () =>
|
|
||||||
new GetCustomerInvoiceUseCase(
|
|
||||||
_service!,
|
|
||||||
transactionManager!,
|
|
||||||
_assemblers!.get,
|
|
||||||
_catalogs!.taxes
|
|
||||||
),
|
|
||||||
create: () =>
|
create: () =>
|
||||||
new CreateCustomerInvoiceUseCase(
|
new CreateCustomerInvoiceUseCase(
|
||||||
_service!,
|
_service!,
|
||||||
|
|||||||
@ -13,7 +13,13 @@ export class ListCustomerInvoicesController extends ExpressController {
|
|||||||
const result = await this.useCase.execute({ criteria: this.criteria, companyId });
|
const result = await this.useCase.execute({ criteria: this.criteria, companyId });
|
||||||
|
|
||||||
return result.match(
|
return result.match(
|
||||||
(data) => this.ok(data),
|
(data) =>
|
||||||
|
this.ok(data, {
|
||||||
|
"X-Total-Count": String(data.total_items),
|
||||||
|
"Pagination-Count": String(data.total_pages),
|
||||||
|
"Pagination-Page": String(data.page),
|
||||||
|
"Pagination-Limit": String(data.per_page),
|
||||||
|
}),
|
||||||
(err) => this.handleError(err)
|
(err) => this.handleError(err)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
import { Response } from "express";
|
|
||||||
|
|
||||||
export type ListResult<T> = {
|
|
||||||
items: T[];
|
|
||||||
total: number;
|
|
||||||
limit: number;
|
|
||||||
offset: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ListPresenterOptions = {
|
|
||||||
includeMetaInBody?: boolean; // por defecto false (solo items en body)
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ListPresenter<T> {
|
|
||||||
constructor(
|
|
||||||
private readonly res: Response,
|
|
||||||
private readonly opts?: ListPresenterOptions
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Envía cabeceras de paginación y devuelve el cuerpo según la opción:
|
|
||||||
por defecto: items[]
|
|
||||||
includeMetaInBody: objeto con { items, total, limit, offset, page }
|
|
||||||
*/
|
|
||||||
present(result: ListResult<T>) {
|
|
||||||
const { total, limit } = result;
|
|
||||||
const safeLimit = Number.isFinite(limit) && limit > 0 ? limit : 25;
|
|
||||||
const page = Math.floor(result.offset / (safeLimit || 1)) + 1;
|
|
||||||
|
|
||||||
// Cabeceras de paginación (ya expuestas por CORS en app.ts)
|
|
||||||
this.res.setHeader("X-Total-Count", String(total));
|
|
||||||
this.res.setHeader("Pagination-Count", String(total));
|
|
||||||
this.res.setHeader("Pagination-Page", String(page));
|
|
||||||
this.res.setHeader("Pagination-Limit", String(safeLimit));
|
|
||||||
|
|
||||||
if (this.opts?.includeMetaInBody) {
|
|
||||||
return this.res.status(200).json({
|
|
||||||
items: result.items,
|
|
||||||
total,
|
|
||||||
limit: safeLimit,
|
|
||||||
offset: result.offset,
|
|
||||||
page,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contrato clásico: solo items en el body
|
|
||||||
return this.res.status(200).json(result.items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Factoría simple para integrarla en dependencies.ts
|
|
||||||
*/
|
|
||||||
export function createListPresenter<T>(res: Response, opts?: ListPresenterOptions) {
|
|
||||||
return new ListPresenter<T>(res, opts);
|
|
||||||
}
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ISequelizeMapper,
|
ISequelizeDomainMapper,
|
||||||
MapperParamsType,
|
MapperParamsType,
|
||||||
SequelizeMapper,
|
SequelizeDomainMapper,
|
||||||
ValidationErrorCollection,
|
ValidationErrorCollection,
|
||||||
ValidationErrorDetail,
|
ValidationErrorDetail,
|
||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
@ -17,19 +17,19 @@ import {
|
|||||||
ItemDiscount,
|
ItemDiscount,
|
||||||
ItemQuantity,
|
ItemQuantity,
|
||||||
ItemTaxes,
|
ItemTaxes,
|
||||||
} from "../../domain";
|
} from "../../../domain";
|
||||||
import { CustomerInvoiceItemCreationAttributes, CustomerInvoiceItemModel } from "../sequelize";
|
import { CustomerInvoiceItemCreationAttributes, CustomerInvoiceItemModel } from "../../sequelize";
|
||||||
import { ItemTaxesMapper } from "./item-taxes.mapper";
|
import { ItemTaxesMapper } from "./item-taxes.full.mapper";
|
||||||
|
|
||||||
export interface ICustomerInvoiceItemMapper
|
export interface ICustomerInvoiceItemMapper
|
||||||
extends ISequelizeMapper<
|
extends ISequelizeDomainMapper<
|
||||||
CustomerInvoiceItemModel,
|
CustomerInvoiceItemModel,
|
||||||
CustomerInvoiceItemCreationAttributes,
|
CustomerInvoiceItemCreationAttributes,
|
||||||
CustomerInvoiceItem
|
CustomerInvoiceItem
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
export class CustomerInvoiceItemMapper
|
export class CustomerInvoiceItemMapper
|
||||||
extends SequelizeMapper<
|
extends SequelizeDomainMapper<
|
||||||
CustomerInvoiceItemModel,
|
CustomerInvoiceItemModel,
|
||||||
CustomerInvoiceItemCreationAttributes,
|
CustomerInvoiceItemCreationAttributes,
|
||||||
CustomerInvoiceItem
|
CustomerInvoiceItem
|
||||||
@ -123,10 +123,14 @@ export class CustomerInvoiceItemMapper
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 4) Taxes (colección a nivel de item/línea)
|
// 4) Taxes (colección a nivel de item/línea)
|
||||||
const taxesResults = this._taxesMapper.mapArrayToDomain(source.taxes, {
|
const taxesResults = this._taxesMapper.mapToDomainCollection(
|
||||||
attributes,
|
source.taxes,
|
||||||
...params,
|
source.taxes.length,
|
||||||
});
|
{
|
||||||
|
attributes,
|
||||||
|
...params,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (taxesResults.isFailure) {
|
if (taxesResults.isFailure) {
|
||||||
errors.push({
|
errors.push({
|
||||||
@ -168,7 +172,7 @@ export class CustomerInvoiceItemMapper
|
|||||||
public mapToPersistence(
|
public mapToPersistence(
|
||||||
source: CustomerInvoiceItem,
|
source: CustomerInvoiceItem,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): InferCreationAttributes<CustomerInvoiceItemModel, {}> {
|
): Result<InferCreationAttributes<CustomerInvoiceItemModel, {}>, Error> {
|
||||||
throw new Error("not implemented");
|
throw new Error("not implemented");
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ISequelizeMapper,
|
ISequelizeDomainMapper,
|
||||||
MapperParamsType,
|
MapperParamsType,
|
||||||
SequelizeMapper,
|
SequelizeDomainMapper,
|
||||||
ValidationErrorCollection,
|
ValidationErrorCollection,
|
||||||
ValidationErrorDetail,
|
ValidationErrorDetail,
|
||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
@ -23,34 +23,38 @@ import {
|
|||||||
CustomerInvoiceProps,
|
CustomerInvoiceProps,
|
||||||
CustomerInvoiceSerie,
|
CustomerInvoiceSerie,
|
||||||
CustomerInvoiceStatus,
|
CustomerInvoiceStatus,
|
||||||
} from "../../domain";
|
} from "../../../domain";
|
||||||
import { InvoiceTaxes } from "../../domain/entities/invoice-taxes";
|
import { InvoiceTaxes } from "../../../domain/entities/invoice-taxes";
|
||||||
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../sequelize";
|
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
|
||||||
import { CustomerInvoiceItemMapper } from "./customer-invoice-item.mapper";
|
import { CustomerInvoiceItemMapper as CustomerInvoiceItemFullMapper } from "./customer-invoice-item.full.mapper";
|
||||||
import { InvoiceRecipientMapper } from "./invoice-recipient.mapper";
|
import { InvoiceRecipientMapper as InvoiceRecipientFullMapper } from "./invoice-recipient.full.mapper";
|
||||||
import { TaxesMapper } from "./taxes.mapper";
|
import { TaxesMapper as TaxesFullMapper } from "./taxes.full.mapper";
|
||||||
|
|
||||||
export interface ICustomerInvoiceMapper
|
export interface ICustomerInvoiceFullMapper
|
||||||
extends ISequelizeMapper<
|
extends ISequelizeDomainMapper<
|
||||||
CustomerInvoiceModel,
|
CustomerInvoiceModel,
|
||||||
CustomerInvoiceCreationAttributes,
|
CustomerInvoiceCreationAttributes,
|
||||||
CustomerInvoice
|
CustomerInvoice
|
||||||
> {}
|
> {}
|
||||||
|
|
||||||
export class CustomerInvoiceMapper
|
export class CustomerInvoiceFullMapper
|
||||||
extends SequelizeMapper<CustomerInvoiceModel, CustomerInvoiceCreationAttributes, CustomerInvoice>
|
extends SequelizeDomainMapper<
|
||||||
implements ICustomerInvoiceMapper
|
CustomerInvoiceModel,
|
||||||
|
CustomerInvoiceCreationAttributes,
|
||||||
|
CustomerInvoice
|
||||||
|
>
|
||||||
|
implements ICustomerInvoiceFullMapper
|
||||||
{
|
{
|
||||||
private _itemsMapper: CustomerInvoiceItemMapper;
|
private _itemsMapper: CustomerInvoiceItemFullMapper;
|
||||||
private _recipientMapper: InvoiceRecipientMapper;
|
private _recipientMapper: InvoiceRecipientFullMapper;
|
||||||
private _taxesMapper: TaxesMapper;
|
private _taxesMapper: TaxesFullMapper;
|
||||||
|
|
||||||
constructor(params: MapperParamsType) {
|
constructor(params: MapperParamsType) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._itemsMapper = new CustomerInvoiceItemMapper(params); // Instanciar el mapper de items
|
this._itemsMapper = new CustomerInvoiceItemFullMapper(params); // Instanciar el mapper de items
|
||||||
this._recipientMapper = new InvoiceRecipientMapper();
|
this._recipientMapper = new InvoiceRecipientFullMapper();
|
||||||
this._taxesMapper = new TaxesMapper(params);
|
this._taxesMapper = new TaxesFullMapper(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapAttributesToDomain(source: CustomerInvoiceModel, params?: MapperParamsType) {
|
private mapAttributesToDomain(source: CustomerInvoiceModel, params?: MapperParamsType) {
|
||||||
@ -169,11 +173,15 @@ export class CustomerInvoiceMapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3) Items (colección)
|
// 3) Items (colección)
|
||||||
const itemsResults = this._itemsMapper.mapArrayToDomain(source.items, {
|
const itemsResults = this._itemsMapper.mapToDomainCollection(
|
||||||
errors,
|
source.items,
|
||||||
attributes,
|
source.items.length,
|
||||||
...params,
|
{
|
||||||
});
|
errors,
|
||||||
|
attributes,
|
||||||
|
...params,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (itemsResults.isFailure) {
|
if (itemsResults.isFailure) {
|
||||||
errors.push({
|
errors.push({
|
||||||
@ -183,11 +191,15 @@ export class CustomerInvoiceMapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4) Taxes (colección a nivel factura)
|
// 4) Taxes (colección a nivel factura)
|
||||||
const taxesResults = this._taxesMapper.mapArrayToDomain(source.taxes, {
|
const taxesResults = this._taxesMapper.mapToDomainCollection(
|
||||||
errors,
|
source.taxes,
|
||||||
attributes,
|
source.taxes.length,
|
||||||
...params,
|
{
|
||||||
});
|
errors,
|
||||||
|
attributes,
|
||||||
|
...params,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (taxesResults.isFailure) {
|
if (taxesResults.isFailure) {
|
||||||
errors.push({
|
errors.push({
|
||||||
@ -260,7 +272,7 @@ export class CustomerInvoiceMapper
|
|||||||
public mapToPersistence(
|
public mapToPersistence(
|
||||||
source: CustomerInvoice,
|
source: CustomerInvoice,
|
||||||
params?: MapperParamsType
|
params?: MapperParamsType
|
||||||
): CustomerInvoiceCreationAttributes {
|
): Result<CustomerInvoiceCreationAttributes, Error> {
|
||||||
throw new Error("not implemented");
|
throw new Error("not implemented");
|
||||||
|
|
||||||
/*const items = this._itemsMapper.mapCollectionToPersistence(source.items, params);
|
/*const items = this._itemsMapper.mapCollectionToPersistence(source.items, params);
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./customer-invoice.full.mapper";
|
||||||
@ -16,8 +16,8 @@ import {
|
|||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
import { CustomerInvoiceProps, InvoiceRecipient } from "../../domain";
|
import { CustomerInvoiceProps, InvoiceRecipient } from "../../../domain";
|
||||||
import { CustomerInvoiceModel } from "../sequelize";
|
import { CustomerInvoiceModel } from "../../sequelize";
|
||||||
|
|
||||||
export class InvoiceRecipientMapper {
|
export class InvoiceRecipientMapper {
|
||||||
public mapToDomain(
|
public mapToDomain(
|
||||||
@ -1,17 +1,20 @@
|
|||||||
import { JsonTaxCatalogProvider } from "@erp/core";
|
import { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import {
|
import {
|
||||||
MapperParamsType,
|
MapperParamsType,
|
||||||
SequelizeMapper,
|
SequelizeDomainMapper,
|
||||||
Tax,
|
Tax,
|
||||||
ValidationErrorCollection,
|
ValidationErrorCollection,
|
||||||
ValidationErrorDetail,
|
ValidationErrorDetail,
|
||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { ItemTax } from "../../domain";
|
import { ItemTax } from "../../../domain";
|
||||||
import { CustomerInvoiceItemCreationAttributes, CustomerInvoiceItemTaxModel } from "../sequelize";
|
import {
|
||||||
|
CustomerInvoiceItemCreationAttributes,
|
||||||
|
CustomerInvoiceItemTaxModel,
|
||||||
|
} from "../../sequelize";
|
||||||
|
|
||||||
export class ItemTaxesMapper extends SequelizeMapper<
|
export class ItemTaxesMapper extends SequelizeDomainMapper<
|
||||||
CustomerInvoiceItemTaxModel,
|
CustomerInvoiceItemTaxModel,
|
||||||
CustomerInvoiceItemCreationAttributes,
|
CustomerInvoiceItemCreationAttributes,
|
||||||
ItemTax
|
ItemTax
|
||||||
@ -1,18 +1,18 @@
|
|||||||
import { JsonTaxCatalogProvider } from "@erp/core";
|
import { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import {
|
import {
|
||||||
MapperParamsType,
|
MapperParamsType,
|
||||||
SequelizeMapper,
|
SequelizeDomainMapper,
|
||||||
Tax,
|
Tax,
|
||||||
ValidationErrorCollection,
|
ValidationErrorCollection,
|
||||||
ValidationErrorDetail,
|
ValidationErrorDetail,
|
||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { CustomerInvoiceProps } from "../../domain";
|
import { CustomerInvoiceProps } from "../../../domain";
|
||||||
import { InvoiceTax } from "../../domain/entities/invoice-taxes";
|
import { InvoiceTax } from "../../../domain/entities/invoice-taxes";
|
||||||
import { CustomerInvoiceTaxCreationAttributes, CustomerInvoiceTaxModel } from "../sequelize";
|
import { CustomerInvoiceTaxCreationAttributes, CustomerInvoiceTaxModel } from "../../sequelize";
|
||||||
|
|
||||||
export class TaxesMapper extends SequelizeMapper<
|
export class TaxesMapper extends SequelizeDomainMapper<
|
||||||
CustomerInvoiceTaxModel,
|
CustomerInvoiceTaxModel,
|
||||||
CustomerInvoiceTaxCreationAttributes,
|
CustomerInvoiceTaxCreationAttributes,
|
||||||
InvoiceTax
|
InvoiceTax
|
||||||
@ -1 +1,2 @@
|
|||||||
export * from "./customer-invoice.mapper";
|
export * from "./full-domain";
|
||||||
|
export * from "./list";
|
||||||
|
|||||||
@ -0,0 +1,192 @@
|
|||||||
|
import {
|
||||||
|
ISequelizeReadModelMapper,
|
||||||
|
MapperParamsType,
|
||||||
|
SequelizeReadModelMapper,
|
||||||
|
ValidationErrorCollection,
|
||||||
|
ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
import {
|
||||||
|
CurrencyCode,
|
||||||
|
LanguageCode,
|
||||||
|
Percentage,
|
||||||
|
UniqueID,
|
||||||
|
UtcDate,
|
||||||
|
maybeFromNullableVO,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
import {
|
||||||
|
CustomerInvoiceNumber,
|
||||||
|
CustomerInvoiceSerie,
|
||||||
|
CustomerInvoiceStatus,
|
||||||
|
InvoiceRecipient,
|
||||||
|
} from "../../../domain";
|
||||||
|
import { CustomerInvoiceModel } from "../../sequelize";
|
||||||
|
import { InvoiceRecipientListMapper } from "./invoice-recipient.list.mapper";
|
||||||
|
|
||||||
|
export type CustomerInvoiceListDTO = {
|
||||||
|
id: UniqueID;
|
||||||
|
companyId: UniqueID;
|
||||||
|
|
||||||
|
isProforma: boolean;
|
||||||
|
invoiceNumber: CustomerInvoiceNumber;
|
||||||
|
status: CustomerInvoiceStatus;
|
||||||
|
series: Maybe<CustomerInvoiceSerie>;
|
||||||
|
|
||||||
|
invoiceDate: UtcDate;
|
||||||
|
operationDate: Maybe<UtcDate>;
|
||||||
|
|
||||||
|
customerId: UniqueID;
|
||||||
|
recipient: InvoiceRecipient;
|
||||||
|
|
||||||
|
languageCode: LanguageCode;
|
||||||
|
currencyCode: CurrencyCode;
|
||||||
|
|
||||||
|
taxes: string;
|
||||||
|
|
||||||
|
discountPercentage: Percentage;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ICustomerInvoiceListMapper
|
||||||
|
extends ISequelizeReadModelMapper<CustomerInvoiceModel, CustomerInvoiceListDTO> {}
|
||||||
|
|
||||||
|
export class CustomerInvoiceListMapper
|
||||||
|
extends SequelizeReadModelMapper<CustomerInvoiceModel, CustomerInvoiceListDTO>
|
||||||
|
implements ICustomerInvoiceListMapper
|
||||||
|
{
|
||||||
|
private _recipientMapper: InvoiceRecipientListMapper;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._recipientMapper = new InvoiceRecipientListMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapToDTO(
|
||||||
|
raw: CustomerInvoiceModel,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<CustomerInvoiceListDTO, Error> {
|
||||||
|
const errors: ValidationErrorDetail[] = [];
|
||||||
|
|
||||||
|
// 1) Valores escalares (atributos generales)
|
||||||
|
const attributes = this.mapAttributesToDTO(raw, { errors, ...params });
|
||||||
|
|
||||||
|
// 2) Recipient (snapshot en la factura o include)
|
||||||
|
const recipientResult = this._recipientMapper.mapToDTO(raw, {
|
||||||
|
errors,
|
||||||
|
attributes,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (recipientResult.isFailure) {
|
||||||
|
errors.push({
|
||||||
|
path: "recipient",
|
||||||
|
message: recipientResult.error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Taxes
|
||||||
|
const taxes = raw.taxes.map((tax) => tax.tax_code).join(", ");
|
||||||
|
|
||||||
|
// 5) Si hubo errores de mapeo, devolvemos colección de validación
|
||||||
|
if (errors.length > 0) {
|
||||||
|
return Result.fail(new ValidationErrorCollection("Customer invoice mapping failed", errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok({
|
||||||
|
id: attributes.invoiceId!,
|
||||||
|
companyId: attributes.companyId!,
|
||||||
|
isProforma: attributes.isProforma,
|
||||||
|
status: attributes.status!,
|
||||||
|
series: attributes.series!,
|
||||||
|
invoiceNumber: attributes.invoiceNumber!,
|
||||||
|
invoiceDate: attributes.invoiceDate!,
|
||||||
|
operationDate: attributes.operationDate!,
|
||||||
|
|
||||||
|
customerId: attributes.customerId!,
|
||||||
|
recipient: recipientResult.data,
|
||||||
|
|
||||||
|
languageCode: attributes.languageCode!,
|
||||||
|
currencyCode: attributes.currencyCode!,
|
||||||
|
|
||||||
|
discountPercentage: attributes.discountPercentage!,
|
||||||
|
|
||||||
|
taxes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapAttributesToDTO(raw: CustomerInvoiceModel, params?: MapperParamsType) {
|
||||||
|
const { errors } = params as {
|
||||||
|
errors: ValidationErrorDetail[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const invoiceId = extractOrPushError(UniqueID.create(raw.id), "id", errors);
|
||||||
|
const companyId = extractOrPushError(UniqueID.create(raw.company_id), "company_id", errors);
|
||||||
|
|
||||||
|
const customerId = extractOrPushError(UniqueID.create(raw.customer_id), "customer_id", errors);
|
||||||
|
|
||||||
|
const isProforma = Boolean(raw.is_proforma);
|
||||||
|
|
||||||
|
const status = extractOrPushError(CustomerInvoiceStatus.create(raw.status), "status", errors);
|
||||||
|
|
||||||
|
const series = extractOrPushError(
|
||||||
|
maybeFromNullableVO(raw.series, (value) => CustomerInvoiceSerie.create(value)),
|
||||||
|
"serie",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const invoiceNumber = extractOrPushError(
|
||||||
|
CustomerInvoiceNumber.create(raw.invoice_number),
|
||||||
|
"invoice_number",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const invoiceDate = extractOrPushError(
|
||||||
|
UtcDate.createFromISO(raw.invoice_date),
|
||||||
|
"invoice_date",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const operationDate = extractOrPushError(
|
||||||
|
maybeFromNullableVO(raw.operation_date, (value) => UtcDate.createFromISO(value)),
|
||||||
|
"operation_date",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const languageCode = extractOrPushError(
|
||||||
|
LanguageCode.create(raw.language_code),
|
||||||
|
"language_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const currencyCode = extractOrPushError(
|
||||||
|
CurrencyCode.create(raw.currency_code),
|
||||||
|
"currency_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const discountPercentage = extractOrPushError(
|
||||||
|
Percentage.create({
|
||||||
|
value: raw.discount_amount_scale,
|
||||||
|
scale: raw.discount_percentage_scale,
|
||||||
|
}),
|
||||||
|
"discount_percentage_value",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
invoiceId,
|
||||||
|
companyId,
|
||||||
|
customerId,
|
||||||
|
isProforma,
|
||||||
|
status,
|
||||||
|
series,
|
||||||
|
invoiceNumber,
|
||||||
|
invoiceDate,
|
||||||
|
operationDate,
|
||||||
|
languageCode,
|
||||||
|
currencyCode,
|
||||||
|
discountPercentage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export * from "./customer-invoice.list.mapper";
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
import {
|
||||||
|
City,
|
||||||
|
Country,
|
||||||
|
Name,
|
||||||
|
PostalCode,
|
||||||
|
Province,
|
||||||
|
Street,
|
||||||
|
TINNumber,
|
||||||
|
maybeFromNullableVO,
|
||||||
|
} from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
import {
|
||||||
|
IReadModelMapperWithBulk,
|
||||||
|
MapperParamsType,
|
||||||
|
SequelizeReadModelMapper,
|
||||||
|
ValidationErrorDetail,
|
||||||
|
extractOrPushError,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
|
||||||
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
import { InvoiceRecipient } from "../../../domain";
|
||||||
|
import { CustomerInvoiceModel } from "../../sequelize";
|
||||||
|
import { CustomerInvoiceListDTO } from "./customer-invoice.list.mapper";
|
||||||
|
|
||||||
|
interface IInvoiceRecipientListMapper
|
||||||
|
extends IReadModelMapperWithBulk<CustomerInvoiceModel, InvoiceRecipient> {}
|
||||||
|
|
||||||
|
export class InvoiceRecipientListMapper
|
||||||
|
extends SequelizeReadModelMapper<CustomerInvoiceModel, InvoiceRecipient>
|
||||||
|
implements IInvoiceRecipientListMapper
|
||||||
|
{
|
||||||
|
public mapToDTO(
|
||||||
|
raw: CustomerInvoiceModel,
|
||||||
|
params?: MapperParamsType
|
||||||
|
): Result<InvoiceRecipient, Error> {
|
||||||
|
/**
|
||||||
|
* - Factura === proforma -> datos de "current_customer"
|
||||||
|
* - Factura !== proforma -> snapshot de los datos (campos customer_*)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { errors, attributes } = params as {
|
||||||
|
errors: ValidationErrorDetail[];
|
||||||
|
attributes: Partial<CustomerInvoiceListDTO>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { isProforma } = attributes;
|
||||||
|
|
||||||
|
if (isProforma && !raw.current_customer) {
|
||||||
|
errors.push({
|
||||||
|
path: "current_customer",
|
||||||
|
message: "Current customer not included in query (InvoiceRecipientListMapper)",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const _name = isProforma ? raw.current_customer.name : raw.customer_name;
|
||||||
|
const _tin = isProforma ? raw.current_customer.tin : raw.customer_tin;
|
||||||
|
const _street = isProforma ? raw.current_customer.street : raw.customer_street;
|
||||||
|
const _street2 = isProforma ? raw.current_customer.street2 : raw.customer_street2;
|
||||||
|
const _city = isProforma ? raw.current_customer.city : raw.customer_city;
|
||||||
|
const _postal_code = isProforma ? raw.current_customer.postal_code : raw.customer_postal_code;
|
||||||
|
const _province = isProforma ? raw.current_customer.province : raw.customer_province;
|
||||||
|
const _country = isProforma ? raw.current_customer.country : raw.customer_country;
|
||||||
|
|
||||||
|
// Customer (snapshot)
|
||||||
|
const customerName = extractOrPushError(Name.create(_name), "customer_name", errors);
|
||||||
|
|
||||||
|
const customerTin = extractOrPushError(TINNumber.create(_tin), "customer_tin", errors);
|
||||||
|
|
||||||
|
const customerStreet = extractOrPushError(
|
||||||
|
maybeFromNullableVO(_street, (value) => Street.create(value)),
|
||||||
|
"customer_street",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const customerStreet2 = extractOrPushError(
|
||||||
|
maybeFromNullableVO(_street2, (value) => Street.create(value)),
|
||||||
|
"customer_street2",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const customerCity = extractOrPushError(
|
||||||
|
maybeFromNullableVO(_city, (value) => City.create(value)),
|
||||||
|
"customer_city",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const customerProvince = extractOrPushError(
|
||||||
|
maybeFromNullableVO(_province, (value) => Province.create(value)),
|
||||||
|
"customer_province",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const customerPostalCode = extractOrPushError(
|
||||||
|
maybeFromNullableVO(_postal_code, (value) => PostalCode.create(value)),
|
||||||
|
"customer_postal_code",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
const customerCountry = extractOrPushError(
|
||||||
|
maybeFromNullableVO(_country, (value) => Country.create(value)),
|
||||||
|
"customer_country",
|
||||||
|
errors
|
||||||
|
);
|
||||||
|
|
||||||
|
return InvoiceRecipient.create({
|
||||||
|
name: customerName!,
|
||||||
|
tin: customerTin!,
|
||||||
|
street: customerStreet!,
|
||||||
|
street2: customerStreet2!,
|
||||||
|
city: customerCity!,
|
||||||
|
postalCode: customerPostalCode!,
|
||||||
|
province: customerProvince!,
|
||||||
|
country: customerCountry!,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,84 +0,0 @@
|
|||||||
import {
|
|
||||||
CreationOptional,
|
|
||||||
DataTypes,
|
|
||||||
InferAttributes,
|
|
||||||
InferCreationAttributes,
|
|
||||||
Model,
|
|
||||||
NonAttribute,
|
|
||||||
Sequelize,
|
|
||||||
} from "sequelize";
|
|
||||||
|
|
||||||
import { ContactAddress_Model, TCreationContactAddress_Attributes } from "./contactAddress.mo.del";
|
|
||||||
|
|
||||||
export type TCreationContact_Model = InferCreationAttributes<
|
|
||||||
Contact_Model,
|
|
||||||
{ omit: "shippingAddress" | "billingAddress" }
|
|
||||||
> & {
|
|
||||||
billingAddress: TCreationContactAddress_Attributes;
|
|
||||||
shippingAddress: TCreationContactAddress_Attributes;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Contact_Model extends Model<
|
|
||||||
InferAttributes<Contact_Model, { omit: "shippingAddress" | "billingAddress" }>,
|
|
||||||
InferCreationAttributes<Contact_Model, { omit: "shippingAddress" | "billingAddress" }>
|
|
||||||
> {
|
|
||||||
// To avoid table creation
|
|
||||||
static async sync(): Promise<any> {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
static associate(connection: Sequelize) {
|
|
||||||
const { Contact_Model, ContactAddress_Model } = connection.models;
|
|
||||||
|
|
||||||
Contact_Model.hasOne(ContactAddress_Model, {
|
|
||||||
as: "shippingAddress",
|
|
||||||
foreignKey: "customer_id",
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
});
|
|
||||||
|
|
||||||
Contact_Model.hasOne(ContactAddress_Model, {
|
|
||||||
as: "billingAddress",
|
|
||||||
foreignKey: "customer_id",
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
declare id: string;
|
|
||||||
declare tin: CreationOptional<string>;
|
|
||||||
declare company_name: CreationOptional<string>;
|
|
||||||
declare first_name: CreationOptional<string>;
|
|
||||||
declare last_name: CreationOptional<string>;
|
|
||||||
|
|
||||||
declare shippingAddress?: NonAttribute<ContactAddress_Model>;
|
|
||||||
declare billingAddress?: NonAttribute<ContactAddress_Model>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (sequelize: Sequelize) => {
|
|
||||||
Contact_Model.init(
|
|
||||||
{
|
|
||||||
id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
primaryKey: true,
|
|
||||||
},
|
|
||||||
tin: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
},
|
|
||||||
company_name: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
},
|
|
||||||
first_name: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
},
|
|
||||||
last_name: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
tableName: "customers",
|
|
||||||
timestamps: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return Contact_Model;
|
|
||||||
};
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import {
|
|
||||||
CreationOptional,
|
|
||||||
DataTypes,
|
|
||||||
ForeignKey,
|
|
||||||
InferAttributes,
|
|
||||||
InferCreationAttributes,
|
|
||||||
Model,
|
|
||||||
NonAttribute,
|
|
||||||
Sequelize,
|
|
||||||
} from "sequelize";
|
|
||||||
import { Contact_Model } from "./contact.mo.del.ts.bak";
|
|
||||||
|
|
||||||
export type TCreationContactAddress_Attributes = InferCreationAttributes<
|
|
||||||
ContactAddress_Model,
|
|
||||||
{ omit: "customer" }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class ContactAddress_Model extends Model<
|
|
||||||
InferAttributes<ContactAddress_Model, { omit: "customer" }>,
|
|
||||||
TCreationContactAddress_Attributes
|
|
||||||
> {
|
|
||||||
// To avoid table creation
|
|
||||||
static async sync(): Promise<any> {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
static associate(connection: Sequelize) {
|
|
||||||
const { Contact_Model, ContactAddress_Model } = connection.models;
|
|
||||||
|
|
||||||
ContactAddress_Model.belongsTo(Contact_Model, {
|
|
||||||
as: "customer",
|
|
||||||
foreignKey: "customer_id",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
declare id: string;
|
|
||||||
declare customer_id: ForeignKey<Contact_Model["id"]>;
|
|
||||||
declare type: string;
|
|
||||||
declare street: CreationOptional<string>;
|
|
||||||
declare postal_code: CreationOptional<string>;
|
|
||||||
declare city: CreationOptional<string>;
|
|
||||||
declare province: CreationOptional<string>;
|
|
||||||
declare country: CreationOptional<string>;
|
|
||||||
declare phone: CreationOptional<string>;
|
|
||||||
declare email: CreationOptional<string>;
|
|
||||||
|
|
||||||
declare customer?: NonAttribute<Contact_Model>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (sequelize: Sequelize) => {
|
|
||||||
ContactAddress_Model.init(
|
|
||||||
{
|
|
||||||
id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
primaryKey: true,
|
|
||||||
},
|
|
||||||
customer_id: DataTypes.UUID,
|
|
||||||
type: DataTypes.STRING(),
|
|
||||||
street: DataTypes.STRING(),
|
|
||||||
postal_code: DataTypes.STRING(),
|
|
||||||
city: DataTypes.STRING,
|
|
||||||
province: DataTypes.STRING,
|
|
||||||
country: DataTypes.STRING,
|
|
||||||
email: DataTypes.STRING,
|
|
||||||
phone: DataTypes.STRING,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
tableName: "customer_addresses",
|
|
||||||
timestamps: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return ContactAddress_Model;
|
|
||||||
};
|
|
||||||
@ -1,10 +1,15 @@
|
|||||||
import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } from "@erp/core/api";
|
import {
|
||||||
|
EntityNotFoundError,
|
||||||
|
IMapperRegistry,
|
||||||
|
SequelizeRepository,
|
||||||
|
translateSequelizeError,
|
||||||
|
} from "@erp/core/api";
|
||||||
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { Sequelize, Transaction } from "sequelize";
|
import { Sequelize, Transaction } from "sequelize";
|
||||||
import { CustomerInvoice, ICustomerInvoiceRepository } from "../../domain";
|
import { CustomerInvoice, ICustomerInvoiceRepository } from "../../domain";
|
||||||
import { ICustomerInvoiceMapper } from "../mappers/customer-invoice.mapper";
|
import { ICustomerInvoiceFullMapper, ICustomerInvoiceListMapper } from "../mappers";
|
||||||
import { CustomerInvoiceItemTaxModel } from "./customer-invoice-item-tax.model";
|
import { CustomerInvoiceItemTaxModel } from "./customer-invoice-item-tax.model";
|
||||||
import { CustomerInvoiceItemModel } from "./customer-invoice-item.model";
|
import { CustomerInvoiceItemModel } from "./customer-invoice-item.model";
|
||||||
import { CustomerInvoiceTaxModel } from "./customer-invoice-tax.model";
|
import { CustomerInvoiceTaxModel } from "./customer-invoice-tax.model";
|
||||||
@ -15,11 +20,11 @@ export class CustomerInvoiceRepository
|
|||||||
implements ICustomerInvoiceRepository
|
implements ICustomerInvoiceRepository
|
||||||
{
|
{
|
||||||
private readonly _database!: Sequelize;
|
private readonly _database!: Sequelize;
|
||||||
private readonly _mapper!: ICustomerInvoiceMapper;
|
private readonly _registry!: IMapperRegistry;
|
||||||
|
|
||||||
constructor(params: { mapper: ICustomerInvoiceMapper; database: Sequelize }) {
|
constructor(params: { mapperRegistry: IMapperRegistry; database: Sequelize }) {
|
||||||
super();
|
super();
|
||||||
this._mapper = params.mapper;
|
this._registry = params.mapperRegistry;
|
||||||
this._database = params.database;
|
this._database = params.database;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,9 +74,17 @@ export class CustomerInvoiceRepository
|
|||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
try {
|
try {
|
||||||
const data = this._mapper.mapToPersistence(invoice);
|
const mapper: ICustomerInvoiceFullMapper = this._registry.getDomainMapper("FULL");
|
||||||
|
const mapperData = mapper.mapToPersistence(invoice);
|
||||||
|
|
||||||
|
if (mapperData.isFailure) {
|
||||||
|
return Result.fail(mapperData.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = mapperData;
|
||||||
|
|
||||||
const [instance] = await CustomerInvoiceModel.upsert(data, { transaction, returning: true });
|
const [instance] = await CustomerInvoiceModel.upsert(data, { transaction, returning: true });
|
||||||
const savedInvoice = this._mapper.mapToDomain(instance);
|
const savedInvoice = mapper.mapToDomain(instance);
|
||||||
return savedInvoice;
|
return savedInvoice;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return Result.fail(translateSequelizeError(err));
|
return Result.fail(translateSequelizeError(err));
|
||||||
@ -97,7 +110,7 @@ export class CustomerInvoiceRepository
|
|||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
return Result.ok(Boolean(count > 0));
|
return Result.ok(Boolean(count > 0));
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
return Result.fail(translateSequelizeError(error));
|
return Result.fail(translateSequelizeError(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,6 +130,7 @@ export class CustomerInvoiceRepository
|
|||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
try {
|
try {
|
||||||
|
const mapper: ICustomerInvoiceFullMapper = this._registry.getReadModelMapper("FULL");
|
||||||
const { CustomerModel } = this._database.models;
|
const { CustomerModel } = this._database.models;
|
||||||
|
|
||||||
const row = await CustomerInvoiceModel.findOne({
|
const row = await CustomerInvoiceModel.findOne({
|
||||||
@ -152,7 +166,7 @@ export class CustomerInvoiceRepository
|
|||||||
return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString()));
|
return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const customer = this._mapper.mapToDomain(row);
|
const customer = mapper.mapToDomain(row);
|
||||||
return customer;
|
return customer;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return Result.fail(translateSequelizeError(err));
|
return Result.fail(translateSequelizeError(err));
|
||||||
@ -174,8 +188,9 @@ export class CustomerInvoiceRepository
|
|||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
): Promise<Result<Collection<CustomerInvoice>, Error>> {
|
): Promise<Result<Collection<any>, Error>> {
|
||||||
try {
|
try {
|
||||||
|
const mapper: ICustomerInvoiceListMapper = this._registry.getReadModelMapper("LIST");
|
||||||
const { CustomerModel } = this._database.models;
|
const { CustomerModel } = this._database.models;
|
||||||
const converter = new CriteriaToSequelizeConverter();
|
const converter = new CriteriaToSequelizeConverter();
|
||||||
const query = converter.convert(criteria);
|
const query = converter.convert(criteria);
|
||||||
@ -199,12 +214,12 @@ export class CustomerInvoiceRepository
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const instances = await CustomerInvoiceModel.findAll({
|
const raws = await CustomerInvoiceModel.findAll({
|
||||||
...query,
|
...query,
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this._mapper.mapArrayToDomain(instances);
|
return mapper.mapToDTOCollection(raws, raws.length);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
return Result.fail(translateSequelizeError(err));
|
return Result.fail(translateSequelizeError(err));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,106 +0,0 @@
|
|||||||
import {
|
|
||||||
CreationOptional,
|
|
||||||
DataTypes,
|
|
||||||
InferAttributes,
|
|
||||||
InferCreationAttributes,
|
|
||||||
Model,
|
|
||||||
NonAttribute,
|
|
||||||
Sequelize,
|
|
||||||
} from "sequelize";
|
|
||||||
import { CustomerInvoiceModel } from "./customer-invoice.model";
|
|
||||||
import {
|
|
||||||
CustomerInvoiceParticipantAddress_Model,
|
|
||||||
TCreationCustomerInvoiceParticipantAddress_Model,
|
|
||||||
} from "./customer-invoiceParticipantAddress.mo.del.ts.bak";
|
|
||||||
|
|
||||||
export type TCreationCustomerInvoiceParticipant_Model = InferCreationAttributes<
|
|
||||||
CustomerInvoiceParticipant_Model,
|
|
||||||
{ omit: "shippingAddress" | "billingAddress" | "customerInvoice" }
|
|
||||||
> & {
|
|
||||||
billingAddress: TCreationCustomerInvoiceParticipantAddress_Model;
|
|
||||||
shippingAddress: TCreationCustomerInvoiceParticipantAddress_Model;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class CustomerInvoiceParticipant_Model extends Model<
|
|
||||||
InferAttributes<
|
|
||||||
CustomerInvoiceParticipant_Model,
|
|
||||||
{ omit: "shippingAddress" | "billingAddress" | "customerInvoice" }
|
|
||||||
>,
|
|
||||||
InferCreationAttributes<
|
|
||||||
CustomerInvoiceParticipant_Model,
|
|
||||||
{ omit: "shippingAddress" | "billingAddress" | "customerInvoice" }
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
static associate(connection: Sequelize) {
|
|
||||||
const { CustomerInvoice_Model, CustomerInvoiceParticipantAddress_Model, CustomerInvoiceParticipant_Model } =
|
|
||||||
connection.models;
|
|
||||||
|
|
||||||
CustomerInvoiceParticipant_Model.belongsTo(CustomerInvoice_Model, {
|
|
||||||
as: "customerInvoice",
|
|
||||||
foreignKey: "customerInvoice_id",
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
});
|
|
||||||
|
|
||||||
CustomerInvoiceParticipant_Model.hasOne(CustomerInvoiceParticipantAddress_Model, {
|
|
||||||
as: "shippingAddress",
|
|
||||||
foreignKey: "participant_id",
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
});
|
|
||||||
|
|
||||||
CustomerInvoiceParticipant_Model.hasOne(CustomerInvoiceParticipantAddress_Model, {
|
|
||||||
as: "billingAddress",
|
|
||||||
foreignKey: "participant_id",
|
|
||||||
onDelete: "CASCADE",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
declare participant_id: string;
|
|
||||||
declare customerInvoice_id: string;
|
|
||||||
declare tin: CreationOptional<string>;
|
|
||||||
declare company_name: CreationOptional<string>;
|
|
||||||
declare first_name: CreationOptional<string>;
|
|
||||||
declare last_name: CreationOptional<string>;
|
|
||||||
|
|
||||||
declare shippingAddress?: NonAttribute<CustomerInvoiceParticipantAddress_Model>;
|
|
||||||
declare billingAddress?: NonAttribute<CustomerInvoiceParticipantAddress_Model>;
|
|
||||||
|
|
||||||
declare customerInvoice?: NonAttribute<CustomerInvoiceModel>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (sequelize: Sequelize) => {
|
|
||||||
CustomerInvoiceParticipant_Model.init(
|
|
||||||
{
|
|
||||||
participant_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
primaryKey: true,
|
|
||||||
},
|
|
||||||
customerInvoice_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
primaryKey: true,
|
|
||||||
},
|
|
||||||
tin: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
company_name: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
first_name: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
last_name: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
tableName: "customerInvoice_participants",
|
|
||||||
timestamps: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return CustomerInvoiceParticipant_Model;
|
|
||||||
};
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
import {
|
|
||||||
CreationOptional,
|
|
||||||
DataTypes,
|
|
||||||
InferAttributes,
|
|
||||||
InferCreationAttributes,
|
|
||||||
Model,
|
|
||||||
NonAttribute,
|
|
||||||
Sequelize,
|
|
||||||
} from "sequelize";
|
|
||||||
import { CustomerInvoiceParticipant_Model } from "./customer-invoiceParticipant.mo.del.ts.bak";
|
|
||||||
|
|
||||||
export type TCreationCustomerInvoiceParticipantAddress_Model = InferCreationAttributes<
|
|
||||||
CustomerInvoiceParticipantAddress_Model,
|
|
||||||
{ omit: "participant" }
|
|
||||||
>;
|
|
||||||
|
|
||||||
export class CustomerInvoiceParticipantAddress_Model extends Model<
|
|
||||||
InferAttributes<CustomerInvoiceParticipantAddress_Model, { omit: "participant" }>,
|
|
||||||
InferCreationAttributes<CustomerInvoiceParticipantAddress_Model, { omit: "participant" }>
|
|
||||||
> {
|
|
||||||
static associate(connection: Sequelize) {
|
|
||||||
const { CustomerInvoiceParticipantAddress_Model, CustomerInvoiceParticipant_Model } = connection.models;
|
|
||||||
CustomerInvoiceParticipantAddress_Model.belongsTo(CustomerInvoiceParticipant_Model, {
|
|
||||||
as: "participant",
|
|
||||||
foreignKey: "participant_id",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
declare address_id: string;
|
|
||||||
declare participant_id: string;
|
|
||||||
declare type: string;
|
|
||||||
declare street: CreationOptional<string>;
|
|
||||||
declare postal_code: CreationOptional<string>;
|
|
||||||
declare city: CreationOptional<string>;
|
|
||||||
declare province: CreationOptional<string>;
|
|
||||||
declare country: CreationOptional<string>;
|
|
||||||
declare phone: CreationOptional<string>;
|
|
||||||
declare email: CreationOptional<string>;
|
|
||||||
|
|
||||||
declare participant?: NonAttribute<CustomerInvoiceParticipant_Model>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (sequelize: Sequelize) => {
|
|
||||||
CustomerInvoiceParticipantAddress_Model.init(
|
|
||||||
{
|
|
||||||
address_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
primaryKey: true,
|
|
||||||
},
|
|
||||||
participant_id: {
|
|
||||||
type: DataTypes.UUID,
|
|
||||||
primaryKey: true,
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: false,
|
|
||||||
},
|
|
||||||
street: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
postal_code: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
city: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
province: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
country: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
phone: {
|
|
||||||
type: new DataTypes.STRING(),
|
|
||||||
allowNull: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sequelize,
|
|
||||||
tableName: "customerInvoice_participant_addresses",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return CustomerInvoiceParticipantAddress_Model;
|
|
||||||
};
|
|
||||||
@ -40,7 +40,9 @@ export class DeleteCustomerUseCase {
|
|||||||
const customerExists = existsCheck.data;
|
const customerExists = existsCheck.data;
|
||||||
|
|
||||||
if (!customerExists) {
|
if (!customerExists) {
|
||||||
return Result.fail(new EntityNotFoundError("Customer", "id", customerId.toString()));
|
return Result.fail(
|
||||||
|
new EntityNotFoundError("Customer", "id", customerId.toObjectString())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.service.deleteCustomerByIdInCompany(customerId, companyId, transaction);
|
return await this.service.deleteCustomerByIdInCompany(customerId, companyId, transaction);
|
||||||
|
|||||||
@ -13,7 +13,13 @@ export class ListCustomersController extends ExpressController {
|
|||||||
const result = await this.listCustomers.execute({ criteria: this.criteria, companyId });
|
const result = await this.listCustomers.execute({ criteria: this.criteria, companyId });
|
||||||
|
|
||||||
return result.match(
|
return result.match(
|
||||||
(data) => this.ok(data),
|
(data) =>
|
||||||
|
this.ok(data, {
|
||||||
|
"X-Total-Count": String(data.total_items),
|
||||||
|
"Pagination-Count": String(data.total_pages),
|
||||||
|
"Pagination-Page": String(data.page),
|
||||||
|
"Pagination-Limit": String(data.per_page),
|
||||||
|
}),
|
||||||
(err) => this.handleError(err)
|
(err) => this.handleError(err)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ISequelizeMapper,
|
ISequelizeMapper,
|
||||||
MapperParamsType,
|
MapperParamsType,
|
||||||
SequelizeMapper,
|
SequelizeDomainMapper,
|
||||||
ValidationErrorCollection,
|
ValidationErrorCollection,
|
||||||
ValidationErrorDetail,
|
ValidationErrorDetail,
|
||||||
extractOrPushError,
|
extractOrPushError,
|
||||||
@ -34,7 +34,7 @@ export interface ICustomerMapper
|
|||||||
extends ISequelizeMapper<CustomerModel, CustomerCreationAttributes, Customer> {}
|
extends ISequelizeMapper<CustomerModel, CustomerCreationAttributes, Customer> {}
|
||||||
|
|
||||||
export class CustomerMapper
|
export class CustomerMapper
|
||||||
extends SequelizeMapper<CustomerModel, CustomerCreationAttributes, Customer>
|
extends SequelizeDomainMapper<CustomerModel, CustomerCreationAttributes, Customer>
|
||||||
implements ICustomerMapper
|
implements ICustomerMapper
|
||||||
{
|
{
|
||||||
public mapToDomain(source: CustomerModel, params?: MapperParamsType): Result<Customer, Error> {
|
public mapToDomain(source: CustomerModel, params?: MapperParamsType): Result<Customer, Error> {
|
||||||
|
|||||||
@ -25,9 +25,9 @@ export class ResultCollection<T, E extends Error = Error> implements IResultColl
|
|||||||
return this._collection.some((result) => result.isFailure);
|
return this._collection.some((result) => result.isFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFirstFaultyResult(): Result<T, E> {
|
public getFirstFaultyResult(): Result<never, E> {
|
||||||
// biome-ignore lint/style/noNonNullAssertion: <explanation>
|
const firstFaultyResult = this._collection.find((result) => result.isFailure);
|
||||||
return this._collection.find((result) => result.isFailure)!;
|
return Result.fail(firstFaultyResult?.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllFaultyResults(): Result<T, E>[] {
|
public getAllFaultyResults(): Result<T, E>[] {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user