Facturas de cliente y clientes
This commit is contained in:
parent
3c1010adad
commit
335c2764b7
@ -1,36 +1,33 @@
|
|||||||
import { ExpressController, errorMapper } from "@erp/core/api";
|
import {
|
||||||
import { CreateCustomerCommandDTO } from "../../../../../common/dto";
|
ExpressController,
|
||||||
import { CreateCustomerUseCase } from "../../../../application";
|
authGuard,
|
||||||
|
errorMapper,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
tenantGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
import { CreateCustomerRequestDTO } from "../../../../common/dto";
|
||||||
|
import { CreateCustomerUseCase } from "../../../application";
|
||||||
|
|
||||||
export class CreateCustomerController extends ExpressController {
|
export class CreateCustomerController extends ExpressController {
|
||||||
public constructor(private readonly createCustomer: CreateCustomerUseCase) {
|
public constructor(private readonly useCase: CreateCustomerUseCase) {
|
||||||
super();
|
super();
|
||||||
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async executeImpl() {
|
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)
|
// Inyectar empresa del usuario autenticado (ownership)
|
||||||
dto.customerCompanyId = user.companyId;
|
dto.customerCompanyId = user.companyId;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const result = await this.createCustomer.execute(dto);
|
const result = await this.useCase.execute(dto);
|
||||||
|
|
||||||
if (result.isFailure) {
|
return result.match(
|
||||||
console.log(result.error);
|
(data) => this.created(data),
|
||||||
const apiError = errorMapper.toApiError(result.error);
|
(err) => this.handleApiError(errorMapper.toApiError(err))
|
||||||
return this.handleApiError(apiError);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return this.created(result.data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,32 +1,28 @@
|
|||||||
import { ExpressController, errorMapper } from "@erp/core/api";
|
import {
|
||||||
import { DeleteCustomerUseCase } from "../../../../application";
|
ExpressController,
|
||||||
|
authGuard,
|
||||||
|
errorMapper,
|
||||||
|
forbidQueryFieldGuard,
|
||||||
|
tenantGuard,
|
||||||
|
} from "@erp/core/api";
|
||||||
|
import { DeleteCustomerUseCase } from "../../../application";
|
||||||
|
|
||||||
export class DeleteCustomerController extends ExpressController {
|
export class DeleteCustomerController extends ExpressController {
|
||||||
public constructor(private readonly deleteCustomer: DeleteCustomerUseCase) {
|
public constructor(private readonly useCase: DeleteCustomerUseCase) {
|
||||||
super();
|
super();
|
||||||
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeImpl(): Promise<any> {
|
async executeImpl(): Promise<any> {
|
||||||
|
const tenantId = this.getTenantId()!; // garantizado por tenantGuard
|
||||||
const { id } = this.req.params;
|
const { id } = this.req.params;
|
||||||
|
|
||||||
/*
|
const result = await this.useCase.execute({ id, tenantId });
|
||||||
const user = this.req.user; // asumimos middleware authenticateJWT inyecta user
|
|
||||||
|
|
||||||
if (!user || !user.companyId) {
|
return result.match(
|
||||||
this.unauthorized(res, "Unauthorized: user or company not found");
|
(data) => this.ok(data),
|
||||||
return;
|
(error) => this.handleApiError(errorMapper.toApiError(error))
|
||||||
}
|
);
|
||||||
*/
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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";
|
import { GetCustomerUseCase } from "../../../application";
|
||||||
|
|
||||||
export class GetCustomerController extends ExpressController {
|
export class GetCustomerController extends ExpressController {
|
||||||
public constructor(private readonly getCustomer: GetCustomerUseCase) {
|
public constructor(private readonly useCase: GetCustomerUseCase) {
|
||||||
super();
|
super();
|
||||||
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async executeImpl() {
|
protected async executeImpl() {
|
||||||
|
const tenantId = this.getTenantId()!; // garantizado por tenantGuard
|
||||||
const { id } = this.req.params;
|
const { id } = this.req.params;
|
||||||
|
|
||||||
/*
|
const result = await this.useCase.execute({ id, tenantId });
|
||||||
const user = this.req.user; // asumimos middleware authenticateJWT inyecta user
|
|
||||||
|
|
||||||
if (!user || !user.companyId) {
|
return result.match(
|
||||||
this.unauthorized(res, "Unauthorized: user or company not found");
|
(data) => this.ok(data),
|
||||||
return;
|
(error) => this.handleApiError(errorMapper.toApiError(error))
|
||||||
}
|
);
|
||||||
*/
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,35 +2,30 @@ import * as z from "zod/v4";
|
|||||||
|
|
||||||
export const CreateCustomerRequestSchema = z.object({
|
export const CreateCustomerRequestSchema = z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
invoice_status: z.string(),
|
reference: z.string(),
|
||||||
invoice_number: z.string().min(1, "Customer invoice number is required"),
|
|
||||||
invoice_series: z.string().min(1, "Customer invoice series is required"),
|
is_freelancer: z.boolean(),
|
||||||
issue_date: z.string().datetime({ offset: true, message: "Invalid issue date format" }),
|
name: z.string(),
|
||||||
operation_date: z.string().datetime({ offset: true, message: "Invalid operation date format" }),
|
trade_name: z.string(),
|
||||||
description: z.string(),
|
tin: 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"),
|
street: z.string(),
|
||||||
notes: z.string().optional(),
|
city: z.string(),
|
||||||
items: z.array(
|
state: z.string(),
|
||||||
z.object({
|
postal_code: z.string(),
|
||||||
description: z.string().min(1, "Item description is required"),
|
country: z.string(),
|
||||||
quantity: z.object({
|
|
||||||
amount: z.number().positive("Quantity amount must be positive"),
|
email: z.string(),
|
||||||
scale: z.number().int().nonnegative("Quantity scale must be a non-negative integer"),
|
phone: z.string(),
|
||||||
}),
|
fax: z.string(),
|
||||||
unit_price: z.object({
|
website: z.string(),
|
||||||
amount: z.number().positive("Unit price amount must be positive"),
|
|
||||||
scale: z.number().int().nonnegative("Unit price scale must be a non-negative integer"),
|
legal_record: z.string(),
|
||||||
currency_code: z
|
|
||||||
.string()
|
default_tax: z.number(),
|
||||||
.min(3, "Unit price currency code must be at least 3 characters long"),
|
status: z.string(),
|
||||||
}),
|
lang_code: z.string(),
|
||||||
discount: z.object({
|
currency_code: z.string(),
|
||||||
amount: z.number().nonnegative("Discount amount cannot be negative"),
|
|
||||||
scale: z.number().int().nonnegative("Discount scale must be a non-negative integer"),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CreateCustomerRequestDTO = z.infer<typeof CreateCustomerRequestSchema>;
|
export type CreateCustomerRequestDTO = z.infer<typeof CreateCustomerRequestSchema>;
|
||||||
|
|||||||
@ -3,13 +3,30 @@ import * as z from "zod/v4";
|
|||||||
|
|
||||||
export const CustomerCreationResponseSchema = z.object({
|
export const CustomerCreationResponseSchema = z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
invoice_status: z.string(),
|
reference: z.string(),
|
||||||
invoice_number: z.string(),
|
|
||||||
invoice_series: z.string(),
|
is_freelancer: z.boolean(),
|
||||||
issue_date: z.iso.datetime({ offset: true }),
|
name: z.string(),
|
||||||
operation_date: z.iso.datetime({ offset: true }),
|
trade_name: z.string(),
|
||||||
language_code: z.string(),
|
tin: z.string(),
|
||||||
currency: 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(),
|
metadata: MetadataSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,13 +3,30 @@ import * as z from "zod/v4";
|
|||||||
|
|
||||||
export const GetCustomerByIdResponseSchema = z.object({
|
export const GetCustomerByIdResponseSchema = z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
invoice_status: z.string(),
|
reference: z.string(),
|
||||||
invoice_number: z.string(),
|
|
||||||
invoice_series: z.string(),
|
is_freelancer: z.boolean(),
|
||||||
issue_date: z.iso.datetime({ offset: true }),
|
name: z.string(),
|
||||||
operation_date: z.iso.datetime({ offset: true }),
|
trade_name: z.string(),
|
||||||
language_code: z.string(),
|
tin: z.string(),
|
||||||
currency: 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(),
|
metadata: MetadataSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -31,5 +31,5 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.14.0"
|
"packageManager": "pnpm@10.15.0"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user