.
This commit is contained in:
parent
36e215ad9c
commit
7e63288e12
@ -59,7 +59,9 @@ function buildSequelize(): Sequelize {
|
|||||||
idle: 10_000,
|
idle: 10_000,
|
||||||
acquire: 30_000,
|
acquire: 30_000,
|
||||||
},
|
},
|
||||||
// dialectOptions: { /* según dialecto (ssl, etc.) */ },
|
dialectOptions: {
|
||||||
|
timezone: ENV.APP_TIMEZONE,
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
if (ENV.DATABASE_URL && ENV.DATABASE_URL.trim() !== "") {
|
if (ENV.DATABASE_URL && ENV.DATABASE_URL.trim() !== "") {
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { RequestWithAuth } from "./auth-types";
|
|||||||
export function mockUser(req: RequestWithAuth, res: Response, next: NextFunction) {
|
export function mockUser(req: RequestWithAuth, res: Response, next: NextFunction) {
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
req.user = {
|
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,
|
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"],
|
roles: ["admin"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { DomainError } from "./domain-error";
|
import { DomainError } from "./domain-error";
|
||||||
|
|
||||||
export class EntityNotFoundError extends DomainError {
|
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);
|
super(`Entity '${entity}' with ${field} '${value}' was not found.`, options);
|
||||||
this.name = "EntityNotFoundError";
|
this.name = "EntityNotFoundError";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props"
|
|||||||
|
|
||||||
type CreateCustomerUseCaseInput = {
|
type CreateCustomerUseCaseInput = {
|
||||||
dto: CreateCustomerRequestDTO;
|
dto: CreateCustomerRequestDTO;
|
||||||
|
companyId: UniqueID;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CreateCustomerUseCase {
|
export class CreateCustomerUseCase {
|
||||||
@ -19,7 +20,7 @@ export class CreateCustomerUseCase {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(params: CreateCustomerUseCaseInput) {
|
public execute(params: CreateCustomerUseCaseInput) {
|
||||||
const { dto } = params;
|
const { dto, companyId } = params;
|
||||||
|
|
||||||
// 1) Mapear DTO → props de dominio
|
// 1) Mapear DTO → props de dominio
|
||||||
const dtoResult = mapDTOToCreateCustomerProps(dto);
|
const dtoResult = mapDTOToCreateCustomerProps(dto);
|
||||||
@ -27,14 +28,12 @@ export class CreateCustomerUseCase {
|
|||||||
return Result.fail(dtoResult.error);
|
return Result.fail(dtoResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapped = dtoResult.data;
|
const { props, id } = dtoResult.data;
|
||||||
const id = mapped.id;
|
|
||||||
const { companyId, ...customerProps } = mapped.props;
|
|
||||||
|
|
||||||
console.debug("Creating customer with props:", customerProps);
|
console.debug("Creating customer with props:", props);
|
||||||
|
|
||||||
// 3) Construir entidad de dominio
|
// 3) Construir entidad de dominio
|
||||||
const buildResult = this.service.buildCustomerInCompany(companyId, customerProps, id);
|
const buildResult = this.service.buildCustomerInCompany(companyId, props, id);
|
||||||
if (buildResult.isFailure) {
|
if (buildResult.isFailure) {
|
||||||
return Result.fail(buildResult.error);
|
return Result.fail(buildResult.error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export function mapDTOToCreateCustomerProps(dto: CreateCustomerRequestDTO) {
|
|||||||
const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
|
||||||
const companyId = extractOrPushError(UniqueID.create(dto.company_id), "company_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 status = extractOrPushError(CustomerStatus.create(dto.status), "status", errors);
|
||||||
const reference = extractOrPushError(
|
const reference = extractOrPushError(
|
||||||
maybeFromNullableVO(dto.reference, (value) => Name.create(value)),
|
maybeFromNullableVO(dto.reference, (value) => Name.create(value)),
|
||||||
@ -146,7 +146,7 @@ export function mapDTOToCreateCustomerProps(dto: CreateCustomerRequestDTO) {
|
|||||||
|
|
||||||
const defaultTaxes = new Collection<TaxCode>();
|
const defaultTaxes = new Collection<TaxCode>();
|
||||||
|
|
||||||
dto.default_taxes.map((taxCode, index) => {
|
dto.default_taxes.split(",").map((taxCode, index) => {
|
||||||
const tax = extractOrPushError(TaxCode.create(taxCode), `default_taxes.${index}`, errors);
|
const tax = extractOrPushError(TaxCode.create(taxCode), `default_taxes.${index}`, errors);
|
||||||
if (tax) {
|
if (tax) {
|
||||||
defaultTaxes.add(tax!);
|
defaultTaxes.add(tax!);
|
||||||
|
|||||||
@ -33,11 +33,13 @@ export class DeleteCustomerUseCase {
|
|||||||
return Result.fail(existsCheck.error);
|
return Result.fail(existsCheck.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existsCheck.data) {
|
const customerExists = existsCheck.data;
|
||||||
|
|
||||||
|
if (!customerExists) {
|
||||||
return Result.fail(new EntityNotFoundError("Customer", "id", validId.toString()));
|
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) {
|
} catch (error: unknown) {
|
||||||
return Result.fail(error as Error);
|
return Result.fail(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export interface ICustomerRepository {
|
|||||||
* Guarda (crea o actualiza) un Customer en la base de datos.
|
* Guarda (crea o actualiza) un Customer en la base de datos.
|
||||||
* Retorna el objeto actualizado tras la operación.
|
* Retorna el objeto actualizado tras la operación.
|
||||||
*/
|
*/
|
||||||
save(customer: Customer, transaction?: any): Promise<Result<Customer, Error>>;
|
save(customer: Customer, transaction: any): Promise<Result<Customer, Error>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comprueba si existe un Customer con un `id` dentro de una `company`.
|
* 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.
|
* Elimina un Customer por su ID, dentro de una empresa.
|
||||||
* Retorna `void` si se elimina correctamente, o `NotFoundError` si no existía.
|
* Retorna `void` si se elimina correctamente, o `NotFoundError` si no existía.
|
||||||
*/
|
*/
|
||||||
deleteByIdInCompany(companyId: UniqueID, id: UniqueID, transaction?: any): Promise<Result<void>>;
|
deleteByIdInCompany(companyId: UniqueID, id: UniqueID, transaction: any): Promise<Result<void>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||||
import { UpdateCustomerRequestDTO } from "../../../../common/dto";
|
import { CreateCustomerRequestDTO } from "../../../../common/dto";
|
||||||
import { CreateCustomerUseCase } from "../../../application";
|
import { CreateCustomerUseCase } from "../../../application";
|
||||||
|
|
||||||
export class CreateCustomerController extends ExpressController {
|
export class CreateCustomerController extends ExpressController {
|
||||||
@ -11,12 +11,9 @@ export class CreateCustomerController extends ExpressController {
|
|||||||
|
|
||||||
protected async executeImpl() {
|
protected async executeImpl() {
|
||||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
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)
|
const result = await this.useCase.execute({ dto, companyId });
|
||||||
dto.company_id = companyId.toString();
|
|
||||||
|
|
||||||
const result = await this.useCase.execute({ dto });
|
|
||||||
|
|
||||||
return result.match(
|
return result.match(
|
||||||
(data) => this.created(data),
|
(data) => this.created(data),
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { ILogger, ModuleParams, validateRequest } 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 {
|
import {
|
||||||
|
CreateCustomerRequestSchema,
|
||||||
CustomerListRequestSchema,
|
CustomerListRequestSchema,
|
||||||
DeleteCustomerByIdRequestSchema,
|
DeleteCustomerByIdRequestSchema,
|
||||||
GetCustomerByIdRequestSchema,
|
GetCustomerByIdRequestSchema,
|
||||||
@ -15,6 +16,7 @@ import {
|
|||||||
GetCustomerController,
|
GetCustomerController,
|
||||||
ListCustomersController,
|
ListCustomersController,
|
||||||
} from "./controllers";
|
} from "./controllers";
|
||||||
|
import { UpdateCustomerController } from "./controllers/update-customer.controller";
|
||||||
|
|
||||||
export const customersRouter = (params: ModuleParams) => {
|
export const customersRouter = (params: ModuleParams) => {
|
||||||
const { app, database, baseRoutePath, logger } = params as {
|
const { app, database, baseRoutePath, logger } = params as {
|
||||||
@ -64,7 +66,7 @@ export const customersRouter = (params: ModuleParams) => {
|
|||||||
"/",
|
"/",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
|
|
||||||
validateRequest(UpdateCustomerRequestSchema),
|
validateRequest(CreateCustomerRequestSchema),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.build.create();
|
const useCase = deps.build.create();
|
||||||
const controller = new CreateCustomerController(useCase);
|
const controller = new CreateCustomerController(useCase);
|
||||||
@ -72,15 +74,17 @@ export const customersRouter = (params: ModuleParams) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/*routes.put(
|
router.put(
|
||||||
"/:customerId",
|
"/:customerId",
|
||||||
validateAndParseBody(IUpdateCustomerRequestSchema),
|
//checkTabContext,
|
||||||
checkTabContext,
|
|
||||||
|
|
||||||
|
validateRequest(UpdateCustomerRequestSchema),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(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(
|
router.delete(
|
||||||
"/:id",
|
"/:id",
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export class CustomerRepository
|
|||||||
async existsByIdInCompany(
|
async existsByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
transaction?: any
|
transaction?: Transaction
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
try {
|
try {
|
||||||
const count = await CustomerModel.count({
|
const count = await CustomerModel.count({
|
||||||
@ -80,7 +80,7 @@ export class CustomerRepository
|
|||||||
async getByIdInCompany(
|
async getByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
transaction?: any
|
transaction?: Transaction
|
||||||
): Promise<Result<Customer, Error>> {
|
): Promise<Result<Customer, Error>> {
|
||||||
try {
|
try {
|
||||||
const row = await CustomerModel.findOne({
|
const row = await CustomerModel.findOne({
|
||||||
@ -113,7 +113,7 @@ export class CustomerRepository
|
|||||||
async findByCriteriaInCompany(
|
async findByCriteriaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: any
|
transaction?: Transaction
|
||||||
): Promise<Result<Collection<Customer>>> {
|
): Promise<Result<Collection<Customer>>> {
|
||||||
try {
|
try {
|
||||||
const converter = new CriteriaToSequelizeConverter();
|
const converter = new CriteriaToSequelizeConverter();
|
||||||
@ -124,8 +124,6 @@ export class CustomerRepository
|
|||||||
company_id: companyId.toString(),
|
company_id: companyId.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
console.debug({ model: "CustomerModel", criteria, query });
|
|
||||||
|
|
||||||
const instances = await CustomerModel.findAll({
|
const instances = await CustomerModel.findAll({
|
||||||
...query,
|
...query,
|
||||||
transaction,
|
transaction,
|
||||||
@ -150,17 +148,17 @@ export class CustomerRepository
|
|||||||
async deleteByIdInCompany(
|
async deleteByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
transaction?: any
|
transaction: Transaction
|
||||||
): Promise<Result<void>> {
|
): Promise<Result<void>> {
|
||||||
try {
|
try {
|
||||||
|
console.log(id, companyId);
|
||||||
const deleted = await CustomerModel.destroy({
|
const deleted = await CustomerModel.destroy({
|
||||||
where: { id: id.toString(), company_id: companyId.toString() },
|
where: { id: id.toString(), company_id: companyId.toString() },
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deleted === 0) {
|
console.log(deleted);
|
||||||
return Result.fail(new Error(`Customer with id ${id} not found in company ${companyId}.`));
|
|
||||||
}
|
|
||||||
return Result.ok<void>();
|
return Result.ok<void>();
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
// , `Error deleting customer ${id} in company ${companyId}`
|
// , `Error deleting customer ${id} in company ${companyId}`
|
||||||
|
|||||||
@ -5,7 +5,7 @@ export const CreateCustomerRequestSchema = z.object({
|
|||||||
company_id: z.uuid(),
|
company_id: z.uuid(),
|
||||||
reference: z.string().default(""),
|
reference: z.string().default(""),
|
||||||
|
|
||||||
is_company: z.boolean().default(false),
|
is_company: z.string().toLowerCase().default("false"),
|
||||||
name: z.string().default(""),
|
name: z.string().default(""),
|
||||||
trade_name: z.string().default(""),
|
trade_name: z.string().default(""),
|
||||||
tin: z.string().default(""),
|
tin: z.string().default(""),
|
||||||
@ -24,10 +24,10 @@ export const CreateCustomerRequestSchema = z.object({
|
|||||||
|
|
||||||
legal_record: z.string().default(""),
|
legal_record: z.string().default(""),
|
||||||
|
|
||||||
default_taxes: z.array(z.string()).default([]),
|
default_taxes: z.string().default(""),
|
||||||
status: z.string().default("active"),
|
status: z.string().toLowerCase().default("active"),
|
||||||
language_code: z.string().default("es"),
|
language_code: z.string().toLowerCase().default("es"),
|
||||||
currency_code: z.string().default("EUR"),
|
currency_code: z.string().toUpperCase().default("EUR"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CreateCustomerRequestDTO = z.infer<typeof CreateCustomerRequestSchema>;
|
export type CreateCustomerRequestDTO = z.infer<typeof CreateCustomerRequestSchema>;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import * as z from "zod/v4";
|
|||||||
export const UpdateCustomerRequestSchema = z.object({
|
export const UpdateCustomerRequestSchema = z.object({
|
||||||
reference: z.string().optional(),
|
reference: z.string().optional(),
|
||||||
|
|
||||||
is_company: z.boolean().optional(),
|
is_company: z.string().optional(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
trade_name: z.string().optional(),
|
trade_name: z.string().optional(),
|
||||||
tin: z.string().optional(),
|
tin: z.string().optional(),
|
||||||
@ -22,7 +22,7 @@ export const UpdateCustomerRequestSchema = z.object({
|
|||||||
|
|
||||||
legal_record: z.string().optional(),
|
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(),
|
language_code: z.string().optional(),
|
||||||
currency_code: z.string().optional(),
|
currency_code: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user