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";
|
||||
|
||||
/**
|
||||
Esquema del objeto normalizado esperado por Criteria.fromPrimitives(...)
|
||||
No aplica defaults ni correciones: solo valida.
|
||||
Esquema del DTO para Criteria.fromPrimitives(...)
|
||||
No aplica defaults ni correciones: solo valida.
|
||||
*/
|
||||
export const FilterPrimitiveSchema = z.object({
|
||||
// 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 "./error.dto";
|
||||
export * from "./list-view.response.dto";
|
||||
export * from "./metadata.dto";
|
||||
export * from "./money.dto";
|
||||
export * from "./percentage.dto";
|
||||
export * from "./quantity.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 = {
|
||||
amount: number | null;
|
||||
scale: number;
|
||||
};
|
||||
import * as z from "zod/v4";
|
||||
import { NumericStringSchema } from "./base.schemas";
|
||||
|
||||
/**
|
||||
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 = {
|
||||
amount: number | null;
|
||||
scale: number;
|
||||
};
|
||||
import * as z from "zod/v4";
|
||||
import { NumericStringSchema } from "./base.schemas";
|
||||
|
||||
/**
|
||||
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 {
|
||||
id: string;
|
||||
typecode: string;
|
||||
taxslug: string;
|
||||
taxrate: IPercentageDTO;
|
||||
equivalencesurcharge: IPercentageDTO;
|
||||
taxrate: PercentageDTO;
|
||||
equivalencesurcharge: PercentageDTO;
|
||||
}
|
||||
|
||||
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 { toEmptyString } from "@repo/rdx-ddd";
|
||||
import { CustomerInvoice } from "../../../domain";
|
||||
|
||||
export class GetCustomerInvoiceAssembler {
|
||||
toDTO(customerInvoice: CustomerInvoice): GetCustomerInvoiceByIdResponseDTO {
|
||||
return {
|
||||
id: customerInvoice.id.toPrimitive(),
|
||||
private _itemsAssembler!: GetCu;
|
||||
constructor() {}
|
||||
|
||||
invoice_status: customerInvoice.status.toString(),
|
||||
invoice_number: customerInvoice.invoiceNumber.toString(),
|
||||
invoice_series: customerInvoice.invoiceSeries.toString(),
|
||||
issue_date: customerInvoice.issueDate.toDateString(),
|
||||
operation_date: customerInvoice.operationDate.toDateString(),
|
||||
language_code: "ES",
|
||||
currency: customerInvoice.currency,
|
||||
public toDTO(invoice: CustomerInvoice): GetCustomerInvoiceByIdResponseDTO {
|
||||
//const items = invoice.items.
|
||||
|
||||
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(),
|
||||
|
||||
metadata: {
|
||||
entity: "customer-invoices",
|
||||
|
||||
@ -1,32 +1,39 @@
|
||||
import { ITransactionManager } from "@erp/core/api";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { ICustomerInvoiceService } from "../../domain";
|
||||
import { GetCustomerInvoiceAssembler } from "./assembler";
|
||||
import { CustomerInvoiceService } from "../../domain";
|
||||
import { GetCustomerInvoiceItemsAssembler } from "./assembler";
|
||||
|
||||
type GetCustomerInvoiceUseCaseInput = {
|
||||
tenantId: string;
|
||||
id: string;
|
||||
companyId: UniqueID;
|
||||
invoice_id: string;
|
||||
};
|
||||
|
||||
export class GetCustomerInvoiceUseCase {
|
||||
constructor(
|
||||
private readonly service: ICustomerInvoiceService,
|
||||
private readonly service: CustomerInvoiceService,
|
||||
private readonly transactionManager: ITransactionManager,
|
||||
private readonly assembler: GetCustomerInvoiceAssembler
|
||||
private readonly assembler: GetCustomerInvoiceItemsAssembler
|
||||
) {}
|
||||
|
||||
public execute(params: GetCustomerInvoiceUseCaseInput) {
|
||||
const { id, tenantId } = params;
|
||||
const idOrError = UniqueID.create(id);
|
||||
const { invoice_id, companyId } = params;
|
||||
|
||||
const idOrError = UniqueID.create(invoice_id);
|
||||
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
const invoiceId = idOrError.data;
|
||||
|
||||
return this.transactionManager.complete(async (transaction) => {
|
||||
try {
|
||||
const invoiceOrError = await this.service.getById(idOrError.data, transaction);
|
||||
const invoiceOrError = await this.service.getInvoiceByIdInCompany(
|
||||
companyId,
|
||||
invoiceId,
|
||||
transaction
|
||||
);
|
||||
if (invoiceOrError.isFailure) {
|
||||
return Result.fail(invoiceOrError.error);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
CustomerInvoiceItemDescription,
|
||||
CustomerInvoiceItemDiscount,
|
||||
CustomerInvoiceItemQuantity,
|
||||
CustomerInvoiceItemUnitPrice,
|
||||
CustomerInvoiceItemUnitAmount,
|
||||
} from "../../domain";
|
||||
import { extractOrPushError } from "./extract-or-push-error";
|
||||
import { hasNoUndefinedFields } from "./has-no-undefined-fields";
|
||||
@ -36,7 +36,7 @@ export function mapDTOToCustomerInvoiceItemsProps(
|
||||
);
|
||||
|
||||
const unitPrice = extractOrPushError(
|
||||
CustomerInvoiceItemUnitPrice.create({
|
||||
CustomerInvoiceItemUnitAmount.create({
|
||||
amount: item.unitPrice.amount,
|
||||
scale: item.unitPrice.scale,
|
||||
currency_code: item.unitPrice.currency,
|
||||
|
||||
@ -2,6 +2,8 @@ import {
|
||||
AggregateRoot,
|
||||
CurrencyCode,
|
||||
LanguageCode,
|
||||
MoneyValue,
|
||||
Percentage,
|
||||
TextValue,
|
||||
UniqueID,
|
||||
UtcDate,
|
||||
@ -16,9 +18,10 @@ import {
|
||||
|
||||
export interface CustomerInvoiceProps {
|
||||
companyId: UniqueID;
|
||||
|
||||
invoiceNumber: CustomerInvoiceNumber;
|
||||
status: CustomerInvoiceStatus;
|
||||
series: Maybe<CustomerInvoiceSerie>;
|
||||
invoiceNumber: CustomerInvoiceNumber;
|
||||
|
||||
issueDate: UtcDate;
|
||||
operationDate: Maybe<UtcDate>;
|
||||
@ -40,6 +43,16 @@ export interface CustomerInvoiceProps {
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
subtotalAmount: MoneyValue;
|
||||
|
||||
discountPercentage: Percentage;
|
||||
//discountAmount: MoneyValue;
|
||||
|
||||
//taxableAmount: MoneyValue;
|
||||
taxAmount: MoneyValue;
|
||||
|
||||
totalAmount: MoneyValue;
|
||||
|
||||
//customer?: CustomerInvoiceCustomer;
|
||||
items?: CustomerInvoiceItems;
|
||||
}
|
||||
@ -82,6 +95,10 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
|
||||
return this.props.companyId;
|
||||
}
|
||||
|
||||
public get status(): CustomerInvoiceStatus {
|
||||
return this.props.status;
|
||||
}
|
||||
|
||||
public get series(): Maybe<CustomerInvoiceSerie> {
|
||||
return this.props.series;
|
||||
}
|
||||
@ -110,8 +127,32 @@ export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
|
||||
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
|
||||
get lineItems(): CustomerInvoiceItems {
|
||||
get items(): CustomerInvoiceItems {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
|
||||
@ -4,15 +4,15 @@ import {
|
||||
CustomerInvoiceItemDescription,
|
||||
CustomerInvoiceItemDiscount,
|
||||
CustomerInvoiceItemQuantity,
|
||||
CustomerInvoiceItemSubtotalPrice,
|
||||
CustomerInvoiceItemTotalPrice,
|
||||
CustomerInvoiceItemUnitPrice,
|
||||
CustomerInvoiceItemSubtotalAmount,
|
||||
CustomerInvoiceItemTotalAmount,
|
||||
CustomerInvoiceItemUnitAmount,
|
||||
} from "../../value-objects";
|
||||
|
||||
export interface CustomerInvoiceItemProps {
|
||||
description: Maybe<CustomerInvoiceItemDescription>;
|
||||
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
|
||||
|
||||
languageCode: LanguageCode;
|
||||
@ -20,8 +20,8 @@ export interface CustomerInvoiceItemProps {
|
||||
}
|
||||
|
||||
export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps> {
|
||||
private _subtotalPrice!: CustomerInvoiceItemSubtotalPrice;
|
||||
private _totalPrice!: CustomerInvoiceItemTotalPrice;
|
||||
private _subtotalAmount!: CustomerInvoiceItemSubtotalAmount;
|
||||
private _totalAmount!: CustomerInvoiceItemTotalAmount;
|
||||
|
||||
public static create(
|
||||
props: CustomerInvoiceItemProps,
|
||||
@ -48,26 +48,26 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
|
||||
return this.props.quantity;
|
||||
}
|
||||
|
||||
get unitPrice(): Maybe<CustomerInvoiceItemUnitPrice> {
|
||||
get unitAmount(): Maybe<CustomerInvoiceItemUnitAmount> {
|
||||
return this.props.unitPrice;
|
||||
}
|
||||
|
||||
get subtotalPrice(): CustomerInvoiceItemSubtotalPrice {
|
||||
if (!this._subtotalPrice) {
|
||||
this._subtotalPrice = this.calculateSubtotal();
|
||||
get subtotalAmount(): CustomerInvoiceItemSubtotalAmount {
|
||||
if (!this._subtotalAmount) {
|
||||
this._subtotalAmount = this.calculateSubtotal();
|
||||
}
|
||||
return this._subtotalPrice;
|
||||
return this._subtotalAmount;
|
||||
}
|
||||
|
||||
get discount(): Maybe<CustomerInvoiceItemDiscount> {
|
||||
return this.props.discount;
|
||||
}
|
||||
|
||||
get totalPrice(): CustomerInvoiceItemTotalPrice {
|
||||
if (!this._totalPrice) {
|
||||
this._totalPrice = this.calculateTotal();
|
||||
get totalPrice(): CustomerInvoiceItemTotalAmount {
|
||||
if (!this._totalAmount) {
|
||||
this._totalAmount = this.calculateTotal();
|
||||
}
|
||||
return this._totalPrice;
|
||||
return this._totalAmount;
|
||||
}
|
||||
|
||||
public get languageCode(): LanguageCode {
|
||||
@ -86,7 +86,7 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
calculateSubtotal(): CustomerInvoiceItemSubtotalPrice {
|
||||
calculateSubtotal(): CustomerInvoiceItemSubtotalAmount {
|
||||
throw new Error("Not implemented");
|
||||
|
||||
/*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*/
|
||||
}
|
||||
|
||||
calculateTotal(): CustomerInvoiceItemTotalPrice {
|
||||
calculateTotal(): CustomerInvoiceItemTotalAmount {
|
||||
throw new Error("Not implemented");
|
||||
//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-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 { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Transaction } from "sequelize";
|
||||
import { CustomerInvoice, CustomerInvoiceProps } from "../aggregates";
|
||||
import { CustomerInvoice, CustomerInvoicePatchProps, CustomerInvoiceProps } from "../aggregates";
|
||||
import { ICustomerInvoiceRepository } from "../repositories";
|
||||
import { ICustomerInvoiceService } from "./customer-invoice-service.interface";
|
||||
|
||||
export class CustomerInvoiceService implements ICustomerInvoiceService {
|
||||
export class CustomerInvoiceService {
|
||||
constructor(private readonly repository: ICustomerInvoiceRepository) {}
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
*/
|
||||
build(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
||||
return CustomerInvoice.create(props, id);
|
||||
buildInvoiceInCompany(
|
||||
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.
|
||||
* @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>> {
|
||||
const saved = await this.repository.save(invoice, transaction);
|
||||
return saved.isSuccess ? Result.ok(invoice) : Result.fail(saved.error);
|
||||
async saveInvoice(
|
||||
invoice: CustomerInvoice,
|
||||
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
|
||||
*
|
||||
* @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.
|
||||
* @returns Result<Boolean, Error> - Existe la factura o no.
|
||||
*/
|
||||
|
||||
async existsById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>> {
|
||||
return this.repository.existsById(id, transaction);
|
||||
async existsByIdInCompany(
|
||||
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.
|
||||
*
|
||||
* @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 transaction - Transacción activa para la operación.
|
||||
* @returns Result<Collection<CustomerInvoice>, Error> - Colección de facturas o error.
|
||||
*/
|
||||
async findByCriteria(
|
||||
async findInvoiceByCriteriaInCompany(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<Collection<CustomerInvoice>, Error>> {
|
||||
const customerInvoicesOrError = await this.repository.findByCriteria(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;
|
||||
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @returns Result<CustomerInvoice, Error> - Factura encontrada o error.
|
||||
*/
|
||||
async getById(id: UniqueID, transaction?: Transaction): Promise<Result<CustomerInvoice>> {
|
||||
return await this.repository.findById(id, transaction);
|
||||
async getInvoiceByIdInCompany(
|
||||
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.
|
||||
* 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 transaction - Transacción activa para la operación.
|
||||
* @returns Result<CustomerInvoice, Error> - Factura actualizada o error.
|
||||
*/
|
||||
async updateById(
|
||||
customerInvoiceId: UniqueID,
|
||||
changes: Partial<CustomerInvoiceProps>,
|
||||
async updateInvoiceByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
invoiceId: UniqueID,
|
||||
changes: CustomerInvoicePatchProps,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
// Verificar si la factura existe
|
||||
const customerInvoiceOrError = await this.repository.getById(customerInvoiceId, transaction);
|
||||
if (customerInvoiceOrError.isFailure) {
|
||||
return Result.fail(new Error("CustomerInvoice not found"));
|
||||
const invoiceResult = await this.getInvoiceByIdInCompany(companyId, invoiceId, transaction);
|
||||
|
||||
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 (updatedCustomerInvoiceOrError.isFailure) {
|
||||
return Result.fail(
|
||||
new Error(`Error updating customerInvoice: ${updatedCustomerInvoiceOrError.error.message}`)
|
||||
);
|
||||
if (updatedInvoice.isFailure) {
|
||||
return Result.fail(updatedInvoice.error);
|
||||
}
|
||||
|
||||
const updateCustomerInvoice = updatedCustomerInvoiceOrError.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);
|
||||
return Result.ok(updatedInvoice.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @returns Result<boolean, Error> - Resultado de la operación.
|
||||
*/
|
||||
async deleteById(id: UniqueID, transaction?: Transaction): Promise<Result<void, Error>> {
|
||||
return this.repository.deleteById(id, transaction);
|
||||
async deleteById(
|
||||
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";
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||
|
||||
export class CustomerInvoiceItemSubtotalPrice extends MoneyValue {
|
||||
export class CustomerInvoiceItemSubtotalAmount extends MoneyValue {
|
||||
public static DEFAULT_SCALE = 4;
|
||||
|
||||
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||
@ -1,6 +1,6 @@
|
||||
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||
|
||||
export class CustomerInvoiceItemTotalPrice extends MoneyValue {
|
||||
export class CustomerInvoiceItemTotalAmount extends MoneyValue {
|
||||
public static DEFAULT_SCALE = 4;
|
||||
|
||||
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||
@ -1,6 +1,6 @@
|
||||
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
|
||||
|
||||
export class CustomerInvoiceItemUnitPrice extends MoneyValue {
|
||||
export class CustomerInvoiceItemUnitAmount extends MoneyValue {
|
||||
public static DEFAULT_SCALE = 4;
|
||||
|
||||
static create({ amount, currency_code, scale }: MoneyValueProps) {
|
||||
@ -12,7 +12,7 @@ export class CustomerInvoiceItemUnitPrice extends MoneyValue {
|
||||
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 = {
|
||||
amount: 0,
|
||||
scale,
|
||||
@ -2,9 +2,9 @@ export * from "./customer-invoice-address-type";
|
||||
export * from "./customer-invoice-item-description";
|
||||
export * from "./customer-invoice-item-discount";
|
||||
export * from "./customer-invoice-item-quantity";
|
||||
export * from "./customer-invoice-item-subtotal-price";
|
||||
export * from "./customer-invoice-item-total-price";
|
||||
export * from "./customer-invoice-item-unit-price";
|
||||
export * from "./customer-invoice-item-subtotal-amount";
|
||||
export * from "./customer-invoice-item-total-amount";
|
||||
export * from "./customer-invoice-item-unit-amount";
|
||||
export * from "./customer-invoice-number";
|
||||
export * from "./customer-invoice-serie";
|
||||
export * from "./customer-invoice-status";
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
CreateCustomerInvoiceUseCase,
|
||||
CreateCustomerInvoicesAssembler,
|
||||
DeleteCustomerInvoiceUseCase,
|
||||
GetCustomerInvoiceAssembler,
|
||||
GetCustomerInvoiceItemsAssembler,
|
||||
GetCustomerInvoiceUseCase,
|
||||
ListCustomerInvoicesAssembler,
|
||||
ListCustomerInvoicesUseCase,
|
||||
@ -22,7 +22,7 @@ type InvoiceDeps = {
|
||||
service: ICustomerInvoiceService;
|
||||
assemblers: {
|
||||
list: ListCustomerInvoicesAssembler;
|
||||
get: GetCustomerInvoiceAssembler;
|
||||
get: GetCustomerInvoiceItemsAssembler;
|
||||
create: CreateCustomerInvoicesAssembler;
|
||||
update: UpdateCustomerInvoiceAssembler;
|
||||
};
|
||||
@ -54,7 +54,7 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
||||
if (!_assemblers) {
|
||||
_assemblers = {
|
||||
list: new ListCustomerInvoicesAssembler(), // transforma domain → ListDTO
|
||||
get: new GetCustomerInvoiceAssembler(), // transforma domain → DetailDTO
|
||||
get: new GetCustomerInvoiceItemsAssembler(), // transforma domain → DetailDTO
|
||||
create: new CreateCustomerInvoicesAssembler(), // transforma domain → CreatedDTO
|
||||
update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO
|
||||
};
|
||||
|
||||
@ -2,20 +2,17 @@ import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from
|
||||
import { GetCustomerInvoiceUseCase } from "../../../application";
|
||||
|
||||
export class GetCustomerInvoiceController extends ExpressController {
|
||||
public constructor(
|
||||
private readonly useCase: GetCustomerInvoiceUseCase
|
||||
/* private readonly presenter: any */
|
||||
) {
|
||||
public constructor(private readonly useCase: GetCustomerInvoiceUseCase) {
|
||||
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 { id } = this.req.params;
|
||||
const companyId = this.getTenantId()!; // garantizado por tenantGuard
|
||||
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(
|
||||
(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 { Application, NextFunction, Request, Response, Router } from "express";
|
||||
import { Sequelize } from "sequelize";
|
||||
@ -24,17 +24,33 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
|
||||
logger: ILogger;
|
||||
};
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
const deps = getInvoiceDependencies(params);
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
// 🔐 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(
|
||||
"/",
|
||||
//checkTabContext,
|
||||
validateRequest(CustomerInvoiceListRequestSchema, "params"),
|
||||
async (req: RequestWithAuth, res: Response, next: NextFunction) => {
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const useCase = deps.build.list();
|
||||
const controller = new ListCustomerInvoicesController(useCase /*, deps.presenters.list */);
|
||||
return controller.execute(req, res, next);
|
||||
@ -64,13 +80,15 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
|
||||
}
|
||||
);
|
||||
|
||||
/*routes.put(
|
||||
"/:customerInvoiceId",
|
||||
validateAndParseBody(IUpdateCustomerInvoiceRequestSchema),
|
||||
checkTabContext,
|
||||
|
||||
/*router.put(
|
||||
"/:customer_id",
|
||||
//checkTabContext,
|
||||
validateRequest(UpdateCustomerInvoiceByIdParamsRequestSchema, "params"),
|
||||
validateRequest(UpdateCustomerInvoiceByIdRequestSchema, "body"),
|
||||
(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,
|
||||
CustomerInvoiceItemDiscount,
|
||||
CustomerInvoiceItemQuantity,
|
||||
CustomerInvoiceItemUnitPrice,
|
||||
CustomerInvoiceItemUnitAmount,
|
||||
} from "../../domain";
|
||||
import {
|
||||
CustomerInvoiceItemCreationAttributes,
|
||||
@ -59,7 +59,7 @@ export class CustomerInvoiceItemMapper
|
||||
}
|
||||
|
||||
// Validación y creación de precio unitario
|
||||
const unitPriceOrError = CustomerInvoiceItemUnitPrice.create({
|
||||
const unitPriceOrError = CustomerInvoiceItemUnitAmount.create({
|
||||
amount: source.unit_price_amount,
|
||||
scale: source.unit_price_scale,
|
||||
currency_code: sourceParent.invoice_currency,
|
||||
@ -123,11 +123,11 @@ export class CustomerInvoiceItemMapper
|
||||
quantity_amount: source.quantity.toPrimitive().amount,
|
||||
quantity_scale: source.quantity.toPrimitive().scale,
|
||||
|
||||
unit_price_amount: source.unitPrice.toPrimitive().amount,
|
||||
unit_price_scale: source.unitPrice.toPrimitive().scale,
|
||||
unit_price_amount: source.unitAmount.toPrimitive().amount,
|
||||
unit_price_scale: source.unitAmount.toPrimitive().scale,
|
||||
|
||||
subtotal_amount: source.subtotalPrice.toPrimitive().amount,
|
||||
subtotal_scale: source.subtotalPrice.toPrimitive().scale,
|
||||
subtotal_amount: source.subtotalAmount.toPrimitive().amount,
|
||||
subtotal_scale: source.subtotalAmount.toPrimitive().scale,
|
||||
|
||||
discount_amount: source.discount.toPrimitive().amount,
|
||||
discount_scale: source.discount.toPrimitive().scale,
|
||||
|
||||
@ -34,12 +34,28 @@ export class CustomerInvoiceModel extends Model<
|
||||
declare currency_code: string;
|
||||
|
||||
// Subtotal
|
||||
declare subtotal_amount: number;
|
||||
declare subtotal_scale: number;
|
||||
declare subtotal_amount_value: 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
|
||||
declare total_amount: number;
|
||||
declare total_scale: number;
|
||||
declare total_amount_value: number;
|
||||
declare total_amount_scale: number;
|
||||
|
||||
// Relaciones
|
||||
declare items: NonAttribute<CustomerInvoiceItemModel[]>;
|
||||
@ -125,23 +141,70 @@ export default (database: Sequelize) => {
|
||||
defaultValue: "EUR",
|
||||
},
|
||||
|
||||
subtotal_amount: {
|
||||
subtotal_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
subtotal_scale: {
|
||||
subtotal_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: true,
|
||||
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
|
||||
allowNull: true,
|
||||
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(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import * as z from "zod/v4";
|
||||
|
||||
export const GetCustomerInvoiceByIdRequestSchema = z.object({
|
||||
id: z.string(),
|
||||
invoice_id: z.string(),
|
||||
});
|
||||
|
||||
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";
|
||||
|
||||
export const GetCustomerInvoiceByIdResponseSchema = z.object({
|
||||
id: z.uuid(),
|
||||
invoice_status: z.string(),
|
||||
company_id: z.uuid(),
|
||||
|
||||
invoice_number: z.string(),
|
||||
invoice_series: z.string(),
|
||||
issue_date: z.iso.datetime({ offset: true }),
|
||||
operation_date: z.iso.datetime({ offset: true }),
|
||||
status: z.string(),
|
||||
series: z.string(),
|
||||
|
||||
issue_date: z.string(),
|
||||
operation_date: z.string(),
|
||||
|
||||
notes: 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(),
|
||||
});
|
||||
|
||||
@ -90,14 +90,14 @@ export class CustomerService {
|
||||
*
|
||||
* @param companyId - Identificador de la empresa a la que pertenece el cliente.
|
||||
* @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.
|
||||
* @returns Result<Customer, Error> - Cliente actualizado o error.
|
||||
*/
|
||||
async updateCustomerByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
customerId: UniqueID,
|
||||
partial: CustomerPatchProps,
|
||||
changes: CustomerPatchProps,
|
||||
transaction?: any
|
||||
): Promise<Result<Customer, Error>> {
|
||||
const customerResult = await this.getCustomerByIdInCompany(companyId, customerId, transaction);
|
||||
@ -107,7 +107,7 @@ export class CustomerService {
|
||||
}
|
||||
|
||||
const customer = customerResult.data;
|
||||
const updatedCustomer = customer.update(partial);
|
||||
const updatedCustomer = customer.update(changes);
|
||||
|
||||
if (updatedCustomer.isFailure) {
|
||||
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 { Application, NextFunction, Request, Response, Router } from "express";
|
||||
import { Sequelize } from "sequelize";
|
||||
@ -33,11 +33,22 @@ export const customersRouter = (params: ModuleParams) => {
|
||||
|
||||
// 🔐 Autenticación + Tenancy para TODO el router
|
||||
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([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(
|
||||
"/",
|
||||
|
||||
@ -5,7 +5,7 @@ export function isNullishOrEmpty(input: unknown): boolean {
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user