Facturas de cliente
This commit is contained in:
parent
2036ec2206
commit
4807e51d82
@ -64,12 +64,12 @@ export class InMemoryPresenterRegistry implements IPresenterRegistry {
|
||||
|
||||
if (!this.registry.has(exactKey)) {
|
||||
throw new ApplicationError(
|
||||
`Error. Presenter ${key.resource} ${key.projection} not registred!`
|
||||
`[InMemoryPresenterRegistry] Presenter not registered: ${key.resource}::${key.projection}`
|
||||
);
|
||||
}
|
||||
|
||||
throw new ApplicationError(
|
||||
`Error. Presenter ${key.resource} / ${key.projection} not registred!`
|
||||
`[InMemoryPresenterRegistry] Presenter not registered: ${key.resource}::${key.projection}`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -10,14 +10,9 @@ export class InMemoryMapperRegistry implements IMapperRegistry {
|
||||
private _mappers: Map<string, any> = new Map();
|
||||
|
||||
private _normalizeKey(key: MapperDomainKey | MapperQueryKey): MapperKey {
|
||||
const { resource, query } = key as {
|
||||
resource: string;
|
||||
query?: string;
|
||||
};
|
||||
|
||||
return {
|
||||
resource,
|
||||
query: query ?? "DOMAIN", // 👈 valor por defecto
|
||||
resource: key.resource,
|
||||
query: "query" in key && key.query ? key.query : "DOMAIN",
|
||||
};
|
||||
}
|
||||
|
||||
@ -37,37 +32,37 @@ export class InMemoryMapperRegistry implements IMapperRegistry {
|
||||
const exactKey = this._buildKey(normalizedKey);
|
||||
|
||||
if (!this._mappers.has(exactKey)) {
|
||||
throw new InfrastructureError(`Error. Mapper ${normalizedKey.resource} not registred!`);
|
||||
throw new InfrastructureError(
|
||||
`[InMemoryMapperRegistry] Mapper not registered: ${normalizedKey.resource}::${normalizedKey.query}`
|
||||
);
|
||||
}
|
||||
|
||||
return this._mappers.get(exactKey);
|
||||
}
|
||||
|
||||
private _registerMapper<T>(key: MapperDomainKey | MapperQueryKey, mapper: T) {
|
||||
const exactKey = this._buildKey(this._normalizeKey(key));
|
||||
this._mappers.set(exactKey, mapper);
|
||||
}
|
||||
|
||||
getDomainMapper<T>(key: MapperDomainKey): T {
|
||||
const normalizedKey = this._normalizeKey({
|
||||
resource: key.resource,
|
||||
return this._getMapper({
|
||||
...key,
|
||||
query: "DOMAIN",
|
||||
});
|
||||
return this._getMapper(normalizedKey);
|
||||
}
|
||||
|
||||
getQueryMapper<T>(key: MapperQueryKey): T {
|
||||
const normalizedKey = this._normalizeKey({
|
||||
resource: key.resource,
|
||||
query: "DOMAIN",
|
||||
});
|
||||
return this._getMapper(normalizedKey);
|
||||
return this._getMapper(key);
|
||||
}
|
||||
|
||||
registerDomainMapper<T>(key: MapperDomainKey, mapper: T) {
|
||||
const exactKey = this._buildKey(this._normalizeKey(key));
|
||||
this._mappers.set(exactKey, mapper);
|
||||
this._registerMapper(key, mapper);
|
||||
return this;
|
||||
}
|
||||
|
||||
registerQueryMapper<T>(key: MapperQueryKey, mapper: T) {
|
||||
const exactKey = this._buildKey(this._normalizeKey(key));
|
||||
this._mappers.set(exactKey, mapper);
|
||||
this._registerMapper(key, mapper);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,10 @@ import { GetCustomerInvoiceByIdResponseDTO } from "../../../../common/dto";
|
||||
import { CustomerInvoice } from "../../../domain";
|
||||
import { CustomerInvoiceItemsFullPresenter } from "./customer-invoice-items.full.presenter";
|
||||
|
||||
export class CustomerInvoiceFullPresenter extends Presenter {
|
||||
export class CustomerInvoiceFullPresenter extends Presenter<
|
||||
CustomerInvoice,
|
||||
GetCustomerInvoiceByIdResponseDTO
|
||||
> {
|
||||
toOutput(invoice: CustomerInvoice): GetCustomerInvoiceByIdResponseDTO {
|
||||
const itemsPresenter = this.presenterRegistry.getPresenter({
|
||||
resource: "customer-invoice-items",
|
||||
|
||||
@ -11,7 +11,10 @@ export class CreateCustomerInvoiceController extends ExpressController {
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId();
|
||||
if (!companyId) {
|
||||
return this.forbiddenError("Tenant ID not found");
|
||||
}
|
||||
const dto = this.req.body as CreateCustomerInvoiceRequestDTO;
|
||||
|
||||
const result = await this.useCase.execute({ dto, companyId });
|
||||
|
||||
@ -9,7 +9,10 @@ export class GetCustomerInvoiceController extends ExpressController {
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId();
|
||||
if (!companyId) {
|
||||
return this.forbiddenError("Tenant ID not found");
|
||||
}
|
||||
const { invoice_id } = this.req.params;
|
||||
|
||||
const result = await this.useCase.execute({ invoice_id, companyId });
|
||||
|
||||
@ -9,7 +9,7 @@ export class ListCustomerInvoicesController extends ExpressController {
|
||||
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
||||
}
|
||||
|
||||
private _xxxx() {
|
||||
private getCriteriaWithDefaultOrder() {
|
||||
if (this.criteria.hasOrder()) {
|
||||
return this.criteria;
|
||||
}
|
||||
@ -19,9 +19,12 @@ export class ListCustomerInvoicesController extends ExpressController {
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId();
|
||||
if (!companyId) {
|
||||
return this.forbiddenError("Tenant ID not found");
|
||||
}
|
||||
|
||||
const criteria = this._xxxx();
|
||||
const criteria = this.getCriteriaWithDefaultOrder();
|
||||
const result = await this.useCase.execute({ criteria, companyId });
|
||||
|
||||
return result.match(
|
||||
|
||||
@ -9,7 +9,10 @@ export class ReportCustomerInvoiceController extends ExpressController {
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId();
|
||||
if (!companyId) {
|
||||
return this.forbiddenError("Tenant ID not found");
|
||||
}
|
||||
const { invoice_id } = this.req.params;
|
||||
|
||||
const result = await this.useCase.execute({ invoice_id, companyId });
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
import { CustomerCreationResponseDTO } from "../../../../common";
|
||||
import { Customer } from "../../../domain";
|
||||
|
||||
export class CreateCustomersPresenter {
|
||||
public toDTO(customer: Customer): CustomerCreationResponseDTO {
|
||||
const address = customer.address.toPrimitive();
|
||||
|
||||
return {
|
||||
id: customer.id.toPrimitive(),
|
||||
company_id: customer.companyId.toPrimitive(),
|
||||
|
||||
reference: toEmptyString(customer.reference, (value) => value.toPrimitive()),
|
||||
|
||||
is_company: String(customer.isCompany),
|
||||
name: customer.name.toPrimitive(),
|
||||
|
||||
trade_name: toEmptyString(customer.tradeName, (value) => value.toPrimitive()),
|
||||
|
||||
tin: toEmptyString(customer.tin, (value) => value.toPrimitive()),
|
||||
|
||||
street: toEmptyString(address.street, (value) => value.toPrimitive()),
|
||||
street2: toEmptyString(address.street2, (value) => value.toPrimitive()),
|
||||
city: toEmptyString(address.city, (value) => value.toPrimitive()),
|
||||
province: toEmptyString(address.province, (value) => value.toPrimitive()),
|
||||
postal_code: toEmptyString(address.postalCode, (value) => value.toPrimitive()),
|
||||
country: toEmptyString(address.country, (value) => value.toPrimitive()),
|
||||
|
||||
email: toEmptyString(customer.email, (value) => value.toPrimitive()),
|
||||
phone: toEmptyString(customer.phone, (value) => value.toPrimitive()),
|
||||
fax: toEmptyString(customer.fax, (value) => value.toPrimitive()),
|
||||
website: toEmptyString(customer.website, (value) => value.toPrimitive()),
|
||||
|
||||
legal_record: toEmptyString(customer.legalRecord, (value) => value.toPrimitive()),
|
||||
|
||||
default_taxes: customer.defaultTaxes.getAll().join(", "),
|
||||
|
||||
status: customer.isActive ? "active" : "inactive",
|
||||
language_code: customer.languageCode.toPrimitive(),
|
||||
currency_code: customer.currencyCode.toPrimitive(),
|
||||
|
||||
metadata: {
|
||||
entity: "customer",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./create-customers.presenter";
|
||||
@ -1 +0,0 @@
|
||||
export * from "./delete-customer.use-case";
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./presenter";
|
||||
export * from "./get-customer.use-case";
|
||||
@ -1,16 +0,0 @@
|
||||
import { CustomerItem } from "#/server/domain";
|
||||
import { IInvoicingContext } from "#/server/intrastructure";
|
||||
import { Collection } from "@rdx/core";
|
||||
|
||||
export const customerItemPresenter = (items: Collection<CustomerItem>, context: IInvoicingContext) =>
|
||||
items.totalCount > 0
|
||||
? items.items.map((item: CustomerItem) => ({
|
||||
description: item.description.toString(),
|
||||
quantity: item.quantity.toString(),
|
||||
unit_measure: "",
|
||||
unit_price: item.unitPrice.toPrimitive() as IMoney_Response_DTO,
|
||||
subtotal: item.calculateSubtotal().toPrimitive() as IMoney_Response_DTO,
|
||||
tax_amount: item.calculateTaxAmount().toPrimitive() as IMoney_Response_DTO,
|
||||
total: item.calculateTotal().toPrimitive() as IMoney_Response_DTO,
|
||||
}))
|
||||
: [];
|
||||
@ -1,26 +0,0 @@
|
||||
import { ICustomerParticipant } from "@/contexts/invoicing/domain";
|
||||
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||
import { ICreateCustomer_Participant_Response_DTO } from "@shared/contexts";
|
||||
import { CustomerParticipantAddressPresenter } from "./CustomerParticipantAddress.presenter";
|
||||
|
||||
export const CustomerParticipantPresenter = async (
|
||||
participant: ICustomerParticipant,
|
||||
context: IInvoicingContext,
|
||||
): Promise<ICreateCustomer_Participant_Response_DTO | undefined> => {
|
||||
return {
|
||||
id: participant.id.toString(),
|
||||
tin: participant.tin.toString(),
|
||||
first_name: participant.firstName.toString(),
|
||||
last_name: participant.lastName.toString(),
|
||||
company_name: participant.companyName.toString(),
|
||||
|
||||
billing_address: await CustomerParticipantAddressPresenter(
|
||||
participant.billingAddress!,
|
||||
context,
|
||||
),
|
||||
shipping_address: await CustomerParticipantAddressPresenter(
|
||||
participant.shippingAddress!,
|
||||
context,
|
||||
),
|
||||
};
|
||||
};
|
||||
@ -1,19 +0,0 @@
|
||||
import { CustomerParticipantAddress } from "@/contexts/invoicing/domain";
|
||||
import { IInvoicingContext } from "@/contexts/invoicing/intrastructure/InvoicingContext";
|
||||
import { ICreateCustomer_AddressParticipant_Response_DTO } from "@shared/contexts";
|
||||
|
||||
export const CustomerParticipantAddressPresenter = async (
|
||||
address: CustomerParticipantAddress,
|
||||
context: IInvoicingContext,
|
||||
): Promise<ICreateCustomer_AddressParticipant_Response_DTO> => {
|
||||
return {
|
||||
id: address.id.toString(),
|
||||
street: address.street.toString(),
|
||||
city: address.city.toString(),
|
||||
postal_code: address.postalCode.toString(),
|
||||
province: address.province.toString(),
|
||||
country: address.country.toString(),
|
||||
email: address.email.toString(),
|
||||
phone: address.phone.toString(),
|
||||
};
|
||||
};
|
||||
@ -1 +0,0 @@
|
||||
export * from "./get-customer.presenter";
|
||||
@ -1,5 +1,2 @@
|
||||
export * from "./create-customer";
|
||||
export * from "./delete-customer";
|
||||
export * from "./get-customer";
|
||||
export * from "./list-customers";
|
||||
export * from "./update-customer";
|
||||
export * from "./presenters";
|
||||
export * from "./use-cases";
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./presenter";
|
||||
export * from "./list-customers.use-case";
|
||||
@ -1,72 +0,0 @@
|
||||
import { Criteria } from "@repo/rdx-criteria/server";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
import { CustomerListResponsetDTO } from "../../../../common/dto";
|
||||
import { Customer } from "../../../domain";
|
||||
|
||||
export class ListCustomersPresenter {
|
||||
toDTO(customers: Collection<Customer>, criteria: Criteria): CustomerListResponsetDTO {
|
||||
const items: CustomerListResponsetDTO["items"] = customers.map((customer) => {
|
||||
const address = customer.address.toPrimitive();
|
||||
|
||||
return {
|
||||
id: customer.id.toPrimitive(),
|
||||
company_id: customer.companyId.toPrimitive(),
|
||||
|
||||
reference: toEmptyString(customer.reference, (value) => value.toPrimitive()),
|
||||
|
||||
is_company: String(customer.isCompany),
|
||||
name: customer.name.toPrimitive(),
|
||||
|
||||
trade_name: toEmptyString(customer.tradeName, (value) => value.toPrimitive()),
|
||||
|
||||
tin: toEmptyString(customer.tin, (value) => value.toPrimitive()),
|
||||
|
||||
street: toEmptyString(address.street, (value) => value.toPrimitive()),
|
||||
street2: toEmptyString(address.street2, (value) => value.toPrimitive()),
|
||||
city: toEmptyString(address.city, (value) => value.toPrimitive()),
|
||||
province: toEmptyString(address.province, (value) => value.toPrimitive()),
|
||||
postal_code: toEmptyString(address.postalCode, (value) => value.toPrimitive()),
|
||||
country: toEmptyString(address.country, (value) => value.toPrimitive()),
|
||||
|
||||
email: toEmptyString(customer.email, (value) => value.toPrimitive()),
|
||||
phone: toEmptyString(customer.phone, (value) => value.toPrimitive()),
|
||||
fax: toEmptyString(customer.fax, (value) => value.toPrimitive()),
|
||||
website: toEmptyString(customer.website, (value) => value.toPrimitive()),
|
||||
|
||||
legal_record: toEmptyString(customer.legalRecord, (value) => value.toPrimitive()),
|
||||
|
||||
default_taxes: customer.defaultTaxes.getAll().join(", "),
|
||||
|
||||
status: customer.isActive ? "active" : "inactive",
|
||||
language_code: customer.languageCode.toPrimitive(),
|
||||
currency_code: customer.currencyCode.toPrimitive(),
|
||||
|
||||
metadata: {
|
||||
entity: "customer",
|
||||
//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}`,
|
||||
//},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,10 @@
|
||||
import { Presenter } from "@erp/core/api";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
import { GetCustomerByIdResponseDTO } from "../../../../common/dto";
|
||||
import { Customer } from "../../../domain";
|
||||
|
||||
export class GetCustomerPresenter {
|
||||
toDTO(customer: Customer): GetCustomerByIdResponseDTO {
|
||||
export class CustomerFullPresenter extends Presenter<Customer, GetCustomerByIdResponseDTO> {
|
||||
toOutput(customer: Customer): GetCustomerByIdResponseDTO {
|
||||
const address = customer.address.toPrimitive();
|
||||
|
||||
return {
|
||||
@ -0,0 +1 @@
|
||||
export * from "./customer.full.presenter";
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./domain";
|
||||
export * from "./queries";
|
||||
@ -1,9 +1,12 @@
|
||||
import { Presenter } from "@erp/core/api";
|
||||
import { CustomerListDTO } from "@erp/customer-invoices/api/infrastructure";
|
||||
import { Criteria } from "@repo/rdx-criteria/server";
|
||||
import { toEmptyString } from "@repo/rdx-ddd";
|
||||
import { UpdateCustomerByIdResponseDTO } from "../../../../common/dto";
|
||||
import { Customer } from "../../../domain";
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
import { CustomerListResponsetDTO } from "../../../../common/dto";
|
||||
|
||||
export class UpdateCustomerPresenter {
|
||||
toDTO(customer: Customer): UpdateCustomerByIdResponseDTO {
|
||||
export class ListCustomersPresenter extends Presenter {
|
||||
protected _mapCustomer(customer: CustomerListDTO) {
|
||||
const address = customer.address.toPrimitive();
|
||||
|
||||
return {
|
||||
@ -31,20 +34,42 @@ export class UpdateCustomerPresenter {
|
||||
fax: toEmptyString(customer.fax, (value) => value.toPrimitive()),
|
||||
website: toEmptyString(customer.website, (value) => value.toPrimitive()),
|
||||
|
||||
legal_record: toEmptyString(customer.legalRecord, (value) => value.toPrimitive()),
|
||||
|
||||
default_taxes: customer.defaultTaxes.getAll().join(", "),
|
||||
|
||||
status: customer.isActive ? "active" : "inactive",
|
||||
status: customer.status ? "active" : "inactive",
|
||||
language_code: customer.languageCode.toPrimitive(),
|
||||
currency_code: customer.currencyCode.toPrimitive(),
|
||||
|
||||
metadata: {
|
||||
entity: "customer",
|
||||
//id: customer.id.toPrimitive(),
|
||||
//created_at: customer.createdAt.toPrimitive(),
|
||||
//updated_at: customer.updatedAt.toPrimitive()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
toOutput(params: {
|
||||
customers: Collection<CustomerListDTO>;
|
||||
criteria: Criteria;
|
||||
}): CustomerListResponsetDTO {
|
||||
const { customers, criteria } = params;
|
||||
|
||||
const items = customers.map((customer) => this._mapCustomer(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/customer-invoices?page=${criteria.pageNumber}&per_page=${criteria.pageSize}`,
|
||||
// first: `/api/customer-invoices?page=1&per_page=${criteria.pageSize}`,
|
||||
// last: `/api/customer-invoices?page=${Math.ceil(totalItems / criteria.pageSize)}&per_page=${criteria.pageSize}`,
|
||||
//},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from "./update-customer.presenter";
|
||||
@ -1,2 +1 @@
|
||||
export * from "./presenter";
|
||||
export * from "./create-customer.use-case";
|
||||
@ -24,8 +24,8 @@ import {
|
||||
maybeFromNullableVO,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Collection, Result, isNullishOrEmpty } from "@repo/rdx-utils";
|
||||
import { CreateCustomerRequestDTO } from "../../../common/dto";
|
||||
import { CustomerProps, CustomerStatus } from "../../domain";
|
||||
import { CreateCustomerRequestDTO } from "../../../../common";
|
||||
import { CustomerProps, CustomerStatus } from "../../../domain";
|
||||
|
||||
/**
|
||||
* Convierte el DTO a las props validadas (CustomerProps).
|
||||
@ -40,9 +40,7 @@ export class DeleteCustomerUseCase {
|
||||
const customerExists = existsCheck.data;
|
||||
|
||||
if (!customerExists) {
|
||||
return Result.fail(
|
||||
new EntityNotFoundError("Customer", "id", customerId.toObjectString())
|
||||
);
|
||||
return Result.fail(new EntityNotFoundError("Customer", "id", customerId.toString()));
|
||||
}
|
||||
|
||||
return await this.service.deleteCustomerByIdInCompany(customerId, companyId, transaction);
|
||||
@ -1,8 +1,8 @@
|
||||
import { ITransactionManager } from "@erp/core/api";
|
||||
import { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { CustomerService } from "../../domain";
|
||||
import { GetCustomerAssembler } from "./assembler";
|
||||
import { CustomerFullPresenter } from "../presenters";
|
||||
|
||||
type GetCustomerUseCaseInput = {
|
||||
companyId: UniqueID;
|
||||
@ -13,19 +13,22 @@ export class GetCustomerUseCase {
|
||||
constructor(
|
||||
private readonly service: CustomerService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
private readonly assembler: GetCustomerAssembler
|
||||
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 {
|
||||
@ -39,8 +42,10 @@ export class GetCustomerUseCase {
|
||||
return Result.fail(customerOrError.error);
|
||||
}
|
||||
|
||||
const getDTO = this.assembler.toDTO(customerOrError.data);
|
||||
return Result.ok(getDTO);
|
||||
const customer = customerOrError.data;
|
||||
const dto = presenter.toOutput(customer);
|
||||
|
||||
return Result.ok(dto);
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
}
|
||||
5
modules/customers/src/api/application/use-cases/index.ts
Normal file
5
modules/customers/src/api/application/use-cases/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./create";
|
||||
export * from "./delete-customer.use-case";
|
||||
export * from "./get-customer.use-case";
|
||||
export * from "./list-customers.use-case";
|
||||
export * from "./update";
|
||||
@ -1,11 +1,11 @@
|
||||
import { ITransactionManager } from "@erp/core/api";
|
||||
import { IPresenterRegistry, ITransactionManager } from "@erp/core/api";
|
||||
import { Criteria } from "@repo/rdx-criteria/server";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Transaction } from "sequelize";
|
||||
import { CustomerListResponsetDTO } from "../../../common/dto";
|
||||
import { CustomerService } from "../../domain";
|
||||
import { ListCustomersAssembler } from "./assembler";
|
||||
import { ListCustomersPresenter } from "../presenters";
|
||||
|
||||
type ListCustomersUseCaseInput = {
|
||||
companyId: UniqueID;
|
||||
@ -14,19 +14,23 @@ type ListCustomersUseCaseInput = {
|
||||
|
||||
export class ListCustomersUseCase {
|
||||
constructor(
|
||||
private readonly customerService: CustomerService,
|
||||
private readonly service: CustomerService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
private readonly assembler: ListCustomersAssembler
|
||||
private readonly presenterRegistry: IPresenterRegistry
|
||||
) {}
|
||||
|
||||
public execute(
|
||||
params: ListCustomersUseCaseInput
|
||||
): Promise<Result<CustomerListResponsetDTO, Error>> {
|
||||
const { criteria, companyId } = params;
|
||||
const presenter = this.presenterRegistry.getPresenter({
|
||||
resource: "customer",
|
||||
projection: "LIST",
|
||||
}) as ListCustomersPresenter;
|
||||
|
||||
return this.transactionManager.complete(async (transaction: Transaction) => {
|
||||
try {
|
||||
const result = await this.customerService.findCustomerByCriteriaInCompany(
|
||||
const result = await this.service.findCustomerByCriteriaInCompany(
|
||||
companyId,
|
||||
criteria,
|
||||
transaction
|
||||
@ -36,7 +40,12 @@ export class ListCustomersUseCase {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
const dto = this.assembler.toDTO(result.data, criteria);
|
||||
const customers = result.data;
|
||||
const dto = presenter.toOutput({
|
||||
customers,
|
||||
criteria,
|
||||
});
|
||||
|
||||
return Result.ok(dto);
|
||||
} catch (error: unknown) {
|
||||
return Result.fail(error as Error);
|
||||
@ -1,2 +1 @@
|
||||
export * from "./presenter";
|
||||
export * from "./update-customer.use-case";
|
||||
@ -1,6 +1,7 @@
|
||||
import { Criteria } from "@repo/rdx-criteria/server";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { CustomerListDTO } from "../../infrastructure";
|
||||
import { Customer, CustomerPatchProps, CustomerProps } from "../aggregates";
|
||||
import { ICustomerRepository } from "../repositories";
|
||||
|
||||
@ -64,7 +65,7 @@ export class CustomerService {
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction?: any
|
||||
): Promise<Result<Collection<Customer>, Error>> {
|
||||
): Promise<Result<Collection<CustomerListDTO>, Error>> {
|
||||
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
|
||||
}
|
||||
|
||||
|
||||
@ -4,16 +4,14 @@ import {
|
||||
InMemoryPresenterRegistry,
|
||||
SequelizeTransactionManager,
|
||||
} from "@erp/core/api";
|
||||
|
||||
import {
|
||||
CreateCustomerUseCase,
|
||||
DeleteCustomerUseCase,
|
||||
GetCustomerUseCase,
|
||||
CustomerFullPresenter,
|
||||
ListCustomersPresenter,
|
||||
ListCustomersUseCase,
|
||||
UpdateCustomerUseCase,
|
||||
} from "../application";
|
||||
import { GetCustomerUseCase } from "../application/use-cases/get-customer.use-case";
|
||||
import { CustomerService } from "../domain";
|
||||
import { CustomerDomainMapper } from "./mappers";
|
||||
import { CustomerDomainMapper, CustomerListMapper } from "./mappers";
|
||||
import { CustomerRepository } from "./sequelize";
|
||||
|
||||
export type CustomerDeps = {
|
||||
@ -25,9 +23,9 @@ export type CustomerDeps = {
|
||||
build: {
|
||||
list: () => ListCustomersUseCase;
|
||||
get: () => GetCustomerUseCase;
|
||||
create: () => CreateCustomerUseCase;
|
||||
/*create: () => CreateCustomerUseCase;
|
||||
update: () => UpdateCustomerUseCase;
|
||||
delete: () => DeleteCustomerUseCase;
|
||||
delete: () => DeleteCustomerUseCase;*/
|
||||
};
|
||||
};
|
||||
|
||||
@ -37,10 +35,12 @@ export function buildCustomerDependencies(params: ModuleParams): CustomerDeps {
|
||||
|
||||
// Mapper Registry
|
||||
const mapperRegistry = new InMemoryMapperRegistry();
|
||||
mapperRegistry.registerDomainMapper({ resource: "customer" }, new CustomerDomainMapper());
|
||||
mapperRegistry
|
||||
.registerDomainMapper({ resource: "customer" }, new CustomerDomainMapper())
|
||||
.registerQueryMapper({ resource: "customer", query: "LIST" }, new CustomerListMapper());
|
||||
|
||||
// Repository & Services
|
||||
const repo = new CustomerRepository({ mapperRegistry: _mapperRegistry, database });
|
||||
const repo = new CustomerRepository({ mapperRegistry, database });
|
||||
const service = new CustomerService(repo);
|
||||
|
||||
// Presenter Registry
|
||||
@ -48,11 +48,11 @@ export function buildCustomerDependencies(params: ModuleParams): CustomerDeps {
|
||||
presenterRegistry.registerPresenters([
|
||||
{
|
||||
key: { resource: "customer", projection: "FULL" },
|
||||
presenter: new ListCustomersAssembler(),
|
||||
presenter: new CustomerFullPresenter(presenterRegistry),
|
||||
},
|
||||
{
|
||||
key: { resource: "customer", projection: "LIST" },
|
||||
presenter: new GetCustomerAssembler(),
|
||||
presenter: new ListCustomersPresenter(presenterRegistry),
|
||||
},
|
||||
]);
|
||||
|
||||
@ -63,8 +63,8 @@ export function buildCustomerDependencies(params: ModuleParams): CustomerDeps {
|
||||
presenterRegistry,
|
||||
service,
|
||||
build: {
|
||||
/*list: () => new ListCustomersUseCase(_service!, transactionManager!, presenterRegistry!),
|
||||
get: () => new GetCustomerUseCase(_service!, transactionManager!, presenterRegistry!),
|
||||
list: () => new ListCustomersUseCase(service, transactionManager, presenterRegistry),
|
||||
/*get: () => new GetCustomerUseCase(_service!, transactionManager!, presenterRegistry!),
|
||||
create: () => new CreateCustomerUseCase(_service!, transactionManager!, presenterRegistry!),
|
||||
update: () => new UpdateCustomerUseCase(_service!, transactionManager!, presenterRegistry!),
|
||||
delete: () => new DeleteCustomerUseCase(_service!, transactionManager!),*/
|
||||
|
||||
@ -10,7 +10,10 @@ export class CreateCustomerController extends ExpressController {
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId();
|
||||
if (!companyId) {
|
||||
return this.forbiddenError("Tenant ID not found");
|
||||
}
|
||||
const dto = this.req.body as CreateCustomerRequestDTO;
|
||||
|
||||
const result = await this.useCase.execute({ dto, companyId });
|
||||
|
||||
@ -9,7 +9,10 @@ export class DeleteCustomerController extends ExpressController {
|
||||
}
|
||||
|
||||
async executeImpl(): Promise<any> {
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId();
|
||||
if (!companyId) {
|
||||
return this.forbiddenError("Tenant ID not found");
|
||||
}
|
||||
const { customer_id } = this.req.params;
|
||||
|
||||
const result = await this.useCase.execute({ customer_id, companyId });
|
||||
|
||||
@ -9,7 +9,10 @@ export class GetCustomerController extends ExpressController {
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId();
|
||||
if (!companyId) {
|
||||
return this.forbiddenError("Tenant ID not found");
|
||||
}
|
||||
const { customer_id } = this.req.params;
|
||||
|
||||
const result = await this.useCase.execute({ customer_id, companyId });
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||
import { Criteria } from "@repo/rdx-criteria/server";
|
||||
import { ListCustomersUseCase } from "../../../application";
|
||||
|
||||
export class ListCustomersController extends ExpressController {
|
||||
@ -8,9 +9,23 @@ export class ListCustomersController extends ExpressController {
|
||||
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
||||
}
|
||||
|
||||
private getCriteriaWithDefaultOrder() {
|
||||
if (this.criteria.hasOrder()) {
|
||||
return this.criteria;
|
||||
}
|
||||
|
||||
const { filters, pageSize, pageNumber } = this.criteria.toPrimitives();
|
||||
return Criteria.fromPrimitives(filters, "name", "ASC", pageSize, pageNumber);
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const result = await this.listCustomers.execute({ criteria: this.criteria, companyId });
|
||||
const companyId = this.getTenantId();
|
||||
if (!companyId) {
|
||||
return this.forbiddenError("Tenant ID not found");
|
||||
}
|
||||
|
||||
const criteria = this.getCriteriaWithDefaultOrder();
|
||||
const result = await this.listCustomers.execute({ criteria, companyId });
|
||||
|
||||
return result.match(
|
||||
(data) =>
|
||||
|
||||
@ -10,7 +10,10 @@ export class UpdateCustomerController extends ExpressController {
|
||||
}
|
||||
|
||||
protected async executeImpl() {
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
const companyId = this.getTenantId();
|
||||
if (!companyId) {
|
||||
return this.forbiddenError("Tenant ID not found");
|
||||
}
|
||||
const { customer_id } = this.req.params;
|
||||
const dto = this.req.body as UpdateCustomerRequestDTO;
|
||||
|
||||
|
||||
@ -2,22 +2,9 @@ import { RequestWithAuth, enforceTenant, enforceUser, mockUser } from "@erp/auth
|
||||
import { ILogger, ModuleParams, validateRequest } from "@erp/core/api";
|
||||
import { Application, NextFunction, Request, Response, Router } from "express";
|
||||
import { Sequelize } from "sequelize";
|
||||
import {
|
||||
CreateCustomerRequestSchema,
|
||||
CustomerListRequestSchema,
|
||||
DeleteCustomerByIdRequestSchema,
|
||||
GetCustomerByIdRequestSchema,
|
||||
UpdateCustomerByIdParamsRequestSchema,
|
||||
UpdateCustomerByIdRequestSchema,
|
||||
} from "../../../common/dto";
|
||||
import { getCustomerDependencies } from "../dependencies";
|
||||
import {
|
||||
CreateCustomerController,
|
||||
DeleteCustomerController,
|
||||
GetCustomerController,
|
||||
ListCustomersController,
|
||||
} from "./controllers";
|
||||
import { UpdateCustomerController } from "./controllers/update-customer.controller";
|
||||
import { CustomerListRequestSchema, GetCustomerByIdRequestSchema } from "../../../common/dto";
|
||||
import { buildCustomerDependencies } from "../dependencies";
|
||||
import { GetCustomerController, ListCustomersController } from "./controllers";
|
||||
|
||||
export const customersRouter = (params: ModuleParams) => {
|
||||
const { app, database, baseRoutePath, logger } = params as {
|
||||
@ -27,7 +14,7 @@ export const customersRouter = (params: ModuleParams) => {
|
||||
logger: ILogger;
|
||||
};
|
||||
|
||||
const deps = getCustomerDependencies(params);
|
||||
const deps = buildCustomerDependencies(params);
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
@ -74,7 +61,7 @@ export const customersRouter = (params: ModuleParams) => {
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
/*router.post(
|
||||
"/",
|
||||
//checkTabContext,
|
||||
|
||||
@ -108,7 +95,7 @@ export const customersRouter = (params: ModuleParams) => {
|
||||
const controller = new DeleteCustomerController(useCase);
|
||||
return controller.execute(req, res, next);
|
||||
}
|
||||
);
|
||||
); */
|
||||
|
||||
app.use(`${baseRoutePath}/customers`, router);
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ export const CustomerListResponseSchema = createListViewResponseSchema(
|
||||
z.object({
|
||||
id: z.uuid(),
|
||||
company_id: z.uuid(),
|
||||
status: z.string(),
|
||||
reference: z.string(),
|
||||
|
||||
is_company: z.string(),
|
||||
@ -23,10 +24,9 @@ export const CustomerListResponseSchema = createListViewResponseSchema(
|
||||
fax: z.string(),
|
||||
website: z.string(),
|
||||
|
||||
legal_record: z.string(),
|
||||
//legal_record: z.string(),
|
||||
//default_taxes: z.string(),
|
||||
|
||||
default_taxes: z.string(),
|
||||
status: z.string(),
|
||||
language_code: z.string(),
|
||||
currency_code: z.string(),
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user