Facturas de cliente

This commit is contained in:
David Arranz 2025-09-05 13:23:45 +02:00
parent b7a58ebad5
commit d40c2279bd
27 changed files with 362 additions and 189 deletions

View File

@ -5,7 +5,7 @@ import { Invoice } from "../aggregates";
export interface IInvoiceRepository {
findAll(transaction?: any): Promise<Result<Collection<Invoice>, Error>>;
getById(id: UniqueID, transaction?: any): Promise<Result<Invoice, Error>>;
deleteById(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
deleteByIdInCompany(id: UniqueID, transaction?: any): Promise<Result<boolean, Error>>;
create(invoice: Invoice, transaction?: any): Promise<void>;
update(invoice: Invoice, transaction?: any): Promise<void>;

View File

@ -7,6 +7,9 @@ import { IInvoiceService } from "./invoice-service.interface";
export class InvoiceService implements IInvoiceService {
constructor(private readonly repo: IInvoiceRepository) {}
deleteInvoiceById(invoiceId: UniqueID, transaction?: any): Promise<Result<boolean, Error>> {
throw new Error("Method not implemented.");
}
async findInvoices(transaction?: Transaction): Promise<Result<Collection<Invoice>, Error>> {
const invoicesOrError = await this.repo.findAll(transaction);
@ -71,10 +74,11 @@ export class InvoiceService implements IInvoiceService {
return Result.ok(newInvoice);
}
async deleteInvoiceById(
async deleteInvoiceByIdInCompnay(
companyId: UniqueID,
invoiceId: UniqueID,
transaction?: Transaction
): Promise<Result<boolean, Error>> {
return this.repo.deleteById(invoiceId, transaction);
return this.repo.deleteByIdInCompany(companyId, invoiceId, transaction);
}
}

View File

@ -1,15 +1,35 @@
import { Result } from "@repo/rdx-utils";
import { ILogger } from "../../logger";
import { ITransactionManager } from "./transaction-manager.interface";
export abstract class TransactionManager implements ITransactionManager {
protected _transaction: any | null = null;
protected _isCompleted = false;
protected readonly logger!: ILogger;
constructor(logger?: ILogger) {
// Si no hay logger, usa console adaptado
this.logger = logger ?? {
info: (msg, meta) => console.info(msg, meta),
warn: (msg, meta) => console.warn(msg, meta),
error: (msg, err) => console.error(msg, err),
debug: (msg, meta) => console.debug(msg, meta),
};
}
/**
* 🔹 Inicia una transacción si no hay una activa
*/
async start(): Promise<void> {
if (!this._transaction) {
this._transaction = await this._startTransaction();
if (this._transaction) {
this.logger.error("❌ Transaction already started. Nested transactions are not allowed.", {
label: "TransactionManager.start",
});
throw new Error("A transaction is already active. Nested transactions are not allowed.");
}
this._transaction = await this._startTransaction();
this._isCompleted = false;
this.logger.debug("Transaction started", { label: "TransactionManager.start" });
}
/**
@ -17,8 +37,16 @@ export abstract class TransactionManager implements ITransactionManager {
*/
getTransaction(): any {
if (!this._transaction) {
this.logger.error("❌ No active transaction. Call start() first.", {
label: "TransactionManager.getTransaction",
});
throw new Error("No active transaction. Call start() first.");
}
if (this._isCompleted) {
this.logger.error("❌ Transaction already completed.");
throw new Error("Transaction already completed.");
}
return this._transaction;
}
@ -26,13 +54,30 @@ export abstract class TransactionManager implements ITransactionManager {
* 🔹 Ejecuta una función dentro de una transacción
*/
async complete<T>(work: (transaction: any) => Promise<T>): Promise<T> {
if (this._transaction) {
this.logger.error(
"❌ Cannot start a new transaction inside another. Nested transactions are not allowed.",
{ label: "TransactionManager.complete" }
);
throw new Error("A transaction is already active. Nested transactions are not allowed.");
}
await this.start();
try {
const result = await work(this.getTransaction());
if (result instanceof Result && result.isFailure) {
throw result.error;
}
await this.commit();
return result;
} catch (error) {
} catch (err) {
await this.rollback();
const error = err as Error;
this.logger.error(`❌ Transaction rolled back due to error: ${error.message}`, {
stack: error.stack,
label: "TransactionManager.start",
});
throw error;
}
}
@ -45,16 +90,59 @@ export abstract class TransactionManager implements ITransactionManager {
protected abstract _rollbackTransaction(): Promise<void>;
async commit(): Promise<void> {
if (this._transaction) {
if (!this._transaction) {
this.logger.error("❌ No transaction to commit.", { label: "TransactionManager.commit" });
throw new Error("No transaction to commit.");
}
if (this._isCompleted) {
this.logger.error("❌ Transaction already completed. Cannot commit again.", {
label: "TransactionManager.commit",
});
throw new Error("Transaction already completed.");
}
try {
await this._commitTransaction();
this.logger.info("Transaction committed.", { label: "TransactionManager.commit" });
} catch (err) {
const error = err as Error;
this.logger.error(`❌ Error during commit: ${error.message}`, {
stack: error.stack,
label: "TransactionManager.commit",
});
throw error;
} finally {
// Siempre limpia el estado interno
this._transaction = null;
this._isCompleted = true;
}
}
async rollback(): Promise<void> {
if (this._transaction) {
if (!this._transaction) {
this.logger.error("❌ No transaction to rollback.", { label: "TransactionManager.rollback" });
throw new Error("No transaction to rollback.");
}
if (this._isCompleted) {
this.logger.error("❌ Transaction already completed. Cannot rollback again.", {
label: "TransactionManager.rollback",
});
throw new Error("Transaction already completed.");
}
try {
await this._rollbackTransaction();
this.logger.info("Transaction rolled back.");
} catch (err) {
const error = err as Error;
this.logger.error(`❌ Error during rollback: ${error.message}`, {
stack: error.stack,
label: "TransactionManager.rollback",
});
throw error;
} finally {
this._transaction = null;
this._isCompleted = true;
}
}
}

View File

@ -1,9 +1,17 @@
import { DatabaseError } from "sequelize";
import { InfrastructureError } from "./infrastructure-errors";
export class InfrastructureRepositoryError extends InfrastructureError {
public readonly code = "REPOSITORY_ERROR" as const;
constructor(message = "Repository operation failed", options?: ErrorOptions) {
super(message, options);
const { cause } = options || {};
let _message = message;
if (cause && cause instanceof DatabaseError) {
_message = `${message}: ${(cause as DatabaseError).message}`;
}
super(_message, options);
}
}

View File

@ -17,14 +17,14 @@ import { Result } from "@repo/rdx-utils";
import { CreateCustomerInvoiceRequestDTO } from "../../../common/dto";
import {
CustomerInvoiceItemDescription,
CustomerInvoiceItemDiscount,
CustomerInvoiceItemProps,
CustomerInvoiceItemQuantity,
CustomerInvoiceItemUnitAmount,
CustomerInvoiceNumber,
CustomerInvoiceProps,
CustomerInvoiceSerie,
CustomerInvoiceStatus,
ItemAmount,
ItemDiscount,
} from "../../domain";
/**
@ -162,15 +162,13 @@ function mapDTOToCreateCustomerInvoiceItemsProps(
);
const unitAmount = extractOrPushError(
maybeFromNullableVO(item.unit_amount, (value) => CustomerInvoiceItemUnitAmount.create(value)),
maybeFromNullableVO(item.unit_amount, (value) => ItemAmount.create(value)),
"unit_amount",
errors
);
const discountPercentage = extractOrPushError(
maybeFromNullableVO(item.discount_percentage, (value) =>
CustomerInvoiceItemDiscount.create(value)
),
maybeFromNullableVO(item.discount_percentage, (value) => ItemDiscount.create(value)),
"discount_percentage",
errors
);

View File

@ -1,22 +1,23 @@
import { EntityNotFoundError, ITransactionManager } from "@erp/core/api";
import { UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { ICustomerInvoiceService } from "../../domain";
import { CustomerInvoiceService } from "../../domain";
type DeleteCustomerInvoiceUseCaseInput = {
tenantId: string;
id: string;
companyId: UniqueID;
invoice_id: string;
};
export class DeleteCustomerInvoiceUseCase {
constructor(
private readonly service: ICustomerInvoiceService,
private readonly service: CustomerInvoiceService,
private readonly transactionManager: ITransactionManager
) {}
public execute(params: DeleteCustomerInvoiceUseCaseInput) {
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);
@ -26,17 +27,25 @@ export class DeleteCustomerInvoiceUseCase {
return this.transactionManager.complete(async (transaction) => {
try {
const existsCheck = await this.service.existsById(invoiceId, transaction);
const existsCheck = await this.service.existsByIdInCompany(
companyId,
invoiceId,
transaction
);
if (existsCheck.isFailure) {
return Result.fail(existsCheck.error);
}
if (!existsCheck.data) {
return Result.fail(new EntityNotFoundError("CustomerInvoice", "id", id.toString()));
const invoiceExists = existsCheck.data;
if (!invoiceExists) {
return Result.fail(
new EntityNotFoundError("Customer invoice", "id", invoiceId.toString())
);
}
return await this.service.deleteById(invoiceId, transaction);
return await this.service.deleteInvoiceByIdInCompany(companyId, invoiceId, transaction);
} catch (error: unknown) {
return Result.fail(error as Error);
}

View File

@ -4,9 +4,9 @@ import { Result } from "@repo/rdx-utils";
import {
CustomerInvoiceItem,
CustomerInvoiceItemDescription,
CustomerInvoiceItemDiscount,
CustomerInvoiceItemQuantity,
CustomerInvoiceItemUnitAmount,
ItemAmount,
ItemDiscount,
} 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(
CustomerInvoiceItemUnitAmount.create({
ItemAmount.create({
value: item.unitPrice.amount,
scale: item.unitPrice.scale,
currency_code: item.unitPrice.currency,
@ -46,7 +46,7 @@ export function mapDTOToCustomerInvoiceItemsProps(
);
const discount = extractOrPushError(
CustomerInvoiceItemDiscount.create({
ItemDiscount.create({
value: item.discount.amount,
scale: item.discount.scale,
}),

View File

@ -9,26 +9,50 @@ import {
import { Maybe, Result } from "@repo/rdx-utils";
import {
CustomerInvoiceItemDescription,
CustomerInvoiceItemDiscount,
CustomerInvoiceItemQuantity,
CustomerInvoiceItemSubtotalAmount,
CustomerInvoiceItemTotalAmount,
CustomerInvoiceItemUnitAmount,
ItemAmount,
ItemDiscount,
ItemQuantity,
} from "../../value-objects";
export interface CustomerInvoiceItemProps {
description: Maybe<CustomerInvoiceItemDescription>;
quantity: Maybe<CustomerInvoiceItemQuantity>; // Cantidad de unidades
unitAmount: Maybe<CustomerInvoiceItemUnitAmount>; // Precio unitario en la moneda de la factura
discountPercentage: Maybe<CustomerInvoiceItemDiscount>; // % descuento
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
discountPercentage: Maybe<ItemDiscount>; // % descuento
languageCode: LanguageCode;
currencyCode: CurrencyCode;
}
export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps> {
private _subtotalAmount!: CustomerInvoiceItemSubtotalAmount;
private _totalAmount!: CustomerInvoiceItemTotalAmount;
export interface ICustomerInvoiceItem {
description: Maybe<CustomerInvoiceItemDescription>;
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
subtotalAmount: ItemAmount;
discountPercentage: Maybe<ItemDiscount>; // % descuento
discountAmount: Maybe<ItemAmount>;
taxableAmount: ItemAmount;
taxesAmount: ItemAmount;
totalAmount: ItemAmount;
languageCode: LanguageCode;
currencyCode: CurrencyCode;
calculateSubtotal(): ItemAmount;
calculateTotal(): ItemAmount;
}
export class CustomerInvoiceItem
extends DomainEntity<CustomerInvoiceItemProps>
implements ICustomerInvoiceItem
{
private _subtotalAmount!: ItemAmount;
private _totalAmount!: ItemAmount;
public static create(
props: CustomerInvoiceItemProps,
@ -51,19 +75,16 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
return this.props.description;
}
get quantity(): Maybe<CustomerInvoiceItemQuantity> {
get quantity(): Maybe<ItemQuantity> {
return this.props.quantity;
}
get unitAmount(): Maybe<CustomerInvoiceItemUnitAmount> {
get unitAmount(): Maybe<ItemAmount> {
return this.props.unitAmount;
}
get subtotalAmount(): CustomerInvoiceItemSubtotalAmount {
if (!this._subtotalAmount) {
this._subtotalAmount = this.calculateSubtotal();
}
return this._subtotalAmount;
get subtotalAmount(): ItemAmount {
throw new Error("Not implemented");
}
get discountPercentage(): Maybe<Percentage> {
@ -74,11 +95,16 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
throw new Error("Not implemented");
}
get totalAmount(): CustomerInvoiceItemTotalAmount {
if (!this._totalAmount) {
this._totalAmount = this.calculateTotal();
}
return this._totalAmount;
get taxableAmount(): ItemAmount {
throw new Error("Not implemented");
}
get taxesAmount(): ItemAmount {
throw new Error("Not implemented");
}
get totalAmount(): ItemAmount {
throw new Error("Not implemented");
}
public get languageCode(): LanguageCode {
@ -89,15 +115,15 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
return this.props.currencyCode;
}
getValue(): CustomerInvoiceItemProps {
getProps(): CustomerInvoiceItemProps {
return this.props;
}
toPrimitive() {
return this.getValue();
return this.getProps();
}
calculateSubtotal(): CustomerInvoiceItemSubtotalAmount {
calculateSubtotal(): ItemAmount {
throw new Error("Not implemented");
/*const unitPrice = this.unitPrice.isSome()
@ -106,7 +132,7 @@ export class CustomerInvoiceItem extends DomainEntity<CustomerInvoiceItemProps>
return this.unitPrice.multiply(this.quantity.toNumber()); // Precio unitario * Cantidad*/
}
calculateTotal(): CustomerInvoiceItemTotalAmount {
calculateTotal(): ItemAmount {
throw new Error("Not implemented");
//return this.subtotalPrice.subtract(this.subtotalPrice.percentage(this.discount.toNumber()));
}

View File

@ -128,7 +128,7 @@ export class CustomerInvoiceService {
* @param transaction - Transacción activa para la operación.
* @returns Result<boolean, Error> - Resultado de la operación.
*/
async deleteById(
async deleteInvoiceByIdInCompany(
companyId: UniqueID,
invoiceId: UniqueID,
transaction?: Transaction

View File

@ -1,3 +0,0 @@
import { Percentage } from "@repo/rdx-ddd";
export class CustomerInvoiceItemDiscount extends Percentage {}

View File

@ -1,14 +0,0 @@
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
export class CustomerInvoiceItemSubtotalAmount extends MoneyValue {
public static DEFAULT_SCALE = 4;
static create({ value, currency_code }: MoneyValueProps) {
const props = {
value: Number(value),
scale: CustomerInvoiceItemSubtotalAmount.DEFAULT_SCALE,
currency_code,
};
return MoneyValue.create(props);
}
}

View File

@ -1,14 +0,0 @@
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
export class ItemTotalAmount extends MoneyValue {
public static DEFAULT_SCALE = 4;
static create({ value, currency_code, scale }: MoneyValueProps) {
const props = {
value: Number(value),
scale: scale ?? MoneyValue.DEFAULT_SCALE,
currency_code,
};
return MoneyValue.create(props);
}
}

View File

@ -1,23 +0,0 @@
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
export class CustomerInvoiceItemUnitAmount extends MoneyValue {
public static DEFAULT_SCALE = 4;
static create({ value: amount, currency_code, scale }: MoneyValueProps) {
const props = {
amount: Number(amount),
scale: scale ?? MoneyValue.DEFAULT_SCALE,
currency_code,
};
return MoneyValue.create(props);
}
static zero(currency_code: string, scale: number = CustomerInvoiceItemUnitAmount.DEFAULT_SCALE) {
const props: MoneyValueProps = {
value: 0,
scale,
currency_code,
};
return MoneyValue.create(props);
}
}

View File

@ -1,10 +1,8 @@
export * from "./customer-invoice-address-type";
export * from "./customer-invoice-item-description";
export * from "./customer-invoice-item-discount";
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";
export * from "./item-amount";
export * from "./item-discount";
export * from "./item-quantity";

View File

@ -0,0 +1,24 @@
import { MoneyValue, MoneyValueProps } from "@repo/rdx-ddd";
type ItemAmountProps = Pick<MoneyValueProps, "value" | "currency_code">;
export class ItemAmount extends MoneyValue {
public static DEFAULT_SCALE = 4;
static create({ value, currency_code }: ItemAmountProps) {
const props = {
value: Number(value),
scale: ItemAmount.DEFAULT_SCALE,
currency_code,
};
return MoneyValue.create(props);
}
static zero(currency_code: string) {
const props = {
value: 0,
currency_code,
};
return ItemAmount.create(props);
}
}

View File

@ -0,0 +1,19 @@
import { Percentage, PercentageProps } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
type ItemDiscountProps = Pick<PercentageProps, "value">;
export class ItemDiscount extends Percentage {
static DEFAULT_SCALE = 2;
static create({ value }: ItemDiscountProps): Result<Percentage> {
return Percentage.create({
value,
scale: ItemDiscount.DEFAULT_SCALE,
});
}
static zero() {
return ItemDiscount.create({ value: 0 });
}
}

View File

@ -1,13 +1,18 @@
import { Quantity, QuantityProps } from "@repo/rdx-ddd";
type ItemQuantityProps = Pick<QuantityProps, "value">;
export class ItemQuantity extends Quantity {
public static DEFAULT_SCALE = 2;
static create({ value }: QuantityProps) {
const props = {
value: Number(value),
static create({ value }: ItemQuantityProps) {
return Quantity.create({
value,
scale: ItemQuantity.DEFAULT_SCALE,
};
return Quantity.create(props);
});
}
static zero() {
return ItemQuantity.create({ value: 0 });
}
}

View File

@ -5,16 +5,22 @@ import {
ValidationErrorDetail,
extractOrPushError,
} from "@erp/core/api";
import { CurrencyCode, LanguageCode, Quantity, UniqueID, maybeFromNullableVO, toNullable } from "@repo/rdx-ddd";
import {
CurrencyCode,
LanguageCode,
UniqueID,
maybeFromNullableVO,
toNullable,
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { InferCreationAttributes } from "sequelize";
import {
CustomerInvoice,
CustomerInvoiceItem,
CustomerInvoiceItemDescription,
CustomerInvoiceItemDiscount,
CustomerInvoiceItemQuantity,
CustomerInvoiceItemUnitAmount,
ItemAmount,
ItemDiscount,
ItemQuantity,
} from "../../domain";
import {
CustomerInvoiceItemCreationAttributes,
@ -48,17 +54,17 @@ export class CustomerInvoiceItemMapper
const itemId = extractOrPushError(UniqueID.create(source.item_id), "item_id", errors);
const languageCode = extractOrPushError(
LanguageCode.create(sourceParent.language_code),
"language_code",
errors
);
const languageCode = extractOrPushError(
LanguageCode.create(sourceParent.language_code),
"language_code",
errors
);
const currencyCode = extractOrPushError(
CurrencyCode.create(sourceParent.currency_code),
"currency_code",
errors
);
const currencyCode = extractOrPushError(
CurrencyCode.create(sourceParent.currency_code),
"currency_code",
errors
);
const description = extractOrPushError(
maybeFromNullableVO(source.description, (value) =>
@ -69,36 +75,27 @@ export class CustomerInvoiceItemMapper
);
const quantity = extractOrPushError(
maybeFromNullableVO(source.quantity_value, (value) =>
Quantity.create({ value, })
CustomerInvoiceItemQuantity.create({
value: source.quantity_amouwnt,
scale: source.quantity_scale,
}),
maybeFromNullableVO(source.quantity_value, (value) => ItemQuantity.create({ value })),
"discount_percentage",
errors
);
const unitAmount = extractOrPushError(
CustomerInvoiceItemUnitAmount.create({
value: source.unit_price_amount,
scale: source.unit_price_scale,
}),
"discount_percentage",
maybeFromNullableVO(source.unit_amount_value, (value) =>
ItemAmount.create({ value, currency_code: currencyCode!.code })
),
"unit_amount",
errors
);
const discountPercentage = extractOrPushError(
CustomerInvoiceItemDiscount.create({
value: source.discount_amount,
scale: source.discount_scale,
}),
maybeFromNullableVO(source.discount_percentage_value, (value) =>
ItemDiscount.create({ value })
),
"discount_percentage",
errors
);
// Creación del objeto de dominio
return CustomerInvoiceItem.create(
{
@ -108,8 +105,8 @@ export class CustomerInvoiceItemMapper
quantity: quantity!,
unitAmount: unitAmount!,
discountPercentage: discountPercentage!,
},º
id
},
itemId
);
}
@ -117,14 +114,12 @@ export class CustomerInvoiceItemMapper
source: CustomerInvoiceItem,
params?: MapperParamsType
): InferCreationAttributes<CustomerInvoiceItemModel, {}> {
1
1;
const { index, sourceParent } = params as {
index: number;
sourceParent: CustomerInvoice;
};
return {
item_id: source.id.toPrimitive(),
invoice_id: sourceParent.id.toPrimitive(),
@ -135,23 +130,41 @@ export class CustomerInvoiceItemMapper
quantity_value: toNullable(source.quantity, (value) => value.toPrimitive().value),
quantity_scale: source.quantity.match(
(value) => value.toPrimitive().scale,
() => CustomerInvoiceItemQuantity.DEFAULT_SCALE),
() => ItemQuantity.DEFAULT_SCALE
),
unit_amount_value: toNullable(source.unitAmount, (value) => value.toPrimitive().value),
unit_amount_scale: source.unitAmount.match(
(value) => value.toPrimitive().scale,
() => CustomerInvoiceItemUnitAmount.DEFAULT_SCALE),
() => ItemAmount.DEFAULT_SCALE
),
subtotal_amount_value: source.subtotalAmount.toPrimitive().value,
subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale,
discount_percentage_value: toNullable(source.discountPercentage, (value) => value.toPrimitive().value),
discount_percentage_value: toNullable(
source.discountPercentage,
(value) => value.toPrimitive().value
),
discount_percentage_scale: source.discountPercentage.match(
(value) => value.toPrimitive().scale,
() => CustomerInvoiceItemUnitAmount.DEFAULT_SCALE),
() => ItemAmount.DEFAULT_SCALE
),
discount_amount_value: source.subtotalAmount.toPrimitive().value,
discount_amount_scale: source.subtotalAmount.toPrimitive().scale,
discount_amount_value: toNullable(
source.discountAmount,
(value) => value.toPrimitive().value
),
discount_amount_scale: source.discountAmount.match(
(value) => value.toPrimitive().scale,
() => ItemDiscount.DEFAULT_SCALE
),
taxable_amount_value: source.taxableAmount.toPrimitive().value,
taxable_amount_scale: source.taxableAmount.toPrimitive().value,
taxes_amount_value: source.taxesAmount.toPrimitive().value,
taxes_amount_scale: source.taxesAmount.toPrimitive().value,
total_amount_value: source.totalAmount.toPrimitive().value,
total_amount_scale: source.totalAmount.toPrimitive().scale,

View File

@ -14,10 +14,12 @@ import {
UniqueID,
UtcDate,
maybeFromNullableVO,
toNullable,
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import {
CustomerInvoice,
CustomerInvoiceItems,
CustomerInvoiceNumber,
CustomerInvoiceProps,
CustomerInvoiceSerie,
@ -149,7 +151,10 @@ export class CustomerInvoiceMapper
discountPercentage: discountPercentage!,
items: items!,
items: CustomerInvoiceItems.create({
languageCode: languageCode!,
currencyCode: currencyCode!,
}),
};
return CustomerInvoice.create(invoiceProps, invoiceId);
@ -162,26 +167,41 @@ export class CustomerInvoiceMapper
source: CustomerInvoice,
params?: MapperParamsType
): CustomerInvoiceCreationAttributes {
const subtotal = source.calculateSubtotal();
const total = source.calculateTotal();
//const subtotal = source.calculateSubtotal();
//const total = source.calculateTotal();
const items = this._itemsMapper.mapCollectionToPersistence(source.items, params);
return {
id: source.id.toString(),
invoice_status: source.status.toPrimitive(),
invoice_series: source.invoiceSeries.toPrimitive(),
id: source.id.toPrimitive(),
company_id: source.companyId.toPrimitive(),
status: source.status.toPrimitive(),
series: toNullable(source.series, (series) => series.toPrimitive()),
invoice_number: source.invoiceNumber.toPrimitive(),
invoice_date: source.invoiceDate.toPrimitive(),
operation_date: source.operationDate.toPrimitive(),
invoice_language: "es",
invoice_currency: source.currency || "EUR",
subtotal_amount: subtotal.amount,
subtotal_scale: subtotal.scale,
operation_date: toNullable(source.operationDate, (operationDate) =>
operationDate.toPrimitive()
),
total_amount: total.amount,
total_scale: total.scale,
notes: toNullable(source.notes, (notes) => notes.toPrimitive()),
language_code: source.languageCode.code,
currency_code: source.currencyCode.code,
subtotal_amount_value: 0, //subtotal.amount,
subtotal_amount_scale: 2, //subtotal.scale,
/*discount_percentage_value: source.discountPercentage.value,
discount_percentage_scale: source.discountPercentage.scale,
discount_amount_value: source.discountAmount.value,
discount_amount_scale: source.discountAmount.scale,*/
total_amount_value: 0, //total.amount,
total_amount_scale: 2, //total.scale,
items,
};

View File

@ -17,7 +17,7 @@ export class CustomerInvoiceItemTaxesModel extends Model<
InferAttributes<CustomerInvoiceItemTaxesModel>,
InferCreationAttributes<CustomerInvoiceItemTaxesModel>
> {
declare id: string;
declare tax_id: string;
declare item_id: string;
declare tax_code: string; //"iva_21"
@ -38,7 +38,7 @@ export class CustomerInvoiceItemTaxesModel extends Model<
CustomerInvoiceItemTaxesModel.belongsTo(CustomerInvoiceItemModel, {
as: "item",
targetKey: "id",
targetKey: "item_id",
foreignKey: "item_id",
onDelete: "CASCADE",
});
@ -50,7 +50,7 @@ export class CustomerInvoiceItemTaxesModel extends Model<
export default (database: Sequelize) => {
CustomerInvoiceItemTaxesModel.init(
{
id: {
tax_id: {
type: new DataTypes.UUID(),
primaryKey: true,
},

View File

@ -195,10 +195,7 @@ export default (database: Sequelize) => {
underscored: true,
indexes: [
{ name: "invoice_idx", fields: ["invoice_id"], unique: false },
{ name: "parent_idx", fields: ["parent_id"], unique: false },
],
indexes: [{ name: "invoice_idx", fields: ["invoice_id"], unique: false }],
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope

View File

@ -17,7 +17,7 @@ export class CustomerInvoiceTaxesModel extends Model<
InferAttributes<CustomerInvoiceTaxesModel>,
InferCreationAttributes<CustomerInvoiceTaxesModel>
> {
declare id: string;
declare tax_id: string;
declare invoice_id: string;
declare tax_code: string; //"iva_21"
@ -50,7 +50,7 @@ export class CustomerInvoiceTaxesModel extends Model<
export default (database: Sequelize) => {
CustomerInvoiceTaxesModel.init(
{
id: {
tax_id: {
type: new DataTypes.UUID(),
primaryKey: true,
},

View File

@ -1,9 +1,19 @@
import customerInvoiceItemTaxesModelInit from "./customer-invoice-item-taxes.model";
import customerInvoiceItemModelInit from "./customer-invoice-item.model";
import customerInvoiceTaxesModelInit from "./customer-invoice-taxes.model";
import customerInvoiceModelInit from "./customer-invoice.model";
export * from "./customer-invoice-item.model"; // exporta las clases, tipos
export * from "./customer-invoice-item-taxes.model";
export * from "./customer-invoice-item.model";
export * from "./customer-invoice-taxes.model";
export * from "./customer-invoice.model";
export * from "./customer-invoice.repository";
// Array de inicializadores para que registerModels() lo use
export const models = [customerInvoiceItemModelInit, customerInvoiceModelInit];
export const models = [
customerInvoiceModelInit,
customerInvoiceItemModelInit,
customerInvoiceTaxesModelInit,
customerInvoiceItemTaxesModelInit,
];

View File

@ -38,6 +38,10 @@ export class CurrencyCode extends ValueObject<CurrencyCodeProps> {
return Result.ok(new CurrencyCode({ value: valueIsValid.data }));
}
get code(): string {
return this.props.value;
}
getProps(): string {
return this.props.value;
}

View File

@ -38,6 +38,10 @@ export class LanguageCode extends ValueObject<LanguageCodeProps> {
return Result.ok(new LanguageCode({ value: valueIsValid.data }));
}
get code(): string {
return this.props.value;
}
getProps(): string {
return this.props.value;
}

View File

@ -34,6 +34,6 @@ export class TextValue extends ValueObject<TextValueProps> {
}
toPrimitive(): string {
return this.getProps();
return String(this.getProps());
}
}

View File

@ -37,10 +37,10 @@ export class UniqueID extends ValueObject<string> {
}
toString(): string {
return this.getProps();
return String(this.getProps());
}
toPrimitive() {
return this.getProps();
return this.toString();
}
}