This commit is contained in:
David Arranz 2025-05-20 12:08:24 +02:00
parent bbd79cc869
commit c05585775d
132 changed files with 234 additions and 468 deletions

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; import { ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
interface IAccountStatusProps { interface IAccountStatusProps {

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; 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";

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; import { ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import { z } from "zod"; import { z } from "zod";

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; 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";

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; 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";

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; 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";

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; import { ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
interface IInvoiceStatusProps { interface IInvoiceStatusProps {

View File

@ -5,6 +5,7 @@
"main": "index.ts", "main": "index.ts",
"scripts": { "scripts": {
"build": "tsc && tsup", "build": "tsc && tsup",
"dev": "node --import=tsx --watch src/index.ts",
"start:dev": "node --import=tsx --watch src/index.ts", "start:dev": "node --import=tsx --watch src/index.ts",
"start:prod": "node dist/index.js", "start:prod": "node dist/index.js",
"clean": "rm -rf dist node_modules", "clean": "rm -rf dist node_modules",
@ -41,6 +42,7 @@
"dependencies": { "dependencies": {
"@erp/auth": "workspace:*", "@erp/auth": "workspace:*",
"@erp/core": "workspace:*", "@erp/core": "workspace:*",
"@erp/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",
@ -49,9 +51,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"helmet": "^8.0.0", "helmet": "^8.0.0",
"http": "0.0.1-security", "http": "0.0.1-security",
"http-status": "^2.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"libphonenumber-js": "^1.11.20",
"luxon": "^3.5.0", "luxon": "^3.5.0",
"module-alias": "^2.2.3", "module-alias": "^2.2.3",
"mysql2": "^3.12.0", "mysql2": "^3.12.0",

View File

@ -1,10 +1,10 @@
import { globalErrorHandler } from "@erp/core";
//import { initPackages } from "@/core/package-loader"; //import { initPackages } from "@/core/package-loader";
import dotenv from "dotenv"; import dotenv from "dotenv";
import express, { Application } from "express"; import express, { Application } from "express";
import helmet from "helmet"; import helmet from "helmet";
import responseTime from "response-time"; import responseTime from "response-time";
import { globalErrorHandler } from "./core/common/presentation"; import { logger } from "./lib/logger";
import { logger } from "./core/logger";
dotenv.config(); dotenv.config();

View File

@ -1,7 +1,6 @@
import { logger } from "@/core/logger"; import { logger } from "@/lib/logger";
import dotenv from "dotenv"; import dotenv from "dotenv";
import { Sequelize } from "sequelize"; import { Sequelize } from "sequelize";
import { registerModels } from "./register-models";
dotenv.config(); dotenv.config();
@ -50,20 +49,19 @@ export async function tryConnectToDatabase() {
if (!database) { if (!database) {
const error = new Error("❌ Database not found."); const error = new Error("❌ Database not found.");
logger.error({ logger.error(error.message, {
message: error.message,
label: "tryConnectToDatabase", label: "tryConnectToDatabase",
}); });
throw error; throw error;
} }
logger.info({ message: `🔸 Connecting to database...`, label: "tryConnectToDatabase" }); logger.info("🔸 Connecting to database...", {
label: "tryConnectToDatabase",
});
try { try {
await database.authenticate(); await database.authenticate();
await registerModels(database);
logger.info({ logger.info(`✔️${" "}Database connection established successfully.`, {
message: `✔️${" "}Database connection established successfully.`,
label: "tryConnectToDatabase", label: "tryConnectToDatabase",
meta: { meta: {
host: process.env.DB_HOST, host: process.env.DB_HOST,
@ -74,8 +72,7 @@ export async function tryConnectToDatabase() {
}); });
return database; return database;
} catch (error) { } catch (error) {
logger.error({ logger.error(`❌ Unable to connect to the database: ${(error as Error).message}`, {
message: `❌ Unable to connect to the database: ${(error as Error).message}`,
error, error,
label: "tryConnectToDatabase", label: "tryConnectToDatabase",
}); });

View File

@ -1,78 +0,0 @@
import * as path from "path";
import { logger } from "@/core/logger";
import * as glob from "glob";
import { DataTypes, Sequelize } from "sequelize";
/**
* 🔹 Registra todos los modelos en Sequelize
*/
export const registerModels = async (database: Sequelize) => {
if (!database) {
const error = new Error("❌ Database not found.");
logger.error({
message: error.message,
label: "initModels",
});
throw error;
}
const cwd = path.resolve(`${__dirname}/../`);
const models: { [key: string]: any } = {};
// Opciones para buscar los modelos
const globOptions = {
cwd,
nocase: true,
nodir: true,
absolute: false,
};
try {
logger.info(`🔎 Searching models in: ${cwd}`);
// Buscamos los ficheros que terminen en .model.js o .model.ts
for (const file of glob.sync("**/*.model.{js,ts}", globOptions)) {
logger.info({ message: `📄 File >> ${file}...`, label: "registerModels" });
const modelFile = require(path.resolve(cwd, file));
const modelDef = modelFile.default;
const model = typeof modelDef === "function" ? modelDef(database, DataTypes) : false;
if (model) {
models[model.name] = model;
logger.info({
message: `🔸 Model "${model.name}" registered (sequelize)`,
label: "registerModels",
});
} else {
logger.info({ message: "🚫 No model", label: "registerModels" });
}
}
} catch (error) {
logger.error("❌ Error registering models:", error);
process.exit(1);
}
// Configurar asociaciones
for (const model of Object.values(models)) {
if (typeof model.associate === "function") {
model.associate(database);
}
}
try {
// Sincronizamos DB en modo desarrollo
if (process.env.NODE_ENV !== "production") {
await database.sync({ force: false, alter: true });
logger.info({ message: `✔️${" "}Database synchronized successfully.`, label: "initModels" });
} else {
logger.warning({
message: "⚠️ Running in production mode - Skipping database sync.",
label: "initModels",
});
}
} catch (err) {
const error = err as Error;
logger.error({ message: "❌ Error synchronizing database:", error, label: "initModels" });
throw error;
}
};

View File

@ -1,27 +0,0 @@
import { IModuleServer, ModuleParams } from "@/core";
import { logger } from "@/core/logger";
import { invoicesRouter, models } from "../../../../../modules/invoices/src/api/intrastructure";
export const invoicesModule: IModuleServer = {
metadata: {
name: "invoices",
version: "1.0.0",
dependencies: [],
},
init(params: ModuleParams) {
// const contacts = getService<ContactsService>("contacts");
invoicesRouter(params);
logger.info({ message: "🚀 Invoices module initialized", label: "invoices" });
},
registerDependencies(params) {
const { database } = params;
logger.info({ message: "🚀 Invoices module dependencies registered", label: "invoices" });
return {
models,
services: {
getInvoice: () => {},
/*...*/
},
};
},
};

View File

@ -1,2 +0,0 @@
export * from "./infrastructure";
export * from "./presentation";

View File

@ -1,4 +1,2 @@
export * from "./sequelize";
export * from "./database";
//export * from "./passport"; //export * from "./passport";
export * from "./sequelize"; //export * from "./sequelize";

View File

@ -1,20 +0,0 @@
export interface IErrorDTO {
detail?: string;
instance?: string;
status: number;
title: string;
type?: string;
context: IErrorContextDTO;
extra: IErrorExtraDTO;
}
export interface IErrorContextDTO {
user?: unknown;
params?: Record<string, any>;
query?: Record<string, any>;
body?: Record<string, any>;
}
export interface IErrorExtraDTO {
errors: Record<string, any>[];
}

View File

@ -1,2 +0,0 @@
export * from "./error.dto";
export * from "./types.dto";

View File

@ -1,15 +0,0 @@
export interface IMoneyDTO {
amount: number | null;
scale: number;
currency_code: string;
}
export interface IPercentageDTO {
amount: number | null;
scale: number;
}
export interface IQuantityDTO {
amount: number | null;
scale: number;
}

View File

@ -1,2 +0,0 @@
export * from "./dto";
export * from "./express";

View File

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

View File

@ -1 +1 @@
export * from "./httpServer"; export * from "../../../lib/httpServer";

View File

@ -1,11 +1,11 @@
import { logger } from "@/lib/logger";
import { DateTime } from "luxon";
import http from "node:http"; import http from "node:http";
import os from "node:os"; import os from "node:os";
import { logger } from "@/core/logger";
import { DateTime } from "luxon";
import { createApp } from "./app"; import { createApp } from "./app";
import { ENV } from "./config"; import { ENV } from "./config";
import { tryConnectToDatabase } from "./config/database"; import { tryConnectToDatabase } from "./config/database";
import { initModules } from "./core/helpers"; import { initModules } from "./lib/modules";
import { registerModules } from "./register-modules"; import { registerModules } from "./register-modules";
// Guardamos información del estado del servidor // Guardamos información del estado del servidor
@ -91,7 +91,7 @@ const server = http
}) })
) )
.on("close", () => .on("close", () =>
logger.info(`Shut down at: ${DateTime.now().toLocaleString(DateTime.DATETIME_FULL)}`) logger.info(`Shutdown at: ${DateTime.now().toLocaleString(DateTime.DATETIME_FULL)}`)
) )
.on("connection", serverConnection) .on("connection", serverConnection)
.on("error", serverError); .on("error", serverError);
@ -149,7 +149,7 @@ process.on("uncaughtException", (error: Error) => {
} }
} }
for (const address in addresses) { for (const address of addresses) {
logger.info(`⚡️ Server accessible at: http://${address}:${currentState.port}`); logger.info(`⚡️ Server accessible at: http://${address}:${currentState.port}`);
} }

View File

@ -0,0 +1,2 @@
export * from "./logger";
export * from "./modules";

View File

@ -12,4 +12,8 @@ export class ConsoleLogger implements ILogger {
error(message: string, error?: Error | any) { error(message: string, error?: Error | any) {
console.error(`[ERROR] ${message}`, error?.stack || error); console.error(`[ERROR] ${message}`, error?.stack || error);
} }
debug(message: string, meta?: any) {
console.debug(`[DEBUG] ${message}`, meta ?? "");
}
} }

View File

@ -19,4 +19,9 @@ export class SentryLogger implements ILogger {
console.error(`[ERROR] ${message}`, error); console.error(`[ERROR] ${message}`, error);
//Sentry.captureException(error); //Sentry.captureException(error);
} }
debug(message: string, meta?: any) {
console.debug(`[DEBUG] ${message}`, meta);
//Sentry.captureMessage(message, "debug");
}
} }

View File

@ -1,7 +1,8 @@
import { authAPIModule } from "@erp/auth"; //import { authAPIModule } from "@erp/auth";
import { registerModule } from "./core/helpers"; import { invoicesAPIModule } from "@erp/invoices";
import { registerModule } from "./lib/modules";
export const registerModules = () => { export const registerModules = () => {
//registerPackage(ContactsPackage); //registerModule(authAPIModule);
registerModule(authAPIModule); registerModule(invoicesAPIModule);
}; };

View File

@ -9,12 +9,6 @@
"outDir": "dist" "outDir": "dist"
}, },
//"files": ["src/index.ts"], // Esta opción compila sólo los archivos listados (y sus dependencias importadas). //"files": ["src/index.ts"], // Esta opción compila sólo los archivos listados (y sus dependencias importadas).
"include": [ "include": ["src"],
"src/**/*.ts",
"../../packages/rdx-ddd/src/aggregate-root.ts",
"../../packages/rdx-ddd/src/domain-entity.ts",
"../../packages/rdx-ddd/src/index.ts",
"../../packages/rdx-ddd/src/aggregate-root-repository.interface.ts"
],
"exclude": ["src/**/__tests__/*", "src/**/*.mock.*", "src/**/*.test.*", "node_modules", "dist"] "exclude": ["src/**/__tests__/*", "src/**/*.mock.*", "src/**/*.test.*", "node_modules", "dist"]
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,11 +10,11 @@ export const authAPIModule: IModuleServer = {
// const contacts = getService<ContactsService>("contacts"); // const contacts = getService<ContactsService>("contacts");
const { logger } = params; const { logger } = params;
//invoicesRouter(params); //invoicesRouter(params);
logger.info({ message: "🚀 Auth module initialized", label: "invoices" }); logger.info("🚀 Auth module initialized", { label: "auth" });
}, },
registerDependencies(params: ModuleParams) { registerDependencies(params: ModuleParams) {
const { database, logger } = params; const { database, logger } = params;
logger.info({ message: "🚀 Auth module dependencies registered", label: "invoices" }); logger.info("🚀 Auth module dependencies registered", { label: "auth" });
return { return {
//models, //models,
services: { services: {

View File

@ -11,6 +11,7 @@
"./components/*": "./src/web/components/*.tsx" "./components/*": "./src/web/components/*.tsx"
}, },
"peerDependencies": { "peerDependencies": {
"express": "^4.18.2",
"joi": "^17.13.3", "joi": "^17.13.3",
"react": "^18 || ^19", "react": "^18 || ^19",
"react-dom": "^18 || ^19", "react-dom": "^18 || ^19",
@ -20,17 +21,22 @@
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",
"@types/axios": "^0.14.4", "@types/axios": "^0.14.4",
"@types/express": "^4.17.21",
"@types/jest": "29.5.14", "@types/jest": "29.5.14",
"@types/react": "^19.1.2", "@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3", "@types/react-dom": "^19.1.3",
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"dependencies": { "dependencies": {
"@repo/rdx-ddd": "workspace:*",
"@repo/rdx-utils": "workspace:*", "@repo/rdx-utils": "workspace:*",
"@repo/rdx-criteria": "workspace:*", "@repo/rdx-criteria": "workspace:*",
"@tanstack/react-query": "^5.75.4", "@tanstack/react-query": "^5.75.4",
"axios": "^1.9.0", "axios": "^1.9.0",
"express": "^4.18.2",
"http-status": "^2.1.0",
"joi": "^17.13.3", "joi": "^17.13.3",
"libphonenumber-js": "^1.11.20",
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.0",

View File

@ -1,3 +1,4 @@
export * from "./dto";
export * from "./infrastructure";
export * from "./logger"; export * from "./logger";
export * from "./modules"; export * from "./modules";
export * from "./dto";

View File

@ -1,4 +1,3 @@
import { logger } from "@/core/logger";
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import httpStatus from "http-status"; import httpStatus from "http-status";
import { ApiError } from "./api-error"; import { ApiError } from "./api-error";
@ -9,7 +8,6 @@ export abstract class ExpressController {
protected next!: NextFunction; protected next!: NextFunction;
static errorResponse(apiError: ApiError, res: Response) { static errorResponse(apiError: ApiError, res: Response) {
logger.error(`[${apiError.status}] ${apiError.title}: ${apiError.detail}`);
return res.status(apiError.status).json(apiError); return res.status(apiError.status).json(apiError);
} }
@ -159,7 +157,6 @@ export abstract class ExpressController {
this.executeImpl(); this.executeImpl();
} catch (error: unknown) { } catch (error: unknown) {
const _error = error as Error; const _error = error as Error;
logger.error(`Unhandled error in controller: ${_error.message}`);
this.internalServerError(_error.message); this.internalServerError(_error.message);
} }
} }

View File

@ -1,4 +1,4 @@
import { logger } from "@/core/logger"; import { logger } from "@/lib/logger";
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import { ApiError } from "../api-error"; import { ApiError } from "../api-error";

View File

@ -0,0 +1,2 @@
export * from "./express";
export * from "./sequelize";

View File

@ -1,4 +1,4 @@
import { DomainEntity } from "@/core/common/domain"; import { DomainEntity } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils"; import { Collection, Result } from "@repo/rdx-utils";
import { Model } from "sequelize"; import { Model } from "sequelize";
@ -50,6 +50,10 @@ export abstract class SequelizeMapper<
params?: MapperParamsType params?: MapperParamsType
): Result<Collection<TEntity>, Error> { ): Result<Collection<TEntity>, Error> {
try { try {
if (source.length === 0) {
return Result.ok(new Collection([], totalCount));
}
const items = source.map( const items = source.map(
(value, index) => this.mapToDomain(value, { index, ...params }).data (value, index) => this.mapToDomain(value, { index, ...params }).data
); );

View File

@ -1,7 +1,6 @@
import { IAggregateRootRepository, UniqueID } from "@/core/common/domain"; import { IAggregateRootRepository, UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { ModelDefined, Sequelize, Transaction } from "sequelize"; import { ModelDefined, Sequelize, Transaction } from "sequelize";
import { logger } from "../logger";
export abstract class SequelizeRepository<T> implements IAggregateRootRepository<T> { export abstract class SequelizeRepository<T> implements IAggregateRootRepository<T> {
protected readonly _database!: Sequelize; protected readonly _database!: Sequelize;
@ -141,7 +140,7 @@ export abstract class SequelizeRepository<T> implements IAggregateRootRepository
): Result<never, Error> { ): Result<never, Error> {
const _error = error as Error; const _error = error as Error;
logger.error({ message: `Database error: ${_error.message}`, label: "SequelizeRepository" }); //logger.error({ message: `Database error: ${_error.message}`, label: "SequelizeRepository" });
// Si la clase hija proporciona un mapeo personalizado, lo usa // Si la clase hija proporciona un mapeo personalizado, lo usa
if (errorMapper) { if (errorMapper) {

View File

@ -2,4 +2,5 @@ export interface ILogger {
info(message: string, meta?: any): void; info(message: string, meta?: any): void;
warn(message: string, meta?: any): void; warn(message: string, meta?: any): void;
error(message: string, error?: Error | any): void; error(message: string, error?: Error | any): void;
debug(message: string, meta?: any): void;
} }

View File

@ -16,25 +16,33 @@
"peerDependencies": { "peerDependencies": {
"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",
"i18next": "^25.1.1", "i18next": "^25.1.1",
"lucide-react": "^0.503.0", "lucide-react": "^0.503.0",
"react": "^18 || ^19", "react": "^18 || ^19",
"react-dom": "^18 || ^19", "react-dom": "^18 || ^19",
"react-i18next": "^15.5.1", "react-i18next": "^15.5.1",
"react-router-dom": "^6.26.0" "react-router-dom": "^6.26.0",
"sequelize": "^6.37.5"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",
"@types/express": "^4.17.21",
"@types/react": "^19.1.2", "@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3", "@types/react-dom": "^19.1.3",
"@types/react-i18next": "^8.1.0",
"sequelize": "^6.37.5",
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"dependencies": { "dependencies": {
"@erp/core": "workspace:*", "@erp/core": "workspace:*",
"@repo/rdx-ddd": "workspace:*",
"@repo/rdx-utils": "workspace:*",
"@repo/rdx-ui": "workspace:*", "@repo/rdx-ui": "workspace:*",
"@repo/shadcn-ui": "workspace:*", "@repo/shadcn-ui": "workspace:*",
"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",
"i18next": "^25.1.1", "i18next": "^25.1.1",
"lucide-react": "^0.503.0", "lucide-react": "^0.503.0",
"react": "^19.1.0", "react": "^19.1.0",

View File

@ -1,6 +1,6 @@
import { UniqueID } from "@/core/common/domain"; 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 "@/lib/logger";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { IInvoiceService, Invoice } from "../domain"; import { IInvoiceService, Invoice } from "../domain";

View File

@ -1,5 +1,5 @@
import { ITransactionManager } from "@/core/common/infrastructure/database"; import { ITransactionManager } from "@/core/common/infrastructure/database";
import { logger } from "@/core/logger"; import { logger } from "@/lib/logger";
import { Collection, Result } from "@repo/rdx-utils"; import { Collection, Result } from "@repo/rdx-utils";
import { IInvoiceService, Invoice } from "../domain"; import { IInvoiceService, Invoice } from "../domain";

View File

@ -1,4 +1,4 @@
import { AggregateRoot, MoneyValue, UniqueID, UtcDate } from "@/core/common/domain"; 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 { InvoiceCustomer, InvoiceItem, InvoiceItems } from "../entities";
import { InvoiceNumber, InvoiceSerie, InvoiceStatus } from "../value-objects"; import { InvoiceNumber, InvoiceSerie, InvoiceStatus } from "../value-objects";

View File

@ -1,9 +1,4 @@
import { import { EmailAddress, Name, PostalAddress, ValueObject } from "@repo/rdx-ddd";
type EmailAddress,
type Name,
type PostalAddress,
ValueObject,
} from "@/core/common/domain";
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 { InvoiceAddressType } from "../../value-objects";
@ -26,12 +21,12 @@ export interface IInvoiceAddress {
export class InvoiceAddress extends ValueObject<IInvoiceAddressProps> implements IInvoiceAddress { export class InvoiceAddress extends ValueObject<IInvoiceAddressProps> implements IInvoiceAddress {
public static create(props: IInvoiceAddressProps) { public static create(props: IInvoiceAddressProps) {
return Result.ok(new this(props)); return Result.ok(new InvoiceAddress(props));
} }
public static createShippingAddress(props: IInvoiceAddressProps) { public static createShippingAddress(props: IInvoiceAddressProps) {
return Result.ok( return Result.ok(
new this({ new InvoiceAddress({
...props, ...props,
type: InvoiceAddressType.create("shipping").data, type: InvoiceAddressType.create("shipping").data,
}) })
@ -40,7 +35,7 @@ export class InvoiceAddress extends ValueObject<IInvoiceAddressProps> implements
public static createBillingAddress(props: IInvoiceAddressProps) { public static createBillingAddress(props: IInvoiceAddressProps) {
return Result.ok( return Result.ok(
new this({ new InvoiceAddress({
...props, ...props,
type: InvoiceAddressType.create("billing").data, type: InvoiceAddressType.create("billing").data,
}) })

View File

@ -1,4 +1,4 @@
import { DomainEntity, Name, TINNumber, UniqueID } from "@/core/common/domain"; 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 { InvoiceAddress } from "./invoice-address";

View File

@ -1,4 +1,4 @@
import { DomainEntity, MoneyValue, Percentage, Quantity, UniqueID } from "@/core/common/domain"; import { DomainEntity, MoneyValue, Percentage, Quantity, UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { InvoiceItemDescription } from "../../value-objects"; import { InvoiceItemDescription } from "../../value-objects";

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; import { ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
interface IInvoiceAddressTypeProps { interface IInvoiceAddressTypeProps {

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; 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";

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; 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";

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; 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";

View File

@ -1,4 +1,4 @@
import { ValueObject } from "@/core/common/domain"; import { ValueObject } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
interface IInvoiceStatusProps { interface IInvoiceStatusProps {
@ -22,7 +22,7 @@ export class InvoiceStatus extends ValueObject<IInvoiceStatusProps> {
}; };
static create(value: string): Result<InvoiceStatus, Error> { static create(value: string): Result<InvoiceStatus, Error> {
if (!this.ALLOWED_STATUSES.includes(value)) { if (!InvoiceStatus.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}`));
} }

View File

@ -1,6 +1,7 @@
import { IModuleServer, ModuleParams } from "@erp/core"; import { IModuleServer, ModuleParams } from "@erp/core";
import { invoicesRouter, models } from "./infrastructure";
export const invoicesModule: IModuleServer = { export const invoicesAPIModule: IModuleServer = {
metadata: { metadata: {
name: "invoices", name: "invoices",
version: "1.0.0", version: "1.0.0",
@ -9,14 +10,14 @@ export const invoicesModule: IModuleServer = {
init(params: ModuleParams) { init(params: ModuleParams) {
// const contacts = getService<ContactsService>("contacts"); // const contacts = getService<ContactsService>("contacts");
const { logger } = params; const { logger } = params;
//invoicesRouter(params); invoicesRouter(params);
logger.info({ message: "🚀 Invoices module initialized", label: "invoices" }); logger.info("🚀 Invoices module initialized", { label: "invoices" });
}, },
registerDependencies(params) { registerDependencies(params) {
const { database, logger } = params; const { database, logger } = params;
logger.info({ message: "🚀 Invoices module dependencies registered", label: "invoices" }); logger.info("🚀 Invoices module dependencies registered", { label: "invoices" });
return { return {
//models, models,
services: { services: {
getInvoice: () => {}, getInvoice: () => {},
/*...*/ /*...*/

View File

@ -1,7 +1,7 @@
import { ModuleParams } from "@/core"; import { ModuleParams } from "@erp/core";
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 { buildGetInvoiceController, buildListInvoicesController } from "../../presentation"; import { buildListInvoicesController } from "../../presentation";
export const invoicesRouter = (params: ModuleParams) => { export const invoicesRouter = (params: ModuleParams) => {
const { app, database, baseRoutePath } = params as { const { app, database, baseRoutePath } = params as {
@ -21,14 +21,14 @@ export const invoicesRouter = (params: ModuleParams) => {
} }
); );
routes.get( /*routes.get(
"/:invoiceId", "/:invoiceId",
//checkTabContext, //checkTabContext,
//checkUser, //checkUser,
(req: Request, res: Response, next: NextFunction) => { (req: Request, res: Response, next: NextFunction) => {
buildGetInvoiceController(database).execute(req, res, next); buildGetInvoiceController(database).execute(req, res, next);
} }
); );*/
/*routes.post( /*routes.post(
"/", "/",

View File

@ -1,18 +1,9 @@
import { Invoice, InvoiceItem, InvoiceItemDescription } from "@/contexts/invoices/domain"; import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core";
import { import { MoneyValue, Percentage, Quantity, UniqueID } from "@repo/rdx-ddd";
type ISequelizeMapper, import { Collection, Result } from "@repo/rdx-utils";
type MapperParamsType,
MoneyValue,
Percentage,
Quantity,
Result,
SequelizeMapper,
UniqueID,
} from "@/core";
import { InvoiceModel } from "../sequelize";
import { InferCreationAttributes } from "sequelize"; import { InferCreationAttributes } from "sequelize";
import { InvoiceItemCreationAttributes, InvoiceItemModel } from "../sequelize"; import { Invoice, InvoiceItem, InvoiceItemDescription } from "../../domain";
import { InvoiceItemCreationAttributes, InvoiceItemModel, InvoiceModel } from "../sequelize";
export interface IInvoiceItemMapper export interface IInvoiceItemMapper
extends ISequelizeMapper<InvoiceItemModel, InvoiceItemCreationAttributes, InvoiceItem> {} extends ISequelizeMapper<InvoiceItemModel, InvoiceItemCreationAttributes, InvoiceItem> {}
@ -21,6 +12,25 @@ export class InvoiceItemMapper
extends SequelizeMapper<InvoiceItemModel, InvoiceItemCreationAttributes, InvoiceItem> extends SequelizeMapper<InvoiceItemModel, InvoiceItemCreationAttributes, InvoiceItem>
implements IInvoiceItemMapper implements IInvoiceItemMapper
{ {
mapArrayToDomain(
source: InvoiceItemModel[],
params?: MapperParamsType
): Result<Collection<InvoiceItem>, Error> {
throw new Error("Method not implemented.");
}
mapArrayAndCountToDomain(
source: InvoiceItemModel[],
totalCount: number,
params?: MapperParamsType
): Result<Collection<InvoiceItem>, Error> {
throw new Error("Method not implemented.");
}
mapCollectionToPersistence(
source: Collection<InvoiceItem>,
params?: MapperParamsType
): InferCreationAttributes<InvoiceItemModel, { omit: "invoice" }>[] {
throw new Error("Method not implemented.");
}
public mapToDomain( public mapToDomain(
source: InvoiceItemModel, source: InvoiceItemModel,
params?: MapperParamsType params?: MapperParamsType

View File

@ -1,12 +1,7 @@
import { Invoice, InvoiceNumber, InvoiceSerie, InvoiceStatus } from "@/contexts/invoices/domain"; import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core";
import { import { UniqueID, UtcDate } from "@repo/rdx-ddd";
type ISequelizeMapper, import { Result } from "@repo/rdx-utils";
type MapperParamsType, import { Invoice, InvoiceNumber, InvoiceSerie, InvoiceStatus } from "../../domain";
Result,
SequelizeMapper,
UniqueID,
UtcDate,
} from "@/core";
import { InvoiceCreationAttributes, InvoiceModel } from "../sequelize"; import { InvoiceCreationAttributes, InvoiceModel } from "../sequelize";
import { InvoiceItemMapper } from "./invoice-item.mapper"; import { InvoiceItemMapper } from "./invoice-item.mapper";

View File

@ -1,5 +1,6 @@
import { Collection, Result, SequelizeRepository, UniqueID } from "@/core"; import { SequelizeRepository } from "@erp/core";
import { logger } from "@/core/logger"; import { UniqueID } from "@repo/rdx-ddd";
import { Collection, Result } from "@repo/rdx-utils";
import { Sequelize, Transaction } from "sequelize"; import { Sequelize, Transaction } from "sequelize";
import { IInvoiceRepository, Invoice } from "../../domain"; import { IInvoiceRepository, Invoice } from "../../domain";
import { IInvoiceMapper } from "../mappers/invoice.mapper"; import { IInvoiceMapper } from "../mappers/invoice.mapper";
@ -37,6 +38,8 @@ export class InvoiceRepository extends SequelizeRepository<Invoice> implements I
async findAll(transaction?: Transaction): Promise<Result<Collection<Invoice>, Error>> { async findAll(transaction?: Transaction): Promise<Result<Collection<Invoice>, Error>> {
try { try {
console.error(this._database.models);
const rawInvoices = await InvoiceModel.findAll({ const rawInvoices = await InvoiceModel.findAll({
include: [ include: [
{ {
@ -47,18 +50,11 @@ export class InvoiceRepository extends SequelizeRepository<Invoice> implements I
transaction, transaction,
}); });
/*if (!rawInvoices === true) { console.error("aqui");
return Result.fail(new Error("Invoice not exists"));
}*/
logger.debug({
message: "rawInvoices",
label: "InvoiceRepository",
meta: { data: rawInvoices },
});
return this._mapper.mapArrayToDomain(rawInvoices); return this._mapper.mapArrayToDomain(rawInvoices);
} catch (error: unknown) { } catch (error: unknown) {
console.error("Error in findAll", error);
return this._handleDatabaseError(error, this._customErrorMapper); return this._handleDatabaseError(error, this._customErrorMapper);
} }
} }
@ -82,8 +78,6 @@ export class InvoiceRepository extends SequelizeRepository<Invoice> implements I
if (!rawInvoice === true) { if (!rawInvoice === true) {
return Result.fail(new Error(`Invoice with id ${id.toString()} not exists`)); return Result.fail(new Error(`Invoice with id ${id.toString()} not exists`));
} }
return this._mapper.mapToDomain(rawInvoice);
} catch (error: any) { } catch (error: any) {
return this._handleDatabaseError(error, this._customErrorMapper); return this._handleDatabaseError(error, this._customErrorMapper);
} }

View File

@ -1,44 +0,0 @@
import { CreateInvoiceUseCase } from "@/contexts/invoices/application/create-invoice.use-case";
import { ExpressController, UniqueID } from "@/core";
import { ICreateInvoiceRequestDTO } from "../../../../common/dto";
import { ICreateInvoicePresenter } from "./presenter";
export class CreateInvoiceController extends ExpressController {
public constructor(
private readonly createInvoice: CreateInvoiceUseCase,
private readonly presenter: ICreateInvoicePresenter
) {
super();
}
protected async executeImpl() {
const createDTO: ICreateInvoiceRequestDTO = this.req.body;
// Validar ID
const invoiceIdOrError = UniqueID.create(createDTO.id);
if (invoiceIdOrError.isFailure) return this.invalidInputError("Invoice ID not valid");
const invoiceOrError = await this.createInvoice.execute(invoiceIdOrError.data, createDTO);
if (invoiceOrError.isFailure) {
return this.handleError(invoiceOrError.error);
}
return this.ok(this.presenter.toDTO(invoiceOrError.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

@ -1,18 +0,0 @@
import { CreateInvoiceUseCase } from "@/contexts/invoices/application/create-invoice.use-case";
import { InvoiceService } from "@/contexts/invoices/domain";
import { InvoiceRepository, invoiceMapper } from "@/contexts/invoices/intrastructure";
import { SequelizeTransactionManager } from "@/core";
import { Sequelize } from "sequelize";
import { CreateInvoiceController } from "./create-invoice.controller";
import { createInvoicePresenter } from "./presenter";
export const buildCreateInvoiceController = (database: Sequelize) => {
const transactionManager = new SequelizeTransactionManager(database);
const invoiceRepository = new InvoiceRepository(database, invoiceMapper);
const invoiceService = new InvoiceService(invoiceRepository);
const useCase = new CreateInvoiceUseCase(invoiceService, transactionManager);
const presenter = createInvoicePresenter;
return new CreateInvoiceController(useCase, presenter);
};

View File

@ -1,26 +0,0 @@
import { IInvoiceParticipant } from "@/contexts/invoicing/domain";
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
import { ICreateInvoice_Participant_Response_DTO } from "@shared/contexts";
import { InvoiceParticipantAddressPresenter } from "./InvoiceParticipantAddress.presenter";
export const InvoiceParticipantPresenter = (
participant: IInvoiceParticipant,
context: IInvoicingContext,
): ICreateInvoice_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: InvoiceParticipantAddressPresenter(
participant.billingAddress!,
context,
),
shipping_address: InvoiceParticipantAddressPresenter(
participant.shippingAddress!,
context,
),
};
};

View File

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

View File

@ -1,28 +0,0 @@
import { Invoice } from "@/contexts/invoices/domain";
import { ICreateInvoiceResponseDTO } from "../../../../../common/dto";
export interface ICreateInvoicePresenter {
toDTO: (invoice: Invoice) => ICreateInvoiceResponseDTO;
}
export const createInvoicePresenter: ICreateInvoicePresenter = {
toDTO: (invoice: Invoice): ICreateInvoiceResponseDTO => ({
id: invoice.id.toString(),
invoice_status: invoice.status.toString(),
invoice_number: invoice.invoiceNumber.toString(),
invoice_series: invoice.invoiceSeries.toString(),
issue_date: invoice.issueDate.toDateString(),
operation_date: invoice.operationDate.toDateString(),
language_code: "es",
currency: invoice.invoiceCurrency || "EUR",
subtotal: invoice.calculateSubtotal().toPrimitive(),
total: invoice.calculateTotal().toPrimitive(),
//sender: {}, //await InvoiceParticipantPresenter(invoice.senderId, context),
//customer: InvoiceParticipantPresenter(invoice.recipient, context),
//items: invoiceItemPresenter(invoice.items, context),
}),
};

View File

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

View File

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

View File

@ -1,19 +0,0 @@
import { InvoiceItem } from "@/contexts/invoicing/domain/InvoiceItems";
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
import { ICollection, IMoney_Response_DTO } from "@shared/contexts";
export const invoiceItemPresenter = (
items: ICollection<InvoiceItem>,
context: IInvoicingContext
) =>
items.totalCount > 0
? items.items.map((item: InvoiceItem) => ({
description: item.description.toString(),
quantity: item.quantity.toString(),
unit_measure: "",
unit_price: item.unitPrice.toPrimitive() as IMoney_Response_DTO,
subtotal: item.calculateSubtotal().toPrimitive() as IMoney_Response_DTO,
tax_amount: item.calculateTaxAmount().toPrimitive() as IMoney_Response_DTO,
total: item.calculateTotal().toPrimitive() as IMoney_Response_DTO,
}))
: [];

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