Facturas de cliente
This commit is contained in:
parent
db4a79422e
commit
5064494b12
23
modules/core/src/common/dto/amount-money.dto.ts
Normal file
23
modules/core/src/common/dto/amount-money.dto.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import * as z from "zod/v4";
|
||||||
|
import { AmountBaseSchema } from "./base.schemas";
|
||||||
|
|
||||||
|
/**
|
||||||
|
Esquema del DTO para valores monetarios con/sin código de moneda.
|
||||||
|
No aplica defaults ni correciones: solo valida.
|
||||||
|
|
||||||
|
- Con moneda -> "Money"
|
||||||
|
- Sin moneda -> "Amount"
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 🔹 Con moneda
|
||||||
|
export const MoneySchema = AmountBaseSchema.extend({
|
||||||
|
currency_code: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 🔹 Sin moneda
|
||||||
|
export const AmountSchema = AmountBaseSchema;
|
||||||
|
|
||||||
|
// 🔹 Tipos DTO
|
||||||
|
export type MoneyDTO = z.infer<typeof MoneySchema>;
|
||||||
|
export type AmountDTO = z.infer<typeof AmountSchema>;
|
||||||
19
modules/core/src/common/dto/base.schemas.ts
Normal file
19
modules/core/src/common/dto/base.schemas.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cadena con valor numérico:
|
||||||
|
*
|
||||||
|
* - Acepta: "" o "123456" (solo dígitos).
|
||||||
|
* - Rechaza: "1 23", "abc123", "12v34", "+123", "12.3"
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
|
||||||
|
export const NumericStringSchema = z
|
||||||
|
.string()
|
||||||
|
.regex(/^\d$/, { message: "Must be empty or contain only digits (0-9)." });
|
||||||
|
|
||||||
|
// Cantidad de dinero (base): solo para la cantidad y la escala, sin moneda
|
||||||
|
export const AmountBaseSchema = z.object({
|
||||||
|
amount: NumericStringSchema,
|
||||||
|
scale: NumericStringSchema,
|
||||||
|
});
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Esquema del objeto normalizado esperado por Criteria.fromPrimitives(...)
|
Esquema del DTO para Criteria.fromPrimitives(...)
|
||||||
No aplica defaults ni correciones: solo valida.
|
No aplica defaults ni correciones: solo valida.
|
||||||
*/
|
*/
|
||||||
export const FilterPrimitiveSchema = z.object({
|
export const FilterPrimitiveSchema = z.object({
|
||||||
// Campos mínimos ya normalizados por el conversor
|
// Campos mínimos ya normalizados por el conversor
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
|
export * from "./amount-money.dto";
|
||||||
|
export * from "./base.schemas";
|
||||||
export * from "./critera.dto";
|
export * from "./critera.dto";
|
||||||
export * from "./error.dto";
|
export * from "./error.dto";
|
||||||
export * from "./list-view.response.dto";
|
export * from "./list-view.response.dto";
|
||||||
export * from "./metadata.dto";
|
export * from "./metadata.dto";
|
||||||
export * from "./money.dto";
|
|
||||||
export * from "./percentage.dto";
|
export * from "./percentage.dto";
|
||||||
export * from "./quantity.dto";
|
export * from "./quantity.dto";
|
||||||
export * from "./tax-type.dto";
|
export * from "./tax-type.dto";
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
export type MoneyDTO = {
|
|
||||||
amount: number | null;
|
|
||||||
scale: number;
|
|
||||||
currency_code: string;
|
|
||||||
};
|
|
||||||
@ -1,4 +1,14 @@
|
|||||||
export type IPercentageDTO = {
|
import * as z from "zod/v4";
|
||||||
amount: number | null;
|
import { NumericStringSchema } from "./base.schemas";
|
||||||
scale: number;
|
|
||||||
};
|
/**
|
||||||
|
Esquema del DTO para valores de porcentajes.
|
||||||
|
No aplica defaults ni correciones: solo valida.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const PercentageSchema = z.object({
|
||||||
|
amount: NumericStringSchema,
|
||||||
|
scale: NumericStringSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PercentageDTO = z.infer<typeof PercentageSchema>;
|
||||||
|
|||||||
@ -1,4 +1,14 @@
|
|||||||
export type IQuantityDTO = {
|
import * as z from "zod/v4";
|
||||||
amount: number | null;
|
import { NumericStringSchema } from "./base.schemas";
|
||||||
scale: number;
|
|
||||||
};
|
/**
|
||||||
|
Esquema del DTO para valores de cantidades.
|
||||||
|
No aplica defaults ni correciones: solo valida.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const QuantitySchema = z.object({
|
||||||
|
amount: NumericStringSchema,
|
||||||
|
scale: NumericStringSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type QuantityDTO = z.infer<typeof QuantitySchema>;
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { IPercentageDTO } from "./percentage.dto";
|
import { PercentageDTO } from "./percentage.dto";
|
||||||
|
|
||||||
export interface ITaxTypeDTO {
|
export interface ITaxTypeDTO {
|
||||||
id: string;
|
id: string;
|
||||||
typecode: string;
|
typecode: string;
|
||||||
taxslug: string;
|
taxslug: string;
|
||||||
taxrate: IPercentageDTO;
|
taxrate: PercentageDTO;
|
||||||
equivalencesurcharge: IPercentageDTO;
|
equivalencesurcharge: PercentageDTO;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITaxTypeResponseDTO extends ITaxTypeDTO {}
|
export interface ITaxTypeResponseDTO extends ITaxTypeDTO {}
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
import { GetCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
|
||||||
|
import { toEmptyString } from "@repo/rdx-ddd";
|
||||||
|
import { CustomerInvoice } from "../../../domain";
|
||||||
|
|
||||||
|
type GetCustomerInvoiceItemsByInvoiceIdResponseDTO = GetCustomerInvoiceByIdResponseDTO["items"];
|
||||||
|
|
||||||
|
export class GetCustomerInvoiceItemsAssembler {
|
||||||
|
toDTO(invoice: CustomerInvoice): GetCustomerInvoiceItemsByInvoiceIdResponseDTO {
|
||||||
|
const { items } = invoice;
|
||||||
|
return items.map((item, index) => ({
|
||||||
|
//id: item.
|
||||||
|
position: index,
|
||||||
|
description: toEmptyString(item.description, (value) => value.toPrimitive()),
|
||||||
|
|
||||||
|
quantity: item.quantity.match(
|
||||||
|
(quantity) => {
|
||||||
|
const { amount, scale } = quantity.toPrimitive();
|
||||||
|
return { amount: amount.toString(), scale: scale.toString() };
|
||||||
|
},
|
||||||
|
() => ({ amount: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
unit_price_amount: item.unitAmount.match(
|
||||||
|
(unitPrice) => {
|
||||||
|
const { amount, scale } = unitPrice.toPrimitive();
|
||||||
|
return { amount: amount.toString(), scale: scale.toString() };
|
||||||
|
},
|
||||||
|
() => ({ amount: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
discount: item.discount.match(
|
||||||
|
(discount) => {
|
||||||
|
const { amount, scale } = discount.toPrimitive();
|
||||||
|
return { amount: amount.toString(), scale: scale.toString() };
|
||||||
|
},
|
||||||
|
() => ({ amount: "", scale: "" })
|
||||||
|
),
|
||||||
|
|
||||||
|
total_amount: item.totalPrice.match(
|
||||||
|
(discount) => {
|
||||||
|
const { amount, scale } = discount.toPrimitive();
|
||||||
|
return { amount: amount.toString(), scale: scale.toString() };
|
||||||
|
},
|
||||||
|
() => ({ amount: "", scale: "" })
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,18 +1,29 @@
|
|||||||
import { GetCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
|
import { GetCustomerInvoiceByIdResponseDTO } from "@erp/customer-invoices/common/dto";
|
||||||
|
import { toEmptyString } from "@repo/rdx-ddd";
|
||||||
import { CustomerInvoice } from "../../../domain";
|
import { CustomerInvoice } from "../../../domain";
|
||||||
|
|
||||||
export class GetCustomerInvoiceAssembler {
|
export class GetCustomerInvoiceAssembler {
|
||||||
toDTO(customerInvoice: CustomerInvoice): GetCustomerInvoiceByIdResponseDTO {
|
private _itemsAssembler!: GetCu;
|
||||||
return {
|
constructor() {}
|
||||||
id: customerInvoice.id.toPrimitive(),
|
|
||||||
|
|
||||||
invoice_status: customerInvoice.status.toString(),
|
public toDTO(invoice: CustomerInvoice): GetCustomerInvoiceByIdResponseDTO {
|
||||||
invoice_number: customerInvoice.invoiceNumber.toString(),
|
//const items = invoice.items.
|
||||||
invoice_series: customerInvoice.invoiceSeries.toString(),
|
|
||||||
issue_date: customerInvoice.issueDate.toDateString(),
|
return {
|
||||||
operation_date: customerInvoice.operationDate.toDateString(),
|
id: invoice.id.toPrimitive(),
|
||||||
language_code: "ES",
|
company_id: invoice.companyId.toPrimitive(),
|
||||||
currency: customerInvoice.currency,
|
|
||||||
|
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(),
|
||||||
|
|
||||||
metadata: {
|
metadata: {
|
||||||
entity: "customer-invoices",
|
entity: "customer-invoices",
|
||||||
|
|||||||
@ -1,32 +1,39 @@
|
|||||||
import { ITransactionManager } from "@erp/core/api";
|
import { 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 { ICustomerInvoiceService } from "../../domain";
|
import { CustomerInvoiceService } from "../../domain";
|
||||||
import { GetCustomerInvoiceAssembler } from "./assembler";
|
import { GetCustomerInvoiceItemsAssembler } from "./assembler";
|
||||||
|
|
||||||
type GetCustomerInvoiceUseCaseInput = {
|
type GetCustomerInvoiceUseCaseInput = {
|
||||||
tenantId: string;
|
companyId: UniqueID;
|
||||||
id: string;
|
invoice_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class GetCustomerInvoiceUseCase {
|
export class GetCustomerInvoiceUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly service: ICustomerInvoiceService,
|
private readonly service: CustomerInvoiceService,
|
||||||
private readonly transactionManager: ITransactionManager,
|
private readonly transactionManager: ITransactionManager,
|
||||||
private readonly assembler: GetCustomerInvoiceAssembler
|
private readonly assembler: GetCustomerInvoiceItemsAssembler
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(params: GetCustomerInvoiceUseCaseInput) {
|
public execute(params: GetCustomerInvoiceUseCaseInput) {
|
||||||
const { id, tenantId } = params;
|
const { invoice_id, companyId } = params;
|
||||||
const idOrError = UniqueID.create(id);
|
|
||||||
|
const idOrError = UniqueID.create(invoice_id);
|
||||||
|
|
||||||
if (idOrError.isFailure) {
|
if (idOrError.isFailure) {
|
||||||
return Result.fail(idOrError.error);
|
return Result.fail(idOrError.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const invoiceId = idOrError.data;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
const invoiceOrError = await this.service.getById(idOrError.data, transaction);
|
const invoiceOrError = await this.service.getInvoiceByIdInCompany(
|
||||||
|
companyId,
|
||||||
|
invoiceId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
if (invoiceOrError.isFailure) {
|
if (invoiceOrError.isFailure) {
|
||||||
return Result.fail(invoiceOrError.error);
|
return Result.fail(invoiceOrError.error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
CustomerInvoiceItemDescription,
|
CustomerInvoiceItemDescription,
|
||||||
CustomerInvoiceItemDiscount,
|
CustomerInvoiceItemDiscount,
|
||||||
CustomerInvoiceItemQuantity,
|
CustomerInvoiceItemQuantity,
|
||||||
CustomerInvoiceItemUnitPrice,
|
CustomerInvoiceItemUnitAmount,
|
||||||
} from "../../domain";
|
} from "../../domain";
|
||||||
import { extractOrPushError } from "./extract-or-push-error";
|
import { extractOrPushError } from "./extract-or-push-error";
|
||||||
import { hasNoUndefinedFields } from "./has-no-undefined-fields";
|
import { hasNoUndefinedFields } from "./has-no-undefined-fields";
|
||||||
@ -36,7 +36,7 @@ export function mapDTOToCustomerInvoiceItemsProps(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const unitPrice = extractOrPushError(
|
const unitPrice = extractOrPushError(
|
||||||
CustomerInvoiceItemUnitPrice.create({
|
CustomerInvoiceItemUnitAmount.create({
|
||||||
amount: item.unitPrice.amount,
|
amount: item.unitPrice.amount,
|
||||||
scale: item.unitPrice.scale,
|
scale: item.unitPrice.scale,
|
||||||
currency_code: item.unitPrice.currency,
|
currency_code: item.unitPrice.currency,
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import {
|
|||||||
AggregateRoot,
|
AggregateRoot,
|
||||||
CurrencyCode,
|
CurrencyCode,
|
||||||
LanguageCode,
|
LanguageCode,
|
||||||
|
MoneyValue,
|
||||||
|
Percentage,
|
||||||
TextValue,
|
TextValue,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
UtcDate,
|
UtcDate,
|
||||||
@ -16,9 +18,10 @@ import {
|
|||||||
|
|
||||||
export interface CustomerInvoiceProps {
|
export interface CustomerInvoiceProps {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
|
|
||||||
|
invoiceNumber: CustomerInvoiceNumber;
|
||||||
status: CustomerInvoiceStatus;
|
status: CustomerInvoiceStatus;
|
||||||
series: Maybe<CustomerInvoiceSerie>;
|
series: Maybe<CustomerInvoiceSerie>;
|
||||||
invoiceNumber: CustomerInvoiceNumber;
|
|
||||||
|
|
||||||
issueDate: UtcDate;
|
issueDate: UtcDate;
|
||||||
operationDate: Maybe<UtcDate>;
|
operationDate: Maybe<UtcDate>;
|
||||||
@ -40,6 +43,16 @@ export interface CustomerInvoiceProps {
|
|||||||
languageCode: LanguageCode;
|
languageCode: LanguageCode;
|
||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
|
|
||||||
|
subtotalAmount: MoneyValue;
|
||||||
|
|
||||||
|
discountPercentage: Percentage;
|
||||||
|
//discountAmount: MoneyValue;
|
||||||
|
|
||||||
|
//taxableAmount: MoneyValue;
|
||||||
|
taxAmount: MoneyValue;
|
||||||
|
|
||||||
|
totalAmount: MoneyValue;
|
||||||
|
|
||||||
//customer?: CustomerInvoiceCustomer;
|
//customer?: CustomerInvoiceCustomer;
|
||||||
items?: CustomerInvoiceItems;
|
items?: CustomerInvoiceItems;
|
||||||
}
|
}
|
||||||
@ -82,6 +95,10 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
|
|||||||
return this.props.companyId;
|
return this.props.companyId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get status(): CustomerInvoiceStatus {
|
||||||
|
return this.props.status;
|
||||||
|
}
|
||||||
|
|
||||||
public get series(): Maybe<CustomerInvoiceSerie> {
|
public get series(): Maybe<CustomerInvoiceSerie> {
|
||||||
return this.props.series;
|
return this.props.series;
|
||||||
}
|
}
|
||||||
@ -110,8 +127,32 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
|
|||||||
return this.props.currencyCode;
|
return this.props.currencyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get subtotalAmount(): MoneyValue {
|
||||||
|
return this.props.subtotalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get discountPercentage(): Percentage {
|
||||||
|
return this.props.discountPercentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get discountAmount(): MoneyValue {
|
||||||
|
throw new Error("discountAmount not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get taxableAmount(): MoneyValue {
|
||||||
|
throw new Error("taxableAmount not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
public get taxAmount(): MoneyValue {
|
||||||
|
return this.props.taxAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get totalAmount(): MoneyValue {
|
||||||
|
throw new Error("totalAmount not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
// Method to get the complete list of line items
|
// Method to get the complete list of line items
|
||||||
get lineItems(): CustomerInvoiceItems {
|
get items(): CustomerInvoiceItems {
|
||||||
return this._items;
|
return this._items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,15 +4,15 @@ import {
|
|||||||
CustomerInvoiceItemDescription,
|
CustomerInvoiceItemDescription,
|
||||||
CustomerInvoiceItemDiscount,
|
CustomerInvoiceItemDiscount,
|
||||||
CustomerInvoiceItemQuantity,
|
CustomerInvoiceItemQuantity,
|
||||||
CustomerInvoiceItemSubtotalPrice,
|
CustomerInvoiceItemSubtotalAmount,
|
||||||
CustomerInvoiceItemTotalPrice,
|
CustomerInvoiceItemTotalAmount,
|
||||||
CustomerInvoiceItemUnitPrice,
|
CustomerInvoiceItemUnitAmount,
|
||||||
} from "../../value-objects";
|
} from "../../value-objects";
|
||||||
|
|
||||||
export interface CustomerInvoiceItemProps {
|
export interface CustomerInvoiceItemProps {
|
||||||
description: Maybe<CustomerInvoiceItemDescription>;
|
description: Maybe<CustomerInvoiceItemDescription>;
|
||||||
quantity: Maybe<CustomerInvoiceItemQuantity>; // Cantidad de unidades
|
quantity: Maybe<CustomerInvoiceItemQuantity>; // Cantidad de unidades
|
||||||
unitPrice: Maybe<CustomerInvoiceItemUnitPrice>; // Precio unitario en la moneda de la factura
|
unitPrice: Maybe<CustomerInvoiceItemUnitAmount>; // Precio unitario en la moneda de la factura
|
||||||
discount: Maybe<CustomerInvoiceItemDiscount>; // % descuento
|
discount: Maybe<CustomerInvoiceItemDiscount>; // % descuento
|
||||||
|
|
||||||
languageCode: LanguageCode;
|
languageCode: LanguageCode;
|
||||||
@ -20,8 +20,8 @@ export interface CustomerInvoiceItemProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps> {
|
export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps> {
|
||||||
private _subtotalPrice!: CustomerInvoiceItemSubtotalPrice;
|
private _subtotalAmount!: CustomerInvoiceItemSubtotalAmount;
|
||||||
private _totalPrice!: CustomerInvoiceItemTotalPrice;
|
private _totalAmount!: CustomerInvoiceItemTotalAmount;
|
||||||
|
|
||||||
public static create(
|
public static create(
|
||||||
props: CustomerInvoiceItemProps,
|
props: CustomerInvoiceItemProps,
|
||||||
@ -48,26 +48,26 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
|
|||||||
return this.props.quantity;
|
return this.props.quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
get unitPrice(): Maybe<CustomerInvoiceItemUnitPrice> {
|
get unitAmount(): Maybe<CustomerInvoiceItemUnitAmount> {
|
||||||
return this.props.unitPrice;
|
return this.props.unitPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
get subtotalPrice(): CustomerInvoiceItemSubtotalPrice {
|
get subtotalAmount(): CustomerInvoiceItemSubtotalAmount {
|
||||||
if (!this._subtotalPrice) {
|
if (!this._subtotalAmount) {
|
||||||
this._subtotalPrice = this.calculateSubtotal();
|
this._subtotalAmount = this.calculateSubtotal();
|
||||||
}
|
}
|
||||||
return this._subtotalPrice;
|
return this._subtotalAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
get discount(): Maybe<CustomerInvoiceItemDiscount> {
|
get discount(): Maybe<CustomerInvoiceItemDiscount> {
|
||||||
return this.props.discount;
|
return this.props.discount;
|
||||||
}
|
}
|
||||||
|
|
||||||
get totalPrice(): CustomerInvoiceItemTotalPrice {
|
get totalPrice(): CustomerInvoiceItemTotalAmount {
|
||||||
if (!this._totalPrice) {
|
if (!this._totalAmount) {
|
||||||
this._totalPrice = this.calculateTotal();
|
this._totalAmount = this.calculateTotal();
|
||||||
}
|
}
|
||||||
return this._totalPrice;
|
return this._totalAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get languageCode(): LanguageCode {
|
public get languageCode(): LanguageCode {
|
||||||
@ -86,7 +86,7 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
|
|||||||
return this.getValue();
|
return this.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateSubtotal(): CustomerInvoiceItemSubtotalPrice {
|
calculateSubtotal(): CustomerInvoiceItemSubtotalAmount {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
|
|
||||||
/*const unitPrice = this.unitPrice.isSome()
|
/*const unitPrice = this.unitPrice.isSome()
|
||||||
@ -95,7 +95,7 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
|
|||||||
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad*/
|
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad*/
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateTotal(): CustomerInvoiceItemTotalPrice {
|
calculateTotal(): CustomerInvoiceItemTotalAmount {
|
||||||
throw new Error("Not implemented");
|
throw new Error("Not implemented");
|
||||||
//return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
|
//return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
|
||||||
}
|
}
|
||||||
@ -1,2 +1,2 @@
|
|||||||
|
export * from "./customer-invoice-items";
|
||||||
export * from "./invoice-customer";
|
export * from "./invoice-customer";
|
||||||
export * from "./invoice-items";
|
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
import { Criteria } from "@repo/rdx-criteria/server";
|
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
|
||||||
import { CustomerInvoice, CustomerInvoiceProps } from "../aggregates";
|
|
||||||
|
|
||||||
export interface ICustomerInvoiceService {
|
|
||||||
build(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error>;
|
|
||||||
|
|
||||||
save(invoice: CustomerInvoice, transaction: any): Promise<Result<CustomerInvoice, Error>>;
|
|
||||||
|
|
||||||
existsById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
|
||||||
|
|
||||||
findByCriteria(
|
|
||||||
criteria: Criteria,
|
|
||||||
transaction?: any
|
|
||||||
): Promise<Result<Collection<CustomerInvoice>, Error>>;
|
|
||||||
|
|
||||||
getById(id: UniqueID, transaction?: any): Promise<Result<CustomerInvoice>>;
|
|
||||||
|
|
||||||
updateById(
|
|
||||||
id: UniqueID,
|
|
||||||
data: Partial<CustomerInvoiceProps>,
|
|
||||||
transaction?: any
|
|
||||||
): Promise<Result<CustomerInvoice, Error>>;
|
|
||||||
|
|
||||||
createCustomerInvoice(
|
|
||||||
id: UniqueID,
|
|
||||||
data: CustomerInvoiceProps,
|
|
||||||
transaction?: any
|
|
||||||
): Promise<Result<CustomerInvoice, Error>>;
|
|
||||||
|
|
||||||
deleteById(id: UniqueID, transaction?: any): Promise<Result<void, Error>>;
|
|
||||||
}
|
|
||||||
@ -2,22 +2,26 @@ import { Criteria } from "@repo/rdx-criteria/server";
|
|||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Collection, Result } from "@repo/rdx-utils";
|
import { Collection, Result } from "@repo/rdx-utils";
|
||||||
import { Transaction } from "sequelize";
|
import { Transaction } from "sequelize";
|
||||||
import { CustomerInvoice, CustomerInvoiceProps } from "../aggregates";
|
import { CustomerInvoice, CustomerInvoicePatchProps, CustomerInvoiceProps } from "../aggregates";
|
||||||
import { ICustomerInvoiceRepository } from "../repositories";
|
import { ICustomerInvoiceRepository } from "../repositories";
|
||||||
import { ICustomerInvoiceService } from "./customer-invoice-service.interface";
|
|
||||||
|
|
||||||
export class CustomerInvoiceService implements ICustomerInvoiceService {
|
export class CustomerInvoiceService {
|
||||||
constructor(private readonly repository: ICustomerInvoiceRepository) {}
|
constructor(private readonly repository: ICustomerInvoiceRepository) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construye un nuevo agregado CustomerInvoice a partir de props validadas.
|
* Construye un nuevo agregado CustomerInvoice a partir de props validadas.
|
||||||
*
|
*
|
||||||
|
* @param companyId - Identificador de la empresa a la que pertenece el cliente.
|
||||||
* @param props - Las propiedades ya validadas para crear la factura.
|
* @param props - Las propiedades ya validadas para crear la factura.
|
||||||
* @param id - Identificador UUID de la factura (opcional).
|
* @param invoiceId - Identificador UUID de la factura (opcional).
|
||||||
* @returns Result<CustomerInvoice, Error> - El agregado construido o un error si falla la creación.
|
* @returns Result<CustomerInvoice, Error> - El agregado construido o un error si falla la creación.
|
||||||
*/
|
*/
|
||||||
build(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
buildInvoiceInCompany(
|
||||||
return CustomerInvoice.create(props, id);
|
companyId: UniqueID,
|
||||||
|
props: Omit<CustomerInvoiceProps, "companyId">,
|
||||||
|
invoiceId?: UniqueID
|
||||||
|
): Result<CustomerInvoice, Error> {
|
||||||
|
return CustomerInvoice.create({ ...props, companyId }, invoiceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,124 +31,108 @@ export class CustomerInvoiceService implements ICustomerInvoiceService {
|
|||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<CustomerInvoice, Error> - El agregado guardado o un error si falla la operación.
|
* @returns Result<CustomerInvoice, Error> - El agregado guardado o un error si falla la operación.
|
||||||
*/
|
*/
|
||||||
async save(invoice: CustomerInvoice, transaction: any): Promise<Result<CustomerInvoice, Error>> {
|
async saveInvoice(
|
||||||
const saved = await this.repository.save(invoice, transaction);
|
invoice: CustomerInvoice,
|
||||||
return saved.isSuccess ? Result.ok(invoice) : Result.fail(saved.error);
|
transaction: any
|
||||||
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
|
return this.repository.save(invoice, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Comprueba si existe o no en persistencia una factura con el ID proporcionado
|
* Comprueba si existe o no en persistencia una factura con el ID proporcionado
|
||||||
*
|
*
|
||||||
* @param id - Identificador UUID de la factura.
|
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
||||||
|
* @param invoiceId - Identificador UUID de la factura.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<Boolean, Error> - Existe la factura o no.
|
* @returns Result<Boolean, Error> - Existe la factura o no.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async existsById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>> {
|
async existsByIdInCompany(
|
||||||
return this.repository.existsById(id, transaction);
|
companyId: UniqueID,
|
||||||
|
invoiceId: UniqueID,
|
||||||
|
transaction?: any
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
return this.repository.existsByIdInCompany(companyId, invoiceId, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtiene una colección de facturas que cumplen con los filtros definidos en un objeto Criteria.
|
* Obtiene una colección de facturas que cumplen con los filtros definidos en un objeto Criteria.
|
||||||
*
|
*
|
||||||
|
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
||||||
* @param criteria - Objeto con condiciones de filtro, paginación y orden.
|
* @param criteria - Objeto con condiciones de filtro, paginación y orden.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<Collection<CustomerInvoice>, Error> - Colección de facturas o error.
|
* @returns Result<Collection<CustomerInvoice>, Error> - Colección de facturas o error.
|
||||||
*/
|
*/
|
||||||
async findByCriteria(
|
async findInvoiceByCriteriaInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
criteria: Criteria,
|
criteria: Criteria,
|
||||||
transaction?: Transaction
|
transaction?: Transaction
|
||||||
): Promise<Result<Collection<CustomerInvoice>, Error>> {
|
): Promise<Result<Collection<CustomerInvoice>, Error>> {
|
||||||
const customerInvoicesOrError = await this.repository.findByCriteria(criteria, transaction);
|
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
|
||||||
if (customerInvoicesOrError.isFailure) {
|
|
||||||
return Result.fail(customerInvoicesOrError.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Solo devolver usuarios activos
|
|
||||||
//const allCustomerInvoices = customerInvoicesOrError.data.filter((customerInvoice) => customerInvoice.isActive);
|
|
||||||
//return Result.ok(new Collection(allCustomerInvoices));
|
|
||||||
|
|
||||||
return customerInvoicesOrError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recupera una factura por su identificador único.
|
* Recupera una factura por su identificador único.
|
||||||
*
|
*
|
||||||
* @param id - Identificador UUID de la factura.
|
* @param invoiceId - Identificador UUID de la factura.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<CustomerInvoice, Error> - Factura encontrada o error.
|
* @returns Result<CustomerInvoice, Error> - Factura encontrada o error.
|
||||||
*/
|
*/
|
||||||
async getById(id: UniqueID, transaction?: Transaction): Promise<Result<CustomerInvoice>> {
|
async getInvoiceByIdInCompany(
|
||||||
return await this.repository.findById(id, transaction);
|
companyId: UniqueID,
|
||||||
|
invoiceId: UniqueID,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<CustomerInvoice>> {
|
||||||
|
return await this.repository.getByIdInCompany(companyId, invoiceId, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actualiza parcialmente una factura existente con nuevos datos.
|
* Actualiza parcialmente una factura existente con nuevos datos.
|
||||||
|
* No lo guarda en el repositorio.
|
||||||
*
|
*
|
||||||
* @param id - Identificador de la factura a actualizar.
|
* @param companyId - Identificador de la empresa a la que pertenece el cliente.
|
||||||
|
* @param invoiceId - Identificador de la factura a actualizar.
|
||||||
* @param changes - Subconjunto de props válidas para aplicar.
|
* @param changes - Subconjunto de props válidas para aplicar.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<CustomerInvoice, Error> - Factura actualizada o error.
|
* @returns Result<CustomerInvoice, Error> - Factura actualizada o error.
|
||||||
*/
|
*/
|
||||||
async updateById(
|
async updateInvoiceByIdInCompany(
|
||||||
customerInvoiceId: UniqueID,
|
companyId: UniqueID,
|
||||||
changes: Partial<CustomerInvoiceProps>,
|
invoiceId: UniqueID,
|
||||||
|
changes: CustomerInvoicePatchProps,
|
||||||
transaction?: Transaction
|
transaction?: Transaction
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
// Verificar si la factura existe
|
// Verificar si la factura existe
|
||||||
const customerInvoiceOrError = await this.repository.getById(customerInvoiceId, transaction);
|
const invoiceResult = await this.getInvoiceByIdInCompany(companyId, invoiceId, transaction);
|
||||||
if (customerInvoiceOrError.isFailure) {
|
|
||||||
return Result.fail(new Error("CustomerInvoice not found"));
|
if (invoiceResult.isFailure) {
|
||||||
|
return Result.fail(invoiceResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.fail(new Error("No implementado"));
|
const invoice = invoiceResult.data;
|
||||||
|
const updatedInvoice = invoice.update(changes);
|
||||||
|
|
||||||
/*const updatedCustomerInvoiceOrError = CustomerInvoice.update(customerInvoiceOrError.data, data);
|
if (updatedInvoice.isFailure) {
|
||||||
if (updatedCustomerInvoiceOrError.isFailure) {
|
return Result.fail(updatedInvoice.error);
|
||||||
return Result.fail(
|
|
||||||
new Error(`Error updating customerInvoice: ${updatedCustomerInvoiceOrError.error.message}`)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateCustomerInvoice = updatedCustomerInvoiceOrError.data;
|
return Result.ok(updatedInvoice.data);
|
||||||
|
|
||||||
await this.repo.update(updateCustomerInvoice, transaction);
|
|
||||||
return Result.ok(updateCustomerInvoice);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
async createCustomerInvoice(
|
|
||||||
customerInvoiceId: UniqueID,
|
|
||||||
data: CustomerInvoiceProps,
|
|
||||||
transaction?: Transaction
|
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
|
||||||
// Verificar si la factura existe
|
|
||||||
const customerInvoiceOrError = await this.repository.getById(customerInvoiceId, transaction);
|
|
||||||
if (customerInvoiceOrError.isSuccess) {
|
|
||||||
return Result.fail(new Error("CustomerInvoice exists"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const newCustomerInvoiceOrError = CustomerInvoice.create(data, customerInvoiceId);
|
|
||||||
if (newCustomerInvoiceOrError.isFailure) {
|
|
||||||
return Result.fail(
|
|
||||||
new Error(`Error creating customerInvoice: ${newCustomerInvoiceOrError.error.message}`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newCustomerInvoice = newCustomerInvoiceOrError.data;
|
|
||||||
|
|
||||||
await this.repository.create(newCustomerInvoice, transaction);
|
|
||||||
return Result.ok(newCustomerInvoice);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elimina (o marca como eliminada) una factura según su ID.
|
* Elimina (o marca como eliminada) una factura según su ID.
|
||||||
*
|
*
|
||||||
* @param id - Identificador UUID de la factura.
|
* @param companyId - Identificador de la empresa a la que pertenece el cliente.
|
||||||
|
* @param invoiceId - Identificador UUID de la factura.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<boolean, Error> - Resultado de la operación.
|
* @returns Result<boolean, Error> - Resultado de la operación.
|
||||||
*/
|
*/
|
||||||
async deleteById(id: UniqueID, transaction?: Transaction): Promise<Result<void, Error>> {
|
async deleteById(
|
||||||
return this.repository.deleteById(id, transaction);
|
companyId: UniqueID,
|
||||||
|
invoiceId: UniqueID,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<void, Error>> {
|
||||||
|
return this.repository.deleteByIdInCompany(companyId, invoiceId, transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
export * from "./customer-invoice-service.interface";
|
|
||||||
export * from "./customer-invoice.service";
|
export * from "./customer-invoice.service";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
export class CustomerInvoiceItemSubtotalPrice extends MoneyValue {
|
export class CustomerInvoiceItemSubtotalAmount extends MoneyValue {
|
||||||
public static DEFAULT_SCALE = 4;
|
public static DEFAULT_SCALE = 4;
|
||||||
|
|
||||||
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
export class CustomerInvoiceItemTotalPrice extends MoneyValue {
|
export class CustomerInvoiceItemTotalAmount extends MoneyValue {
|
||||||
public static DEFAULT_SCALE = 4;
|
public static DEFAULT_SCALE = 4;
|
||||||
|
|
||||||
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
export class CustomerInvoiceItemUnitPrice extends MoneyValue {
|
export class CustomerInvoiceItemUnitAmount extends MoneyValue {
|
||||||
public static DEFAULT_SCALE = 4;
|
public static DEFAULT_SCALE = 4;
|
||||||
|
|
||||||
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||||
@ -12,7 +12,7 @@ export class CustomerInvoiceItemUnitPrice extends MoneyValue {
|
|||||||
return MoneyValue.create(props);
|
return MoneyValue.create(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
static zero(currency_code: string, scale: number = CustomerInvoiceItemUnitPrice.DEFAULT_SCALE) {
|
static zero(currency_code: string, scale: number = CustomerInvoiceItemUnitAmount.DEFAULT_SCALE) {
|
||||||
const props: MoneyValueProps = {
|
const props: MoneyValueProps = {
|
||||||
amount: 0,
|
amount: 0,
|
||||||
scale,
|
scale,
|
||||||
@ -2,9 +2,9 @@ export * from "./customer-invoice-address-type";
|
|||||||
export * from "./customer-invoice-item-description";
|
export * from "./customer-invoice-item-description";
|
||||||
export * from "./customer-invoice-item-discount";
|
export * from "./customer-invoice-item-discount";
|
||||||
export * from "./customer-invoice-item-quantity";
|
export * from "./customer-invoice-item-quantity";
|
||||||
export * from "./customer-invoice-item-subtotal-price";
|
export * from "./customer-invoice-item-subtotal-amount";
|
||||||
export * from "./customer-invoice-item-total-price";
|
export * from "./customer-invoice-item-total-amount";
|
||||||
export * from "./customer-invoice-item-unit-price";
|
export * from "./customer-invoice-item-unit-amount";
|
||||||
export * from "./customer-invoice-number";
|
export * from "./customer-invoice-number";
|
||||||
export * from "./customer-invoice-serie";
|
export * from "./customer-invoice-serie";
|
||||||
export * from "./customer-invoice-status";
|
export * from "./customer-invoice-status";
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import {
|
|||||||
CreateCustomerInvoiceUseCase,
|
CreateCustomerInvoiceUseCase,
|
||||||
CreateCustomerInvoicesAssembler,
|
CreateCustomerInvoicesAssembler,
|
||||||
DeleteCustomerInvoiceUseCase,
|
DeleteCustomerInvoiceUseCase,
|
||||||
GetCustomerInvoiceAssembler,
|
GetCustomerInvoiceItemsAssembler,
|
||||||
GetCustomerInvoiceUseCase,
|
GetCustomerInvoiceUseCase,
|
||||||
ListCustomerInvoicesAssembler,
|
ListCustomerInvoicesAssembler,
|
||||||
ListCustomerInvoicesUseCase,
|
ListCustomerInvoicesUseCase,
|
||||||
@ -22,7 +22,7 @@ type InvoiceDeps = {
|
|||||||
service: ICustomerInvoiceService;
|
service: ICustomerInvoiceService;
|
||||||
assemblers: {
|
assemblers: {
|
||||||
list: ListCustomerInvoicesAssembler;
|
list: ListCustomerInvoicesAssembler;
|
||||||
get: GetCustomerInvoiceAssembler;
|
get: GetCustomerInvoiceItemsAssembler;
|
||||||
create: CreateCustomerInvoicesAssembler;
|
create: CreateCustomerInvoicesAssembler;
|
||||||
update: UpdateCustomerInvoiceAssembler;
|
update: UpdateCustomerInvoiceAssembler;
|
||||||
};
|
};
|
||||||
@ -54,7 +54,7 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
|||||||
if (!_assemblers) {
|
if (!_assemblers) {
|
||||||
_assemblers = {
|
_assemblers = {
|
||||||
list: new ListCustomerInvoicesAssembler(), // transforma domain → ListDTO
|
list: new ListCustomerInvoicesAssembler(), // transforma domain → ListDTO
|
||||||
get: new GetCustomerInvoiceAssembler(), // transforma domain → DetailDTO
|
get: new GetCustomerInvoiceItemsAssembler(), // transforma domain → DetailDTO
|
||||||
create: new CreateCustomerInvoicesAssembler(), // transforma domain → CreatedDTO
|
create: new CreateCustomerInvoicesAssembler(), // transforma domain → CreatedDTO
|
||||||
update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO
|
update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,20 +2,17 @@ import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from
|
|||||||
import { GetCustomerInvoiceUseCase } from "../../../application";
|
import { GetCustomerInvoiceUseCase } from "../../../application";
|
||||||
|
|
||||||
export class GetCustomerInvoiceController extends ExpressController {
|
export class GetCustomerInvoiceController extends ExpressController {
|
||||||
public constructor(
|
public constructor(private readonly useCase: GetCustomerInvoiceUseCase) {
|
||||||
private readonly useCase: GetCustomerInvoiceUseCase
|
|
||||||
/* private readonly presenter: any */
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
// 🔐 Reutiliza guards de auth/tenant y prohíbe 'companyId' en query
|
||||||
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
this.useGuards(authGuard(), tenantGuard(), forbidQueryFieldGuard("companyId"));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async executeImpl() {
|
protected async executeImpl() {
|
||||||
const tenantId = this.getTenantId()!; // garantizado por tenantGuard
|
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||||
const { id } = this.req.params;
|
const { invoice_id } = this.req.params;
|
||||||
|
|
||||||
const result = await this.useCase.execute({ id, tenantId });
|
const result = await this.useCase.execute({ invoice_id, companyId });
|
||||||
|
|
||||||
return result.match(
|
return result.match(
|
||||||
(data) => this.ok(data),
|
(data) => this.ok(data),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { RequestWithAuth, enforceTenant } from "@erp/auth/api";
|
import { RequestWithAuth, enforceTenant, enforceUser, mockUser } 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";
|
||||||
@ -24,17 +24,33 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
|
|||||||
logger: ILogger;
|
logger: ILogger;
|
||||||
};
|
};
|
||||||
|
|
||||||
const router: Router = Router({ mergeParams: true });
|
|
||||||
const deps = getInvoiceDependencies(params);
|
const deps = getInvoiceDependencies(params);
|
||||||
|
|
||||||
|
const router: Router = Router({ mergeParams: true });
|
||||||
|
|
||||||
// 🔐 Autenticación + Tenancy para TODO el router
|
// 🔐 Autenticación + Tenancy para TODO el router
|
||||||
router.use(/* authenticateJWT(), */ enforceTenant() /*checkTabContext*/);
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
router.use(
|
||||||
|
(req: Request, res: Response, next: NextFunction) =>
|
||||||
|
mockUser(req as RequestWithAuth, res, next) // Debe ir antes de las rutas protegidas
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
router.use([
|
||||||
|
(req: Request, res: Response, next: NextFunction) =>
|
||||||
|
enforceUser()(req as RequestWithAuth, res, next), // Debe ir antes de las rutas protegidas
|
||||||
|
|
||||||
|
(req: Request, res: Response, next: NextFunction) =>
|
||||||
|
enforceTenant()(req as RequestWithAuth, res, next), // Debe ir antes de las rutas protegidas
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ----------------------------------------------
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(CustomerInvoiceListRequestSchema, "params"),
|
validateRequest(CustomerInvoiceListRequestSchema, "params"),
|
||||||
async (req: RequestWithAuth, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.build.list();
|
const useCase = deps.build.list();
|
||||||
const controller = new ListCustomerInvoicesController(useCase /*, deps.presenters.list */);
|
const controller = new ListCustomerInvoicesController(useCase /*, deps.presenters.list */);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
@ -64,13 +80,15 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/*routes.put(
|
/*router.put(
|
||||||
"/:customerInvoiceId",
|
"/:customer_id",
|
||||||
validateAndParseBody(IUpdateCustomerInvoiceRequestSchema),
|
//checkTabContext,
|
||||||
checkTabContext,
|
validateRequest(UpdateCustomerInvoiceByIdParamsRequestSchema, "params"),
|
||||||
|
validateRequest(UpdateCustomerInvoiceByIdRequestSchema, "body"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
buildUpdateCustomerInvoiceController().execute(req, res, next);
|
const useCase = deps.build.update();
|
||||||
|
const controller = new UpdateCustomerInvoiceController(useCase);
|
||||||
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
);*/
|
);*/
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
CustomerInvoiceItemDescription,
|
CustomerInvoiceItemDescription,
|
||||||
CustomerInvoiceItemDiscount,
|
CustomerInvoiceItemDiscount,
|
||||||
CustomerInvoiceItemQuantity,
|
CustomerInvoiceItemQuantity,
|
||||||
CustomerInvoiceItemUnitPrice,
|
CustomerInvoiceItemUnitAmount,
|
||||||
} from "../../domain";
|
} from "../../domain";
|
||||||
import {
|
import {
|
||||||
CustomerInvoiceItemCreationAttributes,
|
CustomerInvoiceItemCreationAttributes,
|
||||||
@ -59,7 +59,7 @@ export class CustomerInvoiceItemMapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validación y creación de precio unitario
|
// Validación y creación de precio unitario
|
||||||
const unitPriceOrError = CustomerInvoiceItemUnitPrice.create({
|
const unitPriceOrError = CustomerInvoiceItemUnitAmount.create({
|
||||||
amount: source.unit_price_amount,
|
amount: source.unit_price_amount,
|
||||||
scale: source.unit_price_scale,
|
scale: source.unit_price_scale,
|
||||||
currency_code: sourceParent.invoice_currency,
|
currency_code: sourceParent.invoice_currency,
|
||||||
@ -123,11 +123,11 @@ export class CustomerInvoiceItemMapper
|
|||||||
quantity_amount: source.quantity.toPrimitive().amount,
|
quantity_amount: source.quantity.toPrimitive().amount,
|
||||||
quantity_scale: source.quantity.toPrimitive().scale,
|
quantity_scale: source.quantity.toPrimitive().scale,
|
||||||
|
|
||||||
unit_price_amount: source.unitPrice.toPrimitive().amount,
|
unit_price_amount: source.unitAmount.toPrimitive().amount,
|
||||||
unit_price_scale: source.unitPrice.toPrimitive().scale,
|
unit_price_scale: source.unitAmount.toPrimitive().scale,
|
||||||
|
|
||||||
subtotal_amount: source.subtotalPrice.toPrimitive().amount,
|
subtotal_amount: source.subtotalAmount.toPrimitive().amount,
|
||||||
subtotal_scale: source.subtotalPrice.toPrimitive().scale,
|
subtotal_scale: source.subtotalAmount.toPrimitive().scale,
|
||||||
|
|
||||||
discount_amount: source.discount.toPrimitive().amount,
|
discount_amount: source.discount.toPrimitive().amount,
|
||||||
discount_scale: source.discount.toPrimitive().scale,
|
discount_scale: source.discount.toPrimitive().scale,
|
||||||
|
|||||||
@ -34,12 +34,28 @@ export class CustomerInvoiceModel extends Model<
|
|||||||
declare currency_code: string;
|
declare currency_code: string;
|
||||||
|
|
||||||
// Subtotal
|
// Subtotal
|
||||||
declare subtotal_amount: number;
|
declare subtotal_amount_value: number;
|
||||||
declare subtotal_scale: number;
|
declare subtotal_amount_scale: number;
|
||||||
|
|
||||||
|
// Discount percentage
|
||||||
|
declare discount_percentage_value: number;
|
||||||
|
declare discount_percentage_scale: number;
|
||||||
|
|
||||||
|
// Discount amount
|
||||||
|
declare discount_amount_value: number;
|
||||||
|
declare discount_amount_scale: number;
|
||||||
|
|
||||||
|
// Taxable amount (base imponible)
|
||||||
|
declare taxable_amount_value: number;
|
||||||
|
declare taxable_amount_scale: number;
|
||||||
|
|
||||||
|
// Total tax amount / taxes total
|
||||||
|
declare tax_amount_value: number;
|
||||||
|
declare tax_amount_scale: number;
|
||||||
|
|
||||||
// Total
|
// Total
|
||||||
declare total_amount: number;
|
declare total_amount_value: number;
|
||||||
declare total_scale: number;
|
declare total_amount_scale: number;
|
||||||
|
|
||||||
// Relaciones
|
// Relaciones
|
||||||
declare items: NonAttribute<CustomerInvoiceItemModel[]>;
|
declare items: NonAttribute<CustomerInvoiceItemModel[]>;
|
||||||
@ -125,23 +141,70 @@ export default (database: Sequelize) => {
|
|||||||
defaultValue: "EUR",
|
defaultValue: "EUR",
|
||||||
},
|
},
|
||||||
|
|
||||||
subtotal_amount: {
|
subtotal_amount_value: {
|
||||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
subtotal_scale: {
|
subtotal_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
|
|
||||||
total_amount: {
|
discount_percentage_value: {
|
||||||
|
type: new DataTypes.SMALLINT(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
discount_percentage_scale: {
|
||||||
|
type: new DataTypes.SMALLINT(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
discount_amount_value: {
|
||||||
|
type: new DataTypes.BIGINT(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
discount_amount_scale: {
|
||||||
|
type: new DataTypes.SMALLINT(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
taxable_amount_value: {
|
||||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
},
|
},
|
||||||
total_scale: {
|
taxable_amount_scale: {
|
||||||
|
type: new DataTypes.SMALLINT(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
tax_amount_value: {
|
||||||
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
tax_amount_scale: {
|
||||||
|
type: new DataTypes.SMALLINT(),
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
total_amount_value: {
|
||||||
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
total_amount_scale: {
|
||||||
type: new DataTypes.SMALLINT(),
|
type: new DataTypes.SMALLINT(),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
export const GetCustomerInvoiceByIdRequestSchema = z.object({
|
export const GetCustomerInvoiceByIdRequestSchema = z.object({
|
||||||
id: z.string(),
|
invoice_id: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type GetCustomerInvoiceByIdRequestDTO = z.infer<typeof GetCustomerInvoiceByIdRequestSchema>;
|
export type GetCustomerInvoiceByIdRequestDTO = z.infer<typeof GetCustomerInvoiceByIdRequestSchema>;
|
||||||
|
|||||||
@ -1,15 +1,32 @@
|
|||||||
import { MetadataSchema } from "@erp/core";
|
import { AmountSchema, MetadataSchema, PercentageSchema, QuantitySchema } from "@erp/core";
|
||||||
import * as z from "zod/v4";
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
export const GetCustomerInvoiceByIdResponseSchema = z.object({
|
export const GetCustomerInvoiceByIdResponseSchema = z.object({
|
||||||
id: z.uuid(),
|
id: z.uuid(),
|
||||||
invoice_status: z.string(),
|
company_id: z.uuid(),
|
||||||
|
|
||||||
invoice_number: z.string(),
|
invoice_number: z.string(),
|
||||||
invoice_series: z.string(),
|
status: z.string(),
|
||||||
issue_date: z.iso.datetime({ offset: true }),
|
series: z.string(),
|
||||||
operation_date: z.iso.datetime({ offset: true }),
|
|
||||||
|
issue_date: z.string(),
|
||||||
|
operation_date: z.string(),
|
||||||
|
|
||||||
|
notes: z.string(),
|
||||||
|
|
||||||
language_code: z.string(),
|
language_code: z.string(),
|
||||||
currency: z.string(),
|
currency_code: z.string(),
|
||||||
|
|
||||||
|
items: z.array(
|
||||||
|
z.object({
|
||||||
|
position: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
quantity: QuantitySchema,
|
||||||
|
unit_price_amount: AmountSchema,
|
||||||
|
discount: PercentageSchema,
|
||||||
|
total_amount: AmountSchema,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
metadata: MetadataSchema.optional(),
|
metadata: MetadataSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -90,14 +90,14 @@ export class CustomerService {
|
|||||||
*
|
*
|
||||||
* @param companyId - Identificador de la empresa a la que pertenece el cliente.
|
* @param companyId - Identificador de la empresa a la que pertenece el cliente.
|
||||||
* @param customerId - Identificador del cliente a actualizar.
|
* @param customerId - Identificador del cliente a actualizar.
|
||||||
* @param partial - Subconjunto de props válidas para aplicar.
|
* @param changes - Subconjunto de props válidas para aplicar.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<Customer, Error> - Cliente actualizado o error.
|
* @returns Result<Customer, Error> - Cliente actualizado o error.
|
||||||
*/
|
*/
|
||||||
async updateCustomerByIdInCompany(
|
async updateCustomerByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
customerId: UniqueID,
|
customerId: UniqueID,
|
||||||
partial: CustomerPatchProps,
|
changes: CustomerPatchProps,
|
||||||
transaction?: any
|
transaction?: any
|
||||||
): Promise<Result<Customer, Error>> {
|
): Promise<Result<Customer, Error>> {
|
||||||
const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction);
|
const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction);
|
||||||
@ -107,7 +107,7 @@ export class CustomerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const customer = customerResult.data;
|
const customer = customerResult.data;
|
||||||
const updatedCustomer = customer.update(partial);
|
const updatedCustomer = customer.update(changes);
|
||||||
|
|
||||||
if (updatedCustomer.isFailure) {
|
if (updatedCustomer.isFailure) {
|
||||||
return Result.fail(updatedCustomer.error);
|
return Result.fail(updatedCustomer.error);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { enforceTenant, enforceUser, mockUser } from "@erp/auth/api";
|
import { RequestWithAuth, enforceTenant, enforceUser, mockUser } 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";
|
||||||
@ -33,11 +33,22 @@ export const customersRouter = (params: ModuleParams) => {
|
|||||||
|
|
||||||
// 🔐 Autenticación + Tenancy para TODO el router
|
// 🔐 Autenticación + Tenancy para TODO el router
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
router.use(mockUser); // Debe ir antes de las rutas protegidas
|
router.use(
|
||||||
|
(req: Request, res: Response, next: NextFunction) =>
|
||||||
|
mockUser(req as RequestWithAuth, res, next) // Debe ir antes de las rutas protegidas
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//router.use(/*authenticateJWT(),*/ enforceTenant() /*checkTabContext*/);
|
//router.use(/*authenticateJWT(),*/ enforceTenant() /*checkTabContext*/);
|
||||||
router.use([enforceUser(), enforceTenant()]);
|
router.use([
|
||||||
|
(req: Request, res: Response, next: NextFunction) =>
|
||||||
|
enforceUser()(req as RequestWithAuth, res, next), // Debe ir antes de las rutas protegidas
|
||||||
|
|
||||||
|
(req: Request, res: Response, next: NextFunction) =>
|
||||||
|
enforceTenant()(req as RequestWithAuth, res, next), // Debe ir antes de las rutas protegidas
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ----------------------------------------------
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
|
|||||||
@ -5,7 +5,7 @@ export function isNullishOrEmpty(input: unknown): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Función genérica para asegurar valores básicos
|
// Función genérica para asegurar valores básicos
|
||||||
function ensure<T>(value: T | undefined | null, defaultValue: T): T {
|
export function ensure<T>(value: T | undefined | null, defaultValue: T): T {
|
||||||
return value ?? defaultValue;
|
return value ?? defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user