This commit is contained in:
David Arranz 2026-02-19 18:54:59 +01:00
parent acd61cf6eb
commit e9824ecf80
48 changed files with 647 additions and 380 deletions

View File

@ -1,8 +1,8 @@
import type { ICustomerInvoiceRepository } from "../../../domain";
import type { IIssuedInvoiceRepository } from "../repositories";
import { type IIssuedInvoiceFinder, IssuedInvoiceFinder } from "../services/issued-invoice-finder";
export function buildIssuedInvoiceFinder(
repository: ICustomerInvoiceRepository
repository: IIssuedInvoiceRepository
): IIssuedInvoiceFinder {
return new IssuedInvoiceFinder(repository);
}

View File

@ -9,9 +9,9 @@ import {
IssuedInvoiceVerifactuFullSnapshotBuilder,
} from "../snapshot-builders/full";
import {
IssuedInvoiceItemReportSnapshotBuilder,
IssuedInvoiceReportItemSnapshotBuilder,
IssuedInvoiceReportSnapshotBuilder,
IssuedInvoiceTaxReportSnapshotBuilder,
IssuedInvoiceReportTaxSnapshotBuilder,
} from "../snapshot-builders/report";
export function buildIssuedInvoiceSnapshotBuilders() {
@ -32,8 +32,8 @@ export function buildIssuedInvoiceSnapshotBuilders() {
const listSnapshotBuilder = new IssuedInvoiceListItemSnapshotBuilder();
const itemsReportBuilder = new IssuedInvoiceItemReportSnapshotBuilder();
const taxesReportBuilder = new IssuedInvoiceTaxReportSnapshotBuilder();
const itemsReportBuilder = new IssuedInvoiceReportItemSnapshotBuilder();
const taxesReportBuilder = new IssuedInvoiceReportTaxSnapshotBuilder();
const reportSnapshotBuilder = new IssuedInvoiceReportSnapshotBuilder(
itemsReportBuilder,
taxesReportBuilder

View File

@ -17,8 +17,7 @@ export interface IIssuedInvoiceRepository {
existsByIdInCompany(
companyId: UniqueID,
id: UniqueID,
transaction: unknown,
options: unknown
transaction: unknown
): Promise<Result<boolean, Error>>;
findByCriteriaInCompany(

View File

@ -43,9 +43,7 @@ export class IssuedInvoiceFinder implements IIssuedInvoiceFinder {
invoiceId: UniqueID,
transaction?: Transaction
): Promise<Result<boolean, Error>> {
return this.repository.existsByIdInCompany(companyId, invoiceId, transaction, {
is_proforma: false,
});
return this.repository.existsByIdInCompany(companyId, invoiceId, transaction);
}
async findIssuedInvoicesByCriteria(

View File

@ -41,7 +41,6 @@ export class IssuedInvoiceFullSnapshotBuilder implements IIssuedInvoiceFullSnaps
id: invoice.id.toString(),
company_id: invoice.companyId.toString(),
is_proforma: "false",
invoice_number: invoice.invoiceNumber.toString(),
status: invoice.status.toPrimitive(),
series: maybeToEmptyString(invoice.series, (value) => value.toString()),

View File

@ -7,7 +7,6 @@ export interface IIssuedInvoiceFullSnapshot {
id: string;
company_id: string;
is_proforma: "true" | "false";
invoice_number: string;
status: string;
series: string;

View File

@ -15,6 +15,8 @@ export interface IIssuedInvoiceItemFullSnapshot {
global_discount_percentage: { value: string; scale: string };
global_discount_amount: { value: string; scale: string; currency_code: string };
total_discount_amount: { value: string; scale: string; currency_code: string };
taxable_amount: { value: string; scale: string; currency_code: string };
iva_code: string;

View File

@ -6,7 +6,7 @@ import {
maybeToEmptyString,
} from "@repo/rdx-ddd";
import type { IssuedInvoiceItem, IssuedInvoiceItems } from "../../../../domain";
import { type IssuedInvoiceItem, type IssuedInvoiceItems, ItemAmount } from "../../../../domain";
import type { IIssuedInvoiceItemFullSnapshot } from "./issued-invoice-item-full-snapshot.interface";
@ -17,9 +17,10 @@ export class IssuedInvoiceItemsFullSnapshotBuilder
implements IIssuedInvoiceItemsFullSnapshotBuilder
{
private mapItem(invoiceItem: IssuedInvoiceItem, index: number): IIssuedInvoiceItemFullSnapshot {
const isValued = invoiceItem.isValued;
return {
id: invoiceItem.id.toPrimitive(),
is_valued: String(invoiceItem.isValued),
is_valued: String(isValued),
position: String(index),
description: maybeToEmptyString(invoiceItem.description, (value) => value.toString()),
@ -27,32 +28,50 @@ export class IssuedInvoiceItemsFullSnapshotBuilder
quantity: maybeToEmptyQuantityObjectString(invoiceItem.quantity),
unit_amount: maybeToEmptyMoneyObjectString(invoiceItem.unitAmount),
subtotal_amount: maybeToEmptyMoneyObjectString(invoiceItem.subtotalAmount),
subtotal_amount: isValued
? invoiceItem.subtotalAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
discount_percentage: maybeToEmptyPercentageObjectString(invoiceItem.itemDiscountPercentage),
discount_amount: maybeToEmptyMoneyObjectString(invoiceItem.itemDiscountAmount),
discount_amount: isValued
? invoiceItem.itemDiscountAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
global_discount_percentage: maybeToEmptyPercentageObjectString(
invoiceItem.globalDiscountPercentage
),
global_discount_amount: maybeToEmptyMoneyObjectString(invoiceItem.globalDiscountAmount),
global_discount_amount: isValued
? invoiceItem.globalDiscountAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
taxable_amount: maybeToEmptyMoneyObjectString(invoiceItem.taxableAmount),
total_discount_amount: isValued
? invoiceItem.totalDiscountAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
taxable_amount: isValued
? invoiceItem.taxableAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
iva_code: maybeToEmptyString(invoiceItem.ivaCode),
iva_percentage: maybeToEmptyPercentageObjectString(invoiceItem.ivaPercentage),
iva_amount: maybeToEmptyMoneyObjectString(invoiceItem.ivaAmount),
iva_amount: isValued ? invoiceItem.ivaAmount.toObjectString() : ItemAmount.EMPTY_MONEY_OBJECT,
rec_code: maybeToEmptyString(invoiceItem.recCode),
rec_percentage: maybeToEmptyPercentageObjectString(invoiceItem.recPercentage),
rec_amount: maybeToEmptyMoneyObjectString(invoiceItem.recAmount),
rec_amount: isValued ? invoiceItem.recAmount.toObjectString() : ItemAmount.EMPTY_MONEY_OBJECT,
retention_code: maybeToEmptyString(invoiceItem.retentionCode),
retention_percentage: maybeToEmptyPercentageObjectString(invoiceItem.retentionPercentage),
retention_amount: maybeToEmptyMoneyObjectString(invoiceItem.retentionAmount),
retention_amount: isValued
? invoiceItem.retentionAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
taxes_amount: maybeToEmptyMoneyObjectString(invoiceItem.taxesAmount),
total_amount: maybeToEmptyMoneyObjectString(invoiceItem.totalAmount),
taxes_amount: isValued
? invoiceItem.taxesAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
total_amount: isValued
? invoiceItem.totalAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
};
}

View File

@ -24,7 +24,6 @@ export class IssuedInvoiceListItemSnapshotBuilder implements IIssuedInvoiceListI
return {
id: invoice.id.toString(),
company_id: invoice.companyId.toString(),
is_proforma: invoice.isProforma,
customer_id: invoice.customerId.toString(),

View File

@ -1,7 +1,6 @@
export interface IIssuedInvoiceListItemSnapshot {
id: string;
company_id: string;
is_proforma: boolean;
customer_id: string;

View File

@ -1,6 +1,6 @@
export * from "./issued-invoice-items-report-snapshot-builder";
export * from "./issued-invoice-report-item-snapshot.interface";
export * from "./issued-invoice-report-item-snapshot-builder";
export * from "./issued-invoice-report-snapshot.interface";
export * from "./issued-invoice-report-snapshot-builder";
export * from "./issued-invoice-report-tax-snapshot.interface";
export * from "./issued-invoice-tax-report-snapshot-builder";
export * from "./issued-invoice-report-tax-snapshot-builder";

View File

@ -1,21 +1,20 @@
import { MoneyDTOHelper, PercentageDTOHelper, QuantityDTOHelper } from "@erp/core";
import type { ISnapshotBuilder, ISnapshotBuilderParams } from "@erp/core/api";
import type {
IssuedInvoiceFullSnapshot,
IssuedInvoiceReportItemSnapshot,
} from "../../application-models";
import type { IIssuedInvoiceItemFullSnapshot } from "../full";
export interface IIssuedInvoiceItemReportSnapshotBuilder
extends ISnapshotBuilder<IssuedInvoiceFullSnapshot["items"], IssuedInvoiceReportItemSnapshot[]> {}
import type { IIssuedInvoiceReportItemSnapshot } from "./issued-invoice-report-item-snapshot.interface";
export class IssuedInvoiceItemReportSnapshotBuilder
implements IIssuedInvoiceItemReportSnapshotBuilder
export interface IIssuedInvoiceReportItemSnapshotBuilder
extends ISnapshotBuilder<IIssuedInvoiceItemFullSnapshot[], IIssuedInvoiceReportItemSnapshot[]> {}
export class IssuedInvoiceReportItemSnapshotBuilder
implements IIssuedInvoiceReportItemSnapshotBuilder
{
toOutput(
items: IssuedInvoiceFullSnapshot["items"],
items: IIssuedInvoiceItemFullSnapshot[],
params?: ISnapshotBuilderParams
): IssuedInvoiceReportItemSnapshot[] {
): IIssuedInvoiceReportItemSnapshot[] {
const locale = params?.locale as string;
const moneyOptions = {

View File

@ -1,4 +1,4 @@
export interface IssuedInvoiceReportItemSnapshot {
export interface IIssuedInvoiceReportItemSnapshot {
description: string;
quantity: string;
unit_amount: string;

View File

@ -1,32 +1,25 @@
import { DateHelper, MoneyDTOHelper, PercentageDTOHelper } from "@erp/core";
import type { ISnapshotBuilder, ISnapshotBuilderParams } from "@erp/core/api";
import type {
IssuedInvoiceFullSnapshot,
IssuedInvoiceReportItemSnapshot,
IssuedInvoiceReportSnapshot,
IssuedInvoiceReportTaxSnapshot,
} from "../../application-models";
import type { IIssuedInvoiceFullSnapshot } from "../full";
import type { IIssuedInvoiceReportItemSnapshotBuilder } from "./issued-invoice-report-item-snapshot-builder";
import type { IIssuedInvoiceReportSnapshot } from "./issued-invoice-report-snapshot.interface";
import type { IIssuedInvoiceReportTaxSnapshotBuilder } from "./issued-invoice-report-tax-snapshot-builder";
export interface IIssuedInvoiceReportSnapshotBuilder
extends ISnapshotBuilder<IssuedInvoiceFullSnapshot, IssuedInvoiceReportSnapshot> {}
extends ISnapshotBuilder<IIssuedInvoiceFullSnapshot, IIssuedInvoiceReportSnapshot> {}
export class IssuedInvoiceReportSnapshotBuilder implements IIssuedInvoiceReportSnapshotBuilder {
constructor(
private readonly itemsBuilder: ISnapshotBuilder<
IssuedInvoiceFullSnapshot["items"],
IssuedInvoiceReportItemSnapshot[]
>,
private readonly taxesBuilder: ISnapshotBuilder<
IssuedInvoiceFullSnapshot["taxes"],
IssuedInvoiceReportTaxSnapshot[]
>
private readonly itemsBuilder: IIssuedInvoiceReportItemSnapshotBuilder,
private readonly taxesBuilder: IIssuedInvoiceReportTaxSnapshotBuilder
) {}
toOutput(
snapshot: IssuedInvoiceFullSnapshot,
snapshot: IIssuedInvoiceFullSnapshot,
params?: ISnapshotBuilderParams
): IssuedInvoiceReportSnapshot {
): IIssuedInvoiceReportSnapshot {
const locale = params?.locale as string;
const moneyOptions = {
@ -61,10 +54,10 @@ export class IssuedInvoiceReportSnapshotBuilder implements IIssuedInvoiceReportS
taxes: this.taxesBuilder.toOutput(snapshot.taxes, { locale }),
subtotal_amount: MoneyDTOHelper.format(snapshot.subtotal_amount, locale, moneyOptions),
discount_percentage: PercentageDTOHelper.format(snapshot.discount_percentage, locale, {
discount_percentage: PercentageDTOHelper.format(snapshot.global_discount_percentage, locale, {
hideZeros: true,
}),
discount_amount: MoneyDTOHelper.format(snapshot.discount_amount, locale, moneyOptions),
discount_amount: MoneyDTOHelper.format(snapshot.global_discount_amount, locale, moneyOptions),
taxable_amount: MoneyDTOHelper.format(snapshot.taxable_amount, locale, moneyOptions),
taxes_amount: MoneyDTOHelper.format(snapshot.taxes_amount, locale, moneyOptions),
total_amount: MoneyDTOHelper.format(snapshot.total_amount, locale, moneyOptions),
@ -76,7 +69,7 @@ export class IssuedInvoiceReportSnapshotBuilder implements IIssuedInvoiceReportS
};
}
private formatAddress(recipient: IssuedInvoiceFullSnapshot["recipient"]): string {
private formatAddress(recipient: IIssuedInvoiceFullSnapshot["recipient"]): string {
const lines: string[] = [];
if (recipient.street) lines.push(recipient.street);

View File

@ -1,7 +1,7 @@
import type { IssuedInvoiceReportItemSnapshot } from "./issued-invoice-report-item-snapshot.interface";
import type { IssuedInvoiceReportTaxSnapshot } from "./issued-invoice-report-tax-snapshot.interface";
import type { IIssuedInvoiceReportItemSnapshot } from "./issued-invoice-report-item-snapshot.interface";
import type { IIssuedInvoiceReportTaxSnapshot } from "./issued-invoice-report-tax-snapshot.interface";
export interface IssuedInvoiceReportSnapshot {
export interface IIssuedInvoiceReportSnapshot {
id: string;
company_id: string;
company_slug: string;
@ -23,8 +23,8 @@ export interface IssuedInvoiceReportSnapshot {
format_address: string;
};
items: IssuedInvoiceReportItemSnapshot[];
taxes: IssuedInvoiceReportTaxSnapshot[];
items: IIssuedInvoiceReportItemSnapshot[];
taxes: IIssuedInvoiceReportTaxSnapshot[];
subtotal_amount: string;
discount_percentage: string;

View File

@ -1,21 +1,20 @@
import { MoneyDTOHelper, PercentageDTOHelper } from "@erp/core";
import type { ISnapshotBuilder, ISnapshotBuilderParams } from "@erp/core/api";
import type {
IssuedInvoiceFullSnapshot,
IssuedInvoiceReportTaxSnapshot,
} from "../../application-models";
import type { IIssuedInvoiceTaxFullSnapshot } from "../full";
export interface IIssuedInvoiceTaxReportSnapshotBuilder
extends ISnapshotBuilder<IssuedInvoiceFullSnapshot["taxes"], IssuedInvoiceReportTaxSnapshot[]> {}
import type { IIssuedInvoiceReportTaxSnapshot } from "./issued-invoice-report-tax-snapshot.interface";
export class IssuedInvoiceTaxReportSnapshotBuilder
implements IIssuedInvoiceTaxReportSnapshotBuilder
export interface IIssuedInvoiceReportTaxSnapshotBuilder
extends ISnapshotBuilder<IIssuedInvoiceTaxFullSnapshot[], IIssuedInvoiceReportTaxSnapshot[]> {}
export class IssuedInvoiceReportTaxSnapshotBuilder
implements IIssuedInvoiceReportTaxSnapshotBuilder
{
toOutput(
taxes: IssuedInvoiceFullSnapshot["taxes"],
taxes: IIssuedInvoiceTaxFullSnapshot[],
params?: ISnapshotBuilderParams
): IssuedInvoiceReportTaxSnapshot[] {
): IIssuedInvoiceReportTaxSnapshot[] {
const locale = params?.locale as string;
const moneyOptions = {

View File

@ -1,4 +1,4 @@
export interface IssuedInvoiceReportTaxSnapshot {
export interface IIssuedInvoiceReportTaxSnapshot {
taxable_amount: string;
iva_code: string;

View File

@ -1,6 +1,6 @@
import type { ICustomerInvoiceRepository } from "../../../domain";
import type { IProformaRepository } from "../repositories";
import { type IProformaFinder, ProformaFinder } from "../services";
export function buildProformaFinder(repository: ICustomerInvoiceRepository): IProformaFinder {
export function buildProformaFinder(repository: IProformaRepository): IProformaFinder {
return new ProformaFinder(repository);
}

View File

@ -13,31 +13,31 @@ export interface IProformaRepository {
existsByIdInCompany(
companyId: UniqueID,
id: UniqueID,
tx: unknown
transaction: unknown
): Promise<Result<boolean, Error>>;
getByIdInCompany(
companyId: UniqueID,
id: UniqueID,
tx: unknown
transaction: unknown
): Promise<Result<Proforma, Error>>;
findByCriteriaInCompany(
companyId: UniqueID,
criteria: Criteria,
tx: unknown
transaction: unknown
): Promise<Result<Collection<ProformaListDTO>, Error>>;
deleteByIdInCompany(
companyId: UniqueID,
id: UniqueID,
tx: unknown
transaction: unknown
): Promise<Result<boolean, Error>>;
updateStatusByIdInCompany(
companyId: UniqueID,
id: UniqueID,
newStatus: InvoiceStatus,
tx: unknown
transaction: unknown
): Promise<Result<boolean, Error>>;
}

View File

@ -1,10 +1,11 @@
import type { CustomerInvoiceListDTO } from "@erp/customer-invoices/api/infrastructure";
import type { Criteria } from "@repo/rdx-criteria/server";
import type { UniqueID } from "@repo/rdx-ddd";
import type { Collection, Result } from "@repo/rdx-utils";
import type { Transaction } from "sequelize";
import type { ICustomerInvoiceRepository, Proforma } from "../../../domain";
import type { Proforma } from "../../../domain";
import type { ProformaListDTO } from "../dtos";
import type { IProformaRepository } from "../repositories";
export interface IProformaFinder {
findProformaById(
@ -23,18 +24,18 @@ export interface IProformaFinder {
companyId: UniqueID,
criteria: Criteria,
transaction?: Transaction
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>>;
): Promise<Result<Collection<ProformaListDTO>, Error>>;
}
export class ProformaFinder implements IProformaFinder {
constructor(private readonly repository: ICustomerInvoiceRepository) {}
constructor(private readonly repository: IProformaRepository) {}
async findProformaById(
companyId: UniqueID,
proformaId: UniqueID,
transaction?: Transaction
): Promise<Result<Proforma, Error>> {
return this.repository.getProformaByIdInCompany(companyId, proformaId, transaction, {});
return this.repository.getByIdInCompany(companyId, proformaId, transaction, {});
}
async proformaExists(
@ -42,16 +43,14 @@ export class ProformaFinder implements IProformaFinder {
proformaId: UniqueID,
transaction?: Transaction
): Promise<Result<boolean, Error>> {
return this.repository.existsByIdInCompany(companyId, proformaId, transaction, {
is_proforma: true,
});
return this.repository.existsByIdInCompany(companyId, proformaId, transaction);
}
async findProformasByCriteria(
companyId: UniqueID,
criteria: Criteria,
transaction?: Transaction
): Promise<Result<Collection<CustomerInvoiceListDTO>, Error>> {
return this.repository.findProformasByCriteriaInCompany(companyId, criteria, transaction, {});
): Promise<Result<Collection<ProformaListDTO>, Error>> {
return this.repository.findByCriteriaInCompany(companyId, criteria, transaction, {});
}
}

View File

@ -4,3 +4,5 @@ export * from "./proforma-item-full-snapshot.interface";
export * from "./proforma-items-full-snapshot-builder";
export * from "./proforma-recipient-full-snapshot.interface";
export * from "./proforma-recipient-full-snapshot-builder";
export * from "./proforma-tax-full-snapshot-interface";
export * from "./proforma-taxes-full-snapshot-builder";

View File

@ -83,7 +83,6 @@ export class ProformaFullSnapshotBuilder implements IProformaFullSnapshotBuilder
id: invoice.id.toString(),
company_id: invoice.companyId.toString(),
is_proforma: invoice.isProforma ? "true" : "false",
invoice_number: invoice.invoiceNumber.toString(),
status: invoice.status.toPrimitive(),
series: maybeToEmptyString(invoice.series, (value) => value.toString()),

View File

@ -5,7 +5,6 @@ export interface IProformaFullSnapshot {
id: string;
company_id: string;
is_proforma: "true" | "false";
invoice_number: string;
status: string;
series: string;

View File

@ -1,94 +1,76 @@
import type { ISnapshotBuilder } from "@erp/core/api";
import { maybeToEmptyString } from "@repo/rdx-ddd";
import {
maybeToEmptyMoneyObjectString,
maybeToEmptyPercentageObjectString,
maybeToEmptyQuantityObjectString,
maybeToEmptyString,
} from "@repo/rdx-ddd";
import { Maybe } from "@repo/rdx-utils";
import type { CustomerInvoiceItems, IssuedInvoiceItem } from "../../../../domain";
import { ItemAmount, type ProformaItem, type ProformaItems } from "../../../../domain";
import type { IProformaItemFullSnapshot } from "./proforma-item-full-snapshot.interface";
export interface IProformaItemsFullSnapshotBuilder
extends ISnapshotBuilder<CustomerInvoiceItems, IProformaItemFullSnapshot[]> {}
extends ISnapshotBuilder<ProformaItems, IProformaItemFullSnapshot[]> {}
export class ProformaItemsFullSnapshotBuilder implements IProformaItemsFullSnapshotBuilder {
private mapItem(invoiceItem: IssuedInvoiceItem, index: number): IProformaItemFullSnapshot {
const allAmounts = invoiceItem.calculateAllAmounts();
private mapItem(proformaItem: ProformaItem, index: number): IProformaItemFullSnapshot {
const allAmounts = proformaItem.calculateAllAmounts();
const isValued = proformaItem.isValued;
const noneIfNotValued = <T>(value: Maybe<T>): Maybe<T> => (isValued ? value : Maybe.none<T>());
return {
id: invoiceItem.id.toPrimitive(),
is_valued: String(invoiceItem.isValued),
id: proformaItem.id.toPrimitive(),
is_valued: String(isValued),
position: String(index),
description: maybeToEmptyString(invoiceItem.description, (value) => value.toPrimitive()),
quantity: invoiceItem.quantity.match(
(quantity) => quantity.toObjectString(),
() => ({ value: "", scale: "" })
description: maybeToEmptyString(proformaItem.description, (value) => value.toString()),
quantity: maybeToEmptyQuantityObjectString(proformaItem.quantity),
unit_amount: maybeToEmptyMoneyObjectString(proformaItem.unitAmount),
subtotal_amount: isValued
? allAmounts.subtotalAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
discount_percentage: maybeToEmptyPercentageObjectString(proformaItem.itemDiscountPercentage),
discount_amount: isValued
? allAmounts.itemDiscountAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
global_discount_percentage: maybeToEmptyPercentageObjectString(
proformaItem.globalDiscountPercentage
),
global_discount_amount: maybeToEmptyMoneyObjectString(proformaItem.globalDiscountAmount),
unit_amount: invoiceItem.unitAmount.match(
(unitAmount) => unitAmount.toObjectString(),
() => ({ value: "", scale: "", currency_code: "" })
),
total_discount_amount: isValued
? allAmounts.totalDiscountAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
subtotal_amount: allAmounts.subtotalAmount.toObjectString(),
taxable_amount: isValued
? allAmounts.taxableAmount.toObjectString()
: ItemAmount.EMPTY_MONEY_OBJECT,
discount_percentage: invoiceItem.itemDiscountPercentage.match(
(discountPercentage) => discountPercentage.toObjectString(),
() => ({ value: "", scale: "" })
),
iva_code: maybeToEmptyString(proformaItem.ivaCode),
iva_percentage: maybeToEmptyPercentageObjectString(proformaItem.ivaPercentage),
iva_amount: maybeToEmptyMoneyObjectString(proformaItem.ivaAmount),
discount_amount: allAmounts.itemDiscountAmount.toObjectString(),
rec_code: maybeToEmptyString(proformaItem.recCode),
rec_percentage: maybeToEmptyPercentageObjectString(proformaItem.recPercentage),
rec_amount: maybeToEmptyMoneyObjectString(proformaItem.recAmount),
global_discount_percentage: invoiceItem.globalDiscountPercentage.match(
(discountPercentage) => discountPercentage.toObjectString(),
() => ({ value: "", scale: "" })
),
retention_code: maybeToEmptyString(proformaItem.retentionCode),
retention_percentage: maybeToEmptyPercentageObjectString(proformaItem.retentionPercentage),
retention_amount: maybeToEmptyMoneyObjectString(proformaItem.retentionAmount),
global_discount_amount: allAmounts.globalDiscountAmount.toObjectString(),
taxable_amount: allAmounts.taxableAmount.toObjectString(),
iva_code: invoiceItem.taxes.iva.match(
(iva) => iva.code,
() => ""
),
iva_percentage: invoiceItem.taxes.iva.match(
(iva) => iva.percentage.toObjectString(),
() => ({ value: "", scale: "" })
),
iva_amount: allAmounts.ivaAmount.toObjectString(),
rec_code: invoiceItem.taxes.rec.match(
(rec) => rec.code,
() => ""
),
rec_percentage: invoiceItem.taxes.rec.match(
(rec) => rec.percentage.toObjectString(),
() => ({ value: "", scale: "" })
),
rec_amount: allAmounts.recAmount.toObjectString(),
retention_code: invoiceItem.taxes.retention.match(
(retention) => retention.code,
() => ""
),
retention_percentage: invoiceItem.taxes.retention.match(
(retention) => retention.percentage.toObjectString(),
() => ({ value: "", scale: "" })
),
retention_amount: allAmounts.retentionAmount.toObjectString(),
taxes_amount: allAmounts.taxesAmount.toObjectString(),
total_amount: allAmounts.totalAmount.toObjectString(),
taxes_amount: maybeToEmptyMoneyObjectString(proformaItem.taxesAmount),
total_amount: maybeToEmptyMoneyObjectString(proformaItem.totalAmount),
};
}
toOutput(invoiceItems: CustomerInvoiceItems): IProformaItemFullSnapshot[] {
toOutput(invoiceItems: ProformaItems): IProformaItemFullSnapshot[] {
return invoiceItems.map((item, index) => this.mapItem(item, index));
}
}

View File

@ -2,22 +2,23 @@ import type { ISnapshotBuilder } from "@erp/core/api";
import { DomainValidationError, maybeToEmptyString } from "@repo/rdx-ddd";
import type { InvoiceRecipient, Proforma } from "../../../../domain";
import type { ProformaRecipientFullSnapshot } from "../../application-models";
import type { IProformaRecipientFullSnapshot } from "./proforma-recipient-full-snapshot.interface";
export interface IProformaRecipientFullSnapshotBuilder
extends ISnapshotBuilder<Proforma, ProformaRecipientFullSnapshot> {}
extends ISnapshotBuilder<Proforma, IProformaRecipientFullSnapshot> {}
export class ProformaRecipientFullSnapshotBuilder implements IProformaRecipientFullSnapshotBuilder {
toOutput(invoice: Proforma): ProformaRecipientFullSnapshot {
if (!invoice.recipient) {
toOutput(proforma: Proforma): IProformaRecipientFullSnapshot {
if (!proforma.recipient) {
throw DomainValidationError.requiredValue("recipient", {
cause: invoice,
cause: proforma,
});
}
return invoice.recipient.match(
return proforma.recipient.match(
(recipient: InvoiceRecipient) => ({
id: invoice.customerId.toString(),
id: proforma.customerId.toString(),
name: recipient.name.toString(),
tin: recipient.tin.toString(),
street: maybeToEmptyString(recipient.street, (v) => v.toString()),

View File

@ -0,0 +1,17 @@
export interface IProformaTaxFullSnapshot {
taxable_amount: { value: string; scale: string; currency_code: string };
iva_code: string;
iva_percentage: { value: string; scale: string };
iva_amount: { value: string; scale: string; currency_code: string };
rec_code: string;
rec_percentage: { value: string; scale: string };
rec_amount: { value: string; scale: string; currency_code: string };
retention_code: string;
retention_percentage: { value: string; scale: string };
retention_amount: { value: string; scale: string; currency_code: string };
taxes_amount: { value: string; scale: string; currency_code: string };
}

View File

@ -0,0 +1,39 @@
import type { ISnapshotBuilder } from "@erp/core/api";
import {
maybeToEmptyMoneyObjectString,
maybeToEmptyPercentageObjectString,
maybeToEmptyString,
} from "@repo/rdx-ddd";
import type { ProformaTax, ProformaTaxes } from "../../../../domain";
import type { IProformaTaxFullSnapshot } from "./proforma-tax-full-snapshot-interface";
export interface IProformaTaxesFullSnapshotBuilder
extends ISnapshotBuilder<ProformaTaxes, IProformaTaxFullSnapshot[]> {}
export class ProformaTaxesFullSnapshotBuilder implements IProformaTaxesFullSnapshotBuilder {
private mapItem(proformaTax: ProformaTax, index: number): IProformaTaxFullSnapshot {
return {
taxable_amount: proformaTax.taxableAmount.toObjectString(),
iva_code: proformaTax.ivaCode.toString(),
iva_percentage: proformaTax.ivaPercentage.toObjectString(),
iva_amount: proformaTax.ivaAmount.toObjectString(),
rec_code: maybeToEmptyString(proformaTax.recCode),
rec_percentage: maybeToEmptyPercentageObjectString(proformaTax.recPercentage),
rec_amount: maybeToEmptyMoneyObjectString(proformaTax.recAmount),
retention_code: maybeToEmptyString(proformaTax.retentionCode),
retention_percentage: maybeToEmptyPercentageObjectString(proformaTax.retentionPercentage),
retention_amount: maybeToEmptyMoneyObjectString(proformaTax.retentionAmount),
taxes_amount: proformaTax.taxesAmount.toObjectString(),
};
}
toOutput(invoiceTaxes: ProformaTaxes): IProformaTaxFullSnapshot[] {
return invoiceTaxes.map((item, index) => this.mapItem(item, index));
}
}

View File

@ -1,20 +1,20 @@
import type { ISnapshotBuilder } from "@erp/core/api";
import { maybeToEmptyString } from "@repo/rdx-ddd";
import type { CustomerInvoiceListDTO } from "../../../../infrastructure";
import type { ProformaListItemSnapshot } from "../../application-models";
import type { ProformaListDTO } from "../../dtos";
import type { IProformaListItemSnapshot } from "./proforma-list-item-snapshot.interface";
export interface IProformaListItemSnapshotBuilder
extends ISnapshotBuilder<CustomerInvoiceListDTO, ProformaListItemSnapshot> {}
extends ISnapshotBuilder<ProformaListDTO, IProformaListItemSnapshot> {}
export class ProformaListItemSnapshotBuilder implements IProformaListItemSnapshotBuilder {
toOutput(proforma: CustomerInvoiceListDTO): ProformaListItemSnapshot {
toOutput(proforma: ProformaListDTO): IProformaListItemSnapshot {
const recipient = proforma.recipient.toObjectString();
return {
id: proforma.id.toString(),
company_id: proforma.companyId.toString(),
is_proforma: proforma.isProforma,
customer_id: proforma.customerId.toString(),
invoice_number: proforma.invoiceNumber.toString(),
@ -32,8 +32,7 @@ export class ProformaListItemSnapshotBuilder implements IProformaListItemSnapsho
currency_code: proforma.currencyCode.code,
subtotal_amount: proforma.subtotalAmount.toObjectString(),
discount_percentage: proforma.discountPercentage.toObjectString(),
discount_amount: proforma.discountAmount.toObjectString(),
total_discount_amount: proforma.totalDiscountAmount.toObjectString(),
taxable_amount: proforma.taxableAmount.toObjectString(),
taxes_amount: proforma.taxesAmount.toObjectString(),
total_amount: proforma.totalAmount.toObjectString(),

View File

@ -1,7 +1,6 @@
export interface IProformaListItemSnapshot {
id: string;
company_id: string;
is_proforma: boolean;
customer_id: string;
@ -30,8 +29,7 @@ export interface IProformaListItemSnapshot {
};
subtotal_amount: { value: string; scale: string; currency_code: string };
discount_percentage: { value: string; scale: string };
discount_amount: { value: string; scale: string; currency_code: string };
total_discount_amount: { value: string; scale: string; currency_code: string };
taxable_amount: { value: string; scale: string; currency_code: string };
taxes_amount: { value: string; scale: string; currency_code: string };
total_amount: { value: string; scale: string; currency_code: string };

View File

@ -84,7 +84,6 @@ export class ProformaFullPresenter extends Presenter<Proforma, GetProformaByIdRe
id: proforma.id.toString(),
company_id: proforma.companyId.toString(),
is_proforma: proforma.isProforma ? "true" : "false",
invoice_number: proforma.invoiceNumber.toString(),
status: proforma.status.toPrimitive(),
series: maybeToEmptyString(proforma.series, (value) => value.toString()),

View File

@ -3,7 +3,6 @@ import { maybeToEmptyString } from "@repo/rdx-ddd";
import type { ArrayElement, Collection } from "@repo/rdx-utils";
import type { ListIssuedInvoicesResponseDTO } from "../../../../../common/dto";
import type { CustomerInvoiceListDTO } from "../../../../infrastructure";
export class IssuedInvoiceListPresenter extends Presenter {
protected _mapInvoice(invoice: CustomerInvoiceListDTO) {
@ -21,7 +20,6 @@ export class IssuedInvoiceListPresenter extends Presenter {
const invoiceDTO: ArrayElement<ListIssuedInvoicesResponseDTO["items"]> = {
id: invoice.id.toString(),
company_id: invoice.companyId.toString(),
is_proforma: invoice.isProforma,
customer_id: invoice.customerId.toString(),
invoice_number: invoice.invoiceNumber.toString(),

View File

@ -11,5 +11,4 @@ export * from "./item-amount.vo";
export * from "./item-description.vo";
export * from "./item-discount-percentage.vo";
export * from "./item-quantity.vo";
export * from "./item-tax-group.vo";
export * from "./item-tax-percentage.vo";

View File

@ -2,8 +2,9 @@ import type { Tax } from "@erp/core/api";
import { ValueObject } from "@repo/rdx-ddd";
import { type Maybe, Result } from "@repo/rdx-utils";
import type { ProformaItemTaxGroup } from "../../proformas/value-objects/proforma-item-tax-group.vo";
import { InvoiceAmount } from "./invoice-amount.vo";
import type { ItemTaxGroup } from "./item-tax-group.vo";
export type InvoiceTaxGroupProps = {
taxableAmount: InvoiceAmount;
@ -20,7 +21,7 @@ export class InvoiceTaxGroup extends ValueObject<InvoiceTaxGroupProps> {
/**
* Crea un grupo vacío a partir de un ItemTaxGroup (línea)
*/
static fromItem(lineTaxes: ItemTaxGroup, taxableAmount: InvoiceAmount): InvoiceTaxGroup {
static fromItem(lineTaxes: ProformaItemTaxGroup, taxableAmount: InvoiceAmount): InvoiceTaxGroup {
const iva = lineTaxes.iva.unwrap(); // iva siempre obligatorio
const rec = lineTaxes.rec;
const retention = lineTaxes.retention;

View File

@ -8,7 +8,6 @@ import {
import { type Maybe, Result } from "@repo/rdx-utils";
import type {
InvoiceAmount,
ItemAmount,
ItemDescription,
ItemDiscountPercentage,
@ -31,40 +30,38 @@ export type IssuedInvoiceItemProps = {
quantity: Maybe<ItemQuantity>;
unitAmount: Maybe<ItemAmount>;
subtotalAmount: Maybe<InvoiceAmount>;
subtotalAmount: ItemAmount;
itemDiscountPercentage: Maybe<ItemDiscountPercentage>;
itemDiscountAmount: Maybe<InvoiceAmount>;
itemDiscountAmount: ItemAmount;
globalDiscountPercentage: Maybe<ItemDiscountPercentage>;
globalDiscountAmount: Maybe<InvoiceAmount>;
globalDiscountAmount: ItemAmount;
totalDiscountAmount: Maybe<InvoiceAmount>;
totalDiscountAmount: ItemAmount;
taxableAmount: Maybe<InvoiceAmount>;
taxableAmount: ItemAmount;
ivaCode: Maybe<string>;
ivaPercentage: Maybe<ItemDiscountPercentage>;
ivaAmount: Maybe<InvoiceAmount>;
ivaAmount: ItemAmount;
recCode: Maybe<string>;
recPercentage: Maybe<ItemDiscountPercentage>;
recAmount: Maybe<InvoiceAmount>;
recAmount: ItemAmount;
retentionCode: Maybe<string>;
retentionPercentage: Maybe<ItemDiscountPercentage>;
retentionAmount: Maybe<InvoiceAmount>;
retentionAmount: ItemAmount;
taxesAmount: Maybe<InvoiceAmount>;
totalAmount: Maybe<InvoiceAmount>;
taxesAmount: ItemAmount;
totalAmount: ItemAmount;
languageCode: LanguageCode;
currencyCode: CurrencyCode;
};
export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> {
protected _isValued!: boolean;
public static create(
props: IssuedInvoiceItemProps,
id?: UniqueID
@ -80,14 +77,12 @@ export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> {
protected constructor(props: IssuedInvoiceItemProps, id?: UniqueID) {
super(props, id);
this._isValued = this.quantity.isSome() || this.unitAmount.isSome();
}
// Getters
get isValued(): boolean {
return this._isValued;
return this.quantity.isSome() || this.unitAmount.isSome();
}
get description() {
@ -144,7 +139,7 @@ export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> {
public get ivaPercentage(): Maybe<Percentage> {
return this.props.ivaPercentage;
}
public get ivaAmount(): Maybe<InvoiceAmount> {
public get ivaAmount(): ItemAmount {
return this.props.ivaAmount;
}
@ -154,7 +149,7 @@ export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> {
public get recPercentage(): Maybe<Percentage> {
return this.props.recPercentage;
}
public get recAmount(): Maybe<InvoiceAmount> {
public get recAmount(): ItemAmount {
return this.props.recAmount;
}
@ -164,7 +159,7 @@ export class IssuedInvoiceItem extends DomainEntity<IssuedInvoiceItemProps> {
public get retentionPercentage(): Maybe<Percentage> {
return this.props.retentionPercentage;
}
public get retentionAmount(): Maybe<InvoiceAmount> {
public get retentionAmount(): ItemAmount {
return this.props.retentionAmount;
}

View File

@ -20,12 +20,11 @@ import {
InvoiceTaxGroup,
type ItemAmount,
} from "../../common/value-objects";
import type { ProformaTaxes } from "../entities";
import { ProformaItems } from "../entities/proforma-items";
export type ProformaProps = {
companyId: UniqueID;
isProforma: boolean;
status: InvoiceStatus;
series: Maybe<InvoiceSerie>;
@ -44,18 +43,37 @@ export type ProformaProps = {
languageCode: LanguageCode;
currencyCode: CurrencyCode;
items: ProformaItems;
paymentMethod: Maybe<InvoicePaymentMethod>;
items: ProformaItems;
globalDiscountPercentage: Percentage;
};
export interface IProforma extends AggregateRoot<ProformaProps> {
getTaxes: ProformaTaxes;
getSubtotalAmount: InvoiceAmount;
getItemsDiscountAmount: InvoiceAmount;
getGlobalDiscountPercentage: Percentage;
getGlobalDiscountAmount: InvoiceAmount;
getTotalDiscountAmount: InvoiceAmount;
getTaxableAmount: InvoiceAmount;
getIvaAmount: InvoiceAmount;
getRecAmount: InvoiceAmount;
getRetentionAmount: InvoiceAmount;
getTaxesAmount: InvoiceAmount;
getTotalAmount: InvoiceAmount;
}
export type ProformaPatchProps = Partial<Omit<ProformaProps, "companyId" | "items">> & {
items?: ProformaItems;
};
export class Proforma extends AggregateRoot<ProformaProps> {
export class Proforma extends AggregateRoot<ProformaProps> implements IProforma {
private _items!: ProformaItems;
protected constructor(props: ProformaProps, id?: UniqueID) {
@ -120,10 +138,6 @@ export class Proforma extends AggregateRoot<ProformaProps> {
return this.props.customerId;
}
public get isProforma(): boolean {
return this.props.isProforma;
}
public get status(): InvoiceStatus {
return this.props.status;
}

View File

@ -1 +1,2 @@
export * from "./proforma-items";
export * from "./proforma-taxes";

View File

@ -1,3 +1,4 @@
import type { Tax } from "@erp/core/api";
import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } from "@repo/rdx-ddd";
import { type Maybe, Result } from "@repo/rdx-utils";
@ -6,8 +7,8 @@ import {
type ItemDescription,
ItemDiscountPercentage,
ItemQuantity,
type ItemTaxGroup,
} from "../../../common";
import type { ProformaItemTaxGroup } from "../../value-objects/proforma-item-tax-group.vo";
/**
*
@ -28,21 +29,64 @@ import {
export type ProformaItemProps = {
description: Maybe<ItemDescription>;
quantity: Maybe<ItemQuantity>; // Cantidad de unidades
unitAmount: Maybe<ItemAmount>; // Precio unitario en la moneda de la factura
itemDiscountPercentage: Maybe<ItemDiscountPercentage>; // % descuento
globalDiscountPercentage: Maybe<ItemDiscountPercentage>; // % descuento de la cabecera
itemDiscountPercentage: Maybe<ItemDiscountPercentage>; // % descuento de línea
taxes: ItemTaxGroup;
taxes: ProformaItemTaxGroup;
// Estos campos vienen de la cabecera,
// pero se necesitan para cálculos y representaciones de la línea.
globalDiscountPercentage: Maybe<ItemDiscountPercentage>; // % descuento de la cabecera
languageCode: LanguageCode; // Para formateos específicos de idioma
currencyCode: CurrencyCode; // Para cálculos y formateos de moneda
};
export interface IProformaItem extends ProformaItemProps {
description: Maybe<ItemDescription>;
isValued: boolean; // Indica si el item tiene cantidad o precio (o ambos) para ser considerado "valorizado"
quantity: Maybe<ItemQuantity>;
unitAmount: Maybe<ItemAmount>;
getSubtotalAmount: Maybe<ItemAmount>;
itemDiscountPercentage: Maybe<ItemDiscountPercentage>; // % descuento de línea
getItemDiscountAmount: Maybe<ItemAmount>;
globalDiscountPercentage: Maybe<ItemDiscountPercentage>; // % descuento de la cabecera
getGlobalDiscountAmount: Maybe<ItemAmount>;
getTotalDiscountAmount: Maybe<ItemAmount>;
getTaxableAmount: Maybe<ItemAmount>;
getIva: Maybe<Tax>;
getIvaCode: Maybe<string>;
getIvaPercentage: Maybe<ItemDiscountPercentage>;
getIvaAmount: ItemAmount;
getRec: Maybe<Tax>;
getRecCode: Maybe<string>;
getRecPercentage: Maybe<ItemDiscountPercentage>;
getRecAmount: ItemAmount;
getRetention: Maybe<Tax>;
getRetentionCode: Maybe<string>;
getRetentionPercentage: Maybe<ItemDiscountPercentage>;
getRetentionAmount: ItemAmount;
getTaxesAmount: ItemAmount;
getTotalAmount: ItemAmount;
languageCode: LanguageCode;
currencyCode: CurrencyCode;
};
}
export class ProformaItem extends DomainEntity<ProformaItemProps> {
protected _isValued!: boolean;
public static create(props: ProformaItemProps, id?: UniqueID): Result<ProformaItem, Error> {
const item = new ProformaItem(props, id);
@ -55,14 +99,12 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
protected constructor(props: ProformaItemProps, id?: UniqueID) {
super(props, id);
this._isValued = this.quantity.isSome() || this.unitAmount.isSome();
}
// Getters
get isValued(): boolean {
return this._isValued;
return this.props.quantity.isSome() || this.props.unitAmount.isSome();
}
get description() {
@ -105,6 +147,69 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
return this.getProps();
}
// Getters específicos para cálculos y representaciones
public getSubtotalAmount(): ItemAmount {
return this.calculateAllAmounts().subtotalAmount;
}
public getItemDiscountAmount(): ItemAmount {
return this.calculateAllAmounts().itemDiscountAmount;
}
public getGlobalDiscountAmount(): ItemAmount {
return this.calculateAllAmounts().globalDiscountAmount;
}
public getTotalDiscountAmount(): ItemAmount {
return this.calculateAllAmounts().totalDiscountAmount;
}
public getTaxableAmount(): ItemAmount {
return this.calculateAllAmounts().taxableAmount;
}
public getIva(): Maybe<Tax> {
return this.taxes.iva;
}
public getIvaCode(): Maybe<string> {
return this.taxes.iva.map((tax) => tax.code);
}
public getIvaPercentage(): Maybe<ItemDiscountPercentage> {
return this.taxes.iva.map((tax) => tax.percentage);
}
public getIvaAmount(): ItemAmount {
return this.calculateAllAmounts().ivaAmount;
}
public getRec(): Maybe<Tax> {
return this.taxes.rec;
}
public getRecCode(): Maybe<string> {
return this.taxes.rec.map((tax) => tax.code);
}
public getRecPercentage(): Maybe<ItemDiscountPercentage> {
return this.taxes.rec.map((tax) => tax.percentage);
}
public getIndividualTaxAmounts() {
const { ivaAmount, recAmount, retentionAmount } = this.calculateAllAmounts();
return { ivaAmount, recAmount, retentionAmount };
}
public getTaxesAmount(): ItemAmount {
return this.calculateAllAmounts().taxesAmount;
}
public getTotalAmount(): ItemAmount {
return this.calculateAllAmounts().totalAmount;
}
// Ayudantes
/**
@ -162,13 +267,6 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
return itemDiscountAmount.add(globalDiscountAmount);
}
/**
* @summary Helper puro para calcular impuestos individuales.
*/
private _calculateIndividualTaxes(taxable: ItemAmount) {
return this.taxes.calculateAmounts(taxable);
}
// Cálculos
/**
@ -201,7 +299,8 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
const taxableAmount = subtotalAmount.subtract(totalDiscountAmount);
const { ivaAmount, recAmount, retentionAmount } = this._calculateIndividualTaxes(taxableAmount);
// Calcular impuestos individuales a partir de la base imponible
const { ivaAmount, recAmount, retentionAmount } = this.taxes.calculateAmounts(taxableAmount);
const taxesAmount = ivaAmount.add(recAmount).add(retentionAmount);
const totalAmount = taxableAmount.add(taxesAmount);
@ -223,37 +322,4 @@ export class ProformaItem extends DomainEntity<ProformaItemProps> {
totalAmount,
} as const;
}
public getSubtotalAmount(): ItemAmount {
return this.calculateAllAmounts().subtotalAmount;
}
public getItemDiscountAmount(): ItemAmount {
return this.calculateAllAmounts().itemDiscountAmount;
}
public getGlobalDiscountAmount(): ItemAmount {
return this.calculateAllAmounts().globalDiscountAmount;
}
public getTotalDiscountAmount(): ItemAmount {
return this.calculateAllAmounts().totalDiscountAmount;
}
public getTaxableAmount(): ItemAmount {
return this.calculateAllAmounts().taxableAmount;
}
public getIndividualTaxAmounts() {
const { ivaAmount, recAmount, retentionAmount } = this.calculateAllAmounts();
return { ivaAmount, recAmount, retentionAmount };
}
public getTaxesAmount(): ItemAmount {
return this.calculateAllAmounts().taxesAmount;
}
public getTotalAmount(): ItemAmount {
return this.calculateAllAmounts().totalAmount;
}
}

View File

@ -0,0 +1,2 @@
export * from "./proforma-tax.entity";
export * from "./proforma-taxes.collection";

View File

@ -0,0 +1,70 @@
import { DomainEntity, type Percentage, type UniqueID } from "@repo/rdx-ddd";
import { type Maybe, Result } from "@repo/rdx-utils";
import type { InvoiceAmount } from "../../../common";
export type ProformaTaxProps = {
taxableAmount: InvoiceAmount;
ivaCode: string;
ivaPercentage: Percentage;
ivaAmount: InvoiceAmount;
recCode: Maybe<string>;
recPercentage: Maybe<Percentage>;
recAmount: Maybe<InvoiceAmount>;
retentionCode: Maybe<string>;
retentionPercentage: Maybe<Percentage>;
retentionAmount: Maybe<InvoiceAmount>;
taxesAmount: InvoiceAmount;
};
export class ProformaTax extends DomainEntity<ProformaTaxProps> {
public static create(props: ProformaTaxProps, id?: UniqueID): Result<ProformaTax, Error> {
return Result.ok(new ProformaTax(props, id));
}
public get taxableAmount(): InvoiceAmount {
return this.props.taxableAmount;
}
public get ivaCode(): string {
return this.props.ivaCode;
}
public get ivaPercentage(): Percentage {
return this.props.ivaPercentage;
}
public get ivaAmount(): InvoiceAmount {
return this.props.ivaAmount;
}
public get recCode(): Maybe<string> {
return this.props.recCode;
}
public get recPercentage(): Maybe<Percentage> {
return this.props.recPercentage;
}
public get recAmount(): Maybe<InvoiceAmount> {
return this.props.recAmount;
}
public get retentionCode(): Maybe<string> {
return this.props.retentionCode;
}
public get retentionPercentage(): Maybe<Percentage> {
return this.props.retentionPercentage;
}
public get retentionAmount(): Maybe<InvoiceAmount> {
return this.props.retentionAmount;
}
public get taxesAmount(): InvoiceAmount {
return this.props.taxesAmount;
}
public getProps(): ProformaTaxProps {
return this.props;
}
}

View File

@ -0,0 +1,25 @@
import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
import { Collection } from "@repo/rdx-utils";
import type { ProformaTax } from "./proforma-tax.entity";
export type ProformaTaxesProps = {
taxes?: ProformaTax[];
languageCode: LanguageCode;
currencyCode: CurrencyCode;
};
export class ProformaTaxes extends Collection<ProformaTax> {
private _languageCode!: LanguageCode;
private _currencyCode!: CurrencyCode;
constructor(props: ProformaTaxesProps) {
super(props.taxes ?? []);
this._languageCode = props.languageCode;
this._currencyCode = props.currencyCode;
}
public static create(props: ProformaTaxesProps): ProformaTaxes {
return new ProformaTaxes(props);
}
}

View File

@ -0,0 +1,2 @@
export * from "./proforma-item-tax-group.vo";
export * from "./proforma-tax-group.vo";

View File

@ -2,17 +2,17 @@ import type { Tax } from "@erp/core/api";
import { ValueObject } from "@repo/rdx-ddd";
import { type Maybe, Result } from "@repo/rdx-utils";
import { ItemAmount } from ".";
import { ItemAmount } from "../../common/value-objects";
export interface ItemTaxGroupProps {
export interface ProformaItemTaxGroupProps {
iva: Maybe<Tax>; // si existe
rec: Maybe<Tax>; // si existe
retention: Maybe<Tax>; // si existe
}
export class ItemTaxGroup extends ValueObject<ItemTaxGroupProps> {
static create(props: ItemTaxGroupProps) {
return Result.ok(new ItemTaxGroup(props));
export class ProformaItemTaxGroup extends ValueObject<ProformaItemTaxGroupProps> {
static create(props: ProformaItemTaxGroupProps) {
return Result.ok(new ProformaItemTaxGroup(props));
}
calculateAmounts(taxableAmount: ItemAmount) {

View File

@ -0,0 +1,66 @@
import type { Tax } from "@erp/core/api";
import { ValueObject } from "@repo/rdx-ddd";
import { type Maybe, Result } from "@repo/rdx-utils";
import type { InvoiceAmount } from "../../common";
export type ProformaTaxGroupProps = {
taxableAmount: InvoiceAmount;
iva: Tax;
ivaAmount: InvoiceAmount;
rec: Maybe<Tax>; // si existe
recAmount: Maybe<InvoiceAmount>;
retention: Maybe<Tax>; // si existe
retentionAmount: Maybe<InvoiceAmount>;
taxesAmount: InvoiceAmount;
};
export class ProformaTaxGroup extends ValueObject<ProformaTaxGroupProps> {
static create(props: ProformaTaxGroupProps) {
return Result.ok(new ProformaTaxGroup(props));
}
get taxableAmount(): InvoiceAmount {
return this.props.taxableAmount;
}
get iva(): Tax {
return this.props.iva;
}
get ivaAmount(): InvoiceAmount {
return this.props.ivaAmount;
}
get rec(): Maybe<Tax> {
return this.props.rec;
}
get recAmount(): Maybe<InvoiceAmount> {
return this.props.recAmount;
}
get retention(): Maybe<Tax> {
return this.props.retention;
}
get retentionAmount(): Maybe<InvoiceAmount> {
return this.props.retentionAmount;
}
get taxesAmount(): InvoiceAmount {
return this.props.taxesAmount;
}
getProps() {
return this.props;
}
toPrimitive() {
return this.getProps();
}
}

View File

@ -33,7 +33,7 @@ export class CustomerInvoiceItemModel extends Model<
declare unit_amount_scale: number;
// Subtotal (cantidad * importe unitario)
declare subtotal_amount_value: CreationOptional<number | null>;
declare subtotal_amount_value: number;
declare subtotal_amount_scale: number;
// Discount percentage
@ -41,7 +41,7 @@ export class CustomerInvoiceItemModel extends Model<
declare discount_percentage_scale: number;
// Discount amount
declare discount_amount_value: CreationOptional<number | null>;
declare discount_amount_value: number;
declare discount_amount_scale: number;
// Porcentaje de descuento global proporcional a esta línea.
@ -49,15 +49,15 @@ export class CustomerInvoiceItemModel extends Model<
declare global_discount_percentage_scale: number;
// Importe del descuento global para esta línea
declare global_discount_amount_value: CreationOptional<number | null>;
declare global_discount_amount_value: number;
declare global_discount_amount_scale: number;
// Suma de los dos descuentos: el de la linea + el global
declare total_discount_amount_value: CreationOptional<number | null>;
declare total_discount_amount_value: number;
declare total_discount_amount_scale: number;
// Taxable amount (base imponible tras los dos descuentos)
declare taxable_amount_value: CreationOptional<number | null>;
declare taxable_amount_value: number;
declare taxable_amount_scale: number;
// IVA percentage
@ -67,8 +67,7 @@ export class CustomerInvoiceItemModel extends Model<
declare iva_percentage_scale: number;
// IVA amount
declare iva_amount_value: CreationOptional<number | null>;
declare iva_amount_value: number;
declare iva_amount_scale: number;
// Recargo de equivalencia percentage
@ -78,7 +77,7 @@ export class CustomerInvoiceItemModel extends Model<
declare rec_percentage_scale: number;
// Recargo de equivalencia amount
declare rec_amount_value: CreationOptional<number | null>;
declare rec_amount_value: number;
declare rec_amount_scale: number;
// Retention percentage
@ -88,15 +87,15 @@ export class CustomerInvoiceItemModel extends Model<
declare retention_percentage_scale: number;
// Retention amount
declare retention_amount_value: CreationOptional<number | null>;
declare retention_amount_value: number;
declare retention_amount_scale: number;
// Total taxes amount / taxes total
declare taxes_amount_value: CreationOptional<number | null>;
declare taxes_amount_value: number;
declare taxes_amount_scale: number;
// Total
declare total_amount_value: CreationOptional<number | null>;
declare total_amount_value: number;
declare total_amount_scale: number;
// Relaciones
@ -176,8 +175,8 @@ export default (database: Sequelize) => {
subtotal_amount_value: {
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: 0,
},
subtotal_amount_scale: {
@ -200,8 +199,8 @@ export default (database: Sequelize) => {
discount_amount_value: {
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: 0,
},
discount_amount_scale: {
@ -224,8 +223,8 @@ export default (database: Sequelize) => {
global_discount_amount_value: {
type: new DataTypes.BIGINT(),
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: 0,
},
global_discount_amount_scale: {
@ -236,8 +235,8 @@ export default (database: Sequelize) => {
total_discount_amount_value: {
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: 0,
},
total_discount_amount_scale: {
@ -248,8 +247,8 @@ export default (database: Sequelize) => {
taxable_amount_value: {
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: 0,
},
taxable_amount_scale: {
@ -278,8 +277,8 @@ export default (database: Sequelize) => {
iva_amount_value: {
type: DataTypes.BIGINT,
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: 0,
},
iva_amount_scale: {
type: DataTypes.SMALLINT,
@ -307,8 +306,8 @@ export default (database: Sequelize) => {
rec_amount_value: {
type: DataTypes.BIGINT,
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: 0,
},
rec_amount_scale: {
type: DataTypes.SMALLINT,
@ -336,8 +335,8 @@ export default (database: Sequelize) => {
retention_amount_value: {
type: DataTypes.BIGINT,
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: 0,
},
retention_amount_scale: {
type: DataTypes.SMALLINT,
@ -347,8 +346,8 @@ export default (database: Sequelize) => {
taxes_amount_value: {
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: 0,
},
taxes_amount_scale: {
@ -359,8 +358,8 @@ export default (database: Sequelize) => {
total_amount_value: {
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: 0,
},
total_amount_scale: {

View File

@ -46,7 +46,7 @@ export class CustomerInvoiceTaxModel extends Model<
declare rec_percentage_scale: number;
// Recargo de equivalencia amount
declare rec_amount_value: CreationOptional<number | null>;
declare rec_amount_value: number;
declare rec_amount_scale: number;
// Retention percentage
@ -56,7 +56,7 @@ export class CustomerInvoiceTaxModel extends Model<
declare retention_percentage_scale: number;
// Retention amount
declare retention_amount_value: CreationOptional<number | null>;
declare retention_amount_value: number;
declare retention_amount_scale: number;
// Total taxes amount / taxes total

View File

@ -85,9 +85,10 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
);
const subtotalAmount = extractOrPushError(
maybeFromNullableResult(raw.subtotal_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
),
ItemAmount.create({
value: raw.subtotal_amount_value,
currency_code: attributes.currencyCode?.code,
}),
`items[${index}].subtotal_amount_value`,
errors
);
@ -101,9 +102,10 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
);
const itemDiscountAmount = extractOrPushError(
maybeFromNullableResult(raw.discount_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
),
ItemAmount.create({
value: raw.discount_amount_value,
currency_code: attributes.currencyCode?.code,
}),
`items[${index}].discount_amount_value`,
errors
);
@ -117,21 +119,32 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
);
const globalDiscountAmount = extractOrPushError(
maybeFromNullableResult(raw.global_discount_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
),
ItemAmount.create({
value: raw.global_discount_amount_value,
currency_code: attributes.currencyCode?.code,
}),
`items[${index}].global_discount_amount_value`,
errors
);
const totalDiscountAmount = extractOrPushError(
maybeFromNullableResult(raw.total_discount_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
),
ItemAmount.create({
value: raw.total_discount_amount_value,
currency_code: attributes.currencyCode?.code,
}),
`items[${index}].total_discount_amount_value`,
errors
);
const taxableAmount = extractOrPushError(
ItemAmount.create({
value: raw.taxable_amount_value,
currency_code: attributes.currencyCode?.code,
}),
`items[${index}].taxable_amount_value`,
errors
);
const ivaCode = maybeFromNullableOrEmptyString(raw.iva_code);
const ivaPercentage = extractOrPushError(
@ -143,9 +156,10 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
);
const ivaAmount = extractOrPushError(
maybeFromNullableResult(raw.iva_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
),
ItemAmount.create({
value: raw.iva_amount_value,
currency_code: attributes.currencyCode?.code,
}),
`items[${index}].iva_amount_value`,
errors
);
@ -161,9 +175,10 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
);
const recAmount = extractOrPushError(
maybeFromNullableResult(raw.rec_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
),
ItemAmount.create({
value: raw.rec_amount_value,
currency_code: attributes.currencyCode?.code,
}),
`items[${index}].rec_amount_value`,
errors
);
@ -179,25 +194,28 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
);
const retentionAmount = extractOrPushError(
maybeFromNullableResult(raw.retention_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
),
ItemAmount.create({
value: raw.retention_amount_value,
currency_code: attributes.currencyCode?.code,
}),
`items[${index}].retention_amount_value`,
errors
);
const taxesAmount = extractOrPushError(
maybeFromNullableResult(raw.taxes_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
),
ItemAmount.create({
value: raw.taxes_amount_value,
currency_code: attributes.currencyCode?.code,
}),
`items[${index}].taxes_amount_value`,
errors
);
const totalAmount = extractOrPushError(
maybeFromNullableResult(raw.total_amount_value, (value) =>
ItemAmount.create({ value, currency_code: attributes.currencyCode?.code })
),
ItemAmount.create({
value: raw.total_amount_value,
currency_code: attributes.currencyCode?.code,
}),
`items[${index}].total_amount_value`,
errors
);
@ -218,6 +236,8 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
globalDiscountAmount,
totalDiscountAmount,
taxableAmount,
ivaCode,
ivaPercentage,
ivaAmount,
@ -334,10 +354,8 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
maybeToNullable(source.unitAmount, (v) => v.toPrimitive().scale) ??
ItemAmount.DEFAULT_SCALE,
subtotal_amount_value: maybeToNullable(source.subtotalAmount, (v) => v.toPrimitive().value),
subtotal_amount_scale:
maybeToNullable(source.subtotalAmount, (v) => v.toPrimitive().scale) ??
ItemAmount.DEFAULT_SCALE,
subtotal_amount_value: source.subtotalAmount.toPrimitive().value,
subtotal_amount_scale: source.subtotalAmount.toPrimitive().scale,
discount_percentage_value: maybeToNullable(
source.itemDiscountPercentage,
@ -347,13 +365,8 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
ItemDiscountPercentage.DEFAULT_SCALE,
discount_amount_value: maybeToNullable(
source.itemDiscountAmount,
(v) => v.toPrimitive().value
),
discount_amount_scale:
maybeToNullable(source.itemDiscountPercentage, (v) => v.toPrimitive().scale) ??
ItemDiscountPercentage.DEFAULT_SCALE,
discount_amount_value: source.itemDiscountAmount.toPrimitive().value,
discount_amount_scale: source.itemDiscountAmount.toPrimitive().scale,
global_discount_percentage_value: maybeToNullable(
source.globalDiscountPercentage,
@ -363,30 +376,14 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
maybeToNullable(source.globalDiscountPercentage, (v) => v.toPrimitive().scale) ??
ItemDiscountPercentage.DEFAULT_SCALE,
global_discount_amount_value: maybeToNullable(
source.globalDiscountAmount,
(v) => v.toPrimitive().value
),
global_discount_amount_scale:
maybeToNullable(source.globalDiscountAmount, (v) => v.toPrimitive().scale) ??
ItemAmount.DEFAULT_SCALE,
global_discount_amount_value: source.globalDiscountAmount.value,
global_discount_amount_scale: source.globalDiscountAmount.scale,
total_discount_amount_value: maybeToNullable(
source.totalDiscountAmount,
(v) => v.toPrimitive().value
),
total_discount_amount_scale:
maybeToNullable(source.totalDiscountAmount, (v) => v.toPrimitive().scale) ??
ItemAmount.DEFAULT_SCALE,
total_discount_amount_value: source.totalDiscountAmount.value,
total_discount_amount_scale: source.totalDiscountAmount.scale,
// Te has quedado aquí --- IGNORE ---
// !!!!!!!!!!!!!!!!!!!
//
taxable_amount_value: maybeToNullable(source.taxableAmount, (v) => v.toPrimitive().value),
taxable_amount_scale:
maybeToNullable(source.taxableAmount, (v) => v.toPrimitive().scale) ??
ItemAmount.DEFAULT_SCALE,
taxable_amount_value: source.taxableAmount.value,
taxable_amount_scale: source.taxableAmount.scale,
// IVA
iva_code: maybeToNullableString(source.ivaCode),
@ -395,8 +392,8 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
iva_percentage_scale:
maybeToNullable(source.ivaPercentage, (v) => v.toPrimitive().scale) ?? 2,
iva_amount_value: maybeToNullable(source.ivaAmount, (v) => v.toPrimitive().value),
iva_amount_scale: maybeToNullable(source.ivaAmount, (v) => v.toPrimitive().scale) ?? 4,
iva_amount_value: source.ivaAmount.value,
iva_amount_scale: source.ivaAmount.scale,
// REC
rec_code: maybeToNullableString(source.recCode),
@ -405,8 +402,8 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
rec_percentage_scale:
maybeToNullable(source.recPercentage, (v) => v.toPrimitive().scale) ?? 2,
rec_amount_value: maybeToNullable(source.recAmount, (v) => v.toPrimitive().value),
rec_amount_scale: maybeToNullable(source.recAmount, (v) => v.toPrimitive().scale) ?? 4,
rec_amount_value: source.recAmount.value,
rec_amount_scale: source.recAmount.scale,
// RET
retention_code: maybeToNullableString(source.retentionCode),
@ -418,21 +415,16 @@ export class SequelizeIssuedInvoiceItemDomainMapper extends SequelizeDomainMappe
retention_percentage_scale:
maybeToNullable(source.retentionPercentage, (v) => v.toPrimitive().scale) ?? 2,
retention_amount_value: maybeToNullable(source.retentionAmount, (v) => v.toPrimitive().value),
retention_amount_scale:
maybeToNullable(source.retentionAmount, (v) => v.toPrimitive().scale) ?? 4,
retention_amount_value: source.retentionAmount.value,
retention_amount_scale: source.retentionAmount.scale,
//
taxes_amount_value: maybeToNullable(source.taxesAmount, (v) => v.toPrimitive().value),
taxes_amount_scale:
maybeToNullable(source.taxesAmount, (v) => v.toPrimitive().scale) ??
ItemAmount.DEFAULT_SCALE,
taxes_amount_value: source.taxesAmount.value,
taxes_amount_scale: source.taxesAmount.scale,
//
total_amount_value: maybeToNullable(source.totalAmount, (v) => v.toPrimitive().value),
total_amount_scale:
maybeToNullable(source.totalAmount, (v) => v.toPrimitive().scale) ??
ItemAmount.DEFAULT_SCALE,
total_amount_value: source.totalAmount.value,
total_amount_scale: source.totalAmount.scale,
});
}
}

View File

@ -5,7 +5,6 @@ export const GetIssuedInvoiceByIdResponseSchema = z.object({
id: z.uuid(),
company_id: z.uuid(),
is_proforma: z.string(),
invoice_number: z.string(),
status: z.string(),
series: z.string(),

View File

@ -5,7 +5,6 @@ export const ListIssuedInvoicesResponseSchema = createPaginatedListSchema(
z.object({
id: z.uuid(),
company_id: z.uuid(),
is_proforma: z.boolean(),
customer_id: z.string(),