Facturas de cliente
This commit is contained in:
parent
b302870647
commit
db4a79422e
@ -1,6 +1,13 @@
|
||||
import { AggregateRoot, UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoiceCustomer, CustomerInvoiceItem, CustomerInvoiceItems } from "../entities";
|
||||
import {
|
||||
AggregateRoot,
|
||||
CurrencyCode,
|
||||
LanguageCode,
|
||||
TextValue,
|
||||
UniqueID,
|
||||
UtcDate,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoiceItems } from "../entities";
|
||||
import {
|
||||
CustomerInvoiceNumber,
|
||||
CustomerInvoiceSerie,
|
||||
@ -8,20 +15,19 @@ import {
|
||||
} from "../value-objects";
|
||||
|
||||
export interface CustomerInvoiceProps {
|
||||
invoiceNumber: CustomerInvoiceNumber;
|
||||
invoiceSeries: CustomerInvoiceSerie;
|
||||
|
||||
companyId: UniqueID;
|
||||
status: CustomerInvoiceStatus;
|
||||
series: Maybe<CustomerInvoiceSerie>;
|
||||
invoiceNumber: CustomerInvoiceNumber;
|
||||
|
||||
issueDate: UtcDate;
|
||||
operationDate: UtcDate;
|
||||
operationDate: Maybe<UtcDate>;
|
||||
|
||||
notes: Maybe<TextValue>;
|
||||
|
||||
//dueDate: UtcDate; // ? --> depende de la forma de pago
|
||||
|
||||
//tax: Tax; // ? --> detalles?
|
||||
currency: string;
|
||||
|
||||
//language: Language;
|
||||
|
||||
//purchareOrderNumber: string;
|
||||
//notes: Note;
|
||||
@ -31,53 +37,27 @@ export interface CustomerInvoiceProps {
|
||||
//paymentInstructions: Note;
|
||||
//paymentTerms: string;
|
||||
|
||||
customer?: CustomerInvoiceCustomer;
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
//customer?: CustomerInvoiceCustomer;
|
||||
items?: CustomerInvoiceItems;
|
||||
}
|
||||
|
||||
export interface ICustomerInvoice {
|
||||
id: UniqueID;
|
||||
invoiceNumber: CustomerInvoiceNumber;
|
||||
invoiceSeries: CustomerInvoiceSerie;
|
||||
export type CustomerInvoicePatchProps = Partial<Omit<CustomerInvoiceProps, "companyId">>;
|
||||
|
||||
status: CustomerInvoiceStatus;
|
||||
|
||||
issueDate: UtcDate;
|
||||
operationDate: UtcDate;
|
||||
|
||||
//senderId: UniqueID;
|
||||
|
||||
customer?: CustomerInvoiceCustomer;
|
||||
|
||||
//dueDate
|
||||
|
||||
//tax: Tax;
|
||||
//language: Language;
|
||||
currency: string;
|
||||
|
||||
//purchareOrderNumber: string;
|
||||
//notes: Note;
|
||||
|
||||
//paymentInstructions: Note;
|
||||
//paymentTerms: string;
|
||||
|
||||
items: CustomerInvoiceItems;
|
||||
|
||||
calculateSubtotal: () => MoneyValue;
|
||||
calculateTaxTotal: () => MoneyValue;
|
||||
calculateTotal: () => MoneyValue;
|
||||
}
|
||||
|
||||
export class CustomerInvoice
|
||||
extends AggregateRoot<CustomerInvoiceProps>
|
||||
implements ICustomerInvoice
|
||||
{
|
||||
private _items!: Collection<CustomerInvoiceItem>;
|
||||
export class CustomerInvoice extends AggregateRoot<CustomerInvoiceProps> {
|
||||
private _items!: CustomerInvoiceItems;
|
||||
//protected _status: CustomerInvoiceStatus;
|
||||
|
||||
protected constructor(props: CustomerInvoiceProps, id?: UniqueID) {
|
||||
super(props, id);
|
||||
this._items = props.items || CustomerInvoiceItems.create();
|
||||
this._items =
|
||||
props.items ||
|
||||
CustomerInvoiceItems.create({
|
||||
languageCode: props.languageCode,
|
||||
currencyCode: props.currencyCode,
|
||||
});
|
||||
}
|
||||
|
||||
static create(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {
|
||||
@ -94,50 +74,55 @@ export class CustomerInvoice
|
||||
return Result.ok(customerInvoice);
|
||||
}
|
||||
|
||||
get invoiceNumber() {
|
||||
public update(partialInvoice: CustomerInvoicePatchProps): Result<CustomerInvoice, Error> {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
public get companyId(): UniqueID {
|
||||
return this.props.companyId;
|
||||
}
|
||||
|
||||
public get series(): Maybe<CustomerInvoiceSerie> {
|
||||
return this.props.series;
|
||||
}
|
||||
|
||||
public get invoiceNumber() {
|
||||
return this.props.invoiceNumber;
|
||||
}
|
||||
|
||||
get invoiceSeries() {
|
||||
return this.props.invoiceSeries;
|
||||
public get issueDate(): UtcDate {
|
||||
return this.props.issueDate;
|
||||
}
|
||||
|
||||
get issueDate() {
|
||||
return this.props.issueDate;
|
||||
public get operationDate(): Maybe<UtcDate> {
|
||||
return this.props.operationDate;
|
||||
}
|
||||
|
||||
public get notes(): Maybe<TextValue> {
|
||||
return this.props.notes;
|
||||
}
|
||||
|
||||
public get languageCode(): LanguageCode {
|
||||
return this.props.languageCode;
|
||||
}
|
||||
|
||||
public get currencyCode(): CurrencyCode {
|
||||
return this.props.currencyCode;
|
||||
}
|
||||
|
||||
// Method to get the complete list of line items
|
||||
get lineItems(): CustomerInvoiceItems {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
/*get senderId(): UniqueID {
|
||||
return this.props.senderId;
|
||||
}*/
|
||||
|
||||
get customer(): CustomerInvoiceCustomer | undefined {
|
||||
/* get customer(): CustomerInvoiceCustomer | undefined {
|
||||
return this.props.customer;
|
||||
}
|
||||
|
||||
get operationDate() {
|
||||
return this.props.operationDate;
|
||||
}
|
||||
|
||||
/*get language() {
|
||||
return this.props.language;
|
||||
}*/
|
||||
|
||||
get dueDate() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get tax() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this.props.status;
|
||||
}
|
||||
|
||||
get items() {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
/*get purchareOrderNumber() {
|
||||
return this.props.purchareOrderNumber;
|
||||
}
|
||||
@ -158,19 +143,7 @@ export class CustomerInvoice
|
||||
return this.props.shipTo;
|
||||
}*/
|
||||
|
||||
get currency() {
|
||||
return this.props.currency;
|
||||
}
|
||||
|
||||
/*get notes() {
|
||||
return this.props.notes;
|
||||
}*/
|
||||
|
||||
// Method to get the complete list of line items
|
||||
/*get lineItems(): CustomerInvoiceLineItem[] {
|
||||
return this._lineItems;
|
||||
}
|
||||
|
||||
/*
|
||||
addLineItem(lineItem: CustomerInvoiceLineItem, position?: number): void {
|
||||
if (position === undefined) {
|
||||
this._lineItems.push(lineItem);
|
||||
@ -179,7 +152,7 @@ export class CustomerInvoice
|
||||
}
|
||||
}*/
|
||||
|
||||
calculateSubtotal(): MoneyValue {
|
||||
/*calculateSubtotal(): MoneyValue {
|
||||
const customerInvoiceSubtotal = MoneyValue.create({
|
||||
amount: 0,
|
||||
currency_code: this.props.currency,
|
||||
@ -189,10 +162,10 @@ export class CustomerInvoice
|
||||
return this._items.getAll().reduce((subtotal, item) => {
|
||||
return subtotal.add(item.calculateTotal());
|
||||
}, customerInvoiceSubtotal);
|
||||
}
|
||||
}*/
|
||||
|
||||
// Method to calculate the total tax in the customerInvoice
|
||||
calculateTaxTotal(): MoneyValue {
|
||||
/*calculateTaxTotal(): MoneyValue {
|
||||
const taxTotal = MoneyValue.create({
|
||||
amount: 0,
|
||||
currency_code: this.props.currency,
|
||||
@ -200,10 +173,10 @@ export class CustomerInvoice
|
||||
}).data;
|
||||
|
||||
return taxTotal;
|
||||
}
|
||||
}*/
|
||||
|
||||
// Method to calculate the total customerInvoice amount, including taxes
|
||||
calculateTotal(): MoneyValue {
|
||||
/*calculateTotal(): MoneyValue {
|
||||
return this.calculateSubtotal().add(this.calculateTaxTotal());
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MoneyValue, Percentage, Quantity } from "@/core/common/domain";
|
||||
import { CurrencyCode, LanguageCode, MoneyValue, Percentage, Quantity } from "@repo/rdx-ddd";
|
||||
import { CustomerInvoiceItemDescription } from "../../value-objects";
|
||||
import { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||
|
||||
@ -9,6 +9,8 @@ describe("CustomerInvoiceItem", () => {
|
||||
quantity: Quantity.create({ amount: 200, scale: 2 }),
|
||||
unitPrice: MoneyValue.create(50),
|
||||
discount: Percentage.create(0),
|
||||
languageCode: LanguageCode.create("es"),
|
||||
currencyCode: CurrencyCode.create("EUR"),
|
||||
};
|
||||
|
||||
const result = CustomerInvoiceItem.create(props);
|
||||
@ -0,0 +1,102 @@
|
||||
import { CurrencyCode, DomainEntity, LanguageCode, UniqueID } from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
CustomerInvoiceItemDescription,
|
||||
CustomerInvoiceItemDiscount,
|
||||
CustomerInvoiceItemQuantity,
|
||||
CustomerInvoiceItemSubtotalPrice,
|
||||
CustomerInvoiceItemTotalPrice,
|
||||
CustomerInvoiceItemUnitPrice,
|
||||
} 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
|
||||
discount: Maybe<CustomerInvoiceItemDiscount>; // % descuento
|
||||
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
}
|
||||
|
||||
export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps> {
|
||||
private _subtotalPrice!: CustomerInvoiceItemSubtotalPrice;
|
||||
private _totalPrice!: CustomerInvoiceItemTotalPrice;
|
||||
|
||||
public static create(
|
||||
props: CustomerInvoiceItemProps,
|
||||
id?: UniqueID
|
||||
): Result<CustomerInvoiceItem, Error> {
|
||||
const item = new CustomerInvoiceItem(props, id);
|
||||
|
||||
// Reglas de negocio / validaciones
|
||||
// ...
|
||||
// ...
|
||||
|
||||
// 🔹 Disparar evento de dominio "CustomerInvoiceItemCreatedEvent"
|
||||
//const { customerInvoice } = props;
|
||||
//user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString()));
|
||||
|
||||
return Result.ok(item);
|
||||
}
|
||||
|
||||
get description(): Maybe<CustomerInvoiceItemDescription> {
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
get quantity(): Maybe<CustomerInvoiceItemQuantity> {
|
||||
return this.props.quantity;
|
||||
}
|
||||
|
||||
get unitPrice(): Maybe<CustomerInvoiceItemUnitPrice> {
|
||||
return this.props.unitPrice;
|
||||
}
|
||||
|
||||
get subtotalPrice(): CustomerInvoiceItemSubtotalPrice {
|
||||
if (!this._subtotalPrice) {
|
||||
this._subtotalPrice = this.calculateSubtotal();
|
||||
}
|
||||
return this._subtotalPrice;
|
||||
}
|
||||
|
||||
get discount(): Maybe<CustomerInvoiceItemDiscount> {
|
||||
return this.props.discount;
|
||||
}
|
||||
|
||||
get totalPrice(): CustomerInvoiceItemTotalPrice {
|
||||
if (!this._totalPrice) {
|
||||
this._totalPrice = this.calculateTotal();
|
||||
}
|
||||
return this._totalPrice;
|
||||
}
|
||||
|
||||
public get languageCode(): LanguageCode {
|
||||
return this.props.languageCode;
|
||||
}
|
||||
|
||||
public get currencyCode(): CurrencyCode {
|
||||
return this.props.currencyCode;
|
||||
}
|
||||
|
||||
getValue(): CustomerInvoiceItemProps {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getValue();
|
||||
}
|
||||
|
||||
calculateSubtotal(): CustomerInvoiceItemSubtotalPrice {
|
||||
throw new Error("Not implemented");
|
||||
|
||||
/*const unitPrice = this.unitPrice.isSome()
|
||||
? this.unitPrice.unwrap()
|
||||
: CustomerInvoiceItemUnitPrice.zero();
|
||||
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad*/
|
||||
}
|
||||
|
||||
calculateTotal(): CustomerInvoiceItemTotalPrice {
|
||||
throw new Error("Not implemented");
|
||||
//return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
import { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
import { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||
|
||||
export interface CustomerInvoiceItemsProps {
|
||||
items?: CustomerInvoiceItem[];
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
}
|
||||
|
||||
export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
||||
private _languageCode!: LanguageCode;
|
||||
private _currencyCode!: CurrencyCode;
|
||||
|
||||
constructor(props: CustomerInvoiceItemsProps) {
|
||||
const { items, languageCode, currencyCode } = props;
|
||||
super(items);
|
||||
this._languageCode = languageCode;
|
||||
this._currencyCode = currencyCode;
|
||||
}
|
||||
|
||||
public static create(props: CustomerInvoiceItemsProps): CustomerInvoiceItems {
|
||||
return new CustomerInvoiceItems(props);
|
||||
}
|
||||
}
|
||||
@ -1,2 +1,2 @@
|
||||
export * from "./invoice-item";
|
||||
export * from "./invoice-items";
|
||||
export * from "./customer-invoice-item";
|
||||
export * from "./customer-invoice-items";
|
||||
|
||||
@ -1,107 +0,0 @@
|
||||
import { DomainEntity, UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
CustomerInvoiceItemDescription,
|
||||
CustomerInvoiceItemDiscount,
|
||||
CustomerInvoiceItemQuantity,
|
||||
CustomerInvoiceItemSubtotalPrice,
|
||||
CustomerInvoiceItemTotalPrice,
|
||||
CustomerInvoiceItemUnitPrice,
|
||||
} from "../../value-objects";
|
||||
|
||||
export interface ICustomerInvoiceItemProps {
|
||||
description: CustomerInvoiceItemDescription;
|
||||
quantity: CustomerInvoiceItemQuantity; // Cantidad de unidades
|
||||
unitPrice: CustomerInvoiceItemUnitPrice; // Precio unitario en la moneda de la factura
|
||||
//subtotalPrice?: MoneyValue; // Precio unitario * Cantidad
|
||||
discount: CustomerInvoiceItemDiscount; // % descuento
|
||||
//totalPrice?: MoneyValue;
|
||||
}
|
||||
|
||||
export interface ICustomerInvoiceItem {
|
||||
id: UniqueID;
|
||||
description: CustomerInvoiceItemDescription;
|
||||
quantity: CustomerInvoiceItemQuantity;
|
||||
unitPrice: CustomerInvoiceItemUnitPrice;
|
||||
subtotalPrice: CustomerInvoiceItemSubtotalPrice;
|
||||
discount: CustomerInvoiceItemDiscount;
|
||||
totalPrice: CustomerInvoiceItemTotalPrice;
|
||||
}
|
||||
|
||||
export class CustomerInvoiceItem
|
||||
extends DomainEntity<ICustomerInvoiceItemProps>
|
||||
implements ICustomerInvoiceItem
|
||||
{
|
||||
private _subtotalPrice!: CustomerInvoiceItemSubtotalPrice;
|
||||
private _totalPrice!: CustomerInvoiceItemTotalPrice;
|
||||
|
||||
public static create(
|
||||
props: ICustomerInvoiceItemProps,
|
||||
id?: UniqueID
|
||||
): Result<CustomerInvoiceItem, Error> {
|
||||
const item = new CustomerInvoiceItem(props, id);
|
||||
|
||||
// Reglas de negocio / validaciones
|
||||
// ...
|
||||
// ...
|
||||
|
||||
// 🔹 Disparar evento de dominio "CustomerInvoiceItemCreatedEvent"
|
||||
//const { customerInvoice } = props;
|
||||
//user.addDomainEvent(new CustomerInvoiceAuthenticatedEvent(id, customerInvoice.toString()));
|
||||
|
||||
return Result.ok(item);
|
||||
}
|
||||
|
||||
get description(): CustomerInvoiceItemDescription {
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
get quantity(): CustomerInvoiceItemQuantity {
|
||||
return this.props.quantity;
|
||||
}
|
||||
|
||||
get unitPrice(): CustomerInvoiceItemUnitPrice {
|
||||
return this.props.unitPrice;
|
||||
}
|
||||
|
||||
get subtotalPrice(): CustomerInvoiceItemSubtotalPrice {
|
||||
if (!this._subtotalPrice) {
|
||||
this._subtotalPrice = this.calculateSubtotal();
|
||||
}
|
||||
return this._subtotalPrice;
|
||||
}
|
||||
|
||||
get discount(): CustomerInvoiceItemDiscount {
|
||||
return this.props.discount;
|
||||
}
|
||||
|
||||
get totalPrice(): CustomerInvoiceItemTotalPrice {
|
||||
if (!this._totalPrice) {
|
||||
this._totalPrice = this.calculateTotal();
|
||||
}
|
||||
return this._totalPrice;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return {
|
||||
description: this.description.toPrimitive(),
|
||||
quantity: this.quantity.toPrimitive(),
|
||||
unit_price: this.unitPrice.toPrimitive(),
|
||||
subtotal_price: this.subtotalPrice.toPrimitive(),
|
||||
discount: this.discount.toPrimitive(),
|
||||
total_price: this.totalPrice.toPrimitive(),
|
||||
};
|
||||
}
|
||||
|
||||
calculateSubtotal(): CustomerInvoiceItemSubtotalPrice {
|
||||
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad
|
||||
}
|
||||
|
||||
calculateTotal(): CustomerInvoiceItemTotalPrice {
|
||||
return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
import { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||
|
||||
export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
||||
public static create(items?: CustomerInvoiceItem[]): CustomerInvoiceItems {
|
||||
return new CustomerInvoiceItems(items);
|
||||
}
|
||||
}
|
||||
@ -3,12 +3,15 @@ import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoice } from "../aggregates";
|
||||
|
||||
/**
|
||||
* Interfaz del repositorio para el agregado `CustomerInvoice`.
|
||||
* El escopado multitenant está representado por `companyId`.
|
||||
*/
|
||||
export interface ICustomerInvoiceRepository {
|
||||
existsById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
|
||||
|
||||
/**
|
||||
*
|
||||
* Persiste una nueva factura o actualiza una existente.
|
||||
* Retorna el objeto actualizado tras la operación.
|
||||
*
|
||||
* @param invoice - El agregado a guardar.
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
@ -17,34 +20,55 @@ export interface ICustomerInvoiceRepository {
|
||||
save(invoice: CustomerInvoice, transaction: any): Promise<Result<CustomerInvoice, Error>>;
|
||||
|
||||
/**
|
||||
*
|
||||
* Busca una factura por su identificador único.
|
||||
* @param id - UUID de la factura.
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<CustomerInvoice, Error>
|
||||
* Comprueba si existe una factura con un `id` dentro de una `company`.
|
||||
*/
|
||||
findById(id: UniqueID, transaction: any): Promise<Result<CustomerInvoice, Error>>;
|
||||
existsByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction?: any
|
||||
): Promise<Result<boolean, Error>>;
|
||||
|
||||
/**
|
||||
* Recupera una factura por su ID y companyId.
|
||||
* Devuelve un `NotFoundError` si no se encuentra.
|
||||
*/
|
||||
getByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction?: any
|
||||
): Promise<Result<CustomerInvoice, Error>>;
|
||||
|
||||
/**
|
||||
*
|
||||
* Consulta facturas usando un objeto Criteria (filtros, orden, paginación).
|
||||
* Consulta facturas dentro de una empresa usando un
|
||||
* objeto Criteria (filtros, orden, paginación).
|
||||
* El resultado está encapsulado en un objeto `Collection<T>`.
|
||||
*
|
||||
* @param companyId - ID de la empresa.
|
||||
* @param criteria - Criterios de búsqueda.
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<CustomerInvoice[], Error>
|
||||
*
|
||||
* @see Criteria
|
||||
*/
|
||||
findByCriteria(
|
||||
findByCriteriaInCompany(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction: any
|
||||
): Promise<Result<Collection<CustomerInvoice>, Error>>;
|
||||
|
||||
/**
|
||||
*
|
||||
* Elimina o marca como eliminada una factura.
|
||||
* Elimina o marca como eliminada una factura dentro de una empresa.
|
||||
*
|
||||
* @param companyId - ID de la empresa.
|
||||
* @param id - UUID de la factura a eliminar.
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<void, Error>
|
||||
*/
|
||||
deleteById(id: UniqueID, transaction: any): Promise<Result<void, Error>>;
|
||||
deleteByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction: any
|
||||
): Promise<Result<void, Error>>;
|
||||
}
|
||||
|
||||
@ -14,10 +14,10 @@ export class CustomerInvoiceAddressType extends ValueObject<ICustomerInvoiceAddr
|
||||
private static readonly ALLOWED_TYPES = ["shipping", "billing"];
|
||||
|
||||
static create(value: string): Result<CustomerInvoiceAddressType, Error> {
|
||||
if (!this.ALLOWED_TYPES.includes(value)) {
|
||||
if (!CustomerInvoiceAddressType.ALLOWED_TYPES.includes(value)) {
|
||||
return Result.fail(
|
||||
new Error(
|
||||
`Invalid address type: ${value}. Allowed types are: ${this.ALLOWED_TYPES.join(", ")}`
|
||||
`Invalid address type: ${value}. Allowed types are: ${CustomerInvoiceAddressType.ALLOWED_TYPES.join(", ")}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,11 +3,11 @@ import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
interface ICustomerInvoiceItemDescriptionProps {
|
||||
interface CustomerInvoiceItemDescriptionProps {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class CustomerInvoiceItemDescription extends ValueObject<ICustomerInvoiceItemDescriptionProps> {
|
||||
export class CustomerInvoiceItemDescription extends ValueObject<CustomerInvoiceItemDescriptionProps> {
|
||||
private static readonly MAX_LENGTH = 255;
|
||||
private static readonly FIELD = "invoiceItemDescription";
|
||||
private static readonly ERROR_CODE = "INVALID_INVOICE_ITEM_DESCRIPTION";
|
||||
@ -23,10 +23,10 @@ export class CustomerInvoiceItemDescription extends ValueObject<ICustomerInvoice
|
||||
}
|
||||
|
||||
static create(value: string) {
|
||||
const result = CustomerInvoiceItemDescription.validate(value);
|
||||
const valueIsValid = CustomerInvoiceItemDescription.validate(value);
|
||||
|
||||
if (!result.success) {
|
||||
const detail = result.error.message;
|
||||
if (!valueIsValid.success) {
|
||||
const detail = valueIsValid.error.message;
|
||||
return Result.fail(
|
||||
new DomainValidationError(
|
||||
CustomerInvoiceItemDescription.ERROR_CODE,
|
||||
|
||||
@ -11,4 +11,13 @@ export class CustomerInvoiceItemUnitPrice extends MoneyValue {
|
||||
};
|
||||
return MoneyValue.create(props);
|
||||
}
|
||||
|
||||
static zero(currency_code: string, scale: number = CustomerInvoiceItemUnitPrice.DEFAULT_SCALE) {
|
||||
const props: MoneyValueProps = {
|
||||
amount: 0,
|
||||
scale,
|
||||
currency_code,
|
||||
};
|
||||
return MoneyValue.create(props);
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,10 +23,10 @@ export class CustomerInvoiceNumber extends ValueObject<ICustomerInvoiceNumberPro
|
||||
}
|
||||
|
||||
static create(value: string) {
|
||||
const result = CustomerInvoiceNumber.validate(value);
|
||||
const valueIsValid = CustomerInvoiceNumber.validate(value);
|
||||
|
||||
if (!result.success) {
|
||||
const detail = result.error.message;
|
||||
if (!valueIsValid.success) {
|
||||
const detail = valueIsValid.error.message;
|
||||
return Result.fail(
|
||||
new DomainValidationError(
|
||||
CustomerInvoiceNumber.ERROR_CODE,
|
||||
|
||||
@ -23,10 +23,10 @@ export class CustomerInvoiceSerie extends ValueObject<ICustomerInvoiceSerieProps
|
||||
}
|
||||
|
||||
static create(value: string) {
|
||||
const result = CustomerInvoiceSerie.validate(value);
|
||||
const valueIsValid = CustomerInvoiceSerie.validate(value);
|
||||
|
||||
if (!result.success) {
|
||||
const detail = result.error.message;
|
||||
if (!valueIsValid.success) {
|
||||
const detail = valueIsValid.error.message;
|
||||
return Result.fail(
|
||||
new DomainValidationError(
|
||||
CustomerInvoiceSerie.ERROR_CODE,
|
||||
|
||||
@ -4,18 +4,18 @@ import { customerInvoicesRouter, models } from "./infrastructure";
|
||||
export const customerInvoicesAPIModule: IModuleServer = {
|
||||
name: "customer-invoices",
|
||||
version: "1.0.0",
|
||||
dependencies: [],
|
||||
dependencies: ["customers"],
|
||||
|
||||
async init(params: ModuleParams) {
|
||||
// const contacts = getService<ContactsService>("contacts");
|
||||
const { logger } = params;
|
||||
customerInvoicesRouter(params);
|
||||
logger.info("🚀 CustomerInvoices module initialized", { label: "customer-invoices" });
|
||||
logger.info("🚀 CustomerInvoices module initialized", { label: this.name });
|
||||
},
|
||||
async registerDependencies(params) {
|
||||
const { database, logger } = params;
|
||||
logger.info("🚀 CustomerInvoices module dependencies registered", {
|
||||
label: "customer-invoices",
|
||||
label: this.name,
|
||||
});
|
||||
return {
|
||||
models,
|
||||
|
||||
@ -8,6 +8,8 @@ import {
|
||||
GetCustomerInvoiceUseCase,
|
||||
ListCustomerInvoicesAssembler,
|
||||
ListCustomerInvoicesUseCase,
|
||||
UpdateCustomerInvoiceAssembler,
|
||||
UpdateCustomerInvoiceUseCase,
|
||||
} from "../application";
|
||||
import { CustomerInvoiceService, ICustomerInvoiceService } from "../domain";
|
||||
import { CustomerInvoiceMapper } from "./mappers";
|
||||
@ -22,13 +24,13 @@ type InvoiceDeps = {
|
||||
list: ListCustomerInvoicesAssembler;
|
||||
get: GetCustomerInvoiceAssembler;
|
||||
create: CreateCustomerInvoicesAssembler;
|
||||
//update: UpdateCustomerInvoiceAssembler;
|
||||
update: UpdateCustomerInvoiceAssembler;
|
||||
};
|
||||
build: {
|
||||
list: () => ListCustomerInvoicesUseCase;
|
||||
get: () => GetCustomerInvoiceUseCase;
|
||||
create: () => CreateCustomerInvoiceUseCase;
|
||||
//update: () => UpdateCustomerInvoiceUseCase;
|
||||
update: () => UpdateCustomerInvoiceUseCase;
|
||||
delete: () => DeleteCustomerInvoiceUseCase;
|
||||
};
|
||||
presenters: {
|
||||
@ -54,7 +56,7 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
||||
list: new ListCustomerInvoicesAssembler(), // transforma domain → ListDTO
|
||||
get: new GetCustomerInvoiceAssembler(), // transforma domain → DetailDTO
|
||||
create: new CreateCustomerInvoicesAssembler(), // transforma domain → CreatedDTO
|
||||
//update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO
|
||||
update: new UpdateCustomerInvoiceAssembler(), // transforma domain -> UpdateDTO
|
||||
};
|
||||
}
|
||||
|
||||
@ -70,8 +72,8 @@ export function getInvoiceDependencies(params: ModuleParams): InvoiceDeps {
|
||||
get: () => new GetCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.get),
|
||||
create: () =>
|
||||
new CreateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.create),
|
||||
/*update: () =>
|
||||
new UpdateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.update),*/
|
||||
update: () =>
|
||||
new UpdateCustomerInvoiceUseCase(_service!, transactionManager!, _assemblers!.update),
|
||||
delete: () => new DeleteCustomerInvoiceUseCase(_service!, transactionManager!),
|
||||
},
|
||||
presenters: {
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core/api";
|
||||
import { UniqueID, UtcDate } from "@repo/rdx-ddd";
|
||||
import {
|
||||
ISequelizeMapper,
|
||||
MapperParamsType,
|
||||
SequelizeMapper,
|
||||
ValidationErrorCollection,
|
||||
ValidationErrorDetail,
|
||||
extractOrPushError,
|
||||
} from "@erp/core/api";
|
||||
import { CurrencyCode, LanguageCode, UniqueID, UtcDate, maybeFromNullableVO } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import {
|
||||
CustomerInvoice,
|
||||
CustomerInvoiceNumber,
|
||||
CustomerInvoiceProps,
|
||||
CustomerInvoiceSerie,
|
||||
CustomerInvoiceStatus,
|
||||
} from "../../domain";
|
||||
@ -32,50 +40,75 @@ export class CustomerInvoiceMapper
|
||||
source: CustomerInvoiceModel,
|
||||
params?: MapperParamsType
|
||||
): Result<CustomerInvoice, Error> {
|
||||
const idOrError = UniqueID.create(source.id);
|
||||
const statusOrError = CustomerInvoiceStatus.create(source.invoice_status);
|
||||
const customerInvoiceSeriesOrError = CustomerInvoiceSerie.create(source.invoice_series);
|
||||
const customerInvoiceNumberOrError = CustomerInvoiceNumber.create(source.invoice_number);
|
||||
const issueDateOrError = UtcDate.createFromISO(source.issue_date);
|
||||
const operationDateOrError = UtcDate.createFromISO(source.operation_date);
|
||||
const errors: ValidationErrorDetail[] = [];
|
||||
|
||||
const result = Result.combine([
|
||||
idOrError,
|
||||
statusOrError,
|
||||
customerInvoiceSeriesOrError,
|
||||
customerInvoiceNumberOrError,
|
||||
issueDateOrError,
|
||||
operationDateOrError,
|
||||
]);
|
||||
const invoiceId = extractOrPushError(UniqueID.create(source.id), "id", errors);
|
||||
const companyId = extractOrPushError(UniqueID.create(source.company_id), "company_id", errors);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
const status = extractOrPushError(
|
||||
CustomerInvoiceStatus.create(source.status),
|
||||
"status",
|
||||
errors
|
||||
);
|
||||
const series = extractOrPushError(CustomerInvoiceSerie.create(source.series), "series", errors);
|
||||
const invoiceNumber = extractOrPushError(
|
||||
CustomerInvoiceNumber.create(source.invoice_number),
|
||||
"invoice_number",
|
||||
errors
|
||||
);
|
||||
const issueDate = extractOrPushError(
|
||||
UtcDate.createFromISO(source.issue_date),
|
||||
"issue_date",
|
||||
errors
|
||||
);
|
||||
|
||||
const operationDate = extractOrPushError(
|
||||
maybeFromNullableVO(source.operation_date, (value) => UtcDate.createFromISO(value)),
|
||||
"operation_date",
|
||||
errors
|
||||
);
|
||||
|
||||
const languageCode = extractOrPushError(
|
||||
LanguageCode.create(source.language_code),
|
||||
"language_code",
|
||||
errors
|
||||
);
|
||||
|
||||
const currencyCode = extractOrPushError(
|
||||
CurrencyCode.create(source.currency_code),
|
||||
"currency_code",
|
||||
errors
|
||||
);
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Customer invoice props mapping failed", errors)
|
||||
);
|
||||
}
|
||||
|
||||
// Mapear los items de la factura
|
||||
const itemsOrErrors = this.customerInvoiceItemMapper.mapArrayToDomain(source.items, {
|
||||
/*const itemsOrErrors = this.customerInvoiceItemMapper.mapArrayToDomain(source.items, {
|
||||
sourceParent: source,
|
||||
...params,
|
||||
});
|
||||
|
||||
if (itemsOrErrors.isFailure) {
|
||||
return Result.fail(itemsOrErrors.error);
|
||||
}
|
||||
}*/
|
||||
|
||||
const customerInvoiceCurrency = source.invoice_currency || "EUR";
|
||||
const invoiceProps: CustomerInvoiceProps = {
|
||||
status: status!,
|
||||
series: series!,
|
||||
invoiceNumber: invoiceNumber!,
|
||||
issueDate: issueDate!,
|
||||
operationDate: operationDate!,
|
||||
|
||||
return CustomerInvoice.create(
|
||||
{
|
||||
status: statusOrError.data,
|
||||
invoiceSeries: customerInvoiceSeriesOrError.data,
|
||||
invoiceNumber: customerInvoiceNumberOrError.data,
|
||||
issueDate: issueDateOrError.data,
|
||||
operationDate: operationDateOrError.data,
|
||||
currency: customerInvoiceCurrency,
|
||||
items: itemsOrErrors.data,
|
||||
},
|
||||
idOrError.data
|
||||
);
|
||||
languageCode: languageCode!,
|
||||
currencyCode: currencyCode!,
|
||||
//items: itemsOrErrors.data,
|
||||
};
|
||||
|
||||
return CustomerInvoice.create(invoiceProps, invoiceId);
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
@ -21,26 +20,26 @@ export class CustomerInvoiceItemModel extends Model<
|
||||
declare item_id: string;
|
||||
declare invoice_id: string;
|
||||
|
||||
declare parent_id: CreationOptional<string>;
|
||||
declare parent_id: string;
|
||||
declare position: number;
|
||||
declare item_type: string;
|
||||
|
||||
declare description: CreationOptional<string>;
|
||||
declare description: string;
|
||||
|
||||
declare quantity_amount: CreationOptional<number>;
|
||||
declare quantity_scale: CreationOptional<number>;
|
||||
declare quantity_amount: number;
|
||||
declare quantity_scale: number;
|
||||
|
||||
declare unit_price_amount: CreationOptional<number>;
|
||||
declare unit_price_scale: CreationOptional<number>;
|
||||
declare unit_price_amount: number;
|
||||
declare unit_price_scale: number;
|
||||
|
||||
declare subtotal_amount: CreationOptional<number>;
|
||||
declare subtotal_scale: CreationOptional<number>;
|
||||
declare subtotal_amount: number;
|
||||
declare subtotal_scale: number;
|
||||
|
||||
declare discount_amount: CreationOptional<number>;
|
||||
declare discount_scale: CreationOptional<number>;
|
||||
declare discount_amount: number;
|
||||
declare discount_scale: number;
|
||||
|
||||
declare total_amount: CreationOptional<number>;
|
||||
declare total_scale: CreationOptional<number>;
|
||||
declare total_amount: number;
|
||||
declare total_scale: number;
|
||||
|
||||
declare invoice: NonAttribute<CustomerInvoiceModel>;
|
||||
|
||||
@ -65,14 +64,14 @@ export default (database: Sequelize) => {
|
||||
},
|
||||
invoice_id: {
|
||||
type: new DataTypes.UUID(),
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
parent_id: {
|
||||
type: new DataTypes.UUID(),
|
||||
allowNull: true, // Puede ser nulo para elementos de nivel superior
|
||||
},
|
||||
position: {
|
||||
type: new DataTypes.MEDIUMINT(),
|
||||
type: new DataTypes.MEDIUMINT().UNSIGNED,
|
||||
autoIncrement: false,
|
||||
allowNull: false,
|
||||
},
|
||||
@ -84,6 +83,7 @@ export default (database: Sequelize) => {
|
||||
description: {
|
||||
type: new DataTypes.TEXT(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
quantity_amount: {
|
||||
@ -160,9 +160,17 @@ export default (database: Sequelize) => {
|
||||
},
|
||||
{
|
||||
sequelize: database,
|
||||
underscored: true,
|
||||
tableName: "customer_invoice_items",
|
||||
|
||||
underscored: true,
|
||||
|
||||
indexes: [
|
||||
{ name: "invoice_idx", fields: ["invoice_id"], unique: false },
|
||||
{ name: "parent_idx", fields: ["parent_id"], unique: false },
|
||||
],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
CreationOptional,
|
||||
DataTypes,
|
||||
InferAttributes,
|
||||
InferCreationAttributes,
|
||||
@ -24,22 +23,23 @@ export class CustomerInvoiceModel extends Model<
|
||||
InferCreationAttributes<CustomerInvoiceModel, { omit: "items" }>
|
||||
> {
|
||||
declare id: string;
|
||||
declare company_id: string;
|
||||
|
||||
declare invoice_status: string;
|
||||
declare invoice_series: CreationOptional<string>;
|
||||
declare invoice_number: CreationOptional<string>;
|
||||
declare issue_date: CreationOptional<string>;
|
||||
declare operation_date: CreationOptional<string>;
|
||||
declare invoice_language: string;
|
||||
declare invoice_currency: string;
|
||||
declare status: string;
|
||||
declare series: string;
|
||||
declare invoice_number: string;
|
||||
declare issue_date: string;
|
||||
declare operation_date: string;
|
||||
declare language_code: string;
|
||||
declare currency_code: string;
|
||||
|
||||
// Subtotal
|
||||
declare subtotal_amount: CreationOptional<number>;
|
||||
declare subtotal_scale: CreationOptional<number>;
|
||||
declare subtotal_amount: number;
|
||||
declare subtotal_scale: number;
|
||||
|
||||
// Total
|
||||
declare total_amount: CreationOptional<number>;
|
||||
declare total_scale: CreationOptional<number>;
|
||||
declare total_amount: number;
|
||||
declare total_scale: number;
|
||||
|
||||
// Relaciones
|
||||
declare items: NonAttribute<CustomerInvoiceItemModel[]>;
|
||||
@ -59,14 +59,14 @@ export class CustomerInvoiceModel extends Model<
|
||||
|
||||
static hooks(database: Sequelize) {
|
||||
// Soft-cascade manual: al borrar una factura, marcamos items como borrados (paranoid).
|
||||
CustomerInvoiceModel.addHook("afterDestroy", async (invoice, options) => {
|
||||
/*CustomerInvoiceModel.addHook("afterDestroy", async (invoice, options) => {
|
||||
if (!invoice?.id) return;
|
||||
await CustomerInvoiceItemModel.destroy({
|
||||
where: { invoiceId: invoice.id },
|
||||
where: { invoice_id: invoice.id },
|
||||
individualHooks: true,
|
||||
transaction: options.transaction,
|
||||
});
|
||||
});
|
||||
});*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,12 +78,18 @@ export default (database: Sequelize) => {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
invoice_status: {
|
||||
type: new DataTypes.STRING(),
|
||||
company_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
invoice_series: {
|
||||
status: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
defaultValue: "draft",
|
||||
},
|
||||
|
||||
series: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
@ -107,14 +113,16 @@ export default (database: Sequelize) => {
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
invoice_language: {
|
||||
type: new DataTypes.STRING(),
|
||||
language_code: {
|
||||
type: DataTypes.STRING(2),
|
||||
allowNull: false,
|
||||
defaultValue: "es",
|
||||
},
|
||||
|
||||
invoice_currency: {
|
||||
type: new DataTypes.STRING(3), // ISO 4217
|
||||
currency_code: {
|
||||
type: new DataTypes.STRING(3),
|
||||
allowNull: false,
|
||||
defaultValue: "EUR",
|
||||
},
|
||||
|
||||
subtotal_amount: {
|
||||
@ -151,7 +159,11 @@ export default (database: Sequelize) => {
|
||||
updatedAt: "updated_at",
|
||||
deletedAt: "deleted_at",
|
||||
|
||||
indexes: [{ unique: true, fields: ["invoice_number"] }],
|
||||
indexes: [
|
||||
{ name: "company_idx", fields: ["company_id"], unique: false },
|
||||
{ name: "idx_company_idx", fields: ["id", "company_id"], unique: true },
|
||||
{ unique: true, fields: ["invoice_number"] },
|
||||
],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { SequelizeRepository } from "@erp/core/api";
|
||||
import { EntityNotFoundError, SequelizeRepository, translateSequelizeError } from "@erp/core/api";
|
||||
import { Criteria, CriteriaToSequelizeConverter } from "@repo/rdx-criteria/server";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Collection, Result } from "@repo/rdx-utils";
|
||||
@ -11,7 +11,6 @@ export class CustomerInvoiceRepository
|
||||
extends SequelizeRepository<CustomerInvoice>
|
||||
implements ICustomerInvoiceRepository
|
||||
{
|
||||
//private readonly model: typeof CustomerInvoiceModel;
|
||||
private readonly mapper!: ICustomerInvoiceMapper;
|
||||
|
||||
constructor(mapper: ICustomerInvoiceMapper) {
|
||||
@ -52,16 +51,6 @@ export class CustomerInvoiceRepository
|
||||
};
|
||||
} */
|
||||
|
||||
async existsById(id: UniqueID, transaction?: Transaction): Promise<Result<boolean, Error>> {
|
||||
try {
|
||||
const result = await this._exists(CustomerInvoiceModel, "id", id.toString(), transaction);
|
||||
|
||||
return Result.ok(Boolean(result));
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(translateSequelizeError(err));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Persiste una nueva factura o actualiza una existente.
|
||||
@ -76,29 +65,64 @@ export class CustomerInvoiceRepository
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
try {
|
||||
const data = this.mapper.mapToPersistence(invoice);
|
||||
await CustomerInvoiceModel.upsert(data, { transaction });
|
||||
return Result.ok(invoice);
|
||||
const [instance] = await CustomerInvoiceModel.upsert(data, { transaction, returning: true });
|
||||
const savedInvoice = this.mapper.mapToDomain(instance);
|
||||
return savedInvoice;
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(translateSequelizeError(err));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comprueba si existe una factura con un `id` dentro de una `company`.
|
||||
*
|
||||
* @param companyId - Identificador UUID de la empresa a la que pertenece la factura.
|
||||
* @param id - Identificador UUID de la factura.
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<boolean, Error>
|
||||
*/
|
||||
async existsByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction?: Transaction
|
||||
): Promise<Result<boolean, Error>> {
|
||||
try {
|
||||
const count = await CustomerInvoiceModel.count({
|
||||
where: { id: id.toString(), company_id: companyId.toString() },
|
||||
transaction,
|
||||
});
|
||||
return Result.ok(Boolean(count > 0));
|
||||
} catch (error: any) {
|
||||
return Result.fail(translateSequelizeError(error));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Busca una factura por su identificador único.
|
||||
*
|
||||
* @param companyId - Identificador UUID de la empresa a la que pertenece la factura.
|
||||
* @param id - UUID de la factura.
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<CustomerInvoice, Error>
|
||||
*/
|
||||
async findById(id: UniqueID, transaction: Transaction): Promise<Result<CustomerInvoice, Error>> {
|
||||
async getByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction: Transaction
|
||||
): Promise<Result<CustomerInvoice, Error>> {
|
||||
try {
|
||||
const rawData = await this._findById(CustomerInvoiceModel, id.toString(), { transaction });
|
||||
const row = await CustomerInvoiceModel.findOne({
|
||||
where: { id: id.toString(), company_id: companyId.toString() },
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!rawData) {
|
||||
return Result.fail(new Error(`Invoice with id ${id} not found.`));
|
||||
if (!row) {
|
||||
return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString()));
|
||||
}
|
||||
|
||||
return this.mapper.mapToDomain(rawData);
|
||||
const customer = this.mapper.mapToDomain(row);
|
||||
return customer;
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(translateSequelizeError(err));
|
||||
}
|
||||
@ -107,13 +131,16 @@ export class CustomerInvoiceRepository
|
||||
/**
|
||||
*
|
||||
* Consulta facturas usando un objeto Criteria (filtros, orden, paginación).
|
||||
*
|
||||
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
||||
* @param criteria - Criterios de búsqueda.
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<CustomerInvoice[], Error>
|
||||
*
|
||||
* @see Criteria
|
||||
*/
|
||||
public async findByCriteria(
|
||||
public async findByCriteriaInCompany(
|
||||
companyId: UniqueID,
|
||||
criteria: Criteria,
|
||||
transaction: Transaction
|
||||
): Promise<Result<Collection<CustomerInvoice>, Error>> {
|
||||
@ -121,6 +148,11 @@ export class CustomerInvoiceRepository
|
||||
const converter = new CriteriaToSequelizeConverter();
|
||||
const query = converter.convert(criteria);
|
||||
|
||||
query.where = {
|
||||
...query.where,
|
||||
company_id: companyId.toString(),
|
||||
};
|
||||
|
||||
const instances = await CustomerInvoiceModel.findAll({
|
||||
...query,
|
||||
transaction,
|
||||
@ -135,13 +167,23 @@ export class CustomerInvoiceRepository
|
||||
/**
|
||||
*
|
||||
* Elimina o marca como eliminada una factura.
|
||||
*
|
||||
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
||||
* @param id - UUID de la factura a eliminar.
|
||||
* @param transaction - Transacción activa para la operación.
|
||||
* @returns Result<void, Error>
|
||||
*/
|
||||
async deleteById(id: UniqueID, transaction: any): Promise<Result<void, Error>> {
|
||||
async deleteByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction: any
|
||||
): Promise<Result<void, Error>> {
|
||||
try {
|
||||
await this._deleteById(CustomerInvoiceModel, id, false, transaction);
|
||||
const deleted = await CustomerInvoiceModel.destroy({
|
||||
where: { id: id.toString(), company_id: companyId.toString() },
|
||||
transaction,
|
||||
});
|
||||
|
||||
return Result.ok<void>();
|
||||
} catch (err: unknown) {
|
||||
return Result.fail(translateSequelizeError(err));
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
PhoneNumber,
|
||||
PostalAddress,
|
||||
PostalAddressPatchProps,
|
||||
PostalAddressSnapshot,
|
||||
TINNumber,
|
||||
TaxCode,
|
||||
TextValue,
|
||||
@ -41,32 +40,6 @@ export interface CustomerProps {
|
||||
currencyCode: CurrencyCode;
|
||||
}
|
||||
|
||||
export interface CustomerSnapshot {
|
||||
id: string;
|
||||
companyId: string;
|
||||
status: string;
|
||||
reference: string | null;
|
||||
|
||||
isCompany: boolean;
|
||||
name: string;
|
||||
tradeName: string | null;
|
||||
|
||||
tin: string | null;
|
||||
|
||||
address: PostalAddressSnapshot; // snapshot serializable del VO PostalAddress
|
||||
|
||||
email: string | null;
|
||||
phone: string | null;
|
||||
fax: string | null;
|
||||
website: string | null;
|
||||
|
||||
legalRecord: string | null;
|
||||
defaultTaxes: string[];
|
||||
|
||||
languageCode: string;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export type CustomerPatchProps = Partial<Omit<CustomerProps, "companyId" | "address">> & {
|
||||
address?: PostalAddressPatchProps;
|
||||
};
|
||||
|
||||
@ -4,8 +4,7 @@ import { Collection, Result } from "@repo/rdx-utils";
|
||||
import { Customer } from "../aggregates";
|
||||
|
||||
/**
|
||||
* Contrato del repositorio de Customers.
|
||||
* Define la interfaz de persistencia para el agregado `Customer`.
|
||||
* Interfaz del repositorio para el agregado `Customer`.
|
||||
* El escopado multitenant está representado por `companyId`.
|
||||
*/
|
||||
export interface ICustomerRepository {
|
||||
@ -35,7 +34,8 @@ export interface ICustomerRepository {
|
||||
): Promise<Result<Customer, Error>>;
|
||||
|
||||
/**
|
||||
* Recupera múltiples customers dentro de una empresa según un criterio dinámico (búsqueda, paginación, etc.).
|
||||
* Recupera múltiples customers dentro de una empresa
|
||||
* según un criterio dinámico (búsqueda, paginación, etc.).
|
||||
* El resultado está encapsulado en un objeto `Collection<T>`.
|
||||
*/
|
||||
findByCriteriaInCompany(
|
||||
@ -47,6 +47,11 @@ export interface ICustomerRepository {
|
||||
/**
|
||||
* Elimina un Customer por su ID, dentro de una empresa.
|
||||
* Retorna `void` si se elimina correctamente, o `NotFoundError` si no existía.
|
||||
*
|
||||
*/
|
||||
deleteByIdInCompany(companyId: UniqueID, id: UniqueID, transaction: any): Promise<Result<void>>;
|
||||
deleteByIdInCompany(
|
||||
companyId: UniqueID,
|
||||
id: UniqueID,
|
||||
transaction: any
|
||||
): Promise<Result<void, Error>>;
|
||||
}
|
||||
|
||||
@ -10,12 +10,12 @@ export const customersAPIModule: IModuleServer = {
|
||||
// const contacts = getService<ContactsService>("contacts");
|
||||
const { logger } = params;
|
||||
customersRouter(params);
|
||||
logger.info("🚀 Customers module initialized", { label: "customers" });
|
||||
logger.info("🚀 Customers module initialized", { label: this.name });
|
||||
},
|
||||
async registerDependencies(params) {
|
||||
const { database, logger } = params;
|
||||
logger.info("🚀 Customers module dependencies registered", {
|
||||
label: "customers",
|
||||
label: this.name,
|
||||
});
|
||||
return {
|
||||
models,
|
||||
|
||||
@ -1,128 +0,0 @@
|
||||
import { ISequelizeMapper, MapperParamsType, SequelizeMapper } from "@erp/core/api";
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { InferCreationAttributes } from "sequelize";
|
||||
import {
|
||||
Customer,
|
||||
CustomerItem,
|
||||
CustomerItemDescription,
|
||||
CustomerItemDiscount,
|
||||
CustomerItemQuantity,
|
||||
CustomerItemUnitPrice,
|
||||
} from "../../domain";
|
||||
import { CustomerItemCreationAttributes, CustomerItemModel, CustomerModel } from "../sequelize";
|
||||
|
||||
export interface ICustomerItemMapper
|
||||
extends ISequelizeMapper<CustomerItemModel, CustomerItemCreationAttributes, CustomerItem> {}
|
||||
|
||||
export class CustomerItemMapper
|
||||
extends SequelizeMapper<CustomerItemModel, CustomerItemCreationAttributes, CustomerItem>
|
||||
implements ICustomerItemMapper
|
||||
{
|
||||
public mapToDomain(
|
||||
source: CustomerItemModel,
|
||||
params?: MapperParamsType
|
||||
): Result<CustomerItem, Error> {
|
||||
const { sourceParent } = params as { sourceParent: CustomerModel };
|
||||
|
||||
// Validación y creación de ID único
|
||||
const idOrError = UniqueID.create(source.item_id);
|
||||
if (idOrError.isFailure) {
|
||||
return Result.fail(idOrError.error);
|
||||
}
|
||||
|
||||
// Validación y creación de descripción
|
||||
const descriptionOrError = CustomerItemDescription.create(source.description || "");
|
||||
if (descriptionOrError.isFailure) {
|
||||
return Result.fail(descriptionOrError.error);
|
||||
}
|
||||
|
||||
// Validación y creación de cantidad
|
||||
const quantityOrError = CustomerItemQuantity.create({
|
||||
amount: source.quantity_amount,
|
||||
scale: source.quantity_scale,
|
||||
});
|
||||
if (quantityOrError.isFailure) {
|
||||
return Result.fail(quantityOrError.error);
|
||||
}
|
||||
|
||||
// Validación y creación de precio unitario
|
||||
const unitPriceOrError = CustomerItemUnitPrice.create({
|
||||
amount: source.unit_price_amount,
|
||||
scale: source.unit_price_scale,
|
||||
currency_code: sourceParent.invoice_currency,
|
||||
});
|
||||
if (unitPriceOrError.isFailure) {
|
||||
return Result.fail(unitPriceOrError.error);
|
||||
}
|
||||
|
||||
// Validación y creación de descuento
|
||||
const discountOrError = CustomerItemDiscount.create({
|
||||
amount: source.discount_amount || 0,
|
||||
scale: source.discount_scale || 0,
|
||||
});
|
||||
if (discountOrError.isFailure) {
|
||||
return Result.fail(discountOrError.error);
|
||||
}
|
||||
|
||||
// Combinación de resultados
|
||||
const result = Result.combine([
|
||||
idOrError,
|
||||
descriptionOrError,
|
||||
quantityOrError,
|
||||
unitPriceOrError,
|
||||
discountOrError,
|
||||
]);
|
||||
|
||||
if (result.isFailure) {
|
||||
return Result.fail(result.error);
|
||||
}
|
||||
|
||||
// Creación del objeto de dominio
|
||||
return CustomerItem.create(
|
||||
{
|
||||
description: descriptionOrError.data,
|
||||
quantity: quantityOrError.data,
|
||||
unitPrice: unitPriceOrError.data,
|
||||
discount: discountOrError.data,
|
||||
},
|
||||
idOrError.data
|
||||
);
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
source: CustomerItem,
|
||||
params?: MapperParamsType
|
||||
): InferCreationAttributes<CustomerItemModel, {}> {
|
||||
const { index, sourceParent } = params as {
|
||||
index: number;
|
||||
sourceParent: Customer;
|
||||
};
|
||||
|
||||
const lineData = {
|
||||
parent_id: undefined,
|
||||
invoice_id: sourceParent.id.toPrimitive(),
|
||||
item_type: "simple",
|
||||
position: index,
|
||||
|
||||
item_id: source.id.toPrimitive(),
|
||||
description: source.description.toPrimitive(),
|
||||
|
||||
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,
|
||||
|
||||
subtotal_amount: source.subtotalPrice.toPrimitive().amount,
|
||||
subtotal_scale: source.subtotalPrice.toPrimitive().scale,
|
||||
|
||||
discount_amount: source.discount.toPrimitive().amount,
|
||||
discount_scale: source.discount.toPrimitive().scale,
|
||||
|
||||
total_amount: source.totalPrice.toPrimitive().amount,
|
||||
total_scale: source.totalPrice.toPrimitive().scale,
|
||||
};
|
||||
return lineData;
|
||||
}
|
||||
}
|
||||
@ -171,6 +171,7 @@ export default (database: Sequelize) => {
|
||||
sequelize: database,
|
||||
tableName: "customers",
|
||||
|
||||
underscored: true,
|
||||
paranoid: true, // softs deletes
|
||||
timestamps: true,
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ export class CustomerRepository
|
||||
extends SequelizeRepository<Customer>
|
||||
implements ICustomerRepository
|
||||
{
|
||||
//private readonly model: typeof CustomerModel;
|
||||
private readonly mapper!: ICustomerMapper;
|
||||
|
||||
constructor(mapper: ICustomerMapper) {
|
||||
@ -149,7 +148,6 @@ export class CustomerRepository
|
||||
|
||||
return Result.ok<void>();
|
||||
} catch (err: unknown) {
|
||||
// , `Error deleting customer ${id} in company ${companyId}`
|
||||
return Result.fail(translateSequelizeError(err));
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user