Facturas de cliente

This commit is contained in:
David Arranz 2025-09-04 12:42:11 +02:00
parent e65524ea3c
commit 206d5bf9d3
11 changed files with 222 additions and 18 deletions

View File

@ -1,8 +1,8 @@
import { GetCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
import { UpdateCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
import { toEmptyString } from "@repo/rdx-ddd";
import { CustomerInvoice } from "../../../domain";
type GetCustomerInvoiceItemsByInvoiceIdResponseDTO = GetCustomerInvoiceByIdResponseDTO["items"];
type GetCustomerInvoiceItemsByInvoiceIdResponseDTO = UpdateCustomerInvoiceByIdResponseDTO["items"];
export class GetCustomerInvoiceItemsAssembler {
toDTO(invoice: CustomerInvoice): GetCustomerInvoiceItemsByInvoiceIdResponseDTO {

View File

@ -1,4 +1,4 @@
import { GetCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
import { UpdateCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
import { toEmptyString } from "@repo/rdx-ddd";
import { CustomerInvoice } from "../../../domain";
import { GetCustomerInvoiceItemsAssembler } from "./get-invoice-items.assembler";
@ -10,7 +10,7 @@ export class GetCustomerInvoiceAssembler {
this._itemsAssembler = new GetCustomerInvoiceItemsAssembler();
}
public toDTO(invoice: CustomerInvoice): GetCustomerInvoiceByIdResponseDTO {
public toDTO(invoice: CustomerInvoice): UpdateCustomerInvoiceByIdResponseDTO {
const items = this._itemsAssembler.toDTO(invoice);
return {

View File

@ -2,4 +2,4 @@ export * from "./create-customer-invoice";
export * from "./delete-customer-invoice";
export * from "./get-customer-invoice";
export * from "./list-customer-invoices";
//export * from "./update-customer-invoice";
export * from "./update-customer-invoice";

View File

@ -1,19 +1,20 @@
import { 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 { CustomerInvoiceListResponseDTO } from "../../../common/dto";
import { ICustomerInvoiceService } from "../../domain";
import { CustomerInvoiceService } from "../../domain";
import { ListCustomerInvoicesAssembler } from "./assembler";
type ListCustomerInvoicesUseCaseInput = {
tenantId: string;
companyId: UniqueID;
criteria: Criteria;
};
export class ListCustomerInvoicesUseCase {
constructor(
private readonly customerInvoiceService: ICustomerInvoiceService,
private readonly service: CustomerInvoiceService,
private readonly transactionManager: ITransactionManager,
private readonly assembler: ListCustomerInvoicesAssembler
) {}
@ -21,12 +22,15 @@ export class ListCustomerInvoicesUseCase {
public execute(
params: ListCustomerInvoicesUseCaseInput
): Promise<Result<CustomerInvoiceListResponseDTO, Error>> {
const { criteria, tenantId } = params;
const { criteria, companyId } = params;
return this.transactionManager.complete(async (transaction: Transaction) => {
try {
//const { rows, total, limit, offset } = await this.repo.searchInCompany(criteria, tenantId);
const result = await this.customerInvoiceService.findByCriteria(criteria, transaction);
const result = await this.service.findInvoiceByCriteriaInCompany(
companyId,
criteria,
transaction
);
if (result.isFailure) {
return Result.fail(result.error);

View File

@ -0,0 +1 @@
export * from "./update-invoice.assembler";

View File

@ -0,0 +1,46 @@
import { toEmptyString } from "@repo/rdx-ddd";
import { UpdateCustomerInvoiceByIdResponseDTO } from "../../../../common/dto";
import { CustomerInvoice } from "../../../domain";
type UpdateCustomerInvoiceItemsByInvoiceIdResponseDTO =
UpdateCustomerInvoiceByIdResponseDTO["items"];
export class UpdateCustomerInvoiceItemsAssembler {
toDTO(invoice: CustomerInvoice): UpdateCustomerInvoiceItemsByInvoiceIdResponseDTO {
const { items } = invoice;
return items.map((item, index) => ({
id: item.id.toString(),
position: String(index),
description: toEmptyString(item.description, (value) => value.toPrimitive()),
quantity: item.quantity.match(
(quantity) => {
const { value, scale } = quantity.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
unit_amount: item.unitAmount.match(
(unitAmount) => {
const { value, scale } = unitAmount.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
discount_percentage: item.discountPercentage.match(
(discountPercentage) => {
const { value, scale } = discountPercentage.toPrimitive();
return { value: value.toString(), scale: scale.toString() };
},
() => ({ value: "", scale: "" })
),
total_amount: {
value: item.totalAmount.toPrimitive().value.toString(),
scale: item.totalAmount.toPrimitive().scale.toString(),
},
}));
}
}

View File

@ -0,0 +1,110 @@
import { toEmptyString } from "@repo/rdx-ddd";
import { UpdateCustomerInvoiceByIdResponseDTO } from "../../../../common/dto";
import { CustomerInvoice } from "../../../domain";
import { UpdateCustomerInvoiceItemsAssembler } from "./update-invoice-items.assembler";
export class UpdateCustomerInvoiceAssembler {
private _itemsAssembler!: UpdateCustomerInvoiceItemsAssembler;
constructor() {
this._itemsAssembler = new UpdateCustomerInvoiceItemsAssembler();
}
public toDTO(invoice: CustomerInvoice): UpdateCustomerInvoiceByIdResponseDTO {
const items = this._itemsAssembler.toDTO(invoice);
return {
id: invoice.id.toPrimitive(),
company_id: invoice.companyId.toPrimitive(),
invoice_number: invoice.invoiceNumber.toString(),
status: invoice.status.toPrimitive(),
series: invoice.series.toString(),
issue_date: invoice.issueDate.toDateString(),
operation_date: toEmptyString(invoice.operationDate, (value) => value.toDateString()),
notes: toEmptyString(invoice.notes, (value) => value.toPrimitive()),
language_code: invoice.languageCode.toPrimitive(),
currency_code: invoice.currencyCode.toPrimitive(),
subtotal_amount: {
value: invoice.subtotalAmount.value.toString(),
scale: invoice.subtotalAmount.scale.toString(),
},
discount_percentage: {
value: invoice.discountPercentage.value.toString(),
scale: invoice.discountPercentage.scale.toString(),
},
discount_amount: {
value: invoice.discountAmount.value.toString(),
scale: invoice.discountAmount.scale.toString(),
},
taxable_amount: {
value: invoice.taxableAmount.value.toString(),
scale: invoice.taxableAmount.scale.toString(),
},
tax_amount: {
value: invoice.taxAmount.value.toString(),
scale: invoice.taxAmount.scale.toString(),
},
total_amount: {
value: invoice.totalAmount.value.toString(),
scale: invoice.totalAmount.scale.toString(),
},
items,
metadata: {
entity: "customer-invoices",
},
//subtotal: customerInvoice.calculateSubtotal().toPrimitive(),
//total: customerInvoice.calculateTotal().toPrimitive(),
/*items:
customerInvoice.items.size() > 0
? customerInvoice.items.map((item: CustomerInvoiceItem) => ({
description: item.description.toString(),
quantity: item.quantity.toPrimitive(),
unit_measure: "",
unit_price: item.unitPrice.toPrimitive(),
subtotal: item.calculateSubtotal().toPrimitive(),
//tax_amount: item.calculateTaxAmount().toPrimitive(),
total: item.calculateTotal().toPrimitive(),
}))
: [],*/
//sender: {}, //await CustomerInvoiceParticipantAssembler(customerInvoice.senderId, context),
/*recipient: await CustomerInvoiceParticipantAssembler(customerInvoice.recipient, context),
items: customerInvoiceItemAssembler(customerInvoice.items, context),
payment_term: {
payment_type: "",
due_date: "",
},
due_amount: {
currency: customerInvoice.currency.toString(),
precision: 2,
amount: 0,
},
custom_fields: [],
metadata: {
create_time: "",
last_updated_time: "",
delete_time: "",
},*/
};
}
}

View File

@ -4,6 +4,7 @@ import {
CreateCustomerInvoiceUseCase,
CreateCustomerInvoicesAssembler,
DeleteCustomerInvoiceUseCase,
GetCustomerInvoiceAssembler,
GetCustomerInvoiceItemsAssembler,
GetCustomerInvoiceUseCase,
ListCustomerInvoicesAssembler,
@ -54,7 +55,7 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
if (!_assemblers) {
_assemblers = {
list: new ListCustomerInvoicesAssembler(), // transforma domain → ListDTO
get: new GetCustomerInvoiceItemsAssembler(), // transforma domain → DetailDTO
get: new GetCustomerInvoiceAssembler(), // transforma domain → DetailDTO
create: new CreateCustomerInvoicesAssembler(), // transforma domain → CreatedDTO
update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO
};

View File

@ -2,18 +2,15 @@ import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from
import { ListCustomerInvoicesUseCase } from "../../../application";
export class ListCustomerInvoicesController extends ExpressController {
public constructor(
private readonly useCase: ListCustomerInvoicesUseCase
/* private readonly presenter: any */
) {
public constructor(private readonly useCase: ListCustomerInvoicesUseCase) {
super();
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
}
protected async executeImpl() {
const tenantId = this.getTenantId()!; // garantizado por tenantGuard
const result = await this.useCase.execute({ criteria: this.criteria, tenantId });
const companyId = this.getTenantId()!; // garantizado por tenantGuard
const result = await this.useCase.execute({ criteria: this.criteria, companyId });
return result.match(
(data) => this.ok(data),

View File

@ -1,3 +1,4 @@
export * from "./customer-invoice-creation.response.dto";
export * from "./customer-invoices-list.response.dto";
export * from "./get-customer-invoice-by-id.response.dto";
export * from "./update-customer-invoice-by-id.response.dto";

View File

@ -0,0 +1,44 @@
import { AmountSchema, MetadataSchema, PercentageSchema, QuantitySchema } from "@erp/core";
import * as z from "zod/v4";
export const UpdateCustomerInvoiceByIdResponseSchema = z.object({
id: z.uuid(),
company_id: z.uuid(),
invoice_number: z.string(),
status: z.string(),
series: z.string(),
issue_date: z.string(),
operation_date: z.string(),
notes: z.string(),
language_code: z.string(),
currency_code: z.string(),
subtotal_amount: AmountSchema,
discount_percentage: PercentageSchema,
discount_amount: AmountSchema,
taxable_amount: AmountSchema,
tax_amount: AmountSchema,
total_amount: AmountSchema,
items: z.array(
z.object({
id: z.uuid(),
position: z.string(),
description: z.string(),
quantity: QuantitySchema,
unit_amount: AmountSchema,
discount_percentage: PercentageSchema,
total_amount: AmountSchema,
})
),
metadata: MetadataSchema.optional(),
});
export type UpdateCustomerInvoiceByIdResponseDTO = z.infer<
typeof UpdateCustomerInvoiceByIdResponseSchema
>;