This commit is contained in:
David Arranz 2025-08-21 09:44:07 +02:00
parent b3c5e650fc
commit 3c1010adad
17 changed files with 135 additions and 103 deletions

View File

@ -1,8 +1,8 @@
import { ITransactionManager } from "@erp/core/api"; import { ITransactionManager } from "@erp/core/api";
import { CustomerInvoiceListResponseDTO } from "@erp/customer-invoices/common/dto";
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize"; import { Transaction } from "sequelize";
import { CustomerInvoiceListResponseDTO } from "../../../common/dto";
import { ICustomerInvoiceService } from "../../domain"; import { ICustomerInvoiceService } from "../../domain";
import { ListCustomerInvoicesAssembler } from "./assembler"; import { ListCustomerInvoicesAssembler } from "./assembler";
@ -31,7 +31,7 @@ export class ListCustomerInvoicesUseCase {
return Result.fail(result.error); return Result.fail(result.error);
} }
const dto: CustomerInvoiceListResponseDTO = this.assembler.toDTO(result.data, criteria); const dto = this.assembler.toDTO(result.data, criteria);
return Result.ok(dto); return Result.ok(dto);
} catch (error: unknown) { } catch (error: unknown) {
return Result.fail(error as Error); return Result.fail(error as Error);

View File

@ -32,6 +32,7 @@
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@erp/auth": "workspace:*",
"@erp/customers": "workspace:*", "@erp/customers": "workspace:*",
"@hookform/resolvers": "^5.0.1", "@hookform/resolvers": "^5.0.1",
"@repo/rdx-criteria": "workspace:*", "@repo/rdx-criteria": "workspace:*",

View File

@ -2,20 +2,20 @@ import { Customer } from "@erp/customers/api/domain";
import { CustomersCreationResultDTO } from "@erp/customers/common/dto"; import { CustomersCreationResultDTO } from "@erp/customers/common/dto";
export class CreateCustomersAssembler { export class CreateCustomersAssembler {
public toDTO(invoice: Customer): CustomersCreationResultDTO { public toDTO(customer: Customer): CustomersCreationResultDTO {
return { return {
id: invoice.id.toPrimitive(), id: customer.id.toPrimitive(),
invoice_status: invoice.status.toString(), customer_status: customer.status.toString(),
invoice_number: invoice.invoiceNumber.toString(), customer_number: customer.customerNumber.toString(),
invoice_series: invoice.invoiceSeries.toString(), customer_series: customer.customerSeries.toString(),
issue_date: invoice.issueDate.toISOString(), issue_date: customer.issueDate.toISOString(),
operation_date: invoice.operationDate.toISOString(), operation_date: customer.operationDate.toISOString(),
language_code: "ES", language_code: "ES",
currency: "EUR", currency: "EUR",
//subtotal_price: invoice.calculateSubtotal().toPrimitive(), //subtotal_price: customer.calculateSubtotal().toPrimitive(),
//total_price: invoice.calculateTotal().toPrimitive(), //total_price: customer.calculateTotal().toPrimitive(),
//recipient: CustomerParticipantAssembler(customer.recipient), //recipient: CustomerParticipantAssembler(customer.recipient),

View File

@ -14,21 +14,21 @@ export class CreateCustomerUseCase {
) {} ) {}
public execute(dto: CreateCustomerCommandDTO) { public execute(dto: CreateCustomerCommandDTO) {
const invoicePropsOrError = mapDTOToCustomerProps(dto); const customerPropsOrError = mapDTOToCustomerProps(dto);
if (invoicePropsOrError.isFailure) { if (customerPropsOrError.isFailure) {
return Result.fail(invoicePropsOrError.error); return Result.fail(customerPropsOrError.error);
} }
const { props, id } = invoicePropsOrError.data; const { props, id } = customerPropsOrError.data;
const invoiceOrError = this.service.build(props, id); const customerOrError = this.service.build(props, id);
if (invoiceOrError.isFailure) { if (customerOrError.isFailure) {
return Result.fail(invoiceOrError.error); return Result.fail(customerOrError.error);
} }
const newInvoice = invoiceOrError.data; const newCustomer = customerOrError.data;
return this.transactionManager.complete(async (transaction: Transaction) => { return this.transactionManager.complete(async (transaction: Transaction) => {
try { try {
@ -42,12 +42,12 @@ export class CreateCustomerUseCase {
return Result.fail(new DuplicateEntityError("Customer", id.toString())); return Result.fail(new DuplicateEntityError("Customer", id.toString()));
} }
const result = await this.service.save(newInvoice, transaction); const result = await this.service.save(newCustomer, transaction);
if (result.isFailure) { if (result.isFailure) {
return Result.fail(result.error); return Result.fail(result.error);
} }
const viewDTO = this.assembler.toDTO(newInvoice); const viewDTO = this.assembler.toDTO(newCustomer);
return Result.ok(viewDTO); return Result.ok(viewDTO);
} catch (error: unknown) { } catch (error: unknown) {
return Result.fail(error as Error); return Result.fail(error as Error);

View File

@ -6,9 +6,9 @@ export class GetCustomerAssembler {
return { return {
id: customer.id.toPrimitive(), id: customer.id.toPrimitive(),
invoice_status: customer.status.toString(), customer_status: customer.status.toString(),
invoice_number: customer.invoiceNumber.toString(), customer_number: customer.customerNumber.toString(),
invoice_series: customer.invoiceSeries.toString(), customer_series: customer.customerSeries.toString(),
issue_date: customer.issueDate.toDateString(), issue_date: customer.issueDate.toDateString(),
operation_date: customer.operationDate.toDateString(), operation_date: customer.operationDate.toDateString(),
language_code: "ES", language_code: "ES",

View File

@ -1 +1 @@
export * from "./get-invoice.assembler"; export * from "./get-customer.assembler";

View File

@ -21,12 +21,12 @@ export class GetCustomerUseCase {
return this.transactionManager.complete(async (transaction) => { return this.transactionManager.complete(async (transaction) => {
try { try {
const invoiceOrError = await this.service.getById(idOrError.data, transaction); const customerOrError = await this.service.getById(idOrError.data, transaction);
if (invoiceOrError.isFailure) { if (customerOrError.isFailure) {
return Result.fail(invoiceOrError.error); return Result.fail(customerOrError.error);
} }
const getDTO = this.assembler.toDTO(invoiceOrError.data); const getDTO = this.assembler.toDTO(customerOrError.data);
return Result.ok(getDTO); return Result.ok(getDTO);
} catch (error: unknown) { } catch (error: unknown) {
return Result.fail(error as Error); return Result.fail(error as Error);

View File

@ -19,16 +19,16 @@ import { mapDTOToCustomerItemsProps } from "./map-dto-to-customer-items-props";
export function mapDTOToCustomerProps(dto: CreateCustomerCommandDTO) { export function mapDTOToCustomerProps(dto: CreateCustomerCommandDTO) {
const errors: ValidationErrorDetail[] = []; const errors: ValidationErrorDetail[] = [];
const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", errors); const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
const invoiceNumber = extractOrPushError( const customerNumber = extractOrPushError(
CustomerNumber.create(dto.invoice_number), CustomerNumber.create(dto.customer_number),
"invoice_number", "customer_number",
errors errors
); );
const invoiceSeries = extractOrPushError( const customerSeries = extractOrPushError(
CustomerSerie.create(dto.invoice_series), CustomerSerie.create(dto.customer_series),
"invoice_series", "customer_series",
errors errors
); );
const issueDate = extractOrPushError(UtcDate.createFromISO(dto.issue_date), "issue_date", errors); const issueDate = extractOrPushError(UtcDate.createFromISO(dto.issue_date), "issue_date", errors);
@ -51,23 +51,23 @@ export function mapDTOToCustomerProps(dto: CreateCustomerCommandDTO) {
return Result.fail(new ValidationErrorCollection(errors)); return Result.fail(new ValidationErrorCollection(errors));
} }
const invoiceProps: CustomerProps = { const customerProps: CustomerProps = {
invoiceNumber: invoiceNumber!, customerNumber: customerNumber!,
invoiceSeries: invoiceSeries!, customerSeries: customerSeries!,
issueDate: issueDate!, issueDate: issueDate!,
operationDate: operationDate!, operationDate: operationDate!,
status: CustomerStatus.createDraft(), status: CustomerStatus.createDraft(),
currency, currency,
}; };
return Result.ok({ id: invoiceId!, props: invoiceProps }); return Result.ok({ id: customerId!, props: customerProps });
/*if (hasNoUndefinedFields(invoiceProps)) { /*if (hasNoUndefinedFields(customerProps)) {
const invoiceOrError = Customer.create(invoiceProps, invoiceId); const customerOrError = Customer.create(customerProps, customerId);
if (invoiceOrError.isFailure) { if (customerOrError.isFailure) {
return Result.fail(invoiceOrError.error); return Result.fail(customerOrError.error);
} }
return Result.ok(invoiceOrError.data); return Result.ok(customerOrError.data);
} }
return Result.fail( return Result.fail(

View File

@ -1 +1 @@
export * from "./list-invoices.assembler"; export * from "./list-customers.assembler";

View File

@ -0,0 +1,70 @@
import { Criteria } from "@repo/rdx-criteria/server";
import { Collection } from "@repo/rdx-utils";
import { CustomerListResponsetDTO } from "../../../../common/dto";
import { Customer } from "../../../domain";
export class ListCustomersAssembler {
toDTO(customers: Collection<Customer>, criteria: Criteria): CustomerListResponsetDTO {
const items: CustomerListResponsetDTO["items"] = customers.map((customer) => {
const address = customer.address.toPrimitive();
return {
id: customer.id.toPrimitive(),
reference: customer.reference,
is_freelancer: customer.isFreelancer,
name: customer.name,
trade_name: customer.tradeName.getOrUndefined(),
tin: customer.tin.toString(),
street: address.street,
city: address.city,
state: address.state,
postal_code: address.postalCode,
country: address.country,
email: customer.email.getValue(),
phone: customer.phone.getValue(),
fax: customer.fax.getOrUndefined(),
website: customer.website.getOrUndefined(),
legal_record: customer.legalRecord,
default_tax: customer.defaultTax,
status: customer.isActive ? 'active' : 'inactive',
lang_code: customer.langCode,
currency_code: customer.currencyCode,
metadata: {
entity: "customer",
id: customer.id.toPrimitive(),
//created_at: customer.createdAt.toPrimitive(),
//updated_at: customer.updatedAt.toPrimitive()
}
};
});
const totalItems = customers.total();
return {
page: criteria.pageNumber,
per_page: criteria.pageSize,
total_pages: Math.ceil(totalItems / criteria.pageSize),
total_items: totalItems,
items: items,
metadata: {
entity: "customers",
criteria: criteria.toJSON(),
//links: {
// self: `/api/customers?page=${criteria.pageNumber}&per_page=${criteria.pageSize}`,
// first: `/api/customers?page=1&per_page=${criteria.pageSize}`,
// last: `/api/customers?page=${Math.ceil(totalItems / criteria.pageSize)}&per_page=${criteria.pageSize}`,
//},
},
};
},
}

View File

@ -1,51 +0,0 @@
import { Criteria } from "@repo/rdx-criteria/server";
import { Collection } from "@repo/rdx-utils";
import { CustomerListResponsetDTO } from "../../../../common/dto";
import { Customer } from "../../../domain";
export class ListCustomersAssembler {
toDTO(customers: Collection<Customer>, criteria: Criteria): CustomerListResponsetDTO {
const items = customers.map((invoice) => {
return {
id: invoice.id.toPrimitive(),
invoice_status: invoice.status.toString(),
invoice_number: invoice.invoiceNumber.toString(),
invoice_series: invoice.invoiceSeries.toString(),
issue_date: invoice.issueDate.toISOString(),
operation_date: invoice.operationDate.toISOString(),
language_code: "ES",
currency: "EUR",
subtotal_price: invoice.calculateSubtotal().toPrimitive(),
total_price: invoice.calculateTotal().toPrimitive(),
//recipient: CustomerParticipantAssembler(customer.recipient),
metadata: {
entity: "customer",
},
};
});
const totalItems = customers.total();
return {
page: criteria.pageNumber,
per_page: criteria.pageSize,
total_pages: Math.ceil(totalItems / criteria.pageSize),
total_items: totalItems,
items: items,
metadata: {
entity: "customers",
criteria: criteria.toJSON(),
//links: {
// self: `/api/customers?page=${criteria.pageNumber}&per_page=${criteria.pageSize}`,
// first: `/api/customers?page=1&per_page=${criteria.pageSize}`,
// last: `/api/customers?page=${Math.ceil(totalItems / criteria.pageSize)}&per_page=${criteria.pageSize}`,
//},
},
};
},
};

View File

@ -1,11 +1,16 @@
import { ITransactionManager } from "@erp/core/api"; import { ITransactionManager } from "@erp/core/api";
import { ListCustomersResultDTO } from "@erp/customers/common/dto";
import { Criteria } from "@repo/rdx-criteria/server"; import { Criteria } from "@repo/rdx-criteria/server";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize"; import { Transaction } from "sequelize";
import { CustomerListResponsetDTO } from "../../../common/dto";
import { ICustomerService } from "../../domain"; import { ICustomerService } from "../../domain";
import { ListCustomersAssembler } from "./assembler"; import { ListCustomersAssembler } from "./assembler";
type ListCustomersUseCaseInput = {
tenantId: string;
criteria: Criteria;
};
export class ListCustomersUseCase { export class ListCustomersUseCase {
constructor( constructor(
private readonly customerService: ICustomerService, private readonly customerService: ICustomerService,
@ -13,17 +18,20 @@ export class ListCustomersUseCase {
private readonly assembler: ListCustomersAssembler private readonly assembler: ListCustomersAssembler
) {} ) {}
public execute(criteria: Criteria): Promise<Result<ListCustomersResultDTO, Error>> { public execute(
params: ListCustomersUseCaseInput
): Promise<Result<CustomerListResponsetDTO, Error>> {
const { criteria, tenantId } = params;
return this.transactionManager.complete(async (transaction: Transaction) => { return this.transactionManager.complete(async (transaction: Transaction) => {
try { try {
const result = await this.customerService.findByCriteria(criteria, transaction); const result = await this.customerService.findByCriteria(criteria, transaction);
if (result.isFailure) { if (result.isFailure) {
console.error(result.error);
return Result.fail(result.error); return Result.fail(result.error);
} }
const dto: ListCustomersResultDTO = this.assembler.toDTO(result.data, criteria); const dto = this.assembler.toDTO(result.data, criteria);
return Result.ok(dto); return Result.ok(dto);
} catch (error: unknown) { } catch (error: unknown) {
return Result.fail(error as Error); return Result.fail(error as Error);

View File

@ -5,7 +5,7 @@ import {
forbidQueryFieldGuard, forbidQueryFieldGuard,
tenantGuard, tenantGuard,
} from "@erp/core/api"; } from "@erp/core/api";
import { ListCustomersUseCase } from "../../../../application"; import { ListCustomersUseCase } from "../../../application";
export class ListCustomersController extends ExpressController { export class ListCustomersController extends ExpressController {
public constructor(private readonly listCustomers: ListCustomersUseCase) { public constructor(private readonly listCustomers: ListCustomersUseCase) {

View File

@ -1,3 +1,4 @@
import { enforceTenant } from "@erp/auth/api";
import { ILogger, ModuleParams, validateRequest } from "@erp/core/api"; 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";

View File

@ -3,7 +3,7 @@ import * as z from "zod/v4";
export const CustomerListResponseSchema = createListViewResponseSchema( export const CustomerListResponseSchema = createListViewResponseSchema(
z.object({ z.object({
id: z.string(), id: z.uuid(),
reference: z.string(), reference: z.string(),
is_freelancer: z.boolean(), is_freelancer: z.boolean(),

View File

@ -583,6 +583,9 @@ importers:
'@dnd-kit/utilities': '@dnd-kit/utilities':
specifier: ^3.2.2 specifier: ^3.2.2
version: 3.2.2(react@19.1.0) version: 3.2.2(react@19.1.0)
'@erp/auth':
specifier: workspace:*
version: link:../auth
'@erp/core': '@erp/core':
specifier: workspace:* specifier: workspace:*
version: link:../core version: link:../core