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 { CustomerInvoiceListResponseDTO } from "@erp/customer-invoices/common/dto";
import { Criteria } from "@repo/rdx-criteria/server";
import { Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize";
import { CustomerInvoiceListResponseDTO } from "../../../common/dto";
import { ICustomerInvoiceService } from "../../domain";
import { ListCustomerInvoicesAssembler } from "./assembler";
@ -31,7 +31,7 @@ export class ListCustomerInvoicesUseCase {
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);
} catch (error: unknown) {
return Result.fail(error as Error);

View File

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

View File

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

View File

@ -14,21 +14,21 @@ export class CreateCustomerUseCase {
) {}
public execute(dto: CreateCustomerCommandDTO) {
const invoicePropsOrError = mapDTOToCustomerProps(dto);
const customerPropsOrError = mapDTOToCustomerProps(dto);
if (invoicePropsOrError.isFailure) {
return Result.fail(invoicePropsOrError.error);
if (customerPropsOrError.isFailure) {
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) {
return Result.fail(invoiceOrError.error);
if (customerOrError.isFailure) {
return Result.fail(customerOrError.error);
}
const newInvoice = invoiceOrError.data;
const newCustomer = customerOrError.data;
return this.transactionManager.complete(async (transaction: Transaction) => {
try {
@ -42,12 +42,12 @@ export class CreateCustomerUseCase {
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) {
return Result.fail(result.error);
}
const viewDTO = this.assembler.toDTO(newInvoice);
const viewDTO = this.assembler.toDTO(newCustomer);
return Result.ok(viewDTO);
} catch (error: unknown) {
return Result.fail(error as Error);

View File

@ -6,9 +6,9 @@ export class GetCustomerAssembler {
return {
id: customer.id.toPrimitive(),
invoice_status: customer.status.toString(),
invoice_number: customer.invoiceNumber.toString(),
invoice_series: customer.invoiceSeries.toString(),
customer_status: customer.status.toString(),
customer_number: customer.customerNumber.toString(),
customer_series: customer.customerSeries.toString(),
issue_date: customer.issueDate.toDateString(),
operation_date: customer.operationDate.toDateString(),
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) => {
try {
const invoiceOrError = await this.service.getById(idOrError.data, transaction);
if (invoiceOrError.isFailure) {
return Result.fail(invoiceOrError.error);
const customerOrError = await this.service.getById(idOrError.data, transaction);
if (customerOrError.isFailure) {
return Result.fail(customerOrError.error);
}
const getDTO = this.assembler.toDTO(invoiceOrError.data);
const getDTO = this.assembler.toDTO(customerOrError.data);
return Result.ok(getDTO);
} catch (error: unknown) {
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) {
const errors: ValidationErrorDetail[] = [];
const invoiceId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
const customerId = extractOrPushError(UniqueID.create(dto.id), "id", errors);
const invoiceNumber = extractOrPushError(
CustomerNumber.create(dto.invoice_number),
"invoice_number",
const customerNumber = extractOrPushError(
CustomerNumber.create(dto.customer_number),
"customer_number",
errors
);
const invoiceSeries = extractOrPushError(
CustomerSerie.create(dto.invoice_series),
"invoice_series",
const customerSeries = extractOrPushError(
CustomerSerie.create(dto.customer_series),
"customer_series",
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));
}
const invoiceProps: CustomerProps = {
invoiceNumber: invoiceNumber!,
invoiceSeries: invoiceSeries!,
const customerProps: CustomerProps = {
customerNumber: customerNumber!,
customerSeries: customerSeries!,
issueDate: issueDate!,
operationDate: operationDate!,
status: CustomerStatus.createDraft(),
currency,
};
return Result.ok({ id: invoiceId!, props: invoiceProps });
return Result.ok({ id: customerId!, props: customerProps });
/*if (hasNoUndefinedFields(invoiceProps)) {
const invoiceOrError = Customer.create(invoiceProps, invoiceId);
if (invoiceOrError.isFailure) {
return Result.fail(invoiceOrError.error);
/*if (hasNoUndefinedFields(customerProps)) {
const customerOrError = Customer.create(customerProps, customerId);
if (customerOrError.isFailure) {
return Result.fail(customerOrError.error);
}
return Result.ok(invoiceOrError.data);
return Result.ok(customerOrError.data);
}
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 { ListCustomersResultDTO } from "@erp/customers/common/dto";
import { Criteria } from "@repo/rdx-criteria/server";
import { Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize";
import { CustomerListResponsetDTO } from "../../../common/dto";
import { ICustomerService } from "../../domain";
import { ListCustomersAssembler } from "./assembler";
type ListCustomersUseCaseInput = {
tenantId: string;
criteria: Criteria;
};
export class ListCustomersUseCase {
constructor(
private readonly customerService: ICustomerService,
@ -13,17 +18,20 @@ export class ListCustomersUseCase {
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) => {
try {
const result = await this.customerService.findByCriteria(criteria, transaction);
if (result.isFailure) {
console.error(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);
} catch (error: unknown) {
return Result.fail(error as Error);

View File

@ -5,7 +5,7 @@ import {
forbidQueryFieldGuard,
tenantGuard,
} from "@erp/core/api";
import { ListCustomersUseCase } from "../../../../application";
import { ListCustomersUseCase } from "../../../application";
export class ListCustomersController extends ExpressController {
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 { Application, NextFunction, Request, Response, Router } from "express";
import { Sequelize } from "sequelize";

View File

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

View File

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