This commit is contained in:
David Arranz 2026-03-07 23:11:39 +01:00
parent 05f048cc72
commit 220d57cb7b
17 changed files with 150 additions and 118 deletions

View File

@ -1,32 +1,13 @@
// application/issued-invoices/di/snapshot-builders.di.ts // application/issued-invoices/di/snapshot-builders.di.ts
import { CustomerSummarySnapshotBuilder } from "../snapshot-builders"; import { CustomerFullSnapshotBuilder, CustomerSummarySnapshotBuilder } from "../snapshot-builders";
export function buildCustomerSnapshotBuilders() { export function buildCustomerSnapshotBuilders() {
/*const itemsBuilder = new CustomerItemsFullSnapshotBuilder(); const fullSnapshotBuilder = new CustomerFullSnapshotBuilder();
const taxesBuilder = new CustomerTaxesFullSnapshotBuilder();
const recipientBuilder = new CustomerRecipientFullSnapshotBuilder();
const fullSnapshotBuilder = new CustomerFullSnapshotBuilder(
itemsBuilder,
recipientBuilder,
taxesBuilder
);*/
const summarySnapshotBuilder = new CustomerSummarySnapshotBuilder(); const summarySnapshotBuilder = new CustomerSummarySnapshotBuilder();
/*const itemsReportBuilder = new CustomerItemReportSnapshotBuilder();
const taxesReportBuilder = new CustomerTaxReportSnapshotBuilder();
const reportSnapshotBuilder = new CustomerReportSnapshotBuilder(
itemsReportBuilder,
taxesReportBuilder
);*/
return { return {
//full: fullSnapshotBuilder, full: fullSnapshotBuilder,
summary: summarySnapshotBuilder, summary: summarySnapshotBuilder,
//report: reportSnapshotBuilder,
}; };
} }

View File

@ -1,16 +1,19 @@
import type { ITransactionManager } from "@erp/core/api"; import type { ITransactionManager } from "@erp/core/api";
import type { ICustomerFinder } from "../services"; import type { ICustomerFinder } from "../services";
import type { ICustomerSummarySnapshotBuilder } from "../snapshot-builders"; import type {
import { ListCustomersUseCase } from "../use-cases"; ICustomerFullSnapshotBuilder,
ICustomerSummarySnapshotBuilder,
} from "../snapshot-builders";
import { GetCustomerByIdUseCase, ListCustomersUseCase } from "../use-cases";
/*export function buildGetCustomerByIdUseCase(deps: { export function buildGetCustomerByIdUseCase(deps: {
finder: ICustomerFinder; finder: ICustomerFinder;
fullSnapshotBuilder: ICustomerFullSnapshotBuilder; fullSnapshotBuilder: ICustomerFullSnapshotBuilder;
transactionManager: ITransactionManager; transactionManager: ITransactionManager;
}) { }) {
return new GetCustomerByIdUseCase(deps.finder, deps.fullSnapshotBuilder, deps.transactionManager); return new GetCustomerByIdUseCase(deps.finder, deps.fullSnapshotBuilder, deps.transactionManager);
}*/ }
export function buildListCustomersUseCase(deps: { export function buildListCustomersUseCase(deps: {
finder: ICustomerFinder; finder: ICustomerFinder;

View File

@ -11,12 +11,10 @@ import type {
} from "@repo/rdx-ddd"; } from "@repo/rdx-ddd";
import type { Maybe } from "@repo/rdx-utils"; import type { Maybe } from "@repo/rdx-utils";
import type { CustomerStatus } from "../../domain";
export type CustomerSummary = { export type CustomerSummary = {
id: UniqueID; id: UniqueID;
companyId: UniqueID; companyId: UniqueID;
status: CustomerStatus; isActive: boolean;
reference: Maybe<Name>; reference: Maybe<Name>;
isCompany: boolean; isCompany: boolean;

View File

@ -1 +0,0 @@
export * from "./customer.full.presenter";

View File

@ -1,24 +1,26 @@
import { Presenter } from "@erp/core/api"; import type { ISnapshotBuilder } from "@erp/core/api";
import { maybeToEmptyString } from "@repo/rdx-ddd"; import { maybeToEmptyString } from "@repo/rdx-ddd";
import type { GetCustomerByIdResponseDTO } from "../../../../common/dto";
import type { Customer } from "../../../domain"; import type { Customer } from "../../../domain";
export class CustomerFullPresenter extends Presenter<Customer, GetCustomerByIdResponseDTO> { import type { ICustomerFullSnapshot } from "./customer-snapshot.interface";
toOutput(customer: Customer): GetCustomerByIdResponseDTO {
export interface ICustomerFullSnapshotBuilder
extends ISnapshotBuilder<Customer, ICustomerFullSnapshot> {}
export class CustomerFullSnapshotBuilder implements ICustomerFullSnapshotBuilder {
toOutput(customer: Customer): ICustomerFullSnapshot {
const address = customer.address.toPrimitive(); const address = customer.address.toPrimitive();
return { return {
id: customer.id.toPrimitive(), id: customer.id.toPrimitive(),
company_id: customer.companyId.toPrimitive(), company_id: customer.companyId.toPrimitive(),
status: customer.isActive ? "active" : "inactive",
reference: maybeToEmptyString(customer.reference, (value) => value.toPrimitive()), reference: maybeToEmptyString(customer.reference, (value) => value.toPrimitive()),
is_company: String(customer.isCompany), is_company: String(customer.isCompany),
name: customer.name.toPrimitive(), name: customer.name.toPrimitive(),
trade_name: maybeToEmptyString(customer.tradeName, (value) => value.toPrimitive()), trade_name: maybeToEmptyString(customer.tradeName, (value) => value.toPrimitive()),
tin: maybeToEmptyString(customer.tin, (value) => value.toPrimitive()), tin: maybeToEmptyString(customer.tin, (value) => value.toPrimitive()),
street: maybeToEmptyString(address.street, (value) => value.toPrimitive()), street: maybeToEmptyString(address.street, (value) => value.toPrimitive()),
@ -30,8 +32,10 @@ export class CustomerFullPresenter extends Presenter<Customer, GetCustomerByIdRe
email_primary: maybeToEmptyString(customer.emailPrimary, (value) => value.toPrimitive()), email_primary: maybeToEmptyString(customer.emailPrimary, (value) => value.toPrimitive()),
email_secondary: maybeToEmptyString(customer.emailSecondary, (value) => value.toPrimitive()), email_secondary: maybeToEmptyString(customer.emailSecondary, (value) => value.toPrimitive()),
phone_primary: maybeToEmptyString(customer.phonePrimary, (value) => value.toPrimitive()), phone_primary: maybeToEmptyString(customer.phonePrimary, (value) => value.toPrimitive()),
phone_secondary: maybeToEmptyString(customer.phoneSecondary, (value) => value.toPrimitive()), phone_secondary: maybeToEmptyString(customer.phoneSecondary, (value) => value.toPrimitive()),
mobile_primary: maybeToEmptyString(customer.mobilePrimary, (value) => value.toPrimitive()), mobile_primary: maybeToEmptyString(customer.mobilePrimary, (value) => value.toPrimitive()),
mobile_secondary: maybeToEmptyString(customer.mobileSecondary, (value) => mobile_secondary: maybeToEmptyString(customer.mobileSecondary, (value) =>
value.toPrimitive() value.toPrimitive()
@ -44,7 +48,6 @@ export class CustomerFullPresenter extends Presenter<Customer, GetCustomerByIdRe
default_taxes: customer.defaultTaxes.getAll().map((tax) => tax.toString()), default_taxes: customer.defaultTaxes.getAll().map((tax) => tax.toString()),
status: customer.isActive ? "active" : "inactive",
language_code: customer.languageCode.toPrimitive(), language_code: customer.languageCode.toPrimitive(),
currency_code: customer.currencyCode.toPrimitive(), currency_code: customer.currencyCode.toPrimitive(),

View File

@ -0,0 +1,39 @@
export interface ICustomerFullSnapshot {
id: string;
company_id: string;
status: string;
reference: string;
is_company: string;
name: string;
trade_name: string;
tin: string;
street: string;
street2: string;
city: string;
province: string;
postal_code: string;
country: string;
email_primary: string;
email_secondary: string;
phone_primary: string;
phone_secondary: string;
mobile_primary: string;
mobile_secondary: string;
fax: string;
website: string;
legal_record: string;
default_taxes: string[];
language_code: string;
currency_code: string;
metadata?: Record<string, string>;
}

View File

@ -0,0 +1,2 @@
export * from "./customer-snapshot.interface";
export * from "./customer-snapshot-builder";

View File

@ -1,3 +1,2 @@
//export * from "./full"; export * from "./domain";
export * from "./summary"; export * from "./summary";
//export * from "./report";

View File

@ -15,7 +15,7 @@ export class CustomerSummarySnapshotBuilder implements ICustomerSummarySnapshotB
return { return {
id: customer.id.toString(), id: customer.id.toString(),
company_id: customer.companyId.toString(), company_id: customer.companyId.toString(),
status: customer.status.toString(), status: customer.isActive ? "active" : "inactive",
reference: maybeToEmptyString(customer.reference, (value) => value.toString()), reference: maybeToEmptyString(customer.reference, (value) => value.toString()),
is_company: String(customer.isCompany), is_company: String(customer.isCompany),

View File

@ -1,12 +1,18 @@
import { DuplicateEntityError, IPresenterRegistry, ITransactionManager } from "@erp/core/api"; import {
import { UniqueID } from "@repo/rdx-ddd"; DuplicateEntityError,
type IPresenterRegistry,
type ITransactionManager,
} from "@erp/core/api";
import type { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize"; import type { Transaction } from "sequelize";
import { CreateCustomerRequestDTO } from "../../../../common";
import type { CreateCustomerRequestDTO } from "../../../../common";
import { logger } from "../../..//helpers"; import { logger } from "../../..//helpers";
import { CustomerApplicationService } from "../../customer-application.service"; import type { CustomerApplicationService } from "../../customer-application.service";
import { CustomerFullPresenter } from "../../presenters"; import type { CustomerFullSnapshotBuilder } from "../../presenters";
import { CustomerNotExistsInCompanySpecification } from "../../specs"; import { CustomerNotExistsInCompanySpecification } from "../../specs";
import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props"; import { mapDTOToCreateCustomerProps } from "./map-dto-to-create-customer-props";
type CreateCustomerUseCaseInput = { type CreateCustomerUseCaseInput = {
@ -26,7 +32,7 @@ export class CreateCustomerUseCase {
const presenter = this.presenterRegistry.getPresenter({ const presenter = this.presenterRegistry.getPresenter({
resource: "customer", resource: "customer",
projection: "FULL", projection: "FULL",
}) as CustomerFullPresenter; }) as CustomerFullSnapshotBuilder;
// 1) Mapear DTO → props de dominio // 1) Mapear DTO → props de dominio
const dtoResult = mapDTOToCreateCustomerProps(dto); const dtoResult = mapDTOToCreateCustomerProps(dto);

View File

@ -0,0 +1,50 @@
import type { ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import type { ICustomerFinder } from "../services";
import type { ICustomerFullSnapshotBuilder } from "../snapshot-builders";
type GetCustomerUseCaseInput = {
companyId: UniqueID;
customer_id: string;
};
export class GetCustomerByIdUseCase {
constructor(
private readonly finder: ICustomerFinder,
private readonly fullSnapshotBuilder: ICustomerFullSnapshotBuilder,
private readonly transactionManager: ITransactionManager
) {}
public execute(params: GetCustomerUseCaseInput) {
const { customer_id, companyId } = params;
const idOrError = UniqueID.create(customer_id);
if (idOrError.isFailure) {
return Result.fail(idOrError.error);
}
const customerId = idOrError.data;
return this.transactionManager.complete(async (transaction) => {
try {
const customerResult = await this.finder.findCustomerById(
companyId,
customerId,
transaction
);
if (customerResult.isFailure) {
return Result.fail(customerResult.error);
}
const fullSnapshot = this.fullSnapshotBuilder.toOutput(customerResult.data);
return Result.ok(fullSnapshot);
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -1,54 +0,0 @@
import { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { CustomerApplicationService } from "../../application";
import { CustomerFullPresenter } from "../presenters";
type GetCustomerUseCaseInput = {
companyId: UniqueID;
customer_id: string;
};
export class GetCustomerUseCase {
constructor(
private readonly service: CustomerApplicationService,
private readonly transactionManager: ITransactionManager,
private readonly presenterRegistry: IPresenterRegistry
) {}
public execute(params: GetCustomerUseCaseInput) {
const { customer_id, companyId } = params;
const idOrError = UniqueID.create(customer_id);
if (idOrError.isFailure) {
return Result.fail(idOrError.error);
}
const customerId = idOrError.data;
const presenter = this.presenterRegistry.getPresenter({
resource: "customer",
projection: "FULL",
}) as CustomerFullPresenter;
return this.transactionManager.complete(async (transaction) => {
try {
const customerOrError = await this.service.getCustomerByIdInCompany(
companyId,
customerId,
transaction
);
if (customerOrError.isFailure) {
return Result.fail(customerOrError.error);
}
const customer = customerOrError.data;
const dto = presenter.toOutput(customer);
return Result.ok(dto);
} catch (error: unknown) {
return Result.fail(error as Error);
}
});
}
}

View File

@ -1,5 +1,5 @@
export * from "./create"; export * from "./create";
export * from "./delete-customer.use-case"; export * from "./delete-customer.use-case";
export * from "./get-customer.use-case"; export * from "./get-customer-by-id.use-case";
export * from "./list-customers.use-case"; export * from "./list-customers.use-case";
export * from "./update"; export * from "./update";

View File

@ -1,11 +1,13 @@
import { IPresenterRegistry, ITransactionManager } from "@erp/core/api"; import type { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd"; import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { Transaction } from "sequelize"; import type { Transaction } from "sequelize";
import { UpdateCustomerByIdRequestDTO } from "../../../../common/dto";
import { CustomerPatchProps } from "../../../domain"; import type { UpdateCustomerByIdRequestDTO } from "../../../../common/dto";
import { CustomerApplicationService } from "../../customer-application.service"; import type { CustomerPatchProps } from "../../../domain";
import { CustomerFullPresenter } from "../../presenters"; import type { CustomerApplicationService } from "../../customer-application.service";
import type { CustomerFullSnapshotBuilder } from "../../presenters";
import { mapDTOToUpdateCustomerPatchProps } from "./map-dto-to-update-customer-props"; import { mapDTOToUpdateCustomerPatchProps } from "./map-dto-to-update-customer-props";
type UpdateCustomerUseCaseInput = { type UpdateCustomerUseCaseInput = {
@ -33,7 +35,7 @@ export class UpdateCustomerUseCase {
const presenter = this.presenterRegistry.getPresenter({ const presenter = this.presenterRegistry.getPresenter({
resource: "customer", resource: "customer",
projection: "FULL", projection: "FULL",
}) as CustomerFullPresenter; }) as CustomerFullSnapshotBuilder;
// Mapear DTO → props de dominio // Mapear DTO → props de dominio
const patchPropsResult = mapDTOToUpdateCustomerPatchProps(dto); const patchPropsResult = mapDTOToUpdateCustomerPatchProps(dto);

View File

@ -1,9 +1,11 @@
import { type ModuleParams, buildCatalogs, buildTransactionManager } from "@erp/core/api"; import { type ModuleParams, buildCatalogs, buildTransactionManager } from "@erp/core/api";
import { import {
type GetCustomerByIdUseCase,
type ListCustomersUseCase, type ListCustomersUseCase,
buildCustomerFinder, buildCustomerFinder,
buildCustomerSnapshotBuilders, buildCustomerSnapshotBuilders,
buildGetCustomerByIdUseCase,
buildListCustomersUseCase, buildListCustomersUseCase,
} from "../../application"; } from "../../application";
@ -13,7 +15,7 @@ import { buildCustomerRepository } from "./customer-repositories.di";
export type CustomersInternalDeps = { export type CustomersInternalDeps = {
useCases: { useCases: {
listCustomers: () => ListCustomersUseCase; listCustomers: () => ListCustomersUseCase;
//getCustomerById: () => GetCustomerByIdUseCase; getCustomerById: () => GetCustomerByIdUseCase;
//reportCustomer: () => ReportCustomerUseCase; //reportCustomer: () => ReportCustomerUseCase;
//createCustomer: () => CreateCustomerUseCase; //createCustomer: () => CreateCustomerUseCase;
@ -54,14 +56,14 @@ export function buildCustomersDependencies(params: ModuleParams): CustomersInter
transactionManager, transactionManager,
}), }),
/*getCustomerById: () => getCustomerById: () =>
buildGetCustomerByIdUseCase({ buildGetCustomerByIdUseCase({
finder, finder,
fullSnapshotBuilder: snapshotBuilders.full, fullSnapshotBuilder: snapshotBuilders.full,
transactionManager, transactionManager,
}), }),
reportCustomer: () => /*reportCustomer: () =>
buildReportCustomerUseCase({ buildReportCustomerUseCase({
finder, finder,
fullSnapshotBuilder: snapshotBuilders.full, fullSnapshotBuilder: snapshotBuilders.full,

View File

@ -7,11 +7,13 @@ import { type ModuleParams, RequestWithAuth, validateRequest } from "@erp/core/a
import { type NextFunction, type Request, type Response, Router } from "express"; import { type NextFunction, type Request, type Response, Router } from "express";
import { import {
CustomerListRequestSchema CustomerListRequestSchema,
GetCustomerByIdRequestSchema
} from "../../../common/dto"; } from "../../../common/dto";
import type { CustomersInternalDeps } from "../di"; import type { CustomersInternalDeps } from "../di";
import { import {
GetCustomerController,
ListCustomersController ListCustomersController
} from "./controllers"; } from "./controllers";
@ -51,17 +53,17 @@ export const customersRouter = (params: ModuleParams, deps: CustomersInternalDep
} }
); );
/* router.get( router.get(
"/:customer_id", "/:customer_id",
//checkTabContext, //checkTabContext,
validateRequest(GetCustomerByIdRequestSchema, "params"), validateRequest(GetCustomerByIdRequestSchema, "params"),
(req: Request, res: Response, next: NextFunction) => { (req: Request, res: Response, next: NextFunction) => {
const useCase = deps.useCases.get(); const useCase = deps.useCases.getCustomerById();
const controller = new GetCustomerController(useCase); const controller = new GetCustomerController(useCase);
return controller.execute(req, res, next); return controller.execute(req, res, next);
} }
); */ );
/* router.post( /* router.post(
"/", "/",

View File

@ -190,7 +190,7 @@ export class SequelizeCustomerSummaryMapper
return Result.ok<CustomerSummary>({ return Result.ok<CustomerSummary>({
id: customerId!, id: customerId!,
companyId: companyId!, companyId: companyId!,
status: status!, isActive: status!.isActive(),
reference: reference!, reference: reference!,
isCompany: isCompany, isCompany: isCompany,