diff --git a/apps/server/src/config/database.ts b/apps/server/src/config/database.ts index 06d5a033..1ec3b771 100644 --- a/apps/server/src/config/database.ts +++ b/apps/server/src/config/database.ts @@ -59,7 +59,9 @@ function buildSequelize(): Sequelize { idle: 10_000, acquire: 30_000, }, - // dialectOptions: { /* según dialecto (ssl, etc.) */ }, + dialectOptions: { + timezone: ENV.APP_TIMEZONE, + }, } as const; if (ENV.DATABASE_URL && ENV.DATABASE_URL.trim() !== "") { diff --git a/modules/auth/src/api/lib/express/mock-user.middleware.ts b/modules/auth/src/api/lib/express/mock-user.middleware.ts index d9291eb7..3194a78b 100644 --- a/modules/auth/src/api/lib/express/mock-user.middleware.ts +++ b/modules/auth/src/api/lib/express/mock-user.middleware.ts @@ -5,9 +5,9 @@ import { RequestWithAuth } from "./auth-types"; export function mockUser(req: RequestWithAuth, res: Response, next: NextFunction) { if (process.env.NODE_ENV === "development") { req.user = { - id: UniqueID.create("9e4dc5b3-96b9-4968-9490-14bd032fec5f").data, + userId: UniqueID.create("9e4dc5b3-96b9-4968-9490-14bd032fec5f").data, email: EmailAddress.create("dev@example.com").data, - companyId: UniqueID.create("1e4dc5b3-96b9-4968-9490-14bd032fec5f").data, + companyId: UniqueID.create("5e4dc5b3-96b9-4968-9490-14bd032fec5f").data, roles: ["admin"], }; } diff --git a/modules/core/src/api/domain/errors/entity-not-found-error.ts b/modules/core/src/api/domain/errors/entity-not-found-error.ts index ed262f00..53782206 100644 --- a/modules/core/src/api/domain/errors/entity-not-found-error.ts +++ b/modules/core/src/api/domain/errors/entity-not-found-error.ts @@ -1,7 +1,7 @@ import { DomainError } from "./domain-error"; export class EntityNotFoundError extends DomainError { - constructor(entity: string, field: string, value: string, options?: ErrorOptions) { + constructor(entity: string, field: string, value: any, options?: ErrorOptions) { super(`Entity '${entity}' with ${field} '${value}' was not found.`, options); this.name = "EntityNotFoundError"; } diff --git a/modules/customers/src/api/application/create-customer/create-customer.use-case.ts b/modules/customers/src/api/application/create-customer/create-customer.use-case.ts index 0994114c..b828aaac 100644 --- a/modules/customers/src/api/application/create-customer/create-customer.use-case.ts +++ b/modules/customers/src/api/application/create-customer/create-customer.use-case.ts @@ -9,6 +9,7 @@ import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props" type CreateCustomerUseCaseInput = { dto: CreateCustomerRequestDTO; + companyId: UniqueID; }; export class CreateCustomerUseCase { @@ -19,7 +20,7 @@ export class CreateCustomerUseCase { ) {} public execute(params: CreateCustomerUseCaseInput) { - const { dto } = params; + const { dto, companyId } = params; // 1) Mapear DTO → props de dominio const dtoResult = mapDTOToCreateCustomerProps(dto); @@ -27,14 +28,12 @@ export class CreateCustomerUseCase { return Result.fail(dtoResult.error); } - const mapped = dtoResult.data; - const id = mapped.id; - const { companyId, ...customerProps } = mapped.props; + const { props, id } = dtoResult.data; - console.debug("Creating customer with props:", customerProps); + console.debug("Creating customer with props:", props); // 3) Construir entidad de dominio - const buildResult = this.service.buildCustomerInCompany(companyId, customerProps, id); + const buildResult = this.service.buildCustomerInCompany(companyId, props, id); if (buildResult.isFailure) { return Result.fail(buildResult.error); } diff --git a/modules/customers/src/api/application/create-customer/map-dto-to-create-customer-props.ts b/modules/customers/src/api/application/create-customer/map-dto-to-create-customer-props.ts index 8898ec70..74b394bf 100644 --- a/modules/customers/src/api/application/create-customer/map-dto-to-create-customer-props.ts +++ b/modules/customers/src/api/application/create-customer/map-dto-to-create-customer-props.ts @@ -44,7 +44,7 @@ export function mapDTOToCreateCustomerProps(dto: CreateCustomerRequestDTO) { const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors); const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_id", errors); - const isCompany = dto.is_company; + const isCompany = dto.is_company === "true"; const status = extractOrPushError(CustomerStatus.create(dto.status), "status", errors); const reference = extractOrPushError( maybeFromNullableVO(dto.reference, (value) => Name.create(value)), @@ -146,7 +146,7 @@ export function mapDTOToCreateCustomerProps(dto: CreateCustomerRequestDTO) { const defaultTaxes = new Collection(); - dto.default_taxes.map((taxCode, index) => { + dto.default_taxes.split(",").map((taxCode, index) => { const tax = extractOrPushError(TaxCode.create(taxCode), `default_taxes.${index}`, errors); if (tax) { defaultTaxes.add(tax!); diff --git a/modules/customers/src/api/application/delete-customer/delete-customer.use-case.ts b/modules/customers/src/api/application/delete-customer/delete-customer.use-case.ts index ee874bc0..577f5026 100644 --- a/modules/customers/src/api/application/delete-customer/delete-customer.use-case.ts +++ b/modules/customers/src/api/application/delete-customer/delete-customer.use-case.ts @@ -33,11 +33,13 @@ export class DeleteCustomerUseCase { return Result.fail(existsCheck.error); } - if (!existsCheck.data) { + const customerExists = existsCheck.data; + + if (!customerExists) { return Result.fail(new EntityNotFoundError("Customer", "id", validId.toString())); } - return await this.service.deleteCustomerByIdInCompany(validId, transaction); + return await this.service.deleteCustomerByIdInCompany(validId, companyId, transaction); } catch (error: unknown) { return Result.fail(error as Error); } diff --git a/modules/customers/src/api/domain/repositories/customer-repository.interface.ts b/modules/customers/src/api/domain/repositories/customer-repository.interface.ts index 3b6e5a9a..f8501c88 100644 --- a/modules/customers/src/api/domain/repositories/customer-repository.interface.ts +++ b/modules/customers/src/api/domain/repositories/customer-repository.interface.ts @@ -13,7 +13,7 @@ export interface ICustomerRepository { * Guarda (crea o actualiza) un Customer en la base de datos. * Retorna el objeto actualizado tras la operación. */ - save(customer: Customer, transaction?: any): Promise>; + save(customer: Customer, transaction: any): Promise>; /** * Comprueba si existe un Customer con un `id` dentro de una `company`. @@ -48,5 +48,5 @@ export interface ICustomerRepository { * Elimina un Customer por su ID, dentro de una empresa. * Retorna `void` si se elimina correctamente, o `NotFoundError` si no existía. */ - deleteByIdInCompany(companyId: UniqueID, id: UniqueID, transaction?: any): Promise>; + deleteByIdInCompany(companyId: UniqueID, id: UniqueID, transaction: any): Promise>; } diff --git a/modules/customers/src/api/infrastructure/express/controllers/create-customer.controller.ts b/modules/customers/src/api/infrastructure/express/controllers/create-customer.controller.ts index 52262088..ccd293de 100644 --- a/modules/customers/src/api/infrastructure/express/controllers/create-customer.controller.ts +++ b/modules/customers/src/api/infrastructure/express/controllers/create-customer.controller.ts @@ -1,5 +1,5 @@ import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api"; -import { UpdateCustomerRequestDTO } from "../../../../common/dto"; +import { CreateCustomerRequestDTO } from "../../../../common/dto"; import { CreateCustomerUseCase } from "../../../application"; export class CreateCustomerController extends ExpressController { @@ -11,12 +11,9 @@ export class CreateCustomerController extends ExpressController { protected async executeImpl() { const companyId = this.getTenantId()!; // garantizado por tenantGuard - const dto = this.req.body as UpdateCustomerRequestDTO; + const dto = this.req.body as CreateCustomerRequestDTO; - // Inyectar empresa del usuario autenticado (ownership) - dto.company_id = companyId.toString(); - - const result = await this.useCase.execute({ dto }); + const result = await this.useCase.execute({ dto, companyId }); return result.match( (data) => this.created(data), diff --git a/modules/customers/src/api/infrastructure/express/customers.routes.ts b/modules/customers/src/api/infrastructure/express/customers.routes.ts index ef60013f..4a8bcd38 100644 --- a/modules/customers/src/api/infrastructure/express/customers.routes.ts +++ b/modules/customers/src/api/infrastructure/express/customers.routes.ts @@ -3,6 +3,7 @@ import { ILogger, ModuleParams, validateRequest } from "@erp/core/api"; import { Application, NextFunction, Request, Response, Router } from "express"; import { Sequelize } from "sequelize"; import { + CreateCustomerRequestSchema, CustomerListRequestSchema, DeleteCustomerByIdRequestSchema, GetCustomerByIdRequestSchema, @@ -15,6 +16,7 @@ import { GetCustomerController, ListCustomersController, } from "./controllers"; +import { UpdateCustomerController } from "./controllers/update-customer.controller"; export const customersRouter = (params: ModuleParams) => { const { app, database, baseRoutePath, logger } = params as { @@ -64,7 +66,7 @@ export const customersRouter = (params: ModuleParams) => { "/", //checkTabContext, - validateRequest(UpdateCustomerRequestSchema), + validateRequest(CreateCustomerRequestSchema), (req: Request, res: Response, next: NextFunction) => { const useCase = deps.build.create(); const controller = new CreateCustomerController(useCase); @@ -72,15 +74,17 @@ export const customersRouter = (params: ModuleParams) => { } ); - /*routes.put( + router.put( "/:customerId", - validateAndParseBody(IUpdateCustomerRequestSchema), - checkTabContext, + //checkTabContext, + validateRequest(UpdateCustomerRequestSchema), (req: Request, res: Response, next: NextFunction) => { - buildUpdateCustomerController().execute(req, res, next); + const useCase = deps.build.update(); + const controller = new UpdateCustomerController(useCase); + return controller.execute(req, res, next); } - );*/ + ); router.delete( "/:id", diff --git a/modules/customers/src/api/infrastructure/sequelize/customer.repository.ts b/modules/customers/src/api/infrastructure/sequelize/customer.repository.ts index d781afd2..b7658eba 100644 --- a/modules/customers/src/api/infrastructure/sequelize/customer.repository.ts +++ b/modules/customers/src/api/infrastructure/sequelize/customer.repository.ts @@ -56,7 +56,7 @@ export class CustomerRepository async existsByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: any + transaction?: Transaction ): Promise> { try { const count = await CustomerModel.count({ @@ -80,7 +80,7 @@ export class CustomerRepository async getByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: any + transaction?: Transaction ): Promise> { try { const row = await CustomerModel.findOne({ @@ -113,7 +113,7 @@ export class CustomerRepository async findByCriteriaInCompany( companyId: UniqueID, criteria: Criteria, - transaction?: any + transaction?: Transaction ): Promise>> { try { const converter = new CriteriaToSequelizeConverter(); @@ -124,8 +124,6 @@ export class CustomerRepository company_id: companyId.toString(), }; - console.debug({ model: "CustomerModel", criteria, query }); - const instances = await CustomerModel.findAll({ ...query, transaction, @@ -150,17 +148,17 @@ export class CustomerRepository async deleteByIdInCompany( companyId: UniqueID, id: UniqueID, - transaction?: any + transaction: Transaction ): Promise> { try { + console.log(id, companyId); const deleted = await CustomerModel.destroy({ where: { id: id.toString(), company_id: companyId.toString() }, transaction, }); - if (deleted === 0) { - return Result.fail(new Error(`Customer with id ${id} not found in company ${companyId}.`)); - } + console.log(deleted); + return Result.ok(); } catch (err: unknown) { // , `Error deleting customer ${id} in company ${companyId}` diff --git a/modules/customers/src/common/dto/request/create-customer.request.dto.ts b/modules/customers/src/common/dto/request/create-customer.request.dto.ts index 514ee276..1f19d889 100644 --- a/modules/customers/src/common/dto/request/create-customer.request.dto.ts +++ b/modules/customers/src/common/dto/request/create-customer.request.dto.ts @@ -5,7 +5,7 @@ export const CreateCustomerRequestSchema = z.object({ company_id: z.uuid(), reference: z.string().default(""), - is_company: z.boolean().default(false), + is_company: z.string().toLowerCase().default("false"), name: z.string().default(""), trade_name: z.string().default(""), tin: z.string().default(""), @@ -24,10 +24,10 @@ export const CreateCustomerRequestSchema = z.object({ legal_record: z.string().default(""), - default_taxes: z.array(z.string()).default([]), - status: z.string().default("active"), - language_code: z.string().default("es"), - currency_code: z.string().default("EUR"), + default_taxes: z.string().default(""), + status: z.string().toLowerCase().default("active"), + language_code: z.string().toLowerCase().default("es"), + currency_code: z.string().toUpperCase().default("EUR"), }); export type CreateCustomerRequestDTO = z.infer; diff --git a/modules/customers/src/common/dto/request/update-customer.request.dto.ts b/modules/customers/src/common/dto/request/update-customer.request.dto.ts index 002c729f..5d292a92 100644 --- a/modules/customers/src/common/dto/request/update-customer.request.dto.ts +++ b/modules/customers/src/common/dto/request/update-customer.request.dto.ts @@ -3,7 +3,7 @@ import * as z from "zod/v4"; export const UpdateCustomerRequestSchema = z.object({ reference: z.string().optional(), - is_company: z.boolean().optional(), + is_company: z.string().optional(), name: z.string().optional(), trade_name: z.string().optional(), tin: z.string().optional(), @@ -22,7 +22,7 @@ export const UpdateCustomerRequestSchema = z.object({ legal_record: z.string().optional(), - default_taxes: z.array(z.string()).optional(), // completo (sustituye), o null => vaciar + default_taxes: z.string().optional(), // completo (sustituye), o null => vaciar language_code: z.string().optional(), currency_code: z.string().optional(), });