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 20a2132e..d1a6d24a 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,36 +1,33 @@ -import { ExpressController, errorMapper } from "@erp/core/api"; -import { CreateCustomerCommandDTO } from "../../../../../common/dto"; -import { CreateCustomerUseCase } from "../../../../application"; +import { + ExpressController, + authGuard, + errorMapper, + forbidQueryFieldGuard, + tenantGuard, +} from "@erp/core/api"; +import { CreateCustomerRequestDTO } from "../../../../common/dto"; +import { CreateCustomerUseCase } from "../../../application"; export class CreateCustomerController extends ExpressController { - public constructor(private readonly createCustomer: CreateCustomerUseCase) { + public constructor(private readonly useCase: CreateCustomerUseCase) { super(); // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } protected async executeImpl() { - const dto = this.req.body as CreateCustomerCommandDTO; + const tenantId = this.getTenantId()!; // garantizado por tenantGuard + const dto = this.req.body as CreateCustomerRequestDTO; /* - const user = this.req.user; // asumimos middleware authenticateJWT inyecta user - - if (!user || !user.companyId) { - this.unauthorized(res, "Unauthorized: user or company not found"); - return; - } - // Inyectar empresa del usuario autenticado (ownership) dto.customerCompanyId = user.companyId; */ - const result = await this.createCustomer.execute(dto); + const result = await this.useCase.execute(dto); - if (result.isFailure) { - console.log(result.error); - const apiError = errorMapper.toApiError(result.error); - return this.handleApiError(apiError); - } - - return this.created(result.data); + return result.match( + (data) => this.created(data), + (err) => this.handleApiError(errorMapper.toApiError(err)) + ); } } diff --git a/modules/customers/src/api/infrastructure/express/controllers/delete-customer.controller.ts b/modules/customers/src/api/infrastructure/express/controllers/delete-customer.controller.ts index 25171e8f..7c0aa232 100644 --- a/modules/customers/src/api/infrastructure/express/controllers/delete-customer.controller.ts +++ b/modules/customers/src/api/infrastructure/express/controllers/delete-customer.controller.ts @@ -1,32 +1,28 @@ -import { ExpressController, errorMapper } from "@erp/core/api"; -import { DeleteCustomerUseCase } from "../../../../application"; +import { + ExpressController, + authGuard, + errorMapper, + forbidQueryFieldGuard, + tenantGuard, +} from "@erp/core/api"; +import { DeleteCustomerUseCase } from "../../../application"; export class DeleteCustomerController extends ExpressController { - public constructor(private readonly deleteCustomer: DeleteCustomerUseCase) { + public constructor(private readonly useCase: DeleteCustomerUseCase) { super(); // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } async executeImpl(): Promise { + const tenantId = this.getTenantId()!; // garantizado por tenantGuard const { id } = this.req.params; - /* - const user = this.req.user; // asumimos middleware authenticateJWT inyecta user + const result = await this.useCase.execute({ id, tenantId }); - if (!user || !user.companyId) { - this.unauthorized(res, "Unauthorized: user or company not found"); - return; - } - */ - - const result = await this.deleteCustomer.execute({ id }); - - if (result.isFailure) { - const apiError = errorMapper.toApiError(result.error); - return this.handleApiError(apiError); - } - - return this.ok(result.data); + return result.match( + (data) => this.ok(data), + (error) => this.handleApiError(errorMapper.toApiError(error)) + ); } } diff --git a/modules/customers/src/api/infrastructure/express/controllers/get-customer.controller.ts b/modules/customers/src/api/infrastructure/express/controllers/get-customer.controller.ts index 6d2726e0..52dcb017 100644 --- a/modules/customers/src/api/infrastructure/express/controllers/get-customer.controller.ts +++ b/modules/customers/src/api/infrastructure/express/controllers/get-customer.controller.ts @@ -1,32 +1,28 @@ -import { ExpressController, errorMapper } from "@erp/core/api"; +import { + ExpressController, + authGuard, + errorMapper, + forbidQueryFieldGuard, + tenantGuard, +} from "@erp/core/api"; import { GetCustomerUseCase } from "../../../application"; export class GetCustomerController extends ExpressController { - public constructor(private readonly getCustomer: GetCustomerUseCase) { + public constructor(private readonly useCase: GetCustomerUseCase) { super(); // 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId")); } protected async executeImpl() { + const tenantId = this.getTenantId()!; // garantizado por tenantGuard const { id } = this.req.params; - /* - const user = this.req.user; // asumimos middleware authenticateJWT inyecta user + const result = await this.useCase.execute({ id, tenantId }); - if (!user || !user.companyId) { - this.unauthorized(res, "Unauthorized: user or company not found"); - return; - } - */ - - const result = await this.getCustomer.execute({ id }); - - if (result.isFailure) { - const apiError = errorMapper.toApiError(result.error); - return this.handleApiError(apiError); - } - - return this.ok(result.data); + return result.match( + (data) => this.ok(data), + (error) => this.handleApiError(errorMapper.toApiError(error)) + ); } } 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 b50be40f..991dc2c3 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 @@ -2,35 +2,30 @@ import * as z from "zod/v4"; export const CreateCustomerRequestSchema = z.object({ id: z.uuid(), - invoice_status: z.string(), - invoice_number: z.string().min(1, "Customer invoice number is required"), - invoice_series: z.string().min(1, "Customer invoice series is required"), - issue_date: z.string().datetime({ offset: true, message: "Invalid issue date format" }), - operation_date: z.string().datetime({ offset: true, message: "Invalid operation date format" }), - description: z.string(), - language_code: z.string().min(2, "Language code must be at least 2 characters long"), - currency_code: z.string().min(3, "Currency code must be at least 3 characters long"), - notes: z.string().optional(), - items: z.array( - z.object({ - description: z.string().min(1, "Item description is required"), - quantity: z.object({ - amount: z.number().positive("Quantity amount must be positive"), - scale: z.number().int().nonnegative("Quantity scale must be a non-negative integer"), - }), - unit_price: z.object({ - amount: z.number().positive("Unit price amount must be positive"), - scale: z.number().int().nonnegative("Unit price scale must be a non-negative integer"), - currency_code: z - .string() - .min(3, "Unit price currency code must be at least 3 characters long"), - }), - discount: z.object({ - amount: z.number().nonnegative("Discount amount cannot be negative"), - scale: z.number().int().nonnegative("Discount scale must be a non-negative integer"), - }), - }) - ), + reference: z.string(), + + is_freelancer: z.boolean(), + name: z.string(), + trade_name: z.string(), + tin: z.string(), + + street: z.string(), + city: z.string(), + state: z.string(), + postal_code: z.string(), + country: z.string(), + + email: z.string(), + phone: z.string(), + fax: z.string(), + website: z.string(), + + legal_record: z.string(), + + default_tax: z.number(), + status: z.string(), + lang_code: z.string(), + currency_code: z.string(), }); export type CreateCustomerRequestDTO = z.infer; diff --git a/modules/customers/src/common/dto/response/customer-creation.result.dto.ts b/modules/customers/src/common/dto/response/customer-creation.result.dto.ts index 746343e4..d5c98fce 100644 --- a/modules/customers/src/common/dto/response/customer-creation.result.dto.ts +++ b/modules/customers/src/common/dto/response/customer-creation.result.dto.ts @@ -3,13 +3,30 @@ import * as z from "zod/v4"; export const CustomerCreationResponseSchema = z.object({ id: z.uuid(), - invoice_status: z.string(), - invoice_number: z.string(), - invoice_series: z.string(), - issue_date: z.iso.datetime({ offset: true }), - operation_date: z.iso.datetime({ offset: true }), - language_code: z.string(), - currency: z.string(), + reference: z.string(), + + is_freelancer: z.boolean(), + name: z.string(), + trade_name: z.string(), + tin: z.string(), + + street: z.string(), + city: z.string(), + state: z.string(), + postal_code: z.string(), + country: z.string(), + + email: z.string(), + phone: z.string(), + fax: z.string(), + website: z.string(), + + legal_record: z.string(), + + default_tax: z.number(), + status: z.string(), + lang_code: z.string(), + currency_code: z.string(), metadata: MetadataSchema.optional(), }); diff --git a/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts b/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts index 1ae2fbc5..04aac9fc 100644 --- a/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts +++ b/modules/customers/src/common/dto/response/get-customer-by-id.response.dto.ts @@ -3,13 +3,30 @@ import * as z from "zod/v4"; export const GetCustomerByIdResponseSchema = z.object({ id: z.uuid(), - invoice_status: z.string(), - invoice_number: z.string(), - invoice_series: z.string(), - issue_date: z.iso.datetime({ offset: true }), - operation_date: z.iso.datetime({ offset: true }), - language_code: z.string(), - currency: z.string(), + reference: z.string(), + + is_freelancer: z.boolean(), + name: z.string(), + trade_name: z.string(), + tin: z.string(), + + street: z.string(), + city: z.string(), + state: z.string(), + postal_code: z.string(), + country: z.string(), + + email: z.string(), + phone: z.string(), + fax: z.string(), + website: z.string(), + + legal_record: z.string(), + + default_tax: z.number(), + status: z.string(), + lang_code: z.string(), + currency_code: z.string(), metadata: MetadataSchema.optional(), }); diff --git a/package.json b/package.json index d343ac17..7db28130 100644 --- a/package.json +++ b/package.json @@ -31,5 +31,5 @@ "engines": { "node": ">=18" }, - "packageManager": "pnpm@10.14.0" + "packageManager": "pnpm@10.15.0" }