Facturas de cliente

This commit is contained in:
David Arranz 2025-06-11 17:13:44 +02:00
parent 484f0119a7
commit 7bf24c8f91
161 changed files with 2294 additions and 2161 deletions

View File

@ -42,7 +42,7 @@
"dependencies": { "dependencies": {
"@erp/core": "workspace:*", "@erp/core": "workspace:*",
"@erp/auth": "workspace:*", "@erp/auth": "workspace:*",
"@erp/invoices": "workspace:*", "@erp/customer-invoices": "workspace:*",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"cls-rtracer": "^2.6.3", "cls-rtracer": "^2.6.3",
"cors": "^2.8.5", "cors": "^2.8.5",

View File

@ -134,7 +134,6 @@ process.on("uncaughtException", (error: Error) => {
await initModules({ app, database, baseRoutePath: API_BASE_PATH, logger }); await initModules({ app, database, baseRoutePath: API_BASE_PATH, logger });
logger.info("holaaaaaaaaaaaaaaaaa");
console.log(listRoutes(app._router, API_BASE_PATH)); console.log(listRoutes(app._router, API_BASE_PATH));
server.listen(currentState.port, () => { server.listen(currentState.port, () => {

View File

@ -1,5 +1,5 @@
import { authAPIModule } from "@erp/auth/api"; import { authAPIModule } from "@erp/auth/api";
import { invoicesAPIModule } from "@erp/invoices/api"; import { invoicesAPIModule } from "@erp/customer-invoices/api";
import { registerModule } from "./lib"; import { registerModule } from "./lib";
export const registerModules = () => { export const registerModules = () => {

View File

@ -31,7 +31,7 @@
"dependencies": { "dependencies": {
"@erp/auth": "workspace:*", "@erp/auth": "workspace:*",
"@erp/core": "workspace:*", "@erp/core": "workspace:*",
"@erp/invoices": "workspace:*", "@erp/customer-invoices": "workspace:*",
"@repo/rdx-criteria": "workspace:*", "@repo/rdx-criteria": "workspace:*",
"@repo/rdx-ui": "workspace:*", "@repo/rdx-ui": "workspace:*",
"@repo/shadcn-ui": "workspace:*", "@repo/shadcn-ui": "workspace:*",

View File

@ -45,7 +45,7 @@ export const App = () => {
getAccessToken, getAccessToken,
setAccessToken, setAccessToken,
clearAccessToken, clearAccessToken,
authService: createAuthService(dataSource), authService: createAuthService(),
}} }}
> >
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>

View File

@ -1,5 +1,5 @@
import { AuthModuleManifiest } from "@erp/auth/client"; import { AuthModuleManifiest } from "@erp/auth/client";
import { IModuleClient } from "@erp/core/client"; import { IModuleClient } from "@erp/core/client";
import { InvoicesModuleManifiest } from "@erp/invoices/client"; import { CustomerInvoicesModuleManifiest } from "@erp/customer-invoices/client";
export const modules: IModuleClient[] = [AuthModuleManifiest, InvoicesModuleManifiest]; export const modules: IModuleClient[] = [AuthModuleManifiest, CustomerInvoicesModuleManifiest];

View File

@ -1,13 +1,14 @@
import { IDataSource } from "@erp/core/client"; import { useDataSource } from "@erp/core/client";
import { ILoginRequestDTO, ILoginResponseDTO } from "../../common"; import { ILoginRequestDTO, ILoginResponseDTO } from "../../common";
export interface IAuthService { export interface IAuthService {
login: (credentials: ILoginRequestDTO) => Promise<ILoginResponseDTO>; login: (credentials: ILoginRequestDTO) => Promise<ILoginResponseDTO>;
} }
export const createAuthService = (dataSource: IDataSource): IAuthService => { export const createAuthService = (): IAuthService => {
return { return {
login: async (credentials: ILoginRequestDTO) => { login: async (credentials: ILoginRequestDTO) => {
const dataSource = useDataSource();
const data = await dataSource.custom<ILoginResponseDTO>({ const data = await dataSource.custom<ILoginResponseDTO>({
path: "login", path: "login",
method: "post", method: "post",

View File

@ -1,5 +1,5 @@
{ {
"name": "@erp/invoices", "name": "@erp/customer-invoices",
"version": "0.0.1", "version": "0.0.1",
"main": "src/index.ts", "main": "src/index.ts",
"types": "src/index.ts", "types": "src/index.ts",
@ -23,6 +23,7 @@
"@repo/rdx-utils": "workspace:*", "@repo/rdx-utils": "workspace:*",
"@repo/rdx-ui": "workspace:*", "@repo/rdx-ui": "workspace:*",
"@repo/shadcn-ui": "workspace:*", "@repo/shadcn-ui": "workspace:*",
"@tanstack/react-query": "^5.74.11",
"ag-grid-community": "^33.3.0", "ag-grid-community": "^33.3.0",
"ag-grid-react": "^33.3.0", "ag-grid-react": "^33.3.0",
"express": "^4.18.2", "express": "^4.18.2",

View File

@ -0,0 +1,147 @@
import { UniqueID, UtcDate } from "@/core/common/domain";
import {
type ICustomerInvoiceProps,
type ICustomerInvoiceService,
type CustomerInvoice,
CustomerInvoiceNumber,
CustomerInvoiceSerie,
CustomerInvoiceStatus,
} from "@/contexts/customerCustomerInvoices/domain";
import { ITransactionManager } from "@/core/common/infrastructure/database";
import { logger } from "@/core/logger";
import { Result } from "@repo/rdx-utils";
import { ICreateCustomerInvoiceRequestDTO } from "../../common/dto";
export class CreateCustomerInvoiceUseCase {
constructor(
private readonly customerCustomerInvoiceService: ICustomerInvoiceService,
private readonly transactionManager: ITransactionManager
) {}
public execute(
customerCustomerInvoiceID: UniqueID,
dto: ICreateCustomerInvoiceRequestDTO
): Promise<Result<CustomerInvoice, Error>> {
return this.transactionManager.complete(async (transaction) => {
try {
const validOrErrors = this.validateCustomerInvoiceData(dto);
if (validOrErrors.isFailure) {
return Result.fail(validOrErrors.error);
}
const data = validOrErrors.data;
// Update customerCustomerInvoice with dto
return await this.customerCustomerInvoiceService.createCustomerInvoice(customerCustomerInvoiceID, data, transaction);
} catch (error: unknown) {
logger.error(error as Error);
return Result.fail(error as Error);
}
});
}
private validateCustomerInvoiceData(dto: ICreateCustomerInvoiceRequestDTO): Result<ICustomerInvoiceProps, Error> {
const errors: Error[] = [];
const customerCustomerInvoiceNumerOrError = CustomerInvoiceNumber.create(dto.customerCustomerInvoice_number);
const customerCustomerInvoiceSeriesOrError = CustomerInvoiceSerie.create(dto.customerCustomerInvoice_series);
const issueDateOrError = UtcDate.create(dto.issue_date);
const operationDateOrError = UtcDate.create(dto.operation_date);
const result = Result.combine([
customerCustomerInvoiceNumerOrError,
customerCustomerInvoiceSeriesOrError,
issueDateOrError,
operationDateOrError,
]);
if (result.isFailure) {
return Result.fail(result.error);
}
const validatedData: ICustomerInvoiceProps = {
status: CustomerInvoiceStatus.createDraft(),
customerCustomerInvoiceNumber: customerCustomerInvoiceNumerOrError.data,
customerCustomerInvoiceSeries: customerCustomerInvoiceSeriesOrError.data,
issueDate: issueDateOrError.data,
operationDate: operationDateOrError.data,
customerCustomerInvoiceCurrency: dto.currency,
};
/*if (errors.length > 0) {
const message = errors.map((err) => err.message).toString();
return Result.fail(new Error(message));
}*/
return Result.ok(validatedData);
/*let customerCustomerInvoice_status = CustomerInvoiceStatus.create(dto.status).object;
if (customerCustomerInvoice_status.isEmpty()) {
customerCustomerInvoice_status = CustomerInvoiceStatus.createDraft();
}
let customerCustomerInvoice_series = CustomerInvoiceSeries.create(dto.customerCustomerInvoice_series).object;
if (customerCustomerInvoice_series.isEmpty()) {
customerCustomerInvoice_series = CustomerInvoiceSeries.create(dto.customerCustomerInvoice_series).object;
}
let issue_date = CustomerInvoiceDate.create(dto.issue_date).object;
if (issue_date.isEmpty()) {
issue_date = CustomerInvoiceDate.createCurrentDate().object;
}
let operation_date = CustomerInvoiceDate.create(dto.operation_date).object;
if (operation_date.isEmpty()) {
operation_date = CustomerInvoiceDate.createCurrentDate().object;
}
let customerCustomerInvoiceCurrency = Currency.createFromCode(dto.currency).object;
if (customerCustomerInvoiceCurrency.isEmpty()) {
customerCustomerInvoiceCurrency = Currency.createDefaultCode().object;
}
let customerCustomerInvoiceLanguage = Language.createFromCode(dto.language_code).object;
if (customerCustomerInvoiceLanguage.isEmpty()) {
customerCustomerInvoiceLanguage = Language.createDefaultCode().object;
}
const items = new Collection<CustomerInvoiceItem>(
dto.items?.map(
(item) =>
CustomerInvoiceSimpleItem.create({
description: Description.create(item.description).object,
quantity: Quantity.create(item.quantity).object,
unitPrice: UnitPrice.create({
amount: item.unit_price.amount,
currencyCode: item.unit_price.currency,
precision: item.unit_price.precision,
}).object,
}).object
)
);
if (!customerCustomerInvoice_status.isDraft()) {
throw Error("Error al crear una factura que no es borrador");
}
return DraftCustomerInvoice.create(
{
customerCustomerInvoiceSeries: customerCustomerInvoice_series,
issueDate: issue_date,
operationDate: operation_date,
customerCustomerInvoiceCurrency,
language: customerCustomerInvoiceLanguage,
customerCustomerInvoiceNumber: CustomerInvoiceNumber.create(undefined).object,
//notes: Note.create(customerCustomerInvoiceDTO.notes).object,
//senderId: UniqueID.create(null).object,
recipient,
items,
},
customerCustomerInvoiceId
);*/
}
}

View File

@ -2,18 +2,18 @@ import { UniqueID } from "@/core/common/domain";
import { ITransactionManager } from "@/core/common/infrastructure/database"; import { ITransactionManager } from "@/core/common/infrastructure/database";
import { logger } from "@/core/logger"; import { logger } from "@/core/logger";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { IInvoiceService } from "../domain"; import { ICustomerInvoiceService } from "../domain";
export class DeleteInvoiceUseCase { export class DeleteCustomerInvoiceUseCase {
constructor( constructor(
private readonly invoiceService: IInvoiceService, private readonly customerCustomerInvoiceService: ICustomerInvoiceService,
private readonly transactionManager: ITransactionManager private readonly transactionManager: ITransactionManager
) {} ) {}
public execute(invoiceID: UniqueID): Promise<Result<boolean, Error>> { public execute(customerCustomerInvoiceID: UniqueID): Promise<Result<boolean, Error>> {
return this.transactionManager.complete(async (transaction) => { return this.transactionManager.complete(async (transaction) => {
try { try {
return await this.invoiceService.deleteInvoiceById(invoiceID, transaction); return await this.customerCustomerInvoiceService.deleteCustomerInvoiceById(customerCustomerInvoiceID, transaction);
} catch (error: unknown) { } catch (error: unknown) {
logger.error(error as Error); logger.error(error as Error);
return Result.fail(error as Error); return Result.fail(error as Error);

View File

@ -2,18 +2,18 @@ import { UniqueID } from "@/core/common/domain";
import { ITransactionManager } from "@/core/common/infrastructure/database"; import { ITransactionManager } from "@/core/common/infrastructure/database";
import { logger } from "@/lib/logger"; import { logger } from "@/lib/logger";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { IInvoiceService, Invoice } from "../domain"; import { ICustomerInvoiceService, CustomerInvoice } from "../domain";
export class GetInvoiceUseCase { export class GetCustomerInvoiceUseCase {
constructor( constructor(
private readonly invoiceService: IInvoiceService, private readonly customerCustomerInvoiceService: ICustomerInvoiceService,
private readonly transactionManager: ITransactionManager private readonly transactionManager: ITransactionManager
) {} ) {}
public execute(invoiceID: UniqueID): Promise<Result<Invoice, Error>> { public execute(customerCustomerInvoiceID: UniqueID): Promise<Result<CustomerInvoice, Error>> {
return this.transactionManager.complete(async (transaction) => { return this.transactionManager.complete(async (transaction) => {
try { try {
return await this.invoiceService.findInvoiceById(invoiceID, transaction); return await this.customerCustomerInvoiceService.findCustomerInvoiceById(customerCustomerInvoiceID, transaction);
} catch (error: unknown) { } catch (error: unknown) {
logger.error(error as Error); logger.error(error as Error);
return Result.fail(error as Error); return Result.fail(error as Error);

View File

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

View File

@ -2,18 +2,18 @@ import { ITransactionManager } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import { Collection, Result } from "@repo/rdx-utils"; import { Collection, Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize"; import { Transaction } from "sequelize";
import { IInvoiceService, Invoice } from "../domain"; import { ICustomerInvoiceService, CustomerInvoice } from "../domain";
export class ListInvoicesUseCase { export class ListCustomerInvoicesUseCase {
constructor( constructor(
private readonly invoiceService: IInvoiceService, private readonly customerCustomerInvoiceService: ICustomerInvoiceService,
private readonly transactionManager: ITransactionManager private readonly transactionManager: ITransactionManager
) {} ) {}
public execute(criteria: Criteria): Promise<Result<Collection<Invoice>, Error>> { public execute(criteria: Criteria): Promise<Result<Collection<CustomerInvoice>, Error>> {
return this.transactionManager.complete(async (transaction: Transaction) => { return this.transactionManager.complete(async (transaction: Transaction) => {
try { try {
return await this.invoiceService.findInvoices(criteria, transaction); return await this.customerCustomerInvoiceService.findCustomerInvoices(criteria, transaction);
} catch (error: unknown) { } catch (error: unknown) {
return Result.fail(error as Error); return Result.fail(error as Error);
} }

View File

@ -5,12 +5,12 @@
import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain"; import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
import { Result, UniqueID } from "@shared/contexts"; import { Result, UniqueID } from "@shared/contexts";
import { NullOr } from "@shared/utilities"; import { NullOr } from "@shared/utilities";
import { IInvoiceParticipantAddress, IInvoiceParticipantAddressRepository } from "../../domain"; import { ICustomerInvoiceParticipantAddress, ICustomerInvoiceParticipantAddressRepository } from "../../domain";
export const participantAddressFinder = async ( export const participantAddressFinder = async (
addressId: UniqueID, addressId: UniqueID,
adapter: IAdapter, adapter: IAdapter,
repository: RepositoryBuilder<IInvoiceParticipantAddressRepository> repository: RepositoryBuilder<ICustomerInvoiceParticipantAddressRepository>
) => { ) => {
if (addressId.isNull()) { if (addressId.isNull()) {
return Result.fail<IApplicationServiceError>( return Result.fail<IApplicationServiceError>(
@ -22,7 +22,7 @@ export const participantAddressFinder = async (
} }
const transaction = adapter.startTransaction(); const transaction = adapter.startTransaction();
let address: NullOr<IInvoiceParticipantAddress> = null; let address: NullOr<ICustomerInvoiceParticipantAddress> = null;
try { try {
await transaction.complete(async (t) => { await transaction.complete(async (t) => {
@ -38,7 +38,7 @@ export const participantAddressFinder = async (
); );
} }
return Result.ok<IInvoiceParticipantAddress>(address); return Result.ok<ICustomerInvoiceParticipantAddress>(address);
} catch (error: unknown) { } catch (error: unknown) {
const _error = error as Error; const _error = error as Error;

View File

@ -1,13 +1,13 @@
/* import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain"; /* import { IAdapter, RepositoryBuilder } from "@/contexts/common/domain";
import { UniqueID } from "@shared/contexts"; import { UniqueID } from "@shared/contexts";
import { IInvoiceParticipantRepository } from "../../domain"; import { ICustomerInvoiceParticipantRepository } from "../../domain";
import { InvoiceCustomer } from "../../domain/entities/invoice-customer/invoice-customer"; import { CustomerInvoiceCustomer } from "../../domain/entities/customerCustomerInvoice-customer/customerCustomerInvoice-customer";
export const participantFinder = async ( export const participantFinder = async (
participantId: UniqueID, participantId: UniqueID,
adapter: IAdapter, adapter: IAdapter,
repository: RepositoryBuilder<IInvoiceParticipantRepository> repository: RepositoryBuilder<ICustomerInvoiceParticipantRepository>
): Promise<InvoiceCustomer | undefined> => { ): Promise<CustomerInvoiceCustomer | undefined> => {
if (!participantId || (participantId && participantId.isNull())) { if (!participantId || (participantId && participantId.isNull())) {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }

View File

@ -0,0 +1,401 @@
import { UniqueID } from "@/core/common/domain";
import { ITransactionManager } from "@/core/common/infrastructure/database";
import { Result } from "@repo/rdx-utils";
import { IUpdateCustomerInvoiceRequestDTO } from "../../common/dto";
import { ICustomerInvoiceService, CustomerInvoice } from "../domain";
export class CreateCustomerInvoiceUseCase {
constructor(
private readonly customerCustomerInvoiceService: ICustomerInvoiceService,
private readonly transactionManager: ITransactionManager
) {}
public execute(
customerCustomerInvoiceID: UniqueID,
dto: Partial<IUpdateCustomerInvoiceRequestDTO>
): Promise<Result<CustomerInvoice, Error>> {
return this.transactionManager.complete(async (transaction) => {
return Result.fail(new Error("No implementado"));
/*
try {
const validOrErrors = this.validateCustomerInvoiceData(dto);
if (validOrErrors.isFailure) {
return Result.fail(validOrErrors.error);
}
const data = validOrErrors.data;
// Update customerCustomerInvoice with dto
return await this.customerCustomerInvoiceService.updateCustomerInvoiceById(customerCustomerInvoiceID, data, transaction);
} catch (error: unknown) {
logger.error(error as Error);
return Result.fail(error as Error);
}
*/
});
}
/* private validateCustomerInvoiceData(
dto: Partial<IUpdateCustomerInvoiceRequestDTO>
): Result<Partial<ICustomerInvoiceProps>, Error> {
const errors: Error[] = [];
const validatedData: Partial<ICustomerInvoiceProps> = {};
// Create customerCustomerInvoice
let customerCustomerInvoice_status = CustomerInvoiceStatus.create(customerCustomerInvoiceDTO.status).object;
if (customerCustomerInvoice_status.isEmpty()) {
customerCustomerInvoice_status = CustomerInvoiceStatus.createDraft();
}
let customerCustomerInvoice_series = CustomerInvoiceSeries.create(customerCustomerInvoiceDTO.customerCustomerInvoice_series).object;
if (customerCustomerInvoice_series.isEmpty()) {
customerCustomerInvoice_series = CustomerInvoiceSeries.create(customerCustomerInvoiceDTO.customerCustomerInvoice_series).object;
}
let issue_date = CustomerInvoiceDate.create(customerCustomerInvoiceDTO.issue_date).object;
if (issue_date.isEmpty()) {
issue_date = CustomerInvoiceDate.createCurrentDate().object;
}
let operation_date = CustomerInvoiceDate.create(customerCustomerInvoiceDTO.operation_date).object;
if (operation_date.isEmpty()) {
operation_date = CustomerInvoiceDate.createCurrentDate().object;
}
let customerCustomerInvoiceCurrency = Currency.createFromCode(customerCustomerInvoiceDTO.currency).object;
if (customerCustomerInvoiceCurrency.isEmpty()) {
customerCustomerInvoiceCurrency = Currency.createDefaultCode().object;
}
let customerCustomerInvoiceLanguage = Language.createFromCode(customerCustomerInvoiceDTO.language_code).object;
if (customerCustomerInvoiceLanguage.isEmpty()) {
customerCustomerInvoiceLanguage = Language.createDefaultCode().object;
}
const items = new Collection<CustomerInvoiceItem>(
customerCustomerInvoiceDTO.items?.map(
(item) =>
CustomerInvoiceSimpleItem.create({
description: Description.create(item.description).object,
quantity: Quantity.create(item.quantity).object,
unitPrice: UnitPrice.create({
amount: item.unit_price.amount,
currencyCode: item.unit_price.currency,
precision: item.unit_price.precision,
}).object,
}).object
)
);
if (!customerCustomerInvoice_status.isDraft()) {
throw Error("Error al crear una factura que no es borrador");
}
return DraftCustomerInvoice.create(
{
customerCustomerInvoiceSeries: customerCustomerInvoice_series,
issueDate: issue_date,
operationDate: operation_date,
customerCustomerInvoiceCurrency,
language: customerCustomerInvoiceLanguage,
customerCustomerInvoiceNumber: CustomerInvoiceNumber.create(undefined).object,
//notes: Note.create(customerCustomerInvoiceDTO.notes).object,
//senderId: UniqueID.create(null).object,
recipient,
items,
},
customerCustomerInvoiceId
);
} */
}
/* export type UpdateCustomerInvoiceResponseOrError =
| Result<never, IUseCaseError> // Misc errors (value objects)
| Result<CustomerInvoice, never>; // Success!
export class UpdateCustomerInvoiceUseCase2
implements
IUseCase<{ id: UniqueID; data: IUpdateCustomerInvoice_DTO }, Promise<UpdateCustomerInvoiceResponseOrError>>
{
private _context: IInvoicingContext;
private _adapter: ISequelizeAdapter;
private _repositoryManager: IRepositoryManager;
constructor(context: IInvoicingContext) {
this._context = context;
this._adapter = context.adapter;
this._repositoryManager = context.repositoryManager;
}
private getRepository<T>(name: string) {
return this._repositoryManager.getRepository<T>(name);
}
private handleValidationFailure(
validationError: Error,
message?: string
): Result<never, IUseCaseError> {
return Result.fail<IUseCaseError>(
UseCaseError.create(
UseCaseError.INVALID_INPUT_DATA,
message ? message : validationError.message,
validationError
)
);
}
async execute(request: {
id: UniqueID;
data: IUpdateCustomerInvoice_DTO;
}): Promise<UpdateCustomerInvoiceResponseOrError> {
const { id, data: customerCustomerInvoiceDTO } = request;
// Validaciones
const customerCustomerInvoiceDTOOrError = ensureUpdateCustomerInvoice_DTOIsValid(customerCustomerInvoiceDTO);
if (customerCustomerInvoiceDTOOrError.isFailure) {
return this.handleValidationFailure(customerCustomerInvoiceDTOOrError.error);
}
const transaction = this._adapter.startTransaction();
const customerCustomerInvoiceRepoBuilder = this.getRepository<ICustomerInvoiceRepository>("CustomerInvoice");
let customerCustomerInvoice: CustomerInvoice | null = null;
try {
await transaction.complete(async (t) => {
customerCustomerInvoice = await customerCustomerInvoiceRepoBuilder({ transaction: t }).getById(id);
});
if (customerCustomerInvoice === null) {
return Result.fail<IUseCaseError>(
UseCaseError.create(UseCaseError.NOT_FOUND_ERROR, `CustomerInvoice not found`, {
id: request.id.toString(),
entity: "customerCustomerInvoice",
})
);
}
return Result.ok<CustomerInvoice>(customerCustomerInvoice);
} catch (error: unknown) {
const _error = error as Error;
if (customerCustomerInvoiceRepoBuilder().isRepositoryError(_error)) {
return this.handleRepositoryError(error as BaseError, customerCustomerInvoiceRepoBuilder());
} else {
return this.handleUnexceptedError(error);
}
}
// Recipient validations
const recipientIdOrError = ensureParticipantIdIsValid(
customerCustomerInvoiceDTO?.recipient?.id,
);
if (recipientIdOrError.isFailure) {
return this.handleValidationFailure(
recipientIdOrError.error,
"Recipient ID not valid",
);
}
const recipientId = recipientIdOrError.object;
const recipientBillingIdOrError = ensureParticipantAddressIdIsValid(
customerCustomerInvoiceDTO?.recipient?.billing_address_id,
);
if (recipientBillingIdOrError.isFailure) {
return this.handleValidationFailure(
recipientBillingIdOrError.error,
"Recipient billing address ID not valid",
);
}
const recipientBillingId = recipientBillingIdOrError.object;
const recipientShippingIdOrError = ensureParticipantAddressIdIsValid(
customerCustomerInvoiceDTO?.recipient?.shipping_address_id,
);
if (recipientShippingIdOrError.isFailure) {
return this.handleValidationFailure(
recipientShippingIdOrError.error,
"Recipient shipping address ID not valid",
);
}
const recipientShippingId = recipientShippingIdOrError.object;
const recipientContact = await this.findContact(
recipientId,
recipientBillingId,
recipientShippingId,
);
if (!recipientContact) {
return this.handleValidationFailure(
new Error(`Recipient with ID ${recipientId.toString()} does not exist`),
);
}
// Crear customerCustomerInvoice
const customerCustomerInvoiceOrError = await this.tryUpdateCustomerInvoiceInstance(
customerCustomerInvoiceDTO,
customerCustomerInvoiceIdOrError.object,
//senderId,
//senderBillingId,
//senderShippingId,
recipientContact,
);
if (customerCustomerInvoiceOrError.isFailure) {
const { error: domainError } = customerCustomerInvoiceOrError;
let errorCode = "";
let message = "";
switch (domainError.code) {
case CustomerInvoice.ERROR_CUSTOMER_WITHOUT_NAME:
errorCode = UseCaseError.INVALID_INPUT_DATA;
message =
"El cliente debe ser una compañía o tener nombre y apellidos.";
break;
default:
errorCode = UseCaseError.UNEXCEPTED_ERROR;
message = "";
break;
}
return Result.fail<IUseCaseError>(
UseCaseError.create(errorCode, message, domainError),
);
}
return this.saveCustomerInvoice(customerCustomerInvoiceOrError.object);
}
private async tryUpdateCustomerInvoiceInstance(customerCustomerInvoiceDTO, customerCustomerInvoiceId, recipient) {
// Create customerCustomerInvoice
let customerCustomerInvoice_status = CustomerInvoiceStatus.create(customerCustomerInvoiceDTO.status).object;
if (customerCustomerInvoice_status.isEmpty()) {
customerCustomerInvoice_status = CustomerInvoiceStatus.createDraft();
}
let customerCustomerInvoice_series = CustomerInvoiceSeries.create(customerCustomerInvoiceDTO.customerCustomerInvoice_series).object;
if (customerCustomerInvoice_series.isEmpty()) {
customerCustomerInvoice_series = CustomerInvoiceSeries.create(customerCustomerInvoiceDTO.customerCustomerInvoice_series).object;
}
let issue_date = CustomerInvoiceDate.create(customerCustomerInvoiceDTO.issue_date).object;
if (issue_date.isEmpty()) {
issue_date = CustomerInvoiceDate.createCurrentDate().object;
}
let operation_date = CustomerInvoiceDate.create(customerCustomerInvoiceDTO.operation_date).object;
if (operation_date.isEmpty()) {
operation_date = CustomerInvoiceDate.createCurrentDate().object;
}
let customerCustomerInvoiceCurrency = Currency.createFromCode(customerCustomerInvoiceDTO.currency).object;
if (customerCustomerInvoiceCurrency.isEmpty()) {
customerCustomerInvoiceCurrency = Currency.createDefaultCode().object;
}
let customerCustomerInvoiceLanguage = Language.createFromCode(customerCustomerInvoiceDTO.language_code).object;
if (customerCustomerInvoiceLanguage.isEmpty()) {
customerCustomerInvoiceLanguage = Language.createDefaultCode().object;
}
const items = new Collection<CustomerInvoiceItem>(
customerCustomerInvoiceDTO.items?.map(
(item) =>
CustomerInvoiceSimpleItem.create({
description: Description.create(item.description).object,
quantity: Quantity.create(item.quantity).object,
unitPrice: UnitPrice.create({
amount: item.unit_price.amount,
currencyCode: item.unit_price.currency,
precision: item.unit_price.precision,
}).object,
}).object
)
);
if (!customerCustomerInvoice_status.isDraft()) {
throw Error("Error al crear una factura que no es borrador");
}
return DraftCustomerInvoice.create(
{
customerCustomerInvoiceSeries: customerCustomerInvoice_series,
issueDate: issue_date,
operationDate: operation_date,
customerCustomerInvoiceCurrency,
language: customerCustomerInvoiceLanguage,
customerCustomerInvoiceNumber: CustomerInvoiceNumber.create(undefined).object,
//notes: Note.create(customerCustomerInvoiceDTO.notes).object,
//senderId: UniqueID.create(null).object,
recipient,
items,
},
customerCustomerInvoiceId
);
}
private async findContact(
contactId: UniqueID,
billingAddressId: UniqueID,
shippingAddressId: UniqueID
) {
const contactRepoBuilder = this.getRepository<IContactRepository>("Contact");
const contact = await contactRepoBuilder().getById2(
contactId,
billingAddressId,
shippingAddressId
);
return contact;
}
private async saveCustomerInvoice(customerCustomerInvoice: DraftCustomerInvoice) {
const transaction = this._adapter.startTransaction();
const customerCustomerInvoiceRepoBuilder = this.getRepository<ICustomerInvoiceRepository>("CustomerInvoice");
try {
await transaction.complete(async (t) => {
const customerCustomerInvoiceRepo = customerCustomerInvoiceRepoBuilder({ transaction: t });
await customerCustomerInvoiceRepo.save(customerCustomerInvoice);
});
return Result.ok<DraftCustomerInvoice>(customerCustomerInvoice);
} catch (error: unknown) {
const _error = error as Error;
if (customerCustomerInvoiceRepoBuilder().isRepositoryError(_error)) {
return this.handleRepositoryError(error as BaseError, customerCustomerInvoiceRepoBuilder());
} else {
return this.handleUnexceptedError(error);
}
}
}
private handleUnexceptedError(error): Result<never, IUseCaseError> {
return Result.fail<IUseCaseError>(
UseCaseError.create(UseCaseError.UNEXCEPTED_ERROR, error.message, error)
);
}
private handleRepositoryError(
error: BaseError,
repository: ICustomerInvoiceRepository
): Result<never, IUseCaseError> {
const { message, details } = repository.handleRepositoryError(error);
return Result.fail<IUseCaseError>(
UseCaseError.create(UseCaseError.REPOSITORY_ERROR, message, details)
);
}
}
*/

View File

@ -1,13 +1,13 @@
import { AggregateRoot, MoneyValue, UniqueID, UtcDate } from "@repo/rdx-ddd"; import { AggregateRoot, MoneyValue, UniqueID, UtcDate } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils"; import { Collection, Result } from "@repo/rdx-utils";
import { InvoiceCustomer, InvoiceItem, InvoiceItems } from "../entities"; import { CustomerInvoiceCustomer, CustomerInvoiceItem, CustomerInvoiceItems } from "../entities";
import { InvoiceNumber, InvoiceSerie, InvoiceStatus } from "../value-objects"; import { CustomerInvoiceNumber, CustomerInvoiceSerie, CustomerInvoiceStatus } from "../value-objects";
export interface IInvoiceProps { export interface ICustomerInvoiceProps {
invoiceNumber: InvoiceNumber; customerCustomerInvoiceNumber: CustomerInvoiceNumber;
invoiceSeries: InvoiceSerie; customerCustomerInvoiceSeries: CustomerInvoiceSerie;
status: InvoiceStatus; status: CustomerInvoiceStatus;
issueDate: UtcDate; issueDate: UtcDate;
operationDate: UtcDate; operationDate: UtcDate;
@ -15,7 +15,7 @@ export interface IInvoiceProps {
//dueDate: UtcDate; // ? --> depende de la forma de pago //dueDate: UtcDate; // ? --> depende de la forma de pago
//tax: Tax; // ? --> detalles? //tax: Tax; // ? --> detalles?
invoiceCurrency: string; customerCustomerInvoiceCurrency: string;
//language: Language; //language: Language;
@ -27,29 +27,29 @@ export interface IInvoiceProps {
//paymentInstructions: Note; //paymentInstructions: Note;
//paymentTerms: string; //paymentTerms: string;
customer?: InvoiceCustomer; customer?: CustomerInvoiceCustomer;
items?: InvoiceItems; items?: CustomerInvoiceItems;
} }
export interface IInvoice { export interface ICustomerInvoice {
id: UniqueID; id: UniqueID;
invoiceNumber: InvoiceNumber; customerCustomerInvoiceNumber: CustomerInvoiceNumber;
invoiceSeries: InvoiceSerie; customerCustomerInvoiceSeries: CustomerInvoiceSerie;
status: InvoiceStatus; status: CustomerInvoiceStatus;
issueDate: UtcDate; issueDate: UtcDate;
operationDate: UtcDate; operationDate: UtcDate;
//senderId: UniqueID; //senderId: UniqueID;
customer?: InvoiceCustomer; customer?: CustomerInvoiceCustomer;
//dueDate //dueDate
//tax: Tax; //tax: Tax;
//language: Language; //language: Language;
invoiceCurrency: string; customerCustomerInvoiceCurrency: string;
//purchareOrderNumber: string; //purchareOrderNumber: string;
//notes: Note; //notes: Note;
@ -57,43 +57,43 @@ export interface IInvoice {
//paymentInstructions: Note; //paymentInstructions: Note;
//paymentTerms: string; //paymentTerms: string;
items: InvoiceItems; items: CustomerInvoiceItems;
calculateSubtotal: () => MoneyValue; calculateSubtotal: () => MoneyValue;
calculateTaxTotal: () => MoneyValue; calculateTaxTotal: () => MoneyValue;
calculateTotal: () => MoneyValue; calculateTotal: () => MoneyValue;
} }
export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice { export class CustomerInvoice extends AggregateRoot<ICustomerInvoiceProps> implements ICustomerInvoice {
private _items!: Collection<InvoiceItem>; private _items!: Collection<CustomerInvoiceItem>;
//protected _status: InvoiceStatus; //protected _status: CustomerInvoiceStatus;
protected constructor(props: IInvoiceProps, id?: UniqueID) { protected constructor(props: ICustomerInvoiceProps, id?: UniqueID) {
super(props, id); super(props, id);
this._items = props.items || InvoiceItems.create(); this._items = props.items || CustomerInvoiceItems.create();
} }
static create(props: IInvoiceProps, id?: UniqueID): Result<Invoice, Error> { static create(props: ICustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
const invoice = new Invoice(props, id); const customerCustomerInvoice = new CustomerInvoice(props, id);
// Reglas de negocio / validaciones // Reglas de negocio / validaciones
// ... // ...
// ... // ...
// 🔹 Disparar evento de dominio "InvoiceAuthenticatedEvent" // 🔹 Disparar evento de dominio "CustomerInvoiceAuthenticatedEvent"
//const { invoice } = props; //const { customerCustomerInvoice } = props;
//user.addDomainEvent(new InvoiceAuthenticatedEvent(id, invoice.toString())); //user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerCustomerInvoice.toString()));
return Result.ok(invoice); return Result.ok(customerCustomerInvoice);
} }
get invoiceNumber() { get customerCustomerInvoiceNumber() {
return this.props.invoiceNumber; return this.props.customerCustomerInvoiceNumber;
} }
get invoiceSeries() { get customerCustomerInvoiceSeries() {
return this.props.invoiceSeries; return this.props.customerCustomerInvoiceSeries;
} }
get issueDate() { get issueDate() {
@ -104,7 +104,7 @@ export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
return this.props.senderId; return this.props.senderId;
}*/ }*/
get customer(): InvoiceCustomer | undefined { get customer(): CustomerInvoiceCustomer | undefined {
return this.props.customer; return this.props.customer;
} }
@ -152,8 +152,8 @@ export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
return this.props.shipTo; return this.props.shipTo;
}*/ }*/
get invoiceCurrency() { get customerCustomerInvoiceCurrency() {
return this.props.invoiceCurrency; return this.props.customerCustomerInvoiceCurrency;
} }
/*get notes() { /*get notes() {
@ -161,11 +161,11 @@ export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
}*/ }*/
// Method to get the complete list of line items // Method to get the complete list of line items
/*get lineItems(): InvoiceLineItem[] { /*get lineItems(): CustomerInvoiceLineItem[] {
return this._lineItems; return this._lineItems;
} }
addLineItem(lineItem: InvoiceLineItem, position?: number): void { addLineItem(lineItem: CustomerInvoiceLineItem, position?: number): void {
if (position === undefined) { if (position === undefined) {
this._lineItems.push(lineItem); this._lineItems.push(lineItem);
} else { } else {
@ -174,29 +174,29 @@ export class Invoice extends AggregateRoot<IInvoiceProps> implements IInvoice {
}*/ }*/
calculateSubtotal(): MoneyValue { calculateSubtotal(): MoneyValue {
const invoiceSubtotal = MoneyValue.create({ const customerCustomerInvoiceSubtotal = MoneyValue.create({
amount: 0, amount: 0,
currency_code: this.props.invoiceCurrency, currency_code: this.props.customerCustomerInvoiceCurrency,
scale: 2, scale: 2,
}).data; }).data;
return this._items.getAll().reduce((subtotal, item) => { return this._items.getAll().reduce((subtotal, item) => {
return subtotal.add(item.calculateTotal()); return subtotal.add(item.calculateTotal());
}, invoiceSubtotal); }, customerCustomerInvoiceSubtotal);
} }
// Method to calculate the total tax in the invoice // Method to calculate the total tax in the customerCustomerInvoice
calculateTaxTotal(): MoneyValue { calculateTaxTotal(): MoneyValue {
const taxTotal = MoneyValue.create({ const taxTotal = MoneyValue.create({
amount: 0, amount: 0,
currency_code: this.props.invoiceCurrency, currency_code: this.props.customerCustomerInvoiceCurrency,
scale: 2, scale: 2,
}).data; }).data;
return taxTotal; return taxTotal;
} }
// Method to calculate the total invoice amount, including taxes // Method to calculate the total customerCustomerInvoice amount, including taxes
calculateTotal(): MoneyValue { calculateTotal(): MoneyValue {
return this.calculateSubtotal().add(this.calculateTaxTotal()); return this.calculateSubtotal().add(this.calculateTaxTotal());
} }

View File

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

View File

@ -0,0 +1,2 @@
export * from "./customerCustomerInvoice-customer";
export * from "./customerCustomerInvoice-items";

View File

@ -0,0 +1,2 @@
export * from "./customerCustomerInvoice-address";
export * from "./customerCustomerInvoice-customer";

View File

@ -1,43 +1,43 @@
import { EmailAddress, Name, PostalAddress, ValueObject } from "@repo/rdx-ddd"; import { EmailAddress, Name, PostalAddress, ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { PhoneNumber } from "libphonenumber-js"; import { PhoneNumber } from "libphonenumber-js";
import { InvoiceAddressType } from "../../value-objects"; import { CustomerInvoiceAddressType } from "../../value-objects";
export interface IInvoiceAddressProps { export interface ICustomerInvoiceAddressProps {
type: InvoiceAddressType; type: CustomerInvoiceAddressType;
title: Name; title: Name;
address: PostalAddress; address: PostalAddress;
email: EmailAddress; email: EmailAddress;
phone: PhoneNumber; phone: PhoneNumber;
} }
export interface IInvoiceAddress { export interface ICustomerInvoiceAddress {
type: InvoiceAddressType; type: CustomerInvoiceAddressType;
title: Name; title: Name;
address: PostalAddress; address: PostalAddress;
email: EmailAddress; email: EmailAddress;
phone: PhoneNumber; phone: PhoneNumber;
} }
export class InvoiceAddress extends ValueObject<IInvoiceAddressProps> implements IInvoiceAddress { export class CustomerInvoiceAddress extends ValueObject<ICustomerInvoiceAddressProps> implements ICustomerInvoiceAddress {
public static create(props: IInvoiceAddressProps) { public static create(props: ICustomerInvoiceAddressProps) {
return Result.ok(new InvoiceAddress(props)); return Result.ok(new CustomerInvoiceAddress(props));
} }
public static createShippingAddress(props: IInvoiceAddressProps) { public static createShippingAddress(props: ICustomerInvoiceAddressProps) {
return Result.ok( return Result.ok(
new InvoiceAddress({ new CustomerInvoiceAddress({
...props, ...props,
type: InvoiceAddressType.create("shipping").data, type: CustomerInvoiceAddressType.create("shipping").data,
}) })
); );
} }
public static createBillingAddress(props: IInvoiceAddressProps) { public static createBillingAddress(props: ICustomerInvoiceAddressProps) {
return Result.ok( return Result.ok(
new InvoiceAddress({ new CustomerInvoiceAddress({
...props, ...props,
type: InvoiceAddressType.create("billing").data, type: CustomerInvoiceAddressType.create("billing").data,
}) })
); );
} }
@ -58,11 +58,11 @@ export class InvoiceAddress extends ValueObject<IInvoiceAddressProps> implements
return this.props.phone; return this.props.phone;
} }
get type(): InvoiceAddressType { get type(): CustomerInvoiceAddressType {
return this.props.type; return this.props.type;
} }
getValue(): IInvoiceAddressProps { getValue(): ICustomerInvoiceAddressProps {
return this.props; return this.props;
} }

View File

@ -1,38 +1,38 @@
import { DomainEntity, Name, TINNumber, UniqueID } from "@repo/rdx-ddd"; import { DomainEntity, Name, TINNumber, UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { InvoiceAddress } from "./invoice-address"; import { CustomerInvoiceAddress } from "./customerCustomerInvoice-address";
export interface IInvoiceCustomerProps { export interface ICustomerInvoiceCustomerProps {
tin: TINNumber; tin: TINNumber;
companyName: Name; companyName: Name;
firstName: Name; firstName: Name;
lastName: Name; lastName: Name;
billingAddress?: InvoiceAddress; billingAddress?: CustomerInvoiceAddress;
shippingAddress?: InvoiceAddress; shippingAddress?: CustomerInvoiceAddress;
} }
export interface IInvoiceCustomer { export interface ICustomerInvoiceCustomer {
id: UniqueID; id: UniqueID;
tin: TINNumber; tin: TINNumber;
companyName: Name; companyName: Name;
firstName: Name; firstName: Name;
lastName: Name; lastName: Name;
billingAddress?: InvoiceAddress; billingAddress?: CustomerInvoiceAddress;
shippingAddress?: InvoiceAddress; shippingAddress?: CustomerInvoiceAddress;
} }
export class InvoiceCustomer export class CustomerInvoiceCustomer
extends DomainEntity<IInvoiceCustomerProps> extends DomainEntity<ICustomerInvoiceCustomerProps>
implements IInvoiceCustomer implements ICustomerInvoiceCustomer
{ {
public static create( public static create(
props: IInvoiceCustomerProps, props: ICustomerInvoiceCustomerProps,
id?: UniqueID id?: UniqueID
): Result<InvoiceCustomer, Error> { ): Result<CustomerInvoiceCustomer, Error> {
const participant = new InvoiceCustomer(props, id); const participant = new CustomerInvoiceCustomer(props, id);
return Result.ok<InvoiceCustomer>(participant); return Result.ok<CustomerInvoiceCustomer>(participant);
} }
get tin(): TINNumber { get tin(): TINNumber {

View File

@ -0,0 +1,2 @@
export * from "./customerCustomerInvoice-item";
export * from "./customerCustomerInvoice-items";

View File

@ -0,0 +1,83 @@
import { MoneyValue, Percentage, Quantity } from "@/core/common/domain";
import { CustomerInvoiceItemDescription } from "../../value-objects";
import { CustomerInvoiceItem } from "./customerCustomerInvoice-item";
describe("CustomerInvoiceItem", () => {
it("debería calcular correctamente el subtotal (unitPrice * quantity)", () => {
const props = {
description: CustomerInvoiceItemDescription.create("Producto A"),
quantity: Quantity.create({ amount: 200, scale: 2 }),
unitPrice: MoneyValue.create(50),
discount: Percentage.create(0),
};
const result = CustomerInvoiceItem.create(props);
expect(result.isOk()).toBe(true);
const customerCustomerInvoiceItem = result.unwrap();
expect(customerCustomerInvoiceItem.subtotalPrice.value).toBe(100); // 50 * 2
});
it("debería calcular correctamente el total con descuento", () => {
const props = {
description: new CustomerInvoiceItemDescription("Producto B"),
quantity: new Quantity(3),
unitPrice: new MoneyValue(30),
discount: new Percentage(10), // 10%
};
const result = CustomerInvoiceItem.create(props);
expect(result.isOk()).toBe(true);
const customerCustomerInvoiceItem = result.unwrap();
expect(customerCustomerInvoiceItem.totalPrice.value).toBe(81); // (30 * 3) - 10% de (30 * 3)
});
it("debería devolver los valores correctos de las propiedades", () => {
const props = {
description: new CustomerInvoiceItemDescription("Producto C"),
quantity: new Quantity(1),
unitPrice: new MoneyValue(100),
discount: new Percentage(5),
};
const result = CustomerInvoiceItem.create(props);
expect(result.isOk()).toBe(true);
const customerCustomerInvoiceItem = result.unwrap();
expect(customerCustomerInvoiceItem.description.value).toBe("Producto C");
expect(customerCustomerInvoiceItem.quantity.value).toBe(1);
expect(customerCustomerInvoiceItem.unitPrice.value).toBe(100);
expect(customerCustomerInvoiceItem.discount.value).toBe(5);
});
it("debería manejar correctamente un descuento del 0%", () => {
const props = {
description: new CustomerInvoiceItemDescription("Producto D"),
quantity: new Quantity(4),
unitPrice: new MoneyValue(25),
discount: new Percentage(0),
};
const result = CustomerInvoiceItem.create(props);
expect(result.isOk()).toBe(true);
const customerCustomerInvoiceItem = result.unwrap();
expect(customerCustomerInvoiceItem.totalPrice.value).toBe(100); // 25 * 4
});
it("debería manejar correctamente un descuento del 100%", () => {
const props = {
description: new CustomerInvoiceItemDescription("Producto E"),
quantity: new Quantity(2),
unitPrice: new MoneyValue(50),
discount: new Percentage(100),
};
const result = CustomerInvoiceItem.create(props);
expect(result.isOk()).toBe(true);
const customerCustomerInvoiceItem = result.unwrap();
expect(customerCustomerInvoiceItem.totalPrice.value).toBe(0); // (50 * 2) - 100% de (50 * 2)
});
});

View File

@ -0,0 +1,101 @@
import { DomainEntity, UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import {
CustomerInvoiceItemDescription,
CustomerInvoiceItemDiscount,
CustomerInvoiceItemQuantity,
CustomerInvoiceItemSubtotalPrice,
CustomerInvoiceItemTotalPrice,
CustomerInvoiceItemUnitPrice,
} from "../../value-objects";
export interface ICustomerInvoiceItemProps {
description: CustomerInvoiceItemDescription;
quantity: CustomerInvoiceItemQuantity; // Cantidad de unidades
unitPrice: CustomerInvoiceItemUnitPrice; // Precio unitario en la moneda de la factura
//subtotalPrice?: MoneyValue; // Precio unitario * Cantidad
discount: CustomerInvoiceItemDiscount; // % descuento
//totalPrice?: MoneyValue;
}
export interface ICustomerInvoiceItem {
id: UniqueID;
description: CustomerInvoiceItemDescription;
quantity: CustomerInvoiceItemQuantity;
unitPrice: CustomerInvoiceItemUnitPrice;
subtotalPrice: CustomerInvoiceItemSubtotalPrice;
discount: CustomerInvoiceItemDiscount;
totalPrice: CustomerInvoiceItemTotalPrice;
}
export class CustomerInvoiceItem extends DomainEntity<ICustomerInvoiceItemProps> implements ICustomerInvoiceItem {
private _subtotalPrice!: CustomerInvoiceItemSubtotalPrice;
private _totalPrice!: CustomerInvoiceItemTotalPrice;
public static create(props: ICustomerInvoiceItemProps, id?: UniqueID): Result<CustomerInvoiceItem, Error> {
const item = new CustomerInvoiceItem(props, id);
// Reglas de negocio / validaciones
// ...
// ...
// 🔹 Disparar evento de dominio "CustomerInvoiceItemCreatedEvent"
//const { customerCustomerInvoice } = props;
//user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerCustomerInvoice.toString()));
return Result.ok(item);
}
get description(): CustomerInvoiceItemDescription {
return this.props.description;
}
get quantity(): CustomerInvoiceItemQuantity {
return this.props.quantity;
}
get unitPrice(): CustomerInvoiceItemUnitPrice {
return this.props.unitPrice;
}
get subtotalPrice(): CustomerInvoiceItemSubtotalPrice {
if (!this._subtotalPrice) {
this._subtotalPrice = this.calculateSubtotal();
}
return this._subtotalPrice;
}
get discount(): CustomerInvoiceItemDiscount {
return this.props.discount;
}
get totalPrice(): CustomerInvoiceItemTotalPrice {
if (!this._totalPrice) {
this._totalPrice = this.calculateTotal();
}
return this._totalPrice;
}
getValue() {
return this.props;
}
toPrimitive() {
return {
description: this.description.toPrimitive(),
quantity: this.quantity.toPrimitive(),
unit_price: this.unitPrice.toPrimitive(),
subtotal_price: this.subtotalPrice.toPrimitive(),
discount: this.discount.toPrimitive(),
total_price: this.totalPrice.toPrimitive(),
};
}
calculateSubtotal(): CustomerInvoiceItemSubtotalPrice {
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad
}
calculateTotal(): CustomerInvoiceItemTotalPrice {
return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
}
}

View File

@ -0,0 +1,8 @@
import { Collection } from "@repo/rdx-utils";
import { CustomerInvoiceItem } from "./customerCustomerInvoice-item";
export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
public static create(items?: CustomerInvoiceItem[]): CustomerInvoiceItems {
return new CustomerInvoiceItems(items);
}
}

View File

@ -0,0 +1,13 @@
import { Criteria } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils";
import { CustomerInvoice } from "../aggregates";
export interface ICustomerInvoiceRepository {
findAll(criteria: Criteria, transaction?: any): Promise<Result<Collection<CustomerInvoice>, Error>>;
getById(id: UniqueID, transaction?: any): Promise<Result<CustomerInvoice, Error>>;
deleteById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
create(customerCustomerInvoice: CustomerInvoice, transaction?: any): Promise<void>;
update(customerCustomerInvoice: CustomerInvoice, transaction?: any): Promise<void>;
}

View File

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

View File

@ -0,0 +1,23 @@
import { Criteria } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils";
import { ICustomerInvoiceProps, CustomerInvoice } from "../aggregates";
export interface ICustomerInvoiceService {
findCustomerInvoices(criteria: Criteria, transaction?: any): Promise<Result<Collection<CustomerInvoice>, Error>>;
findCustomerInvoiceById(customerCustomerInvoiceId: UniqueID, transaction?: any): Promise<Result<CustomerInvoice>>;
updateCustomerInvoiceById(
customerCustomerInvoiceId: UniqueID,
data: Partial<ICustomerInvoiceProps>,
transaction?: any
): Promise<Result<CustomerInvoice, Error>>;
createCustomerInvoice(
customerCustomerInvoiceId: UniqueID,
data: ICustomerInvoiceProps,
transaction?: any
): Promise<Result<CustomerInvoice, Error>>;
deleteCustomerInvoiceById(customerCustomerInvoiceId: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
}

View File

@ -0,0 +1,86 @@
import { Criteria } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize";
import { ICustomerInvoiceProps, CustomerInvoice } from "../aggregates";
import { ICustomerInvoiceRepository } from "../repositories";
import { ICustomerInvoiceService } from "./customerCustomerInvoice-service.interface";
export class CustomerInvoiceService implements ICustomerInvoiceService {
constructor(private readonly repo: ICustomerInvoiceRepository) {}
async findCustomerInvoices(
criteria: Criteria,
transaction?: Transaction
): Promise<Result<Collection<CustomerInvoice>, Error>> {
const customerCustomerInvoicesOrError = await this.repo.findAll(criteria, transaction);
if (customerCustomerInvoicesOrError.isFailure) {
return Result.fail(customerCustomerInvoicesOrError.error);
}
// Solo devolver usuarios activos
//const allCustomerInvoices = customerCustomerInvoicesOrError.data.filter((customerCustomerInvoice) => customerCustomerInvoice.isActive);
//return Result.ok(new Collection(allCustomerInvoices));
return customerCustomerInvoicesOrError;
}
async findCustomerInvoiceById(customerCustomerInvoiceId: UniqueID, transaction?: Transaction): Promise<Result<CustomerInvoice>> {
return await this.repo.getById(customerCustomerInvoiceId, transaction);
}
async updateCustomerInvoiceById(
customerCustomerInvoiceId: UniqueID,
data: Partial<ICustomerInvoiceProps>,
transaction?: Transaction
): Promise<Result<CustomerInvoice, Error>> {
// Verificar si la factura existe
const customerCustomerInvoiceOrError = await this.repo.getById(customerCustomerInvoiceId, transaction);
if (customerCustomerInvoiceOrError.isFailure) {
return Result.fail(new Error("CustomerInvoice not found"));
}
return Result.fail(new Error("No implementado"));
/*const updatedCustomerInvoiceOrError = CustomerInvoice.update(customerCustomerInvoiceOrError.data, data);
if (updatedCustomerInvoiceOrError.isFailure) {
return Result.fail(
new Error(`Error updating customerCustomerInvoice: ${updatedCustomerInvoiceOrError.error.message}`)
);
}
const updateCustomerInvoice = updatedCustomerInvoiceOrError.data;
await this.repo.update(updateCustomerInvoice, transaction);
return Result.ok(updateCustomerInvoice);*/
}
async createCustomerInvoice(
customerCustomerInvoiceId: UniqueID,
data: ICustomerInvoiceProps,
transaction?: Transaction
): Promise<Result<CustomerInvoice, Error>> {
// Verificar si la factura existe
const customerCustomerInvoiceOrError = await this.repo.getById(customerCustomerInvoiceId, transaction);
if (customerCustomerInvoiceOrError.isSuccess) {
return Result.fail(new Error("CustomerInvoice exists"));
}
const newCustomerInvoiceOrError = CustomerInvoice.create(data, customerCustomerInvoiceId);
if (newCustomerInvoiceOrError.isFailure) {
return Result.fail(new Error(`Error creating customerCustomerInvoice: ${newCustomerInvoiceOrError.error.message}`));
}
const newCustomerInvoice = newCustomerInvoiceOrError.data;
await this.repo.create(newCustomerInvoice, transaction);
return Result.ok(newCustomerInvoice);
}
async deleteCustomerInvoiceById(
customerCustomerInvoiceId: UniqueID,
transaction?: Transaction
): Promise<Result<boolean, Error>> {
return this.repo.deleteById(customerCustomerInvoiceId, transaction);
}
}

View File

@ -0,0 +1,2 @@
export * from "./customerCustomerInvoice-service.interface";
export * from "./customerCustomerInvoice.service";

View File

@ -1,7 +1,7 @@
import { ValueObject } from "@repo/rdx-ddd"; import { ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
interface IInvoiceAddressTypeProps { interface ICustomerInvoiceAddressTypeProps {
value: string; value: string;
} }
@ -10,10 +10,10 @@ export enum INVOICE_ADDRESS_TYPE {
BILLING = "billing", BILLING = "billing",
} }
export class InvoiceAddressType extends ValueObject<IInvoiceAddressTypeProps> { export class CustomerInvoiceAddressType extends ValueObject<ICustomerInvoiceAddressTypeProps> {
private static readonly ALLOWED_TYPES = ["shipping", "billing"]; private static readonly ALLOWED_TYPES = ["shipping", "billing"];
static create(value: string): Result<InvoiceAddressType, Error> { static create(value: string): Result<CustomerInvoiceAddressType, Error> {
if (!this.ALLOWED_TYPES.includes(value)) { if (!this.ALLOWED_TYPES.includes(value)) {
return Result.fail( return Result.fail(
new Error( new Error(
@ -21,7 +21,7 @@ export class InvoiceAddressType extends ValueObject<IInvoiceAddressTypeProps> {
) )
); );
} }
return Result.ok(new InvoiceAddressType({ value })); return Result.ok(new CustomerInvoiceAddressType({ value }));
} }
getValue(): string { getValue(): string {

View File

@ -0,0 +1,50 @@
import { ValueObject } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import { z } from "zod";
interface ICustomerInvoiceItemDescriptionProps {
value: string;
}
export class CustomerInvoiceItemDescription extends ValueObject<ICustomerInvoiceItemDescriptionProps> {
private static readonly MAX_LENGTH = 255;
protected static validate(value: string) {
const schema = z
.string()
.trim()
.max(CustomerInvoiceItemDescription.MAX_LENGTH, {
message: `Description must be at most ${CustomerInvoiceItemDescription.MAX_LENGTH} characters long`,
});
return schema.safeParse(value);
}
static create(value: string) {
const valueIsValid = CustomerInvoiceItemDescription.validate(value);
if (!valueIsValid.success) {
return Result.fail(new Error(valueIsValid.error.errors[0].message));
}
return Result.ok(new CustomerInvoiceItemDescription({ value }));
}
static createNullable(value?: string): Result<Maybe<CustomerInvoiceItemDescription>, Error> {
if (!value || value.trim() === "") {
return Result.ok(Maybe.none<CustomerInvoiceItemDescription>());
}
return CustomerInvoiceItemDescription.create(value).map((value) => Maybe.some(value));
}
getValue(): string {
return this.props.value;
}
toString(): string {
return this.getValue();
}
toPrimitive() {
return this.getValue();
}
}

View File

@ -0,0 +1,3 @@
import { Percentage } from "@repo/rdx-ddd";
export class CustomerInvoiceItemDiscount extends Percentage {}

View File

@ -0,0 +1,3 @@
import { Quantity } from "@repo/rdx-ddd";
export class CustomerInvoiceItemQuantity extends Quantity {}

View File

@ -1,6 +1,6 @@
import { IMoneyValueProps, MoneyValue } from "@repo/rdx-ddd"; import { IMoneyValueProps, MoneyValue } from "@repo/rdx-ddd";
export class InvoiceItemSubtotalPrice extends MoneyValue { export class CustomerInvoiceItemSubtotalPrice extends MoneyValue {
public static DEFAULT_SCALE = 4; public static DEFAULT_SCALE = 4;
static create({ amount, currency_code, scale }: IMoneyValueProps) { static create({ amount, currency_code, scale }: IMoneyValueProps) {

View File

@ -1,6 +1,6 @@
import { IMoneyValueProps, MoneyValue } from "@repo/rdx-ddd"; import { IMoneyValueProps, MoneyValue } from "@repo/rdx-ddd";
export class InvoiceItemTotalPrice extends MoneyValue { export class CustomerInvoiceItemTotalPrice extends MoneyValue {
public static DEFAULT_SCALE = 4; public static DEFAULT_SCALE = 4;
static create({ amount, currency_code, scale }: IMoneyValueProps) { static create({ amount, currency_code, scale }: IMoneyValueProps) {

View File

@ -1,6 +1,6 @@
import { IMoneyValueProps, MoneyValue } from "@repo/rdx-ddd"; import { IMoneyValueProps, MoneyValue } from "@repo/rdx-ddd";
export class InvoiceItemUnitPrice extends MoneyValue { export class CustomerInvoiceItemUnitPrice extends MoneyValue {
public static DEFAULT_SCALE = 4; public static DEFAULT_SCALE = 4;
static create({ amount, currency_code, scale }: IMoneyValueProps) { static create({ amount, currency_code, scale }: IMoneyValueProps) {

View File

@ -2,30 +2,30 @@ import { ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { z } from "zod"; import { z } from "zod";
interface IInvoiceNumberProps { interface ICustomerInvoiceNumberProps {
value: string; value: string;
} }
export class InvoiceNumber extends ValueObject<IInvoiceNumberProps> { export class CustomerInvoiceNumber extends ValueObject<ICustomerInvoiceNumberProps> {
private static readonly MAX_LENGTH = 255; private static readonly MAX_LENGTH = 255;
protected static validate(value: string) { protected static validate(value: string) {
const schema = z const schema = z
.string() .string()
.trim() .trim()
.max(InvoiceNumber.MAX_LENGTH, { .max(CustomerInvoiceNumber.MAX_LENGTH, {
message: `Name must be at most ${InvoiceNumber.MAX_LENGTH} characters long`, message: `Name must be at most ${CustomerInvoiceNumber.MAX_LENGTH} characters long`,
}); });
return schema.safeParse(value); return schema.safeParse(value);
} }
static create(value: string) { static create(value: string) {
const valueIsValid = InvoiceNumber.validate(value); const valueIsValid = CustomerInvoiceNumber.validate(value);
if (!valueIsValid.success) { if (!valueIsValid.success) {
return Result.fail(new Error(valueIsValid.error.errors[0].message)); return Result.fail(new Error(valueIsValid.error.errors[0].message));
} }
return Result.ok(new InvoiceNumber({ value })); return Result.ok(new CustomerInvoiceNumber({ value }));
} }
getValue(): string { getValue(): string {

View File

@ -2,38 +2,38 @@ import { ValueObject } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils"; import { Maybe, Result } from "@repo/rdx-utils";
import { z } from "zod"; import { z } from "zod";
interface IInvoiceSerieProps { interface ICustomerInvoiceSerieProps {
value: string; value: string;
} }
export class InvoiceSerie extends ValueObject<IInvoiceSerieProps> { export class CustomerInvoiceSerie extends ValueObject<ICustomerInvoiceSerieProps> {
private static readonly MAX_LENGTH = 255; private static readonly MAX_LENGTH = 255;
protected static validate(value: string) { protected static validate(value: string) {
const schema = z const schema = z
.string() .string()
.trim() .trim()
.max(InvoiceSerie.MAX_LENGTH, { .max(CustomerInvoiceSerie.MAX_LENGTH, {
message: `Name must be at most ${InvoiceSerie.MAX_LENGTH} characters long`, message: `Name must be at most ${CustomerInvoiceSerie.MAX_LENGTH} characters long`,
}); });
return schema.safeParse(value); return schema.safeParse(value);
} }
static create(value: string) { static create(value: string) {
const valueIsValid = InvoiceSerie.validate(value); const valueIsValid = CustomerInvoiceSerie.validate(value);
if (!valueIsValid.success) { if (!valueIsValid.success) {
return Result.fail(new Error(valueIsValid.error.errors[0].message)); return Result.fail(new Error(valueIsValid.error.errors[0].message));
} }
return Result.ok(new InvoiceSerie({ value })); return Result.ok(new CustomerInvoiceSerie({ value }));
} }
static createNullable(value?: string): Result<Maybe<InvoiceSerie>, Error> { static createNullable(value?: string): Result<Maybe<CustomerInvoiceSerie>, Error> {
if (!value || value.trim() === "") { if (!value || value.trim() === "") {
return Result.ok(Maybe.none<InvoiceSerie>()); return Result.ok(Maybe.none<CustomerInvoiceSerie>());
} }
return InvoiceSerie.create(value).map((value) => Maybe.some(value)); return CustomerInvoiceSerie.create(value).map((value) => Maybe.some(value));
} }
getValue(): string { getValue(): string {

View File

@ -1,7 +1,7 @@
import { ValueObject } from "@repo/rdx-ddd"; import { ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
interface IInvoiceStatusProps { interface ICustomerInvoiceStatusProps {
value: string; value: string;
} }
@ -11,7 +11,7 @@ export enum INVOICE_STATUS {
SENT = "sent", SENT = "sent",
REJECTED = "rejected", REJECTED = "rejected",
} }
export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> { export class CustomerInvoiceStatus extends ValueObject<ICustomerInvoiceStatusProps> {
private static readonly ALLOWED_STATUSES = ["draft", "emitted", "sent", "rejected"]; private static readonly ALLOWED_STATUSES = ["draft", "emitted", "sent", "rejected"];
private static readonly TRANSITIONS: Record<string, string[]> = { private static readonly TRANSITIONS: Record<string, string[]> = {
@ -21,36 +21,36 @@ export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
rejected: [], rejected: [],
}; };
static create(value: string): Result<InvoiceStatus, Error> { static create(value: string): Result<CustomerInvoiceStatus, Error> {
if (!InvoiceStatus.ALLOWED_STATUSES.includes(value)) { if (!CustomerInvoiceStatus.ALLOWED_STATUSES.includes(value)) {
return Result.fail(new Error(`Estado de la factura no válido: ${value}`)); return Result.fail(new Error(`Estado de la factura no válido: ${value}`));
} }
return Result.ok( return Result.ok(
value === "rejected" value === "rejected"
? InvoiceStatus.createRejected() ? CustomerInvoiceStatus.createRejected()
: value === "sent" : value === "sent"
? InvoiceStatus.createSent() ? CustomerInvoiceStatus.createSent()
: value === "emitted" : value === "emitted"
? InvoiceStatus.createSent() ? CustomerInvoiceStatus.createSent()
: InvoiceStatus.createDraft() : CustomerInvoiceStatus.createDraft()
); );
} }
public static createDraft(): InvoiceStatus { public static createDraft(): CustomerInvoiceStatus {
return new InvoiceStatus({ value: INVOICE_STATUS.DRAFT }); return new CustomerInvoiceStatus({ value: INVOICE_STATUS.DRAFT });
} }
public static createEmitted(): InvoiceStatus { public static createEmitted(): CustomerInvoiceStatus {
return new InvoiceStatus({ value: INVOICE_STATUS.EMITTED }); return new CustomerInvoiceStatus({ value: INVOICE_STATUS.EMITTED });
} }
public static createSent(): InvoiceStatus { public static createSent(): CustomerInvoiceStatus {
return new InvoiceStatus({ value: INVOICE_STATUS.SENT }); return new CustomerInvoiceStatus({ value: INVOICE_STATUS.SENT });
} }
public static createRejected(): InvoiceStatus { public static createRejected(): CustomerInvoiceStatus {
return new InvoiceStatus({ value: INVOICE_STATUS.REJECTED }); return new CustomerInvoiceStatus({ value: INVOICE_STATUS.REJECTED });
} }
getValue(): string { getValue(): string {
@ -62,16 +62,16 @@ export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
} }
canTransitionTo(nextStatus: string): boolean { canTransitionTo(nextStatus: string): boolean {
return InvoiceStatus.TRANSITIONS[this.props.value].includes(nextStatus); return CustomerInvoiceStatus.TRANSITIONS[this.props.value].includes(nextStatus);
} }
transitionTo(nextStatus: string): Result<InvoiceStatus, Error> { transitionTo(nextStatus: string): Result<CustomerInvoiceStatus, Error> {
if (!this.canTransitionTo(nextStatus)) { if (!this.canTransitionTo(nextStatus)) {
return Result.fail( return Result.fail(
new Error(`Transición no permitida de ${this.props.value} a ${nextStatus}`) new Error(`Transición no permitida de ${this.props.value} a ${nextStatus}`)
); );
} }
return InvoiceStatus.create(nextStatus); return CustomerInvoiceStatus.create(nextStatus);
} }
toString(): string { toString(): string {

View File

@ -0,0 +1,10 @@
export * from "./customerCustomerInvoice-address-type";
export * from "./customerCustomerInvoice-item-description";
export * from "./customerCustomerInvoice-item-discount";
export * from "./customerCustomerInvoice-item-quantity";
export * from "./customerCustomerInvoice-item-subtotal-price";
export * from "./customerCustomerInvoice-item-total-price";
export * from "./customerCustomerInvoice-item-unit-price";
export * from "./customerCustomerInvoice-number";
export * from "./customerCustomerInvoice-serie";
export * from "./customerCustomerInvoice-status";

View File

@ -0,0 +1,26 @@
import { IModuleServer, ModuleParams } from "@erp/core/api";
import { customerCustomerInvoicesRouter, models } from "./infrastructure";
export const customerCustomerInvoicesAPIModule: IModuleServer = {
name: "customerCustomerInvoices",
version: "1.0.0",
dependencies: [],
init(params: ModuleParams) {
// const contacts = getService<ContactsService>("contacts");
const { logger } = params;
customerCustomerInvoicesRouter(params);
logger.info("🚀 CustomerInvoices module initialized", { label: "customerCustomerInvoices" });
},
registerDependencies(params) {
const { database, logger } = params;
logger.info("🚀 CustomerInvoices module dependencies registered", { label: "customerCustomerInvoices" });
return {
models,
services: {
getCustomerInvoice: () => {},
/*...*/
},
};
},
};

View File

@ -1,9 +1,9 @@
import { ModuleParams } from "@erp/core/api"; import { ModuleParams } from "@erp/core/api";
import { Application, NextFunction, Request, Response, Router } from "express"; import { Application, NextFunction, Request, Response, Router } from "express";
import { Sequelize } from "sequelize"; import { Sequelize } from "sequelize";
import { buildListInvoicesController } from "../../presentation"; import { buildListCustomerInvoicesController } from "../../presentation";
export const invoicesRouter = (params: ModuleParams) => { export const customerCustomerInvoicesRouter = (params: ModuleParams) => {
const { app, database, baseRoutePath } = params as { const { app, database, baseRoutePath } = params as {
app: Application; app: Application;
database: Sequelize; database: Sequelize;
@ -17,48 +17,48 @@ export const invoicesRouter = (params: ModuleParams) => {
//checkTabContext, //checkTabContext,
//checkUser, //checkUser,
(req: Request, res: Response, next: NextFunction) => { (req: Request, res: Response, next: NextFunction) => {
buildListInvoicesController(database).execute(req, res, next); buildListCustomerInvoicesController(database).execute(req, res, next);
} }
); );
app.use(`${baseRoutePath}/invoices`, routes); app.use(`${baseRoutePath}/customerCustomerInvoices`, routes);
/*routes.get( /*routes.get(
"/:invoiceId", "/:customerCustomerInvoiceId",
//checkTabContext, //checkTabContext,
//checkUser, //checkUser,
(req: Request, res: Response, next: NextFunction) => { (req: Request, res: Response, next: NextFunction) => {
buildGetInvoiceController(database).execute(req, res, next); buildGetCustomerInvoiceController(database).execute(req, res, next);
} }
);*/ );*/
/*routes.post( /*routes.post(
"/", "/",
validateAndParseBody(ICreateInvoiceRequestSchema, { sanitize: false }), validateAndParseBody(ICreateCustomerInvoiceRequestSchema, { sanitize: false }),
//checkTabContext, //checkTabContext,
//checkUser, //checkUser,
(req: Request, res: Response, next: NextFunction) => { (req: Request, res: Response, next: NextFunction) => {
buildCreateInvoiceController(database).execute(req, res, next); buildCreateCustomerInvoiceController(database).execute(req, res, next);
} }
); );
routes.put( routes.put(
"/:invoiceId", "/:customerCustomerInvoiceId",
validateAndParseBody(IUpdateInvoiceRequestSchema), validateAndParseBody(IUpdateCustomerInvoiceRequestSchema),
checkTabContext, checkTabContext,
//checkUser, //checkUser,
(req: Request, res: Response, next: NextFunction) => { (req: Request, res: Response, next: NextFunction) => {
buildUpdateInvoiceController().execute(req, res, next); buildUpdateCustomerInvoiceController().execute(req, res, next);
} }
); );
routes.delete( routes.delete(
"/:invoiceId", "/:customerCustomerInvoiceId",
validateAndParseBody(IDeleteInvoiceRequestSchema), validateAndParseBody(IDeleteCustomerInvoiceRequestSchema),
checkTabContext, checkTabContext,
//checkUser, //checkUser,
(req: Request, res: Response, next: NextFunction) => { (req: Request, res: Response, next: NextFunction) => {
buildDeleteInvoiceController().execute(req, res, next); buildDeleteCustomerInvoiceController().execute(req, res, next);
} }
);*/ );*/
}; };

View File

@ -0,0 +1 @@
export * from "./customerInvoices.routes";

View File

@ -3,27 +3,27 @@ import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { InferCreationAttributes } from "sequelize"; import { InferCreationAttributes } from "sequelize";
import { import {
Invoice, CustomerInvoice,
InvoiceItem, CustomerInvoiceItem,
InvoiceItemDescription, CustomerInvoiceItemDescription,
InvoiceItemDiscount, CustomerInvoiceItemDiscount,
InvoiceItemQuantity, CustomerInvoiceItemQuantity,
InvoiceItemUnitPrice, CustomerInvoiceItemUnitPrice,
} from "../../domain"; } from "../../domain";
import { InvoiceItemCreationAttributes, InvoiceItemModel, InvoiceModel } from "../sequelize"; import { CustomerInvoiceItemCreationAttributes, CustomerInvoiceItemModel, CustomerInvoiceModel } from "../sequelize";
export interface IInvoiceItemMapper export interface ICustomerInvoiceItemMapper
extends ISequelizeMapper<InvoiceItemModel, InvoiceItemCreationAttributes, InvoiceItem> {} extends ISequelizeMapper<CustomerInvoiceItemModel, CustomerInvoiceItemCreationAttributes, CustomerInvoiceItem> {}
export class InvoiceItemMapper export class CustomerInvoiceItemMapper
extends SequelizeMapper<InvoiceItemModel, InvoiceItemCreationAttributes, InvoiceItem> extends SequelizeMapper<CustomerInvoiceItemModel, CustomerInvoiceItemCreationAttributes, CustomerInvoiceItem>
implements IInvoiceItemMapper implements ICustomerInvoiceItemMapper
{ {
public mapToDomain( public mapToDomain(
source: InvoiceItemModel, source: CustomerInvoiceItemModel,
params?: MapperParamsType params?: MapperParamsType
): Result<InvoiceItem, Error> { ): Result<CustomerInvoiceItem, Error> {
const { sourceParent } = params as { sourceParent: InvoiceModel }; const { sourceParent } = params as { sourceParent: CustomerInvoiceModel };
// Validación y creación de ID único // Validación y creación de ID único
const idOrError = UniqueID.create(source.item_id); const idOrError = UniqueID.create(source.item_id);
@ -32,13 +32,13 @@ export class InvoiceItemMapper
} }
// Validación y creación de descripción // Validación y creación de descripción
const descriptionOrError = InvoiceItemDescription.create(source.description || ""); const descriptionOrError = CustomerInvoiceItemDescription.create(source.description || "");
if (descriptionOrError.isFailure) { if (descriptionOrError.isFailure) {
return Result.fail(descriptionOrError.error); return Result.fail(descriptionOrError.error);
} }
// Validación y creación de cantidad // Validación y creación de cantidad
const quantityOrError = InvoiceItemQuantity.create({ const quantityOrError = CustomerInvoiceItemQuantity.create({
amount: source.quantity_amount, amount: source.quantity_amount,
scale: source.quantity_scale, scale: source.quantity_scale,
}); });
@ -47,17 +47,17 @@ export class InvoiceItemMapper
} }
// Validación y creación de precio unitario // Validación y creación de precio unitario
const unitPriceOrError = InvoiceItemUnitPrice.create({ const unitPriceOrError = CustomerInvoiceItemUnitPrice.create({
amount: source.unit_price_amount, amount: source.unit_price_amount,
scale: source.unit_price_scale, scale: source.unit_price_scale,
currency_code: sourceParent.invoice_currency, currency_code: sourceParent.customerCustomerInvoice_currency,
}); });
if (unitPriceOrError.isFailure) { if (unitPriceOrError.isFailure) {
return Result.fail(unitPriceOrError.error); return Result.fail(unitPriceOrError.error);
} }
// Validación y creación de descuento // Validación y creación de descuento
const discountOrError = InvoiceItemDiscount.create({ const discountOrError = CustomerInvoiceItemDiscount.create({
amount: source.discount_amount || 0, amount: source.discount_amount || 0,
scale: source.discount_scale || 0, scale: source.discount_scale || 0,
}); });
@ -79,7 +79,7 @@ export class InvoiceItemMapper
} }
// Creación del objeto de dominio // Creación del objeto de dominio
return InvoiceItem.create( return CustomerInvoiceItem.create(
{ {
description: descriptionOrError.data, description: descriptionOrError.data,
quantity: quantityOrError.data, quantity: quantityOrError.data,
@ -91,17 +91,17 @@ export class InvoiceItemMapper
} }
public mapToPersistence( public mapToPersistence(
source: InvoiceItem, source: CustomerInvoiceItem,
params?: MapperParamsType params?: MapperParamsType
): InferCreationAttributes<InvoiceItemModel, {}> { ): InferCreationAttributes<CustomerInvoiceItemModel, {}> {
const { index, sourceParent } = params as { const { index, sourceParent } = params as {
index: number; index: number;
sourceParent: Invoice; sourceParent: CustomerInvoice;
}; };
const lineData = { const lineData = {
parent_id: undefined, parent_id: undefined,
invoice_id: sourceParent.id.toPrimitive(), customerCustomerInvoice_id: sourceParent.id.toPrimitive(),
item_type: "simple", item_type: "simple",
position: index, position: index,

View File

@ -0,0 +1,97 @@
import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core/api";
import { UniqueID, UtcDate } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { CustomerInvoice, CustomerInvoiceNumber, CustomerInvoiceSerie, CustomerInvoiceStatus } from "../../domain";
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../sequelize";
import { CustomerInvoiceItemMapper } from "./customerCustomerInvoice-item.mapper";
export interface ICustomerInvoiceMapper
extends ISequelizeMapper<CustomerInvoiceModel, CustomerInvoiceCreationAttributes, CustomerInvoice> {}
export class CustomerInvoiceMapper
extends SequelizeMapper<CustomerInvoiceModel, CustomerInvoiceCreationAttributes, CustomerInvoice>
implements ICustomerInvoiceMapper
{
private customerCustomerInvoiceItemMapper: CustomerInvoiceItemMapper;
constructor() {
super();
this.customerCustomerInvoiceItemMapper = new CustomerInvoiceItemMapper(); // Instanciar el mapper de items
}
public mapToDomain(source: CustomerInvoiceModel, params?: MapperParamsType): Result<CustomerInvoice, Error> {
const idOrError = UniqueID.create(source.id);
const statusOrError = CustomerInvoiceStatus.create(source.customerCustomerInvoice_status);
const customerCustomerInvoiceSeriesOrError = CustomerInvoiceSerie.create(source.customerCustomerInvoice_series);
const customerCustomerInvoiceNumberOrError = CustomerInvoiceNumber.create(source.customerCustomerInvoice_number);
const issueDateOrError = UtcDate.create(source.issue_date);
const operationDateOrError = UtcDate.create(source.operation_date);
const result = Result.combine([
idOrError,
statusOrError,
customerCustomerInvoiceSeriesOrError,
customerCustomerInvoiceNumberOrError,
issueDateOrError,
operationDateOrError,
]);
if (result.isFailure) {
return Result.fail(result.error);
}
// Mapear los items de la factura
const itemsOrErrors = this.customerCustomerInvoiceItemMapper.mapArrayToDomain(source.items, {
sourceParent: source,
...params,
});
if (itemsOrErrors.isFailure) {
return Result.fail(itemsOrErrors.error);
}
const customerCustomerInvoiceCurrency = source.customerCustomerInvoice_currency || "EUR";
return CustomerInvoice.create(
{
status: statusOrError.data,
customerCustomerInvoiceSeries: customerCustomerInvoiceSeriesOrError.data,
customerCustomerInvoiceNumber: customerCustomerInvoiceNumberOrError.data,
issueDate: issueDateOrError.data,
operationDate: operationDateOrError.data,
customerCustomerInvoiceCurrency,
items: itemsOrErrors.data,
},
idOrError.data
);
}
public mapToPersistence(source: CustomerInvoice, params?: MapperParamsType): CustomerInvoiceCreationAttributes {
const subtotal = source.calculateSubtotal();
const total = source.calculateTotal();
const items = this.customerCustomerInvoiceItemMapper.mapCollectionToPersistence(source.items, params);
return {
id: source.id.toString(),
customerCustomerInvoice_status: source.status.toPrimitive(),
customerCustomerInvoice_series: source.customerCustomerInvoiceSeries.toPrimitive(),
customerCustomerInvoice_number: source.customerCustomerInvoiceNumber.toPrimitive(),
issue_date: source.issueDate.toPrimitive(),
operation_date: source.operationDate.toPrimitive(),
customerCustomerInvoice_language: "es",
customerCustomerInvoice_currency: source.customerCustomerInvoiceCurrency || "EUR",
subtotal_amount: subtotal.amount,
subtotal_scale: subtotal.scale,
total_amount: total.amount,
total_scale: total.scale,
items,
};
}
}
const customerCustomerInvoiceMapper: CustomerInvoiceMapper = new CustomerInvoiceMapper();
export { customerCustomerInvoiceMapper };

View File

@ -1,50 +1,50 @@
import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure"; import { ISequelizeMapper, SequelizeMapper } from "@/contexts/common/infrastructure";
import { Name, TINNumber, UniqueID } from "@shared/contexts"; import { Name, TINNumber, UniqueID } from "@shared/contexts";
import { import {
IInvoiceCustomerProps, ICustomerInvoiceCustomerProps,
Invoice, CustomerInvoice,
InvoiceCustomer, CustomerInvoiceCustomer,
InvoiceParticipantBillingAddress, CustomerInvoiceParticipantBillingAddress,
InvoiceParticipantShippingAddress, CustomerInvoiceParticipantShippingAddress,
} from "../../domain"; } from "../../domain";
import { IInvoicingContext } from "../InvoicingContext"; import { IInvoicingContext } from "../InvoicingContext";
import { InvoiceParticipant_Model, TCreationInvoiceParticipant_Model } from "../sequelize"; import { CustomerInvoiceParticipant_Model, TCreationCustomerInvoiceParticipant_Model } from "../sequelize";
import { import {
IInvoiceParticipantAddressMapper, ICustomerInvoiceParticipantAddressMapper,
createInvoiceParticipantAddressMapper, createCustomerInvoiceParticipantAddressMapper,
} from "./invoiceParticipantAddress.mapper"; } from "./customerCustomerInvoiceParticipantAddress.mapper";
export interface IInvoiceParticipantMapper export interface ICustomerInvoiceParticipantMapper
extends ISequelizeMapper< extends ISequelizeMapper<
InvoiceParticipant_Model, CustomerInvoiceParticipant_Model,
TCreationInvoiceParticipant_Model, TCreationCustomerInvoiceParticipant_Model,
InvoiceCustomer CustomerInvoiceCustomer
> {} > {}
export const createInvoiceParticipantMapper = ( export const createCustomerInvoiceParticipantMapper = (
context: IInvoicingContext context: IInvoicingContext
): IInvoiceParticipantMapper => ): ICustomerInvoiceParticipantMapper =>
new InvoiceParticipantMapper({ new CustomerInvoiceParticipantMapper({
context, context,
addressMapper: createInvoiceParticipantAddressMapper(context), addressMapper: createCustomerInvoiceParticipantAddressMapper(context),
}); });
class InvoiceParticipantMapper class CustomerInvoiceParticipantMapper
extends SequelizeMapper< extends SequelizeMapper<
InvoiceParticipant_Model, CustomerInvoiceParticipant_Model,
TCreationInvoiceParticipant_Model, TCreationCustomerInvoiceParticipant_Model,
InvoiceCustomer CustomerInvoiceCustomer
> >
implements IInvoiceParticipantMapper implements ICustomerInvoiceParticipantMapper
{ {
public constructor(props: { public constructor(props: {
addressMapper: IInvoiceParticipantAddressMapper; addressMapper: ICustomerInvoiceParticipantAddressMapper;
context: IInvoicingContext; context: IInvoicingContext;
}) { }) {
super(props); super(props);
} }
protected toDomainMappingImpl(source: InvoiceParticipant_Model, params: any) { protected toDomainMappingImpl(source: CustomerInvoiceParticipant_Model, params: any) {
/*if (!source.billingAddress) { /*if (!source.billingAddress) {
this.handleRequiredFieldError( this.handleRequiredFieldError(
"billingAddress", "billingAddress",
@ -60,20 +60,20 @@ class InvoiceParticipantMapper
} }
*/ */
const billingAddress = source.billingAddress const billingAddress = source.billingAddress
? ((this.props.addressMapper as IInvoiceParticipantAddressMapper).mapToDomain( ? ((this.props.addressMapper as ICustomerInvoiceParticipantAddressMapper).mapToDomain(
source.billingAddress, source.billingAddress,
params params
) as InvoiceParticipantBillingAddress) ) as CustomerInvoiceParticipantBillingAddress)
: undefined; : undefined;
const shippingAddress = source.shippingAddress const shippingAddress = source.shippingAddress
? ((this.props.addressMapper as IInvoiceParticipantAddressMapper).mapToDomain( ? ((this.props.addressMapper as ICustomerInvoiceParticipantAddressMapper).mapToDomain(
source.shippingAddress, source.shippingAddress,
params params
) as InvoiceParticipantShippingAddress) ) as CustomerInvoiceParticipantShippingAddress)
: undefined; : undefined;
const props: IInvoiceCustomerProps = { const props: ICustomerInvoiceCustomerProps = {
tin: this.mapsValue(source, "tin", TINNumber.create), tin: this.mapsValue(source, "tin", TINNumber.create),
firstName: this.mapsValue(source, "first_name", Name.create), firstName: this.mapsValue(source, "first_name", Name.create),
lastName: this.mapsValue(source, "last_name", Name.create), lastName: this.mapsValue(source, "last_name", Name.create),
@ -83,7 +83,7 @@ class InvoiceParticipantMapper
}; };
const id = this.mapsValue(source, "participant_id", UniqueID.create); const id = this.mapsValue(source, "participant_id", UniqueID.create);
const participantOrError = InvoiceCustomer.create(props, id); const participantOrError = CustomerInvoiceCustomer.create(props, id);
if (participantOrError.isFailure) { if (participantOrError.isFailure) {
throw participantOrError.error; throw participantOrError.error;
@ -93,13 +93,13 @@ class InvoiceParticipantMapper
} }
protected toPersistenceMappingImpl( protected toPersistenceMappingImpl(
source: InvoiceCustomer, source: CustomerInvoiceCustomer,
params: { sourceParent: Invoice } params: { sourceParent: CustomerInvoice }
): TCreationInvoiceParticipant_Model { ): TCreationCustomerInvoiceParticipant_Model {
const { sourceParent } = params; const { sourceParent } = params;
return { return {
invoice_id: sourceParent.id.toPrimitive(), customerCustomerInvoice_id: sourceParent.id.toPrimitive(),
participant_id: source.id.toPrimitive(), participant_id: source.id.toPrimitive(),
tin: source.tin.toPrimitive(), tin: source.tin.toPrimitive(),
@ -108,11 +108,11 @@ class InvoiceParticipantMapper
company_name: source.companyName.toPrimitive(), company_name: source.companyName.toPrimitive(),
billingAddress: ( billingAddress: (
this.props.addressMapper as IInvoiceParticipantAddressMapper this.props.addressMapper as ICustomerInvoiceParticipantAddressMapper
).mapToPersistence(source.billingAddress!, { sourceParent: source }), ).mapToPersistence(source.billingAddress!, { sourceParent: source }),
shippingAddress: ( shippingAddress: (
this.props.addressMapper as IInvoiceParticipantAddressMapper this.props.addressMapper as ICustomerInvoiceParticipantAddressMapper
).mapToPersistence(source.shippingAddress!, { sourceParent: source }), ).mapToPersistence(source.shippingAddress!, { sourceParent: source }),
}; };
} }

View File

@ -11,39 +11,39 @@ import {
UniqueID, UniqueID,
} from "@shared/contexts"; } from "@shared/contexts";
import { import {
IInvoiceParticipantAddressProps, ICustomerInvoiceParticipantAddressProps,
InvoiceCustomer, CustomerInvoiceCustomer,
InvoiceParticipantAddress, CustomerInvoiceParticipantAddress,
} from "../../domain"; } from "../../domain";
import { IInvoicingContext } from "../InvoicingContext"; import { IInvoicingContext } from "../InvoicingContext";
import { import {
InvoiceParticipantAddress_Model, CustomerInvoiceParticipantAddress_Model,
TCreationInvoiceParticipantAddress_Model, TCreationCustomerInvoiceParticipantAddress_Model,
} from "../sequelize"; } from "../sequelize";
export interface IInvoiceParticipantAddressMapper export interface ICustomerInvoiceParticipantAddressMapper
extends ISequelizeMapper< extends ISequelizeMapper<
InvoiceParticipantAddress_Model, CustomerInvoiceParticipantAddress_Model,
TCreationInvoiceParticipantAddress_Model, TCreationCustomerInvoiceParticipantAddress_Model,
InvoiceParticipantAddress CustomerInvoiceParticipantAddress
> {} > {}
export const createInvoiceParticipantAddressMapper = ( export const createCustomerInvoiceParticipantAddressMapper = (
context: IInvoicingContext context: IInvoicingContext
): IInvoiceParticipantAddressMapper => new InvoiceParticipantAddressMapper({ context }); ): ICustomerInvoiceParticipantAddressMapper => new CustomerInvoiceParticipantAddressMapper({ context });
class InvoiceParticipantAddressMapper class CustomerInvoiceParticipantAddressMapper
extends SequelizeMapper< extends SequelizeMapper<
InvoiceParticipantAddress_Model, CustomerInvoiceParticipantAddress_Model,
TCreationInvoiceParticipantAddress_Model, TCreationCustomerInvoiceParticipantAddress_Model,
InvoiceParticipantAddress CustomerInvoiceParticipantAddress
> >
implements IInvoiceParticipantAddressMapper implements ICustomerInvoiceParticipantAddressMapper
{ {
protected toDomainMappingImpl(source: InvoiceParticipantAddress_Model, params: any) { protected toDomainMappingImpl(source: CustomerInvoiceParticipantAddress_Model, params: any) {
const id = this.mapsValue(source, "address_id", UniqueID.create); const id = this.mapsValue(source, "address_id", UniqueID.create);
const props: IInvoiceParticipantAddressProps = { const props: ICustomerInvoiceParticipantAddressProps = {
type: source.type, type: source.type,
street: this.mapsValue(source, "street", Street.create), street: this.mapsValue(source, "street", Street.create),
city: this.mapsValue(source, "city", City.create), city: this.mapsValue(source, "city", City.create),
@ -55,7 +55,7 @@ class InvoiceParticipantAddressMapper
notes: this.mapsValue(source, "notes", Note.create), notes: this.mapsValue(source, "notes", Note.create),
}; };
const addressOrError = InvoiceParticipantAddress.create(props, id); const addressOrError = CustomerInvoiceParticipantAddress.create(props, id);
if (addressOrError.isFailure) { if (addressOrError.isFailure) {
throw addressOrError.error; throw addressOrError.error;
@ -65,8 +65,8 @@ class InvoiceParticipantAddressMapper
} }
protected toPersistenceMappingImpl( protected toPersistenceMappingImpl(
source: InvoiceParticipantAddress, source: CustomerInvoiceParticipantAddress,
params: { sourceParent: InvoiceCustomer } params: { sourceParent: CustomerInvoiceCustomer }
) { ) {
const { sourceParent } = params; const { sourceParent } = params;

View File

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

View File

@ -7,19 +7,19 @@ import {
NonAttribute, NonAttribute,
Sequelize, Sequelize,
} from "sequelize"; } from "sequelize";
import { InvoiceModel } from "./invoice.model"; import { CustomerInvoiceModel } from "./customerCustomerInvoice.model";
export type InvoiceItemCreationAttributes = InferCreationAttributes< export type CustomerInvoiceItemCreationAttributes = InferCreationAttributes<
InvoiceItemModel, CustomerInvoiceItemModel,
{ omit: "invoice" } { omit: "customerCustomerInvoice" }
>; >;
export class InvoiceItemModel extends Model< export class CustomerInvoiceItemModel extends Model<
InferAttributes<InvoiceItemModel>, InferAttributes<CustomerInvoiceItemModel>,
InferCreationAttributes<InvoiceItemModel, { omit: "invoice" }> InferCreationAttributes<CustomerInvoiceItemModel, { omit: "customerCustomerInvoice" }>
> { > {
declare item_id: string; declare item_id: string;
declare invoice_id: string; declare customerCustomerInvoice_id: string;
declare parent_id: CreationOptional<string>; declare parent_id: CreationOptional<string>;
declare position: number; declare position: number;
@ -42,27 +42,27 @@ export class InvoiceItemModel extends Model<
declare total_amount: CreationOptional<number>; declare total_amount: CreationOptional<number>;
declare total_scale: CreationOptional<number>; declare total_scale: CreationOptional<number>;
declare invoice: NonAttribute<InvoiceModel>; declare customerCustomerInvoice: NonAttribute<CustomerInvoiceModel>;
static associate(database: Sequelize) { static associate(database: Sequelize) {
/*const { Invoice_Model, InvoiceItem_Model } = connection.models; /*const { CustomerInvoice_Model, CustomerInvoiceItem_Model } = connection.models;
InvoiceItem_Model.belongsTo(Invoice_Model, { CustomerInvoiceItem_Model.belongsTo(CustomerInvoice_Model, {
as: "invoice", as: "customerCustomerInvoice",
foreignKey: "invoice_id", foreignKey: "customerCustomerInvoice_id",
onDelete: "CASCADE", onDelete: "CASCADE",
});*/ });*/
} }
} }
export default (database: Sequelize) => { export default (database: Sequelize) => {
InvoiceItemModel.init( CustomerInvoiceItemModel.init(
{ {
item_id: { item_id: {
type: new DataTypes.UUID(), type: new DataTypes.UUID(),
primaryKey: true, primaryKey: true,
}, },
invoice_id: { customerCustomerInvoice_id: {
type: new DataTypes.UUID(), type: new DataTypes.UUID(),
primaryKey: true, primaryKey: true,
}, },
@ -159,7 +159,7 @@ export default (database: Sequelize) => {
}, },
{ {
sequelize: database, sequelize: database,
tableName: "invoice_items", tableName: "customerCustomerInvoice_items",
defaultScope: {}, defaultScope: {},
@ -167,5 +167,5 @@ export default (database: Sequelize) => {
} }
); );
return InvoiceItemModel; return CustomerInvoiceItemModel;
}; };

View File

@ -7,25 +7,25 @@ import {
NonAttribute, NonAttribute,
Sequelize, Sequelize,
} from "sequelize"; } from "sequelize";
import { InvoiceItemCreationAttributes, InvoiceItemModel } from "./invoice-item.model"; import { CustomerInvoiceItemCreationAttributes, CustomerInvoiceItemModel } from "./customerCustomerInvoice-item.model";
export type InvoiceCreationAttributes = InferCreationAttributes<InvoiceModel, { omit: "items" }> & { export type CustomerInvoiceCreationAttributes = InferCreationAttributes<CustomerInvoiceModel, { omit: "items" }> & {
items?: InvoiceItemCreationAttributes[]; items?: CustomerInvoiceItemCreationAttributes[];
}; };
export class InvoiceModel extends Model< export class CustomerInvoiceModel extends Model<
InferAttributes<InvoiceModel>, InferAttributes<CustomerInvoiceModel>,
InferCreationAttributes<InvoiceModel, { omit: "items" }> InferCreationAttributes<CustomerInvoiceModel, { omit: "items" }>
> { > {
declare id: string; declare id: string;
declare invoice_status: string; declare customerCustomerInvoice_status: string;
declare invoice_series: CreationOptional<string>; declare customerCustomerInvoice_series: CreationOptional<string>;
declare invoice_number: CreationOptional<string>; declare customerCustomerInvoice_number: CreationOptional<string>;
declare issue_date: CreationOptional<string>; declare issue_date: CreationOptional<string>;
declare operation_date: CreationOptional<string>; declare operation_date: CreationOptional<string>;
declare invoice_language: string; declare customerCustomerInvoice_language: string;
declare invoice_currency: string; declare customerCustomerInvoice_currency: string;
// Subtotal // Subtotal
declare subtotal_amount: CreationOptional<number>; declare subtotal_amount: CreationOptional<number>;
@ -36,40 +36,40 @@ export class InvoiceModel extends Model<
declare total_scale: CreationOptional<number>; declare total_scale: CreationOptional<number>;
// Relaciones // Relaciones
declare items: NonAttribute<InvoiceItemModel[]>; declare items: NonAttribute<CustomerInvoiceItemModel[]>;
//declare customer: NonAttribute<InvoiceParticipant_Model[]>; //declare customer: NonAttribute<CustomerInvoiceParticipant_Model[]>;
static associate(database: Sequelize) { static associate(database: Sequelize) {
const { InvoiceModel, InvoiceItemModel } = database.models; const { CustomerInvoiceModel, CustomerInvoiceItemModel } = database.models;
InvoiceModel.hasMany(InvoiceItemModel, { CustomerInvoiceModel.hasMany(CustomerInvoiceItemModel, {
as: "items", as: "items",
foreignKey: "invoice_id", foreignKey: "customerCustomerInvoice_id",
onDelete: "CASCADE", onDelete: "CASCADE",
}); });
} }
} }
export default (database: Sequelize) => { export default (database: Sequelize) => {
InvoiceModel.init( CustomerInvoiceModel.init(
{ {
id: { id: {
type: new DataTypes.UUID(), type: new DataTypes.UUID(),
primaryKey: true, primaryKey: true,
}, },
invoice_status: { customerCustomerInvoice_status: {
type: new DataTypes.STRING(), type: new DataTypes.STRING(),
allowNull: false, allowNull: false,
}, },
invoice_series: { customerCustomerInvoice_series: {
type: new DataTypes.STRING(), type: new DataTypes.STRING(),
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
}, },
invoice_number: { customerCustomerInvoice_number: {
type: new DataTypes.STRING(), type: new DataTypes.STRING(),
allowNull: true, allowNull: true,
defaultValue: null, defaultValue: null,
@ -87,12 +87,12 @@ export default (database: Sequelize) => {
defaultValue: null, defaultValue: null,
}, },
invoice_language: { customerCustomerInvoice_language: {
type: new DataTypes.STRING(), type: new DataTypes.STRING(),
allowNull: false, allowNull: false,
}, },
invoice_currency: { customerCustomerInvoice_currency: {
type: new DataTypes.STRING(3), // ISO 4217 type: new DataTypes.STRING(3), // ISO 4217
allowNull: false, allowNull: false,
}, },
@ -121,7 +121,7 @@ export default (database: Sequelize) => {
}, },
{ {
sequelize: database, sequelize: database,
tableName: "invoices", tableName: "customerCustomerInvoices",
paranoid: true, // softs deletes paranoid: true, // softs deletes
timestamps: true, timestamps: true,
@ -130,7 +130,7 @@ export default (database: Sequelize) => {
updatedAt: "updated_at", updatedAt: "updated_at",
deletedAt: "deleted_at", deletedAt: "deleted_at",
indexes: [{ unique: true, fields: ["invoice_number"] }], indexes: [{ unique: true, fields: ["customerCustomerInvoice_number"] }],
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
@ -140,5 +140,5 @@ export default (database: Sequelize) => {
} }
); );
return InvoiceModel; return CustomerInvoiceModel;
}; };

View File

@ -0,0 +1,107 @@
import { SequelizeRepository } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils";
import { Sequelize, Transaction } from "sequelize";
import { ICustomerInvoiceRepository, CustomerInvoice } from "../../domain";
import { ICustomerInvoiceMapper } from "../mappers/customerCustomerInvoice.mapper";
import { CustomerInvoiceItemModel } from "./customerCustomerInvoice-item.model";
import { CustomerInvoiceModel } from "./customerCustomerInvoice.model";
export class CustomerInvoiceRepository extends SequelizeRepository<CustomerInvoice> implements ICustomerInvoiceRepository {
private readonly _mapper!: ICustomerInvoiceMapper;
/**
* 🔹 Función personalizada para mapear errores de unicidad en autenticación
*/
private _customErrorMapper(error: Error): string | null {
if (error.name === "SequelizeUniqueConstraintError") {
return "CustomerInvoice with this email already exists";
}
return null;
}
constructor(database: Sequelize, mapper: ICustomerInvoiceMapper) {
super(database);
this._mapper = mapper;
}
async customerCustomerInvoiceExists(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
try {
const _customerCustomerInvoice = await this._getById(CustomerInvoiceModel, id, {}, transaction);
return Result.ok(Boolean(id.equals(_customerCustomerInvoice.id)));
} catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper);
}
}
async findAll(
criteria: Criteria,
transaction?: Transaction
): Promise<Result<Collection<CustomerInvoice>, Error>> {
try {
const rawCustomerInvoices = await CustomerInvoiceModel.findAll({
include: [
{
model: CustomerInvoiceItemModel,
as: "items",
},
],
transaction,
...this.convertCriteria(criteria),
});
console.error("aqui");
return this._mapper.mapArrayToDomain(rawCustomerInvoices);
} catch (error: unknown) {
console.error("Error in findAll", error);
return this._handleDatabaseError(error, this._customErrorMapper);
}
}
async getById(id: UniqueID, transaction?: Transaction): Promise<Result<CustomerInvoice, Error>> {
try {
const rawCustomerInvoice: any = await this._getById(
CustomerInvoiceModel,
id,
{
include: [
{
model: CustomerInvoiceItemModel,
as: "items",
},
],
},
transaction
);
if (!rawCustomerInvoice === true) {
return Result.fail(new Error(`CustomerInvoice with id ${id.toString()} not exists`));
}
} catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper);
}
}
async deleteById(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
try {
this._deleteById(CustomerInvoiceModel, id, false, transaction);
return Result.ok<boolean>(true);
} catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper);
}
}
async create(customerCustomerInvoice: CustomerInvoice, transaction?: Transaction): Promise<void> {
const customerCustomerInvoiceData = this._mapper.mapToPersistence(customerCustomerInvoice);
await this._save(CustomerInvoiceModel, customerCustomerInvoice.id, customerCustomerInvoiceData, {}, transaction);
}
async update(customerCustomerInvoice: CustomerInvoice, transaction?: Transaction): Promise<void> {
const customerCustomerInvoiceData = this._mapper.mapToPersistence(customerCustomerInvoice);
await this._save(CustomerInvoiceModel, customerCustomerInvoice.id, customerCustomerInvoiceData, {}, transaction);
}
}

View File

@ -0,0 +1,106 @@
import {
CreationOptional,
DataTypes,
InferAttributes,
InferCreationAttributes,
Model,
NonAttribute,
Sequelize,
} from "sequelize";
import { CustomerInvoiceModel } from "./customerCustomerInvoice.model";
import {
CustomerInvoiceParticipantAddress_Model,
TCreationCustomerInvoiceParticipantAddress_Model,
} from "./customerCustomerInvoiceParticipantAddress.mo.del.ts.bak";
export type TCreationCustomerInvoiceParticipant_Model = InferCreationAttributes<
CustomerInvoiceParticipant_Model,
{ omit: "shippingAddress" | "billingAddress" | "customerCustomerInvoice" }
> & {
billingAddress: TCreationCustomerInvoiceParticipantAddress_Model;
shippingAddress: TCreationCustomerInvoiceParticipantAddress_Model;
};
export class CustomerInvoiceParticipant_Model extends Model<
InferAttributes<
CustomerInvoiceParticipant_Model,
{ omit: "shippingAddress" | "billingAddress" | "customerCustomerInvoice" }
>,
InferCreationAttributes<
CustomerInvoiceParticipant_Model,
{ omit: "shippingAddress" | "billingAddress" | "customerCustomerInvoice" }
>
> {
static associate(connection: Sequelize) {
const { CustomerInvoice_Model, CustomerInvoiceParticipantAddress_Model, CustomerInvoiceParticipant_Model } =
connection.models;
CustomerInvoiceParticipant_Model.belongsTo(CustomerInvoice_Model, {
as: "customerCustomerInvoice",
foreignKey: "customerCustomerInvoice_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 customerCustomerInvoice_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 customerCustomerInvoice?: NonAttribute<CustomerInvoiceModel>;
}
export default (sequelize: Sequelize) => {
CustomerInvoiceParticipant_Model.init(
{
participant_id: {
type: new DataTypes.UUID(),
primaryKey: true,
},
customerCustomerInvoice_id: {
type: new 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: "customerCustomerInvoice_participants",
timestamps: false,
}
);
return CustomerInvoiceParticipant_Model;
};

View File

@ -7,20 +7,20 @@ import {
NonAttribute, NonAttribute,
Sequelize, Sequelize,
} from "sequelize"; } from "sequelize";
import { InvoiceParticipant_Model } from "./invoiceParticipant.mo.del.ts.bak"; import { CustomerInvoiceParticipant_Model } from "./customerCustomerInvoiceParticipant.mo.del.ts.bak";
export type TCreationInvoiceParticipantAddress_Model = InferCreationAttributes< export type TCreationCustomerInvoiceParticipantAddress_Model = InferCreationAttributes<
InvoiceParticipantAddress_Model, CustomerInvoiceParticipantAddress_Model,
{ omit: "participant" } { omit: "participant" }
>; >;
export class InvoiceParticipantAddress_Model extends Model< export class CustomerInvoiceParticipantAddress_Model extends Model<
InferAttributes<InvoiceParticipantAddress_Model, { omit: "participant" }>, InferAttributes<CustomerInvoiceParticipantAddress_Model, { omit: "participant" }>,
InferCreationAttributes<InvoiceParticipantAddress_Model, { omit: "participant" }> InferCreationAttributes<CustomerInvoiceParticipantAddress_Model, { omit: "participant" }>
> { > {
static associate(connection: Sequelize) { static associate(connection: Sequelize) {
const { InvoiceParticipantAddress_Model, InvoiceParticipant_Model } = connection.models; const { CustomerInvoiceParticipantAddress_Model, CustomerInvoiceParticipant_Model } = connection.models;
InvoiceParticipantAddress_Model.belongsTo(InvoiceParticipant_Model, { CustomerInvoiceParticipantAddress_Model.belongsTo(CustomerInvoiceParticipant_Model, {
as: "participant", as: "participant",
foreignKey: "participant_id", foreignKey: "participant_id",
}); });
@ -37,11 +37,11 @@ export class InvoiceParticipantAddress_Model extends Model<
declare phone: CreationOptional<string>; declare phone: CreationOptional<string>;
declare email: CreationOptional<string>; declare email: CreationOptional<string>;
declare participant?: NonAttribute<InvoiceParticipant_Model>; declare participant?: NonAttribute<CustomerInvoiceParticipant_Model>;
} }
export default (sequelize: Sequelize) => { export default (sequelize: Sequelize) => {
InvoiceParticipantAddress_Model.init( CustomerInvoiceParticipantAddress_Model.init(
{ {
address_id: { address_id: {
type: new DataTypes.UUID(), type: new DataTypes.UUID(),
@ -86,9 +86,9 @@ export default (sequelize: Sequelize) => {
}, },
{ {
sequelize, sequelize,
tableName: "invoice_participant_addresses", tableName: "customerCustomerInvoice_participant_addresses",
} }
); );
return InvoiceParticipantAddress_Model; return CustomerInvoiceParticipantAddress_Model;
}; };

View File

@ -0,0 +1,9 @@
import customerCustomerInvoiceItemModelInit from "./customerCustomerInvoice-item.model";
import customerCustomerInvoiceModelInit from "./customerCustomerInvoice.model";
export * from "./customerCustomerInvoice-item.model"; // exporta las clases, tipos
export * from "./customerCustomerInvoice.model";
export * from "./customerCustomerInvoice.repository";
// Array de inicializadores para que registerModels() lo use
export const models = [customerCustomerInvoiceItemModelInit, customerCustomerInvoiceModelInit];

View File

@ -0,0 +1,12 @@
import { DeleteCustomerInvoiceUseCase } from "@/contexts/customerCustomerInvoices/application";
import { ExpressController } from "@/core/common/presentation";
export class DeleteCustomerInvoiceController extends ExpressController {
public constructor(private readonly deleteCustomerInvoice: DeleteCustomerInvoiceUseCase) {
super();
}
async executeImpl(): Promise<any> {
return this.noContent();
}
}

View File

@ -0,0 +1,14 @@
import { DeleteCustomerInvoiceUseCase } from "@/contexts/customerCustomerInvoices/application";
import { CustomerInvoiceService } from "@/contexts/customerCustomerInvoices/domain";
import { customerCustomerInvoiceRepository } from "@/contexts/customerCustomerInvoices/intrastructure";
import { SequelizeTransactionManager } from "@/core/common/infrastructure";
import { DeleteCustomerInvoiceController } from "./delete-customer-invoice.controller";
export const buildDeleteCustomerInvoiceController = () => {
const transactionManager = new SequelizeTransactionManager();
const customerCustomerInvoiceService = new CustomerInvoiceService(customerCustomerInvoiceRepository);
const useCase = new DeleteCustomerInvoiceUseCase(customerCustomerInvoiceService, transactionManager);
return new DeleteCustomerInvoiceController(useCase);
};

View File

@ -0,0 +1,44 @@
import { ExpressController } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { GetCustomerInvoiceUseCase } from "../../application";
import { IGetCustomerInvoicePresenter } from "./presenter";
export class GetCustomerInvoiceController extends ExpressController {
public constructor(
private readonly getCustomerInvoice: GetCustomerInvoiceUseCase,
private readonly presenter: IGetCustomerInvoicePresenter
) {
super();
}
protected async executeImpl() {
const { customerCustomerInvoiceId } = this.req.params;
// Validar ID
const customerCustomerInvoiceIdOrError = UniqueID.create(customerCustomerInvoiceId);
if (customerCustomerInvoiceIdOrError.isFailure) return this.invalidInputError("CustomerInvoice ID not valid");
const customerCustomerInvoiceOrError = await this.getCustomerInvoice.execute(customerCustomerInvoiceIdOrError.data);
if (customerCustomerInvoiceOrError.isFailure) {
return this.handleError(customerCustomerInvoiceOrError.error);
}
return this.ok(this.presenter.toDTO(customerCustomerInvoiceOrError.data));
}
private handleError(error: Error) {
const message = error.message;
if (
message.includes("Database connection lost") ||
message.includes("Database request timed out")
) {
return this.unavailableError(
"Database service is currently unavailable. Please try again later."
);
}
return this.conflictError(message);
}
}

View File

@ -0,0 +1,24 @@
import { SequelizeTransactionManager } from "@erp/core/api";
import { Sequelize } from "sequelize";
import { CustomerInvoiceService } from "../../domain";
import { CustomerInvoiceRepository, customerCustomerInvoiceMapper } from "../../infrastructure";
import { GetCustomerInvoiceUseCase } from "../../application";
import { GetCustomerInvoiceController } from "./get-customer-invoice.controller";
import { getCustomerInvoicePresenter } from "./presenter";
export const buildGetCustomerInvoiceController = (database: Sequelize) => {
const transactionManager = new SequelizeTransactionManager(database);
const customerCustomerInvoiceRepository = new CustomerInvoiceRepository(
database,
customerCustomerInvoiceMapper
);
const customerCustomerInvoiceService = new CustomerInvoiceService(
customerCustomerInvoiceRepository
);
const useCase = new GetCustomerInvoiceUseCase(customerCustomerInvoiceService, transactionManager);
const presenter = getCustomerInvoicePresenter;
return new GetCustomerInvoiceController(useCase, presenter);
};

View File

@ -1,10 +1,10 @@
import { InvoiceItem } from "#/server/domain"; import { CustomerInvoiceItem } from "#/server/domain";
import { IInvoicingContext } from "#/server/intrastructure"; import { IInvoicingContext } from "#/server/intrastructure";
import { Collection } from "@rdx/core"; import { Collection } from "@rdx/core";
export const invoiceItemPresenter = (items: Collection<InvoiceItem>, context: IInvoicingContext) => export const customerCustomerInvoiceItemPresenter = (items: Collection<CustomerInvoiceItem>, context: IInvoicingContext) =>
items.totalCount > 0 items.totalCount > 0
? items.items.map((item: InvoiceItem) => ({ ? items.items.map((item: CustomerInvoiceItem) => ({
description: item.description.toString(), description: item.description.toString(),
quantity: item.quantity.toString(), quantity: item.quantity.toString(),
unit_measure: "", unit_measure: "",

View File

@ -0,0 +1,26 @@
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,11 +1,11 @@
import { InvoiceParticipantAddress } from "@/contexts/invoicing/domain"; import { CustomerInvoiceParticipantAddress } from "@/contexts/invoicing/domain";
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext"; import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
import { ICreateInvoice_AddressParticipant_Response_DTO } from "@shared/contexts"; import { ICreateCustomerInvoice_AddressParticipant_Response_DTO } from "@shared/contexts";
export const InvoiceParticipantAddressPresenter = async ( export const CustomerInvoiceParticipantAddressPresenter = async (
address: InvoiceParticipantAddress, address: CustomerInvoiceParticipantAddress,
context: IInvoicingContext, context: IInvoicingContext,
): Promise<ICreateInvoice_AddressParticipant_Response_DTO> => { ): Promise<ICreateCustomerInvoice_AddressParticipant_Response_DTO> => {
return { return {
id: address.id.toString(), id: address.id.toString(),
street: address.street.toString(), street: address.street.toString(),

View File

@ -0,0 +1,59 @@
import { IGetCustomerInvoiceResponseDTO } from "../../../../../common/dto";
import { CustomerInvoice, CustomerInvoiceItem } from "../../../../domain";
export interface IGetCustomerInvoicePresenter {
toDTO: (customerCustomerInvoice: CustomerInvoice) => IGetCustomerInvoiceResponseDTO;
}
export const getCustomerInvoicePresenter: IGetCustomerInvoicePresenter = {
toDTO: (customerCustomerInvoice: CustomerInvoice): IGetCustomerInvoiceResponseDTO => ({
id: customerCustomerInvoice.id.toPrimitive(),
customerCustomerInvoice_status: customerCustomerInvoice.status.toString(),
customerCustomerInvoice_number: customerCustomerInvoice.customerCustomerInvoiceNumber.toString(),
customerCustomerInvoice_series: customerCustomerInvoice.customerCustomerInvoiceSeries.toString(),
issue_date: customerCustomerInvoice.issueDate.toDateString(),
operation_date: customerCustomerInvoice.operationDate.toDateString(),
language_code: "ES",
currency: customerCustomerInvoice.customerCustomerInvoiceCurrency.toString(),
subtotal: customerCustomerInvoice.calculateSubtotal().toPrimitive(),
total: customerCustomerInvoice.calculateTotal().toPrimitive(),
items:
customerCustomerInvoice.items.size() > 0
? customerCustomerInvoice.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(customerCustomerInvoice.senderId, context),
/*recipient: await CustomerInvoiceParticipantPresenter(customerCustomerInvoice.recipient, context),
items: customerCustomerInvoiceItemPresenter(customerCustomerInvoice.items, context),
payment_term: {
payment_type: "",
due_date: "",
},
due_amount: {
currency: customerCustomerInvoice.currency.toString(),
precision: 2,
amount: 0,
},
custom_fields: [],
metadata: {
create_time: "",
last_updated_time: "",
delete_time: "",
},*/
}),
};

View File

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

View File

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

View File

@ -0,0 +1,26 @@
import { SequelizeTransactionManager } from "@erp/core/api";
import { Sequelize } from "sequelize";
import { ListCustomerInvoicesUseCase } from "../../application";
import { CustomerInvoiceService } from "../../domain";
import { CustomerInvoiceRepository, customerCustomerInvoiceMapper } from "../../infrastructure";
import { ListCustomerInvoicesController } from "./list-customer-invoices.controller";
import { listCustomerInvoicesPresenter } from "./presenter";
export const buildListCustomerInvoicesController = (database: Sequelize) => {
const transactionManager = new SequelizeTransactionManager(database);
const customerCustomerInvoiceRepository = new CustomerInvoiceRepository(
database,
customerCustomerInvoiceMapper
);
const customerCustomerInvoiceService = new CustomerInvoiceService(
customerCustomerInvoiceRepository
);
const useCase = new ListCustomerInvoicesUseCase(
customerCustomerInvoiceService,
transactionManager
);
const presenter = listCustomerInvoicesPresenter;
return new ListCustomerInvoicesController(useCase, presenter);
};

View File

@ -0,0 +1,38 @@
import { ExpressController } from "@erp/core/api";
import { ListCustomerInvoicesUseCase } from "../../application";
import { IListCustomerInvoicesPresenter } from "./presenter";
export class ListCustomerInvoicesController extends ExpressController {
public constructor(
private readonly listCustomerInvoices: ListCustomerInvoicesUseCase,
private readonly presenter: IListCustomerInvoicesPresenter
) {
super();
}
protected async executeImpl() {
const criteria = this.criteria;
const customerCustomerInvoicesOrError = await this.listCustomerInvoices.execute(criteria);
if (customerCustomerInvoicesOrError.isFailure) {
return this.handleError(customerCustomerInvoicesOrError.error);
}
return this.ok(this.presenter.toDTO(customerCustomerInvoicesOrError.data, criteria));
}
private handleError(error: Error) {
const message = error.message;
if (
message.includes("Database connection lost") ||
message.includes("Database request timed out")
) {
return this.unavailableError(
"Database service is currently unavailable. Please try again later."
);
}
return this.conflictError(message);
}
}

View File

@ -0,0 +1,22 @@
import { ICustomerInvoiceParticipant } from "@/contexts/invoicing/domain";
import { IListCustomerInvoice_Participant_Response_DTO } from "@shared/contexts";
import { CustomerInvoiceParticipantAddressPresenter } from "./CustomerInvoiceParticipantAddress.presenter";
export const CustomerInvoiceParticipantPresenter = (
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: CustomerInvoiceParticipantAddressPresenter(
participant?.billingAddress!,
),
shipping_address: CustomerInvoiceParticipantAddressPresenter(
participant?.shippingAddress!,
),
};
};

View File

@ -1,6 +1,6 @@
export const InvoiceParticipantAddressPresenter = ( export const CustomerInvoiceParticipantAddressPresenter = (
address: InvoiceParticipantAddress address: CustomerInvoiceParticipantAddress
): IListInvoice_AddressParticipant_Response_DTO => { ): IListCustomerInvoice_AddressParticipant_Response_DTO => {
return { return {
address_id: address?.id.toString(), address_id: address?.id.toString(),
street: address?.street.toString(), street: address?.street.toString(),

View File

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

View File

@ -0,0 +1,54 @@
import { IListResponseDTO } from "@erp/core/api";
import { Criteria } from "@repo/rdx-criteria/server";
import { Collection } from "@repo/rdx-utils";
import { IListCustomerInvoicesResponseDTO } from "../../../../common/dto";
import { CustomerInvoice } from "../../../domain";
export interface IListCustomerInvoicesPresenter {
toDTO: (
customerCustomerInvoices: Collection<CustomerInvoice>,
criteria: Criteria
) => IListResponseDTO<IListCustomerInvoicesResponseDTO>;
}
export const listCustomerInvoicesPresenter: IListCustomerInvoicesPresenter = {
toDTO: (
customerCustomerInvoices: Collection<CustomerInvoice>,
criteria: Criteria
): IListResponseDTO<IListCustomerInvoicesResponseDTO> => {
const items = customerInvoices.map((customerCustomerInvoice) => {
return {
id: customerCustomerInvoice.id.toPrimitive(),
customerCustomerInvoice_status: customerCustomerInvoice.status.toString(),
customerCustomerInvoice_number:
customerCustomerInvoice.customerCustomerInvoiceNumber.toString(),
customerCustomerInvoice_series:
customerCustomerInvoice.customerCustomerInvoiceSeries.toString(),
issue_date: customerCustomerInvoice.issueDate.toISOString(),
operation_date: customerCustomerInvoice.operationDate.toISOString(),
language_code: "ES",
currency: customerCustomerInvoice.customerCustomerInvoiceCurrency.toString(),
subtotal: customerCustomerInvoice.calculateSubtotal().toPrimitive(),
total: customerCustomerInvoice.calculateTotal().toPrimitive(),
//recipient: CustomerInvoiceParticipantPresenter(customerCustomerInvoice.recipient),
metadata: {
entity: "customerCustomerInvoice",
},
} as IListCustomerInvoicesResponseDTO;
});
const totalItems = customerInvoices.total();
return {
page: criteria.pageNumber,
per_page: criteria.pageSize,
total_pages: Math.ceil(totalItems / criteria.pageSize),
total_items: totalItems,
items: items,
};
},
};

View File

@ -1,37 +1,37 @@
import { IInvoicingContext } from "#/server/intrastructure"; import { IInvoicingContext } from "#/server/intrastructure";
import { InvoiceRepository } from "#/server/intrastructure/Invoice.repository"; import { CustomerInvoiceRepository } from "#/server/intrastructure/CustomerInvoice.repository";
export const updateInvoiceController = (context: IInvoicingContext) => { export const updateCustomerInvoiceController = (context: IInvoicingContext) => {
const adapter = context.adapter; const adapter = context.adapter;
const repoManager = context.repositoryManager; const repoManager = context.repositoryManager;
repoManager.registerRepository("Invoice", (params = { transaction: null }) => { repoManager.registerRepository("CustomerInvoice", (params = { transaction: null }) => {
const { transaction } = params; const { transaction } = params;
return new InvoiceRepository({ return new CustomerInvoiceRepository({
transaction, transaction,
adapter, adapter,
mapper: createInvoiceMapper(context), mapper: createCustomerInvoiceMapper(context),
}); });
}); });
repoManager.registerRepository("Participant", (params = { transaction: null }) => { repoManager.registerRepository("Participant", (params = { transaction: null }) => {
const { transaction } = params; const { transaction } = params;
return new InvoiceParticipantRepository({ return new CustomerInvoiceParticipantRepository({
transaction, transaction,
adapter, adapter,
mapper: createInvoiceParticipantMapper(context), mapper: createCustomerInvoiceParticipantMapper(context),
}); });
}); });
repoManager.registerRepository("ParticipantAddress", (params = { transaction: null }) => { repoManager.registerRepository("ParticipantAddress", (params = { transaction: null }) => {
const { transaction } = params; const { transaction } = params;
return new InvoiceParticipantAddressRepository({ return new CustomerInvoiceParticipantAddressRepository({
transaction, transaction,
adapter, adapter,
mapper: createInvoiceParticipantAddressMapper(context), mapper: createCustomerInvoiceParticipantAddressMapper(context),
}); });
}); });
@ -45,12 +45,12 @@ export const updateInvoiceController = (context: IInvoicingContext) => {
}); });
}); });
const updateInvoiceUseCase = new UpdateInvoiceUseCase(context); const updateCustomerInvoiceUseCase = new UpdateCustomerInvoiceUseCase(context);
return new UpdateInvoiceController( return new UpdateCustomerInvoiceController(
{ {
useCase: updateInvoiceUseCase, useCase: updateCustomerInvoiceUseCase,
presenter: updateInvoicePresenter, presenter: updateCustomerInvoicePresenter,
}, },
context context
); );

View File

@ -1,13 +1,13 @@
import { InvoiceItem } from "@/contexts/invoicing/domain/InvoiceItems"; import { CustomerInvoiceItem } from "@/contexts/invoicing/domain/CustomerInvoiceItems";
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext"; import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
import { ICollection, IMoney_Response_DTO } from "@shared/contexts"; import { ICollection, IMoney_Response_DTO } from "@shared/contexts";
export const invoiceItemPresenter = ( export const customerCustomerInvoiceItemPresenter = (
items: ICollection<InvoiceItem>, items: ICollection<CustomerInvoiceItem>,
context: IInvoicingContext context: IInvoicingContext
) => ) =>
items.totalCount > 0 items.totalCount > 0
? items.items.map((item: InvoiceItem) => ({ ? items.items.map((item: CustomerInvoiceItem) => ({
description: item.description.toString(), description: item.description.toString(),
quantity: item.quantity.toString(), quantity: item.quantity.toString(),
unit_measure: "", unit_measure: "",

View File

@ -0,0 +1,26 @@
import { ICustomerInvoiceParticipant } from "@/contexts/invoicing/domain";
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
import { IUpdateCustomerInvoice_Participant_Response_DTO } from "@shared/contexts";
import { CustomerInvoiceParticipantAddressPresenter } from "./CustomerInvoiceParticipantAddress.presenter";
export const CustomerInvoiceParticipantPresenter = (
participant: ICustomerInvoiceParticipant,
context: IInvoicingContext,
): IUpdateCustomerInvoice_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: CustomerInvoiceParticipantAddressPresenter(
participant.billingAddress!,
context,
),
shipping_address: CustomerInvoiceParticipantAddressPresenter(
participant.shippingAddress!,
context,
),
};
};

View File

@ -1,11 +1,11 @@
import { InvoiceParticipantAddress } from "@/contexts/invoicing/domain"; import { CustomerInvoiceParticipantAddress } from "@/contexts/invoicing/domain";
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext"; import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
import { IUpdateInvoice_AddressParticipant_Response_DTO } from "@shared/contexts"; import { IUpdateCustomerInvoice_AddressParticipant_Response_DTO } from "@shared/contexts";
export const InvoiceParticipantAddressPresenter = ( export const CustomerInvoiceParticipantAddressPresenter = (
address: InvoiceParticipantAddress, address: CustomerInvoiceParticipantAddress,
context: IInvoicingContext, context: IInvoicingContext,
): IUpdateInvoice_AddressParticipant_Response_DTO => { ): IUpdateCustomerInvoice_AddressParticipant_Response_DTO => {
return { return {
id: address.id.toString(), id: address.id.toString(),
street: address.street.toString(), street: address.street.toString(),

View File

@ -0,0 +1,33 @@
import { CustomerInvoice } from "@/contexts/invoicing/domain";
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
import { IUpdateCustomerInvoice_Response_DTO } from "@shared/contexts";
import { customerCustomerInvoiceItemPresenter } from "./CustomerInvoiceItem.presenter";
import { CustomerInvoiceParticipantPresenter } from "./CustomerInvoiceParticipant.presenter";
export interface IUpdateCustomerInvoicePresenter {
map: (customerCustomerInvoice: CustomerInvoice, context: IInvoicingContext) => IUpdateCustomerInvoice_Response_DTO;
}
export const updateCustomerInvoicePresenter: IUpdateCustomerInvoicePresenter = {
map: (customerCustomerInvoice: CustomerInvoice, context: IInvoicingContext): IUpdateCustomerInvoice_Response_DTO => {
return {
id: customerCustomerInvoice.id.toString(),
customerCustomerInvoice_status: customerCustomerInvoice.status.toString(),
customerCustomerInvoice_number: customerCustomerInvoice.customerCustomerInvoiceNumber.toString(),
customerCustomerInvoice_series: customerCustomerInvoice.customerCustomerInvoiceSeries.toString(),
issue_date: customerCustomerInvoice.issueDate.toISO8601(),
operation_date: customerCustomerInvoice.operationDate.toISO8601(),
language_code: customerCustomerInvoice.language.toString(),
currency: customerCustomerInvoice.currency.toString(),
subtotal: customerCustomerInvoice.calculateSubtotal().toPrimitive(),
total: customerCustomerInvoice.calculateTotal().toPrimitive(),
//sender: {}, //await CustomerInvoiceParticipantPresenter(customerCustomerInvoice.senderId, context),
recipient: CustomerInvoiceParticipantPresenter(customerCustomerInvoice.recipient, context),
items: customerCustomerInvoiceItemPresenter(customerCustomerInvoice.items, context),
};
},
};

View File

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

View File

@ -1,16 +1,16 @@
import { IInvoicingContext } from "#/server/intrastructure"; import { IInvoicingContext } from "#/server/intrastructure";
import { ExpressController } from "@rdx/core"; import { ExpressController } from "@rdx/core";
import { IUpdateInvoicePresenter } from "./presenter"; import { IUpdateCustomerInvoicePresenter } from "./presenter";
export class UpdateInvoiceController extends ExpressController { export class UpdateCustomerInvoiceController extends ExpressController {
private useCase: UpdateInvoiceUseCase2; private useCase: UpdateCustomerInvoiceUseCase2;
private presenter: IUpdateInvoicePresenter; private presenter: IUpdateCustomerInvoicePresenter;
private context: IInvoicingContext; private context: IInvoicingContext;
constructor( constructor(
props: { props: {
useCase: UpdateInvoiceUseCase; useCase: UpdateCustomerInvoiceUseCase;
presenter: IUpdateInvoicePresenter; presenter: IUpdateCustomerInvoicePresenter;
}, },
context: IInvoicingContext context: IInvoicingContext
) { ) {
@ -23,16 +23,16 @@ export class UpdateInvoiceController extends ExpressController {
} }
async executeImpl(): Promise<any> { async executeImpl(): Promise<any> {
const { invoiceId } = this.req.params; const { customerCustomerInvoiceId } = this.req.params;
const request: IUpdateInvoice_DTO = this.req.body; const request: IUpdateCustomerInvoice_DTO = this.req.body;
if (RuleValidator.validate(RuleValidator.RULE_NOT_NULL_OR_UNDEFINED, invoiceId).isFailure) { if (RuleValidator.validate(RuleValidator.RULE_NOT_NULL_OR_UNDEFINED, customerCustomerInvoiceId).isFailure) {
return this.invalidInputError("Invoice Id param is required!"); return this.invalidInputError("CustomerInvoice Id param is required!");
} }
const idOrError = UniqueID.create(invoiceId); const idOrError = UniqueID.create(customerCustomerInvoiceId);
if (idOrError.isFailure) { if (idOrError.isFailure) {
return this.invalidInputError("Invalid invoice Id param!"); return this.invalidInputError("Invalid customerCustomerInvoice Id param!");
} }
try { try {
@ -46,7 +46,7 @@ export class UpdateInvoiceController extends ExpressController {
switch (error.code) { switch (error.code) {
case UseCaseError.NOT_FOUND_ERROR: case UseCaseError.NOT_FOUND_ERROR:
return this.notFoundError("Invoice not found", error); return this.notFoundError("CustomerInvoice not found", error);
case UseCaseError.INVALID_INPUT_DATA: case UseCaseError.INVALID_INPUT_DATA:
return this.invalidInputError(error.message); return this.invalidInputError(error.message);
@ -62,9 +62,9 @@ export class UpdateInvoiceController extends ExpressController {
} }
} }
const invoice = <Invoice>result.object; const customerCustomerInvoice = <CustomerInvoice>result.object;
return this.ok<IUpdateInvoice_Response_DTO>(this.presenter.map(invoice, this.context)); return this.ok<IUpdateCustomerInvoice_Response_DTO>(this.presenter.map(customerCustomerInvoice, this.context));
} catch (e: unknown) { } catch (e: unknown) {
return this.fail(e as IServerError); return this.fail(e as IServerError);
} }

View File

@ -1,17 +1,17 @@
export type IListInvoicesRequestDTO = {} export type IListCustomerInvoicesRequestDTO = {}
export interface ICreateInvoiceRequestDTO { export interface ICreateCustomerInvoiceRequestDTO {
id: string; id: string;
invoice_number: string; customerCustomerInvoice_number: string;
invoice_series: string; customerCustomerInvoice_series: string;
issue_date: string; issue_date: string;
operation_date: string; operation_date: string;
language_code: string; language_code: string;
currency: string; currency: string;
} }
export interface IUpdateInvoiceRequestDTO { export interface IUpdateCustomerInvoiceRequestDTO {
is_freelancer: boolean; is_freelancer: boolean;
name: string; name: string;
trade_name: string; trade_name: string;

View File

@ -1,11 +1,11 @@
import { IMetadataDTO, IMoneyDTO, IQuantityDTO } from "@erp/core"; import { IMetadataDTO, IMoneyDTO, IQuantityDTO } from "@erp/core";
export interface IListInvoicesResponseDTO { export interface IListCustomerInvoicesResponseDTO {
id: string; id: string;
invoice_status: string; customerCustomerInvoice_status: string;
invoice_number: string; customerCustomerInvoice_number: string;
invoice_series: string; customerCustomerInvoice_series: string;
issue_date: string; issue_date: string;
operation_date: string; operation_date: string;
language_code: string; language_code: string;
@ -17,12 +17,12 @@ export interface IListInvoicesResponseDTO {
metadata?: IMetadataDTO; metadata?: IMetadataDTO;
} }
export interface IGetInvoiceResponseDTO { export interface IGetCustomerInvoiceResponseDTO {
id: string; id: string;
invoice_status: string; customerCustomerInvoice_status: string;
invoice_number: string; customerCustomerInvoice_number: string;
invoice_series: string; customerCustomerInvoice_series: string;
issue_date: string; issue_date: string;
operation_date: string; operation_date: string;
language_code: string; language_code: string;
@ -46,12 +46,12 @@ export interface IGetInvoiceResponseDTO {
metadata?: IMetadataDTO; metadata?: IMetadataDTO;
} }
export interface ICreateInvoiceResponseDTO { export interface ICreateCustomerInvoiceResponseDTO {
id: string; id: string;
invoice_status: string; customerCustomerInvoice_status: string;
invoice_number: string; customerCustomerInvoice_number: string;
invoice_series: string; customerCustomerInvoice_series: string;
issue_date: string; issue_date: string;
operation_date: string; operation_date: string;
language_code: string; language_code: string;
@ -64,12 +64,12 @@ export interface ICreateInvoiceResponseDTO {
// Inferir el tipo en TypeScript desde el esquema Zod // Inferir el tipo en TypeScript desde el esquema Zod
//export type IUpdateAcccountResponseDTO = z.infer<typeof IUpdateAcccountResponseDTOSchema>; //export type IUpdateAcccountResponseDTO = z.infer<typeof IUpdateAcccountResponseDTOSchema>;
export interface IUpdateInvoiceResponseDTO { export interface IUpdateCustomerInvoiceResponseDTO {
id: string; id: string;
invoice_status: string; customerCustomerInvoice_status: string;
invoice_number: string; customerCustomerInvoice_number: string;
invoice_series: string; customerCustomerInvoice_series: string;
issue_date: string; issue_date: string;
operation_date: string; operation_date: string;
language_code: string; language_code: string;

View File

@ -1,9 +1,9 @@
import { z } from "zod"; import { z } from "zod";
export const ICreateInvoiceRequestSchema = z.object({ export const ICreateCustomerInvoiceRequestSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
invoice_number: z.string().min(1), customerCustomerInvoice_number: z.string().min(1),
invoice_series: z.string().min(1), customerCustomerInvoice_series: z.string().min(1),
issue_date: z.string().refine((date) => { issue_date: z.string().refine((date) => {
const dateStr = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Invalid YYYY-MM-DD format"); const dateStr = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Invalid YYYY-MM-DD format");
return dateStr.safeParse(date).success; return dateStr.safeParse(date).success;
@ -38,6 +38,6 @@ export const ICreateInvoiceRequestSchema = z.object({
.optional(), .optional(),
}); });
export const IUpdateInvoiceRequestSchema = z.object({}); export const IUpdateCustomerInvoiceRequestSchema = z.object({});
export const IDeleteInvoiceRequestSchema = z.object({}); export const IDeleteCustomerInvoiceRequestSchema = z.object({});

View File

@ -0,0 +1,3 @@
export * from "./customer-invoices.request.dto";
export * from "./customer-invoices.response.dto";
export * from "./customer-invoices.schemas";

View File

@ -0,0 +1,26 @@
{
"customerInvoices": {
"title": "Customer invoices",
"description": "Manage your customer invoices",
"list": {
"title": "Customer invoice list",
"description": "List all customer invoices"
},
"create": {
"title": "Create customer invoice",
"description": "Create a new customer invoice"
},
"edit": {
"title": "Edit customer invoice",
"description": "Edit the selected customer invoice"
},
"delete": {
"title": "Delete customer invoice",
"description": "Delete the selected customer invoice"
},
"view": {
"title": "View customer invoice",
"description": "View the details of the selected customer invoice"
}
}
}

View File

@ -1,5 +1,5 @@
{ {
"invoices": { "customerInvoices": {
"title": "Facturas", "title": "Facturas",
"description": "Gestiona tus facturas", "description": "Gestiona tus facturas",
"list": { "list": {

View File

@ -9,7 +9,7 @@ ModuleRegistry.registerModules([AllCommunityModule]);
// Core CSS // Core CSS
import { AgGridReact } from "ag-grid-react"; import { AgGridReact } from "ag-grid-react";
import { useInvoices } from "../hooks/use-invoices"; import { useCustomerInvoices } from "../hooks";
/** /**
* Fetch example Json data * Fetch example Json data
@ -47,10 +47,10 @@ interface IRow {
} }
// Create new GridExample component // Create new GridExample component
export const InvoicesGrid = () => { export const CustomerInvoicesGrid = () => {
const { useList } = useInvoices(); //const { useList } = useCustomerInvoices();
const { data, isLoading, isPending, isError, error } = useList({}); const { data, isLoading, isPending, isError, error } = useCustomerInvoices({});
// Column Definitions: Defines & controls grid columns. // Column Definitions: Defines & controls grid columns.
const [colDefs] = useState<ColDef[]>([ const [colDefs] = useState<ColDef[]>([

View File

@ -0,0 +1,6 @@
import { PropsWithChildren } from "react";
import { CustomerInvoicesProvider } from "../context";
export const CustomerInvoicesLayout = ({ children }: PropsWithChildren) => {
return <CustomerInvoicesProvider>{children}</CustomerInvoicesProvider>;
};

View File

@ -0,0 +1,2 @@
export * from "./customer-invoices-grid";
export * from "./customer-invoices-layout";

View File

@ -0,0 +1,56 @@
import { CustomerInvoiceService } from "@erp/customerCustomerInvoices/api/domain/services/customer-invoice.service";
import { PropsWithChildren, createContext } from "react";
/**
*
* 💡 Posibles usos del InvoicingContext
*
* Este contexto se diseña para encapsular estado y lógica compartida dentro del
* bounded context de facturación (facturas), proporcionando acceso global a datos
* o funciones relevantes para múltiples vistas (listado, detalle, edición, etc).
*
* Usos recomendados:
*
* 1. 🔎 Gestión de filtros globales:
* - Permite que los filtros aplicados en el listado de facturas se conserven
* cuando el usuario navega a otras vistas (detalle, edición) y luego regresa.
* - Mejora la experiencia de usuario evitando la necesidad de reestablecer filtros.
*
* 2. 🛡 Gestión de permisos o configuración de acciones disponibles:
* - Permite definir qué acciones están habilitadas para el usuario actual
* (crear, editar, eliminar).
* - Útil para mostrar u ocultar botones de acción en diferentes pantallas.
*
* 3. 🧭 Control del layout:
* - Si el layout tiene elementos dinámicos (tabs, breadcrumb, loading global),
* este contexto puede coordinar su estado desde componentes hijos.
* - Ejemplo: seleccionar una pestaña activa que aplica en todas las subrutas.
*
* 4. 📦 Cacheo liviano de datos compartidos:
* - Puede almacenar la última factura abierta, borradores de edición,
* o referencias temporales para operaciones CRUD sin tener que usar la URL.
*
* 5. 🚀 Coordinación de side-effects:
* - Permite exponer funciones comunes como `refetch`, `resetFilters`,
* o `notifyInvoiceChanged`, usadas desde cualquier subcomponente del dominio.
*
* Alternativas:
* - Si el estado compartido es muy mutable, grande o requiere persistencia,
* podría ser preferible usar Zustand o Redux Toolkit.
* - No usar contextos para valores que cambian frecuentemente en tiempo real,
* ya que pueden causar renders innecesarios.
*
*
*/
export type CustomerInvoicesContextType = {};
export type CustomerInvoicesContextParamsType = {
service: CustomerInvoiceService;
};
export const CustomerInvoicesContext = createContext<CustomerInvoicesContextType>({});
export const CustomerInvoicesProvider = ({ children }: PropsWithChildren) => {
return <CustomerInvoicesContext.Provider value={{}}>{children}</CustomerInvoicesContext.Provider>;
};

View File

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

View File

@ -0,0 +1,58 @@
import { ModuleClientParams } from "@erp/core/client";
import { lazy } from "react";
import { Outlet, RouteObject } from "react-router-dom";
// Lazy load components
const CustomerInvoicesLayout = lazy(() =>
import("./components").then((m) => ({ default: m.CustomerInvoicesLayout }))
);
const CustomerInvoicesList = lazy(() => import("./pages").then((m) => ({ default: m.CustomerInvoicesList })));
//const LogoutPage = lazy(() => import("./app").then((m) => ({ default: m.LogoutPage })));
/*const DealerLayout = lazy(() => import("./app").then((m) => ({ default: m.DealerLayout })));
const DealersList = lazy(() => import("./app").then((m) => ({ default: m.DealersList })));
const LoginPageWithLanguageSelector = lazy(() =>
import("./app").then((m) => ({ default: m.LoginPageWithLanguageSelector }))
);
const CustomerInvoiceCreate = lazy(() => import("./app").then((m) => ({ default: m.CustomerInvoiceCreate })));
const CustomerInvoiceEdit = lazy(() => import("./app").then((m) => ({ default: m.CustomerInvoiceEdit })));
const SettingsEditor = lazy(() => import("./app").then((m) => ({ default: m.SettingsEditor })));
const SettingsLayout = lazy(() => import("./app").then((m) => ({ default: m.SettingsLayout })));
const CatalogLayout = lazy(() => import("./app").then((m) => ({ default: m.CatalogLayout })));
const CatalogList = lazy(() => import("./app").then((m) => ({ default: m.CatalogList })));
const DashboardPage = lazy(() => import("./app").then((m) => ({ default: m.DashboardPage })));
const CustomerInvoicesLayout = lazy(() => import("./app").then((m) => ({ default: m.CustomerInvoicesLayout })));
const CustomerInvoicesList = lazy(() => import("./app").then((m) => ({ default: m.CustomerInvoicesList })));*/
export const CustomerInvoiceRoutes = (params: ModuleClientParams): RouteObject[] => {
return [
{
path: "*",
element: (
<CustomerInvoicesLayout>
<Outlet context={params} />
</CustomerInvoicesLayout>
),
children: [
{ path: "", element: <CustomerInvoicesList /> }, // index
{ path: "list", element: <CustomerInvoicesList /> },
{ path: "*", element: <CustomerInvoicesList /> },
//
/*{ path: "create", element: <CustomerInvoicesList /> },
{ path: ":id", element: <CustomerInvoicesList /> },
{ path: ":id/edit", element: <CustomerInvoicesList /> },
{ path: ":id/delete", element: <CustomerInvoicesList /> },
{ path: ":id/view", element: <CustomerInvoicesList /> },
{ path: ":id/print", element: <CustomerInvoicesList /> },
{ path: ":id/email", element: <CustomerInvoicesList /> },
{ path: ":id/download", element: <CustomerInvoicesList /> },
{ path: ":id/duplicate", element: <CustomerInvoicesList /> },
{ path: ":id/preview", element: <CustomerInvoicesList /> },*/
],
},
];
};

Some files were not shown because too many files have changed in this diff Show More