Facturas de cliente -> cambio en impuestos
This commit is contained in:
parent
7d895d147a
commit
5a6a81738f
@ -1,4 +1,4 @@
|
||||
import { TaxCatalogProvider } from "@erp/core";
|
||||
import type { TaxCatalogProvider } from "@erp/core";
|
||||
import { Percentage, ValueObject } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
@ -42,7 +42,7 @@ export class IssuedInvoiceItemsFullPresenter extends Presenter {
|
||||
discount_amount: allAmounts.discountAmount.toObjectString(),
|
||||
|
||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||
tax_codes: invoiceItem.taxes.getCodesToString().split(","),
|
||||
tax_codes: invoiceItem.taxes.getCodesArray(),
|
||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||
|
||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||
|
||||
@ -40,7 +40,7 @@ export class ProformaItemsFullPresenter extends Presenter {
|
||||
discount_amount: allAmounts.discountAmount.toObjectString(),
|
||||
|
||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||
tax_codes: proformaItem.taxes.getCodesToString().split(","),
|
||||
tax_codes: proformaItem.taxes.getCodesArray(),
|
||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||
|
||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||
|
||||
@ -39,7 +39,11 @@ export class IssuedInvoiceListPresenter extends Presenter {
|
||||
language_code: invoice.languageCode.code,
|
||||
currency_code: invoice.currencyCode.code,
|
||||
|
||||
taxes: invoice.taxes,
|
||||
taxes: invoice.taxes.map((t) => ({
|
||||
tax_code: t.tax_code,
|
||||
taxable_amount: t.taxable_amount.toObjectString(),
|
||||
taxes_amount: t.taxes_amount.toObjectString(),
|
||||
})),
|
||||
|
||||
subtotal_amount: invoice.subtotalAmount.toObjectString(),
|
||||
discount_percentage: invoice.discountPercentage.toObjectString(),
|
||||
|
||||
@ -30,7 +30,11 @@ export class ProformaListPresenter extends Presenter {
|
||||
language_code: proforma.languageCode.code,
|
||||
currency_code: proforma.currencyCode.code,
|
||||
|
||||
taxes: proforma.taxes,
|
||||
taxes: proforma.taxes.map((t) => ({
|
||||
tax_code: t.tax_code,
|
||||
taxable_amount: t.taxable_amount.toObjectString(),
|
||||
taxes_amount: t.taxes_amount.toObjectString(),
|
||||
})),
|
||||
|
||||
subtotal_amount: proforma.subtotalAmount.toObjectString(),
|
||||
discount_percentage: proforma.discountPercentage.toObjectString(),
|
||||
|
||||
@ -18,9 +18,14 @@ export class IssuedInvoiceTaxesReportPresenter extends Presenter<IssuedInvoiceTa
|
||||
|
||||
const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
||||
|
||||
const taxName = taxCatalogItem.match(
|
||||
(item) => item.name,
|
||||
() => taxItem.tax_code // fallback
|
||||
);
|
||||
|
||||
return {
|
||||
tax_code: taxItem.tax_code,
|
||||
tax_name: taxCatalogItem.unwrap().name,
|
||||
tax_name: taxName,
|
||||
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
||||
taxes_amount: MoneyDTOHelper.format(taxItem.taxes_amount, this._locale, moneyOptions),
|
||||
};
|
||||
|
||||
@ -18,9 +18,14 @@ export class ProformaTaxesReportPresenter extends Presenter<ProformaTaxesDTO, un
|
||||
|
||||
const taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
||||
|
||||
const taxName = taxCatalogItem.match(
|
||||
(item) => item.name,
|
||||
() => taxItem.tax_code // fallback
|
||||
);
|
||||
|
||||
return {
|
||||
tax_code: taxItem.tax_code,
|
||||
tax_name: taxCatalogItem.unwrap().name,
|
||||
tax_name: taxName,
|
||||
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
||||
taxes_amount: MoneyDTOHelper.format(taxItem.taxes_amount, this._locale, moneyOptions),
|
||||
};
|
||||
|
||||
@ -23,9 +23,6 @@ export class IssuedInvoiceReportHTMLPresenter extends TemplatePresenter {
|
||||
const invoiceDTO = dtoPresenter.toOutput(invoice);
|
||||
const prettyDTO = prePresenter.toOutput(invoiceDTO);
|
||||
|
||||
console.log(prettyDTO.verifactu);
|
||||
|
||||
|
||||
// Obtener y compilar la plantilla HTML
|
||||
const template = this.templateResolver.compileTemplate(
|
||||
"customer-invoices",
|
||||
|
||||
@ -23,8 +23,6 @@ export class IssuedInvoiceReportPDFPresenter extends Presenter<
|
||||
format: "HTML",
|
||||
}) as IssuedInvoiceReportHTMLPresenter;
|
||||
|
||||
console.log(invoice);
|
||||
|
||||
const htmlData = htmlPresenter.toOutput(invoice, params);
|
||||
|
||||
// Generar el PDF con Puppeteer
|
||||
|
||||
@ -236,6 +236,26 @@ export class CustomerInvoice
|
||||
return this.paymentMethod.isSome();
|
||||
}
|
||||
|
||||
/* CALCULOS INTERNOS */
|
||||
|
||||
private _getSubtotalAmount(): InvoiceAmount {
|
||||
const itemsSubtotal = this.items.getSubtotalAmount().convertScale(2);
|
||||
|
||||
return InvoiceAmount.create({
|
||||
value: itemsSubtotal.value,
|
||||
currency_code: this.currencyCode.code,
|
||||
}).data;
|
||||
}
|
||||
|
||||
private _getItemsDiscountAmount(): InvoiceAmount {
|
||||
const itemsDiscountAmount = this.items.getDiscountAmount().convertScale(2);
|
||||
|
||||
return InvoiceAmount.create({
|
||||
value: itemsDiscountAmount.value,
|
||||
currency_code: this.currencyCode.code,
|
||||
}).data;
|
||||
}
|
||||
|
||||
private _getHeaderDiscountAmount(
|
||||
subtotalAmount: InvoiceAmount,
|
||||
itemsDiscountAmount: InvoiceAmount
|
||||
@ -251,79 +271,43 @@ export class CustomerInvoice
|
||||
return subtotalAmount.subtract(itemsDiscountAmount).subtract(headerDiscountAmount);
|
||||
}
|
||||
|
||||
private _getTaxesAmount(taxableAmount: InvoiceAmount): InvoiceAmount {
|
||||
let amount = InvoiceAmount.zero(this.currencyCode.code);
|
||||
|
||||
const itemTaxes = this.items.getTaxesAmountByTaxes();
|
||||
|
||||
for (const taxItem of itemTaxes) {
|
||||
amount = amount.add(
|
||||
InvoiceAmount.create({
|
||||
value: taxItem.taxesAmount.convertScale(2).value,
|
||||
currency_code: this.currencyCode.code,
|
||||
}).data
|
||||
);
|
||||
}
|
||||
return amount;
|
||||
// total impuestos suma(iva + rec + retenciones)
|
||||
private _getTaxesAmount(): InvoiceAmount {
|
||||
const { iva, rec, retention } = this.items.getAggregatedTaxesByType();
|
||||
const total = iva.add(rec).add(retention);
|
||||
return InvoiceAmount.create({
|
||||
value: total.convertScale(2).value,
|
||||
currency_code: this.currencyCode.code,
|
||||
}).data;
|
||||
}
|
||||
|
||||
private _getTotalAmount(taxableAmount: InvoiceAmount, taxesAmount: InvoiceAmount): InvoiceAmount {
|
||||
return taxableAmount.add(taxesAmount);
|
||||
}
|
||||
|
||||
public _getSubtotalAmount(): InvoiceAmount {
|
||||
const itemsSubtotal = this.items.getSubtotalAmount().convertScale(2);
|
||||
|
||||
return InvoiceAmount.create({
|
||||
value: itemsSubtotal.value,
|
||||
currency_code: this.currencyCode.code,
|
||||
}).data as InvoiceAmount;
|
||||
}
|
||||
|
||||
public _getItemsDiscountAmount(): InvoiceAmount {
|
||||
const itemsDiscountAmount = this.items.getDiscountAmount().convertScale(2);
|
||||
|
||||
return InvoiceAmount.create({
|
||||
value: itemsDiscountAmount.value,
|
||||
currency_code: this.currencyCode.code,
|
||||
}).data as InvoiceAmount;
|
||||
}
|
||||
|
||||
/*public getHeaderDiscountAmount(): InvoiceAmount {
|
||||
return this._getHeaderDiscountAmount(this.getSubtotalAmount());
|
||||
}
|
||||
|
||||
public getTaxableAmount(): InvoiceAmount {
|
||||
return this._getTaxableAmount(this.getSubtotalAmount(), this.getHeaderDiscountAmount());
|
||||
}
|
||||
|
||||
public getTaxesAmount(): InvoiceAmount {
|
||||
return this._getTaxesAmount(this.getTaxableAmount());
|
||||
}
|
||||
|
||||
public getTotalAmount(): InvoiceAmount {
|
||||
const taxableAmount = this.getTaxableAmount();
|
||||
const taxesAmount = this._getTaxesAmount(taxableAmount);
|
||||
|
||||
return this._getTotalAmount(taxableAmount, taxesAmount);
|
||||
}*/
|
||||
/** Totales expuestos */
|
||||
|
||||
public getTaxes(): InvoiceTaxTotal[] {
|
||||
const itemTaxes = this.items.getTaxesAmountByTaxes();
|
||||
const map = this.items.getAggregatedTaxesByCode();
|
||||
const currency = this.currencyCode.code;
|
||||
|
||||
return itemTaxes.map((item) => {
|
||||
return {
|
||||
tax: item.tax,
|
||||
const result: InvoiceTaxTotal[] = [];
|
||||
|
||||
for (const [tax_code, entry] of map.entries()) {
|
||||
result.push({
|
||||
tax: entry.tax,
|
||||
taxableAmount: InvoiceAmount.create({
|
||||
value: item.taxableAmount.convertScale(2).value,
|
||||
currency_code: this.currencyCode.code,
|
||||
value: entry.taxable.convertScale(2).value,
|
||||
currency_code: currency,
|
||||
}).data,
|
||||
taxesAmount: InvoiceAmount.create({
|
||||
value: item.taxesAmount.convertScale(2).value,
|
||||
currency_code: this.currencyCode.code,
|
||||
value: entry.total.convertScale(2).value,
|
||||
currency_code: currency,
|
||||
}).data,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public getAllAmounts() {
|
||||
@ -337,7 +321,7 @@ export class CustomerInvoice
|
||||
headerDiscountAmount
|
||||
); //
|
||||
|
||||
const taxesAmount = this._getTaxesAmount(taxableAmount);
|
||||
const taxesAmount = this._getTaxesAmount();
|
||||
const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount);
|
||||
|
||||
return {
|
||||
|
||||
@ -1,10 +1,4 @@
|
||||
import {
|
||||
type CurrencyCode,
|
||||
DomainEntity,
|
||||
type LanguageCode,
|
||||
type Percentage,
|
||||
type UniqueID,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } from "@repo/rdx-ddd";
|
||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import {
|
||||
@ -13,7 +7,7 @@ import {
|
||||
ItemDiscount,
|
||||
ItemQuantity,
|
||||
} from "../../value-objects";
|
||||
import type { ItemTaxTotal, ItemTaxes } from "../item-taxes";
|
||||
import type { ItemTaxGroup } from "../../value-objects/item-tax-group";
|
||||
|
||||
export interface CustomerInvoiceItemProps {
|
||||
description: Maybe<CustomerInvoiceItemDescription>;
|
||||
@ -22,7 +16,7 @@ export interface CustomerInvoiceItemProps {
|
||||
|
||||
discountPercentage: Maybe<ItemDiscount>; // % descuento
|
||||
|
||||
taxes: ItemTaxes;
|
||||
taxes: ItemTaxGroup;
|
||||
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
@ -38,7 +32,7 @@ export interface ICustomerInvoiceItem {
|
||||
|
||||
discountPercentage: Maybe<ItemDiscount>; // % descuento
|
||||
|
||||
taxes: ItemTaxes;
|
||||
taxes: ItemTaxGroup;
|
||||
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
@ -80,31 +74,25 @@ export class CustomerInvoiceItem
|
||||
return this._isValued;
|
||||
}
|
||||
|
||||
get description(): Maybe<CustomerInvoiceItemDescription> {
|
||||
get description() {
|
||||
return this.props.description;
|
||||
}
|
||||
|
||||
get quantity(): Maybe<ItemQuantity> {
|
||||
get quantity() {
|
||||
return this.props.quantity;
|
||||
}
|
||||
|
||||
get unitAmount(): Maybe<ItemAmount> {
|
||||
get unitAmount() {
|
||||
return this.props.unitAmount;
|
||||
}
|
||||
|
||||
get discountPercentage(): Maybe<Percentage> {
|
||||
get discountPercentage() {
|
||||
return this.props.discountPercentage;
|
||||
}
|
||||
|
||||
get languageCode(): LanguageCode {
|
||||
get languageCode() {
|
||||
return this.props.languageCode;
|
||||
}
|
||||
|
||||
get currencyCode(): CurrencyCode {
|
||||
get currencyCode() {
|
||||
return this.props.currencyCode;
|
||||
}
|
||||
|
||||
get taxes(): ItemTaxes {
|
||||
get taxes() {
|
||||
return this.props.taxes;
|
||||
}
|
||||
|
||||
@ -124,7 +112,7 @@ export class CustomerInvoiceItem
|
||||
*/
|
||||
private _getDiscountAmount(subtotalAmount: ItemAmount): ItemAmount {
|
||||
const discount = this.discountPercentage.match(
|
||||
(percentage) => percentage,
|
||||
(discount) => discount,
|
||||
() => ItemDiscount.zero()
|
||||
);
|
||||
return subtotalAmount.percentage(discount);
|
||||
@ -141,6 +129,11 @@ export class CustomerInvoiceItem
|
||||
return subtotalAmount.subtract(discountAmount);
|
||||
}
|
||||
|
||||
/* importes individuales: iva / rec / ret */
|
||||
private _getIndividualTaxAmounts(taxableAmount: ItemAmount) {
|
||||
return this.props.taxes.calculateAmounts(taxableAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @summary Calcula el importe total de impuestos sobre la base imponible.
|
||||
@ -148,7 +141,8 @@ export class CustomerInvoiceItem
|
||||
* @returns El importe de impuestos calculado.
|
||||
*/
|
||||
private _getTaxesAmount(taxableAmount: ItemAmount): ItemAmount {
|
||||
return this.props.taxes.getTaxesAmount(taxableAmount);
|
||||
const { ivaAmount, recAmount, retentionAmount } = this._getIndividualTaxAmounts(taxableAmount);
|
||||
return ivaAmount.add(recAmount).add(retentionAmount); // retención ya es negativa
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,17 +163,15 @@ export class CustomerInvoiceItem
|
||||
* Si la cantidad o el importe unitario no están definidos, se asumen valores cero.
|
||||
*/
|
||||
public getSubtotalAmount(): ItemAmount {
|
||||
const curCode = this.currencyCode.code;
|
||||
const quantity = this.quantity.match(
|
||||
const qty = this.quantity.match(
|
||||
(quantity) => quantity,
|
||||
() => ItemQuantity.zero()
|
||||
);
|
||||
const unitAmount = this.unitAmount.match(
|
||||
const unit = this.unitAmount.match(
|
||||
(unitAmount) => unitAmount,
|
||||
() => ItemAmount.zero(curCode)
|
||||
() => ItemAmount.zero(this.currencyCode.code)
|
||||
);
|
||||
|
||||
return unitAmount.multiply(quantity);
|
||||
return unit.multiply(qty);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -198,6 +190,11 @@ export class CustomerInvoiceItem
|
||||
return this._getTaxableAmount(this.getSubtotalAmount(), this.getDiscountAmount());
|
||||
}
|
||||
|
||||
/* importes individuales: iva / rec / ret */
|
||||
public getIndividualTaxAmounts() {
|
||||
return this._getIndividualTaxAmounts(this.getTaxableAmount());
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Calcula el importe total de impuestos aplicados al ítem.
|
||||
* @returns Un `ItemAmount` con el total de impuestos.
|
||||
@ -217,14 +214,6 @@ export class CustomerInvoiceItem
|
||||
return this._getTotalAmount(taxableAmount, taxesAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Obtiene los importes de impuestos agrupados por tipo de impuesto.
|
||||
* @returns Una colección con la base imponible y el importe de impuestos por cada tipo.
|
||||
*/
|
||||
public getTaxesAmountByTaxes(): ItemTaxTotal[] {
|
||||
return this.taxes.getTaxesAmountByTaxes(this.getTaxableAmount());
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Devuelve todos los importes calculados del ítem en un único objeto.
|
||||
* @returns Un objeto con las propiedades:
|
||||
|
||||
@ -3,7 +3,6 @@ import type { CurrencyCode, LanguageCode } from "@repo/rdx-ddd";
|
||||
import { Collection } from "@repo/rdx-utils";
|
||||
|
||||
import { ItemAmount } from "../../value-objects";
|
||||
import { ItemTaxes } from "../item-taxes";
|
||||
|
||||
import type { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||
|
||||
@ -18,10 +17,9 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
||||
private _currencyCode!: CurrencyCode;
|
||||
|
||||
constructor(props: CustomerInvoiceItemsProps) {
|
||||
const { items = [], languageCode, currencyCode } = props;
|
||||
super(items);
|
||||
this._languageCode = languageCode;
|
||||
this._currencyCode = currencyCode;
|
||||
super(props.items ?? []);
|
||||
this._languageCode = props.languageCode;
|
||||
this._currencyCode = props.currencyCode;
|
||||
}
|
||||
|
||||
public static create(props: CustomerInvoiceItemsProps): CustomerInvoiceItems {
|
||||
@ -105,65 +103,83 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Agrupa los importes imponibles e impuestos por tipo de impuesto.
|
||||
* @returns Un array con objetos que contienen:
|
||||
* - `tax`: El tipo de impuesto.
|
||||
* - `taxableAmount`: El total de base imponible asociada a ese impuesto.
|
||||
* - `taxesAmount`: El importe total de impuestos calculado.
|
||||
* @remarks
|
||||
* Los impuestos se agrupan por su `tax.code` (código fiscal).
|
||||
* Si existen varios impuestos con el mismo código, sus importes se agregan.
|
||||
* En caso de conflicto de datos en impuestos con mismo código, prevalece
|
||||
* el primer `Tax` encontrado.
|
||||
*/
|
||||
public getTaxesAmountByTaxes(): Array<{
|
||||
tax: Tax;
|
||||
taxableAmount: ItemAmount;
|
||||
taxesAmount: ItemAmount;
|
||||
}> {
|
||||
const getTaxCode = (tax: Tax): string => tax.code; // clave estable para Map
|
||||
const currencyCode = this._currencyCode.code;
|
||||
|
||||
// Mapeamos por clave (tax code), pero también guardamos el Tax original
|
||||
const resultMap = new Map<
|
||||
string,
|
||||
{ tax: Tax; taxableAmount: ItemAmount; taxesAmount: ItemAmount }
|
||||
>();
|
||||
/* totales de iva/rec/ret a nivel factura */
|
||||
public getAggregatedTaxesByType() {
|
||||
let iva = ItemAmount.zero(this._currencyCode.code);
|
||||
let rec = ItemAmount.zero(this._currencyCode.code);
|
||||
let retention = ItemAmount.zero(this._currencyCode.code);
|
||||
|
||||
for (const item of this.getAll()) {
|
||||
for (const { taxableAmount, tax, taxesAmount } of item.getTaxesAmountByTaxes()) {
|
||||
const key = getTaxCode(tax);
|
||||
const current = resultMap.get(key) ?? {
|
||||
tax,
|
||||
taxableAmount: ItemAmount.zero(currencyCode),
|
||||
taxesAmount: ItemAmount.zero(currencyCode),
|
||||
};
|
||||
const { ivaAmount, recAmount, retentionAmount } = item.getIndividualTaxAmounts();
|
||||
|
||||
resultMap.set(key, {
|
||||
tax: current.tax,
|
||||
taxableAmount: current.taxableAmount.add(taxableAmount),
|
||||
taxesAmount: current.taxesAmount.add(taxesAmount),
|
||||
});
|
||||
}
|
||||
iva = iva.add(ivaAmount);
|
||||
rec = rec.add(recAmount);
|
||||
retention = retention.add(retentionAmount);
|
||||
}
|
||||
|
||||
return Array.from(resultMap.values());
|
||||
return { iva, rec, retention };
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Obtiene la lista de impuestos únicos aplicados en todos los ítems.
|
||||
* @returns Un objeto `ItemTaxes` que contiene todos los impuestos distintos.
|
||||
* @remarks
|
||||
* Los impuestos se deduplican por su código (`tax.code`).
|
||||
* Si existen varios impuestos con el mismo código, el último encontrado
|
||||
* sobrescribirá los anteriores sin generar error.
|
||||
*/
|
||||
public getTaxes(): ItemTaxes {
|
||||
const taxes = this.getAll()
|
||||
.flatMap((item) => item.taxes.getAll())
|
||||
.reduce((map, tax) => map.set(tax.code, tax), new Map<string, Tax>());
|
||||
/* agrupación por código fiscal → usado para customer_invoice_taxes */
|
||||
public getAggregatedTaxesByCode() {
|
||||
const map = new Map<string, { tax: Tax; taxable: ItemAmount; total: ItemAmount }>();
|
||||
|
||||
return ItemTaxes.create([...taxes.values()]);
|
||||
for (const item of this.getAll()) {
|
||||
const taxable = item.getTaxableAmount();
|
||||
const { ivaAmount, recAmount, retentionAmount } = item.getIndividualTaxAmounts();
|
||||
|
||||
item.taxes.iva.match(
|
||||
(iva) => {
|
||||
const prev = map.get(iva.code) ?? {
|
||||
taxable: ItemAmount.zero(taxable.currencyCode),
|
||||
total: ItemAmount.zero(taxable.currencyCode),
|
||||
};
|
||||
map.set(iva.code, {
|
||||
tax: iva,
|
||||
taxable: prev.taxable.add(taxable),
|
||||
total: prev.total.add(ivaAmount),
|
||||
});
|
||||
},
|
||||
() => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
||||
item.taxes.rec.match(
|
||||
(rec) => {
|
||||
const prev = map.get(rec.code) ?? {
|
||||
taxable: ItemAmount.zero(taxable.currencyCode),
|
||||
total: ItemAmount.zero(taxable.currencyCode),
|
||||
};
|
||||
map.set(rec.code, {
|
||||
tax: rec,
|
||||
taxable: prev.taxable.add(taxable),
|
||||
total: prev.total.add(recAmount),
|
||||
});
|
||||
},
|
||||
() => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
||||
item.taxes.retention.match(
|
||||
(retention) => {
|
||||
const prev = map.get(retention.code) ?? {
|
||||
taxable: ItemAmount.zero(taxable.currencyCode),
|
||||
total: ItemAmount.zero(taxable.currencyCode),
|
||||
};
|
||||
map.set(retention.code, {
|
||||
tax: retention,
|
||||
taxable: prev.taxable.add(taxable),
|
||||
total: prev.total.add(retentionAmount),
|
||||
});
|
||||
},
|
||||
() => {
|
||||
//
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
export * from "./customer-invoice-items";
|
||||
export * from "./invoice-payment-method";
|
||||
export * from "./invoice-taxes";
|
||||
export * from "./item-taxes";
|
||||
export * from "./verifactu-record";
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Tax } from "@erp/core/api";
|
||||
import { DomainEntity, UniqueID } from "@repo/rdx-ddd";
|
||||
import type { Tax } from "@erp/core/api";
|
||||
import { DomainEntity, type UniqueID } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { InvoiceAmount } from "../../value-objects/invoice-amount";
|
||||
|
||||
import type { InvoiceAmount } from "../../value-objects/invoice-amount";
|
||||
|
||||
export interface InvoiceTaxProps {
|
||||
tax: Tax;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Tax, Taxes } from "@erp/core/api";
|
||||
import { type Tax, Taxes } from "@erp/core/api";
|
||||
|
||||
import { InvoiceAmount } from "../../value-objects";
|
||||
|
||||
export type InvoiceTaxTotal = {
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export * from "./item-taxes";
|
||||
@ -1,44 +0,0 @@
|
||||
import { type Tax, Taxes } from "@erp/core/api";
|
||||
|
||||
import { ItemAmount } from "../../value-objects";
|
||||
|
||||
export type ItemTaxTotal = {
|
||||
tax: Tax;
|
||||
taxableAmount: ItemAmount;
|
||||
taxesAmount: ItemAmount;
|
||||
};
|
||||
|
||||
export class ItemTaxes extends Taxes {
|
||||
constructor(items: Tax[] = [], totalItems: number | null = null) {
|
||||
super(items, totalItems);
|
||||
}
|
||||
|
||||
public getTaxesAmount(taxableAmount: ItemAmount): ItemAmount {
|
||||
return this.getAll().reduce(
|
||||
(total, tax) => total.add(taxableAmount.percentage(tax.percentage)),
|
||||
ItemAmount.zero(taxableAmount.currencyCode)
|
||||
);
|
||||
}
|
||||
|
||||
public getTaxesAmountByTaxCode(taxCode: string, taxableAmount: ItemAmount): ItemAmount {
|
||||
const currencyCode = taxableAmount.currencyCode;
|
||||
|
||||
return this.filter((itemTax) => itemTax.code === taxCode).reduce((totalAmount, itemTax) => {
|
||||
return taxableAmount.percentage(itemTax.percentage).add(totalAmount);
|
||||
}, ItemAmount.zero(currencyCode));
|
||||
}
|
||||
|
||||
public getTaxesAmountByTaxes(taxableAmount: ItemAmount): ItemTaxTotal[] {
|
||||
return this.getAll().map((taxItem) => ({
|
||||
taxableAmount,
|
||||
tax: taxItem,
|
||||
taxesAmount: this.getTaxesAmountByTaxCode(taxItem.code, taxableAmount),
|
||||
}));
|
||||
}
|
||||
|
||||
public getCodesToString(): string {
|
||||
return this.getAll()
|
||||
.map((taxItem) => taxItem.code)
|
||||
.join(", ");
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import { UniqueID } from "@repo/rdx-ddd";
|
||||
import { Maybe, Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoiceNumber, CustomerInvoiceSerie } from "../value-objects";
|
||||
import type { UniqueID } from "@repo/rdx-ddd";
|
||||
import type { Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import type { CustomerInvoiceNumber, CustomerInvoiceSerie } from "../value-objects";
|
||||
|
||||
/**
|
||||
* Servicio de dominio que define cómo se genera el siguiente número de factura.
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { CompositeSpecification } from "@repo/rdx-ddd";
|
||||
import { CustomerInvoice } from "../aggregates";
|
||||
|
||||
import type { CustomerInvoice } from "../aggregates";
|
||||
import { INVOICE_STATUS } from "../value-objects";
|
||||
|
||||
export class ProformaCanTranstionToIssuedSpecification extends CompositeSpecification<CustomerInvoice> {
|
||||
|
||||
@ -8,4 +8,5 @@ export * from "./invoice-recipient";
|
||||
export * from "./item-amount";
|
||||
export * from "./item-discount";
|
||||
export * from "./item-quantity";
|
||||
export * from "./item-tax-group";
|
||||
export * from "./verifactu-status";
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MoneyValue, MoneyValueProps, Percentage, Quantity } from "@repo/rdx-ddd";
|
||||
import { MoneyValue, type MoneyValueProps, type Percentage, type Quantity } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
type InvoiceAmountProps = Pick<MoneyValueProps, "value" | "currency_code">;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { MoneyValue, MoneyValueProps, Percentage, Quantity } from "@repo/rdx-ddd";
|
||||
import { MoneyValue, type MoneyValueProps, type Percentage, type Quantity } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
|
||||
type ItemAmountProps = Pick<MoneyValueProps, "value" | "currency_code">;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Percentage, PercentageProps } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
||||
import type { Result } from "@repo/rdx-utils";
|
||||
|
||||
type ItemDiscountProps = Pick<PercentageProps, "value">;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Quantity, QuantityProps } from "@repo/rdx-ddd";
|
||||
import { Quantity, type QuantityProps } from "@repo/rdx-ddd";
|
||||
|
||||
type ItemQuantityProps = Pick<QuantityProps, "value">;
|
||||
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
import type { Tax } from "@erp/core/api";
|
||||
import { ValueObject } from "@repo/rdx-ddd";
|
||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||
|
||||
import { ItemAmount } from ".";
|
||||
|
||||
export interface ItemTaxGroupProps {
|
||||
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));
|
||||
}
|
||||
|
||||
calculateAmounts(taxableAmount: ItemAmount) {
|
||||
const ivaAmount = this.props.iva.match(
|
||||
(iva) => taxableAmount.percentage(iva.percentage),
|
||||
() => ItemAmount.zero(taxableAmount.currencyCode)
|
||||
);
|
||||
|
||||
const recAmount = this.props.rec.match(
|
||||
(rec) => taxableAmount.percentage(rec.percentage),
|
||||
() => ItemAmount.zero(taxableAmount.currencyCode)
|
||||
);
|
||||
|
||||
const retentionAmount = this.props.retention.match(
|
||||
(retention) => taxableAmount.percentage(retention.percentage).multiply(-1),
|
||||
() => ItemAmount.zero(taxableAmount.currencyCode)
|
||||
);
|
||||
|
||||
return { ivaAmount, recAmount, retentionAmount };
|
||||
}
|
||||
|
||||
get iva(): Maybe<Tax> {
|
||||
return this.props.iva;
|
||||
}
|
||||
|
||||
get rec(): Maybe<Tax> {
|
||||
return this.props.rec;
|
||||
}
|
||||
|
||||
get retention(): Maybe<Tax> {
|
||||
return this.props.retention;
|
||||
}
|
||||
|
||||
public getCodesArray(): string[] {
|
||||
const codes: string[] = [];
|
||||
|
||||
this.props.iva.match(
|
||||
(iva) => codes.push(iva.code),
|
||||
() => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
||||
this.props.rec.match(
|
||||
(rec) => codes.push(rec.code),
|
||||
() => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
||||
this.props.retention.match(
|
||||
(retention) => codes.push(retention.code),
|
||||
() => {
|
||||
//
|
||||
}
|
||||
);
|
||||
|
||||
return codes;
|
||||
}
|
||||
|
||||
public getCodesToString(): string {
|
||||
return this.getCodesArray().join(", ");
|
||||
}
|
||||
|
||||
getProps() {
|
||||
return this.props;
|
||||
}
|
||||
|
||||
toPrimitive() {
|
||||
return this.getProps();
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||
import {
|
||||
type ISequelizeDomainMapper,
|
||||
type MapperParamsType,
|
||||
SequelizeDomainMapper,
|
||||
Tax,
|
||||
} from "@erp/core/api";
|
||||
import {
|
||||
UniqueID,
|
||||
@ -22,15 +24,13 @@ import {
|
||||
ItemAmount,
|
||||
ItemDiscount,
|
||||
ItemQuantity,
|
||||
ItemTaxes,
|
||||
ItemTaxGroup,
|
||||
} from "../../../domain";
|
||||
import type {
|
||||
CustomerInvoiceItemCreationAttributes,
|
||||
CustomerInvoiceItemModel,
|
||||
} from "../../sequelize";
|
||||
|
||||
import { ItemTaxesDomainMapper } from "./item-taxes.mapper";
|
||||
|
||||
export interface ICustomerInvoiceItemDomainMapper
|
||||
extends ISequelizeDomainMapper<
|
||||
CustomerInvoiceItemModel,
|
||||
@ -46,11 +46,19 @@ export class CustomerInvoiceItemDomainMapper
|
||||
>
|
||||
implements ICustomerInvoiceItemDomainMapper
|
||||
{
|
||||
private _taxesMapper: ItemTaxesDomainMapper;
|
||||
private _taxCatalog!: JsonTaxCatalogProvider;
|
||||
|
||||
constructor(params: MapperParamsType) {
|
||||
super();
|
||||
this._taxesMapper = new ItemTaxesDomainMapper(params);
|
||||
const { taxCatalog } = params as {
|
||||
taxCatalog: JsonTaxCatalogProvider;
|
||||
};
|
||||
|
||||
if (!taxCatalog) {
|
||||
throw new Error('taxCatalog not defined ("CustomerInvoiceItemDomainMapper")');
|
||||
}
|
||||
|
||||
this._taxCatalog = taxCatalog;
|
||||
}
|
||||
|
||||
private mapAttributesToDomain(
|
||||
@ -97,6 +105,26 @@ export class CustomerInvoiceItemDomainMapper
|
||||
errors
|
||||
);
|
||||
|
||||
const iva = extractOrPushError(
|
||||
maybeFromNullableVO(source.iva_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
|
||||
`items[${index}].iva_code`,
|
||||
errors
|
||||
);
|
||||
|
||||
const rec = extractOrPushError(
|
||||
maybeFromNullableVO(source.rec_code, (code) => Tax.createFromCode(code, this._taxCatalog)),
|
||||
`items[${index}].rec_code`,
|
||||
errors
|
||||
);
|
||||
|
||||
const retention = extractOrPushError(
|
||||
maybeFromNullableVO(source.retention_code, (code) =>
|
||||
Tax.createFromCode(code, this._taxCatalog)
|
||||
),
|
||||
`items[${index}].retention_code`,
|
||||
errors
|
||||
);
|
||||
|
||||
return {
|
||||
itemId,
|
||||
|
||||
@ -106,6 +134,12 @@ export class CustomerInvoiceItemDomainMapper
|
||||
quantity,
|
||||
unitAmount,
|
||||
discountPercentage,
|
||||
|
||||
taxes: ItemTaxGroup.create({
|
||||
iva: iva!,
|
||||
rec: rec!,
|
||||
retention: retention!,
|
||||
}).data,
|
||||
};
|
||||
}
|
||||
|
||||
@ -122,23 +156,6 @@ export class CustomerInvoiceItemDomainMapper
|
||||
// 1) Valores escalares (atributos generales)
|
||||
const attributes = this.mapAttributesToDomain(source, params);
|
||||
|
||||
// 2) Taxes (colección a nivel de item/línea)
|
||||
const taxesResults = this._taxesMapper.mapToDomainCollection(
|
||||
source.taxes,
|
||||
source.taxes.length,
|
||||
{
|
||||
attributes,
|
||||
...params,
|
||||
}
|
||||
);
|
||||
|
||||
if (taxesResults.isFailure) {
|
||||
errors.push({
|
||||
path: "taxes",
|
||||
message: taxesResults.error.message,
|
||||
});
|
||||
}
|
||||
|
||||
// Si hubo errores de mapeo, devolvemos colección de validación
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(
|
||||
@ -146,9 +163,7 @@ export class CustomerInvoiceItemDomainMapper
|
||||
);
|
||||
}
|
||||
|
||||
const taxes = ItemTaxes.create(taxesResults.data.getAll());
|
||||
|
||||
// 3) Construcción del elemento de dominio
|
||||
// 2) Construcción del elemento de dominio
|
||||
const createResult = CustomerInvoiceItem.create(
|
||||
{
|
||||
languageCode: attributes.languageCode!,
|
||||
@ -157,7 +172,7 @@ export class CustomerInvoiceItemDomainMapper
|
||||
quantity: attributes.quantity!,
|
||||
unitAmount: attributes.unitAmount!,
|
||||
discountPercentage: attributes.discountPercentage!,
|
||||
taxes,
|
||||
taxes: attributes.taxes!,
|
||||
},
|
||||
attributes.itemId
|
||||
);
|
||||
@ -183,18 +198,6 @@ export class CustomerInvoiceItemDomainMapper
|
||||
errors: ValidationErrorDetail[];
|
||||
};
|
||||
|
||||
const taxesResults = this._taxesMapper.mapToPersistenceArray(source.taxes, {
|
||||
...params,
|
||||
parent: source,
|
||||
});
|
||||
|
||||
if (taxesResults.isFailure) {
|
||||
errors.push({
|
||||
path: "taxes",
|
||||
message: taxesResults.error.message,
|
||||
});
|
||||
}
|
||||
|
||||
const allAmounts = source.getAllAmounts();
|
||||
|
||||
return Result.ok({
|
||||
@ -235,7 +238,9 @@ export class CustomerInvoiceItemDomainMapper
|
||||
total_amount_value: allAmounts.totalAmount.value,
|
||||
total_amount_scale: allAmounts.totalAmount.scale,
|
||||
|
||||
taxes: taxesResults.data,
|
||||
iva_code: toNullable(source.taxes.iva, (t) => t.code),
|
||||
rec_code: toNullable(source.taxes.rec, (t) => t.code),
|
||||
retention_code: toNullable(source.taxes.retention, (t) => t.code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,14 +220,6 @@ export class CustomerInvoiceDomainMapper
|
||||
...params,
|
||||
});
|
||||
|
||||
/*if (recipientResult.isFailure) {
|
||||
errors.push({
|
||||
path: "recipient",
|
||||
|
||||
message: recipientResult.error.message,
|
||||
});
|
||||
}*/
|
||||
|
||||
// 3) Verifactu (snapshot en la factura o include)
|
||||
const verifactuResult = this._verifactuMapper.mapToDomain(source.verifactu, {
|
||||
errors,
|
||||
@ -235,14 +227,6 @@ export class CustomerInvoiceDomainMapper
|
||||
...params,
|
||||
});
|
||||
|
||||
/*if (verifactuResult.isFailure) {
|
||||
errors.push({
|
||||
path: "verifactu",
|
||||
|
||||
message: verifactuResult.error.message,
|
||||
});
|
||||
}*/
|
||||
|
||||
// 4) Items (colección)
|
||||
const itemsResults = this._itemsMapper.mapToDomainCollection(
|
||||
source.items,
|
||||
@ -254,16 +238,6 @@ export class CustomerInvoiceDomainMapper
|
||||
}
|
||||
);
|
||||
|
||||
/*if (itemsResults.isFailure) {
|
||||
errors.push({
|
||||
path: "items",
|
||||
message: itemsResults.error.message,
|
||||
});
|
||||
}*/
|
||||
|
||||
// Nota: los impuestos a nivel factura (tabla customer_invoice_taxes) se derivan de los items.
|
||||
// El agregado expone un getter `taxes` (derivado). No se incluye en las props.
|
||||
|
||||
// 5) Si hubo errores de mapeo, devolvemos colección de validación
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(
|
||||
@ -273,9 +247,6 @@ export class CustomerInvoiceDomainMapper
|
||||
|
||||
// 6) Construcción del agregado (Dominio)
|
||||
|
||||
const verifactu = verifactuResult.data;
|
||||
const recipient = recipientResult.data;
|
||||
|
||||
const items = CustomerInvoiceItems.create({
|
||||
languageCode: attributes.languageCode!,
|
||||
currencyCode: attributes.currencyCode!,
|
||||
@ -294,7 +265,7 @@ export class CustomerInvoiceDomainMapper
|
||||
operationDate: attributes.operationDate!,
|
||||
|
||||
customerId: attributes.customerId!,
|
||||
recipient: recipient,
|
||||
recipient: recipientResult.data,
|
||||
|
||||
reference: attributes.reference!,
|
||||
description: attributes.description!,
|
||||
@ -308,7 +279,7 @@ export class CustomerInvoiceDomainMapper
|
||||
paymentMethod: attributes.paymentMethod!,
|
||||
|
||||
items,
|
||||
verifactu,
|
||||
verifactu: verifactuResult.data,
|
||||
};
|
||||
|
||||
const createResult = CustomerInvoice.create(invoiceProps, attributes.invoiceId);
|
||||
|
||||
@ -1,11 +1,26 @@
|
||||
import { JsonTaxCatalogProvider } from "@erp/core";
|
||||
import { MapperParamsType, SequelizeDomainMapper, Tax } from "@erp/core/api";
|
||||
import { UniqueID, ValidationErrorDetail } from "@repo/rdx-ddd";
|
||||
|
||||
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||
import { type MapperParamsType, SequelizeDomainMapper, Tax } from "@erp/core/api";
|
||||
import { UniqueID, type ValidationErrorDetail } from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoice, CustomerInvoiceItemProps, ItemAmount } from "../../../domain";
|
||||
import { CustomerInvoiceTaxCreationAttributes, CustomerInvoiceTaxModel } from "../../sequelize";
|
||||
|
||||
import { type CustomerInvoice, type CustomerInvoiceItemProps, ItemAmount } from "../../../domain";
|
||||
import type {
|
||||
CustomerInvoiceTaxCreationAttributes,
|
||||
CustomerInvoiceTaxModel,
|
||||
} from "../../sequelize";
|
||||
|
||||
/**
|
||||
* Mapper para customer_invoice_taxes
|
||||
*
|
||||
* Domina estructuras:
|
||||
* {
|
||||
* tax: Tax
|
||||
* taxableAmount: ItemAmount
|
||||
* taxesAmount: ItemAmount
|
||||
* }
|
||||
*
|
||||
* Cada fila = un impuesto agregado en toda la factura.
|
||||
*/
|
||||
export class TaxesDomainMapper extends SequelizeDomainMapper<
|
||||
CustomerInvoiceTaxModel,
|
||||
CustomerInvoiceTaxCreationAttributes,
|
||||
@ -41,15 +56,17 @@ export class TaxesDomainMapper extends SequelizeDomainMapper<
|
||||
attributes: Partial<CustomerInvoiceItemProps>;
|
||||
};
|
||||
|
||||
const currency_code = attributes.currencyCode!.code;
|
||||
|
||||
return Result.ok({
|
||||
taxableAmount: ItemAmount.create({
|
||||
value: source.taxable_amount_value,
|
||||
currency_code: attributes.currencyCode!.code,
|
||||
currency_code,
|
||||
}).data,
|
||||
tax: Tax.createFromCode(source.tax_code, this._taxCatalog).data,
|
||||
taxesAmount: ItemAmount.create({
|
||||
value: source.taxes_amount_value,
|
||||
currency_code: attributes.currencyCode!.code,
|
||||
currency_code,
|
||||
}).data,
|
||||
});
|
||||
}
|
||||
@ -67,8 +84,6 @@ export class TaxesDomainMapper extends SequelizeDomainMapper<
|
||||
errors: ValidationErrorDetail[];
|
||||
};
|
||||
|
||||
source;
|
||||
|
||||
return Result.ok({
|
||||
tax_id: UniqueID.generateNewID().toPrimitive(),
|
||||
invoice_id: parent.id.toPrimitive(),
|
||||
|
||||
@ -1,112 +0,0 @@
|
||||
import { JsonTaxCatalogProvider } from "@erp/core";
|
||||
import {
|
||||
ISequelizeDomainMapper,
|
||||
MapperParamsType,
|
||||
SequelizeDomainMapper,
|
||||
Tax,
|
||||
} from "@erp/core/api";
|
||||
|
||||
import {
|
||||
extractOrPushError,
|
||||
UniqueID,
|
||||
ValidationErrorCollection,
|
||||
ValidationErrorDetail,
|
||||
} from "@repo/rdx-ddd";
|
||||
import { Result } from "@repo/rdx-utils";
|
||||
import { CustomerInvoiceItem } from "../../../domain";
|
||||
import {
|
||||
CustomerInvoiceItemTaxCreationAttributes,
|
||||
CustomerInvoiceItemTaxModel,
|
||||
} from "../../sequelize";
|
||||
|
||||
export interface IItemTaxesDomainMapper
|
||||
extends ISequelizeDomainMapper<
|
||||
CustomerInvoiceItemTaxModel,
|
||||
CustomerInvoiceItemTaxCreationAttributes,
|
||||
Tax
|
||||
> {}
|
||||
|
||||
export class ItemTaxesDomainMapper
|
||||
extends SequelizeDomainMapper<
|
||||
CustomerInvoiceItemTaxModel,
|
||||
CustomerInvoiceItemTaxCreationAttributes,
|
||||
Tax
|
||||
>
|
||||
implements IItemTaxesDomainMapper
|
||||
{
|
||||
private _taxCatalog!: JsonTaxCatalogProvider;
|
||||
|
||||
constructor(params: MapperParamsType) {
|
||||
super();
|
||||
const { taxCatalog } = params as {
|
||||
taxCatalog: JsonTaxCatalogProvider;
|
||||
};
|
||||
|
||||
if (!taxCatalog) {
|
||||
throw new Error('taxCatalog not defined ("ItemTaxesMapper")');
|
||||
}
|
||||
|
||||
this._taxCatalog = taxCatalog;
|
||||
}
|
||||
|
||||
public mapToDomain(
|
||||
source: CustomerInvoiceItemTaxModel,
|
||||
params?: MapperParamsType
|
||||
): Result<Tax, Error> {
|
||||
const { errors, index } = params as {
|
||||
index: number;
|
||||
errors: ValidationErrorDetail[];
|
||||
};
|
||||
|
||||
const tax = extractOrPushError(
|
||||
Tax.createFromCode(source.tax_code, this._taxCatalog),
|
||||
`taxes[${index}].tax_code`,
|
||||
errors
|
||||
);
|
||||
|
||||
// Si hubo errores de mapeo, devolvemos colección de validación
|
||||
if (errors.length > 0) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Invoice item tax mapping failed [mapToDomain]", errors)
|
||||
);
|
||||
}
|
||||
|
||||
// Creación del objeto de dominio
|
||||
const createResult = Tax.create(tax!);
|
||||
if (createResult.isFailure) {
|
||||
return Result.fail(
|
||||
new ValidationErrorCollection("Invoice item tax creation failed", [
|
||||
{ path: `taxes[${index}]`, message: createResult.error.message },
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return createResult;
|
||||
}
|
||||
|
||||
public mapToPersistence(
|
||||
source: Tax,
|
||||
params?: MapperParamsType
|
||||
): Result<CustomerInvoiceItemTaxCreationAttributes, Error> {
|
||||
const { errors, parent } = params as {
|
||||
parent: CustomerInvoiceItem;
|
||||
errors: ValidationErrorDetail[];
|
||||
};
|
||||
|
||||
const taxableAmount = parent.getTaxableAmount();
|
||||
const taxAmount = taxableAmount.percentage(source.percentage);
|
||||
|
||||
return Result.ok({
|
||||
tax_id: UniqueID.generateNewID().toPrimitive(),
|
||||
item_id: parent.id.toPrimitive(),
|
||||
|
||||
tax_code: source.code,
|
||||
|
||||
taxable_amount_value: taxableAmount.value,
|
||||
taxable_amount_scale: taxableAmount.scale,
|
||||
|
||||
taxes_amount_value: taxAmount.value,
|
||||
taxes_amount_scale: taxAmount.scale,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,7 @@ import {
|
||||
CustomerInvoiceStatus,
|
||||
InvoiceAmount,
|
||||
type InvoiceRecipient,
|
||||
ItemAmount,
|
||||
type VerifactuRecord,
|
||||
} from "../../../domain";
|
||||
import type { CustomerInvoiceModel } from "../../sequelize";
|
||||
@ -50,7 +51,11 @@ export type CustomerInvoiceListDTO = {
|
||||
languageCode: LanguageCode;
|
||||
currencyCode: CurrencyCode;
|
||||
|
||||
taxes: string;
|
||||
taxes: {
|
||||
tax_code: string;
|
||||
taxable_amount: InvoiceAmount;
|
||||
taxes_amount: InvoiceAmount;
|
||||
}[];
|
||||
|
||||
discountPercentage: Percentage;
|
||||
|
||||
@ -103,7 +108,23 @@ export class CustomerInvoiceListMapper
|
||||
}
|
||||
|
||||
// 3) Taxes
|
||||
const taxes = raw.taxes.map((tax) => tax.tax_code).join(", ");
|
||||
const taxes = raw.taxes.map((tax) => {
|
||||
const taxableAmount = ItemAmount.create({
|
||||
value: tax.taxable_amount_value || 0,
|
||||
currency_code: attributes.currencyCode!.code,
|
||||
}).data;
|
||||
|
||||
const taxesAmount = ItemAmount.create({
|
||||
value: tax.taxes_amount_value || 0,
|
||||
currency_code: attributes.currencyCode!.code,
|
||||
}).data;
|
||||
|
||||
return {
|
||||
tax_code: tax.tax_code,
|
||||
taxable_amount: taxableAmount,
|
||||
taxes_amount: taxesAmount,
|
||||
};
|
||||
});
|
||||
|
||||
// 4) Verifactu record
|
||||
let verifactu: Maybe<VerifactuRecord> = Maybe.none();
|
||||
|
||||
@ -22,7 +22,6 @@ import type {
|
||||
|
||||
import { CustomerInvoiceModel } from "./models/customer-invoice.model";
|
||||
import { CustomerInvoiceItemModel } from "./models/customer-invoice-item.model";
|
||||
import { CustomerInvoiceItemTaxModel } from "./models/customer-invoice-item-tax.model";
|
||||
import { CustomerInvoiceTaxModel } from "./models/customer-invoice-tax.model";
|
||||
import { VerifactuRecordModel } from "./models/verifactu-record.model";
|
||||
|
||||
@ -102,12 +101,7 @@ export class CustomerInvoiceRepository
|
||||
// 3. Inserta items + sus taxes
|
||||
if (Array.isArray(items) && items.length > 0) {
|
||||
for (const item of items) {
|
||||
const { taxes: itemTaxes, ...itemData } = item;
|
||||
await CustomerInvoiceItemModel.create(itemData, { transaction });
|
||||
|
||||
if (Array.isArray(itemTaxes) && itemTaxes.length > 0) {
|
||||
await CustomerInvoiceItemTaxModel.bulkCreate(itemTaxes, { transaction });
|
||||
}
|
||||
await CustomerInvoiceItemModel.create(item, { transaction });
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,12 +165,7 @@ export class CustomerInvoiceRepository
|
||||
// 4. Inserta items + sus taxes
|
||||
if (Array.isArray(items) && items.length > 0) {
|
||||
for (const item of items) {
|
||||
const { taxes: itemTaxes, ...itemData } = item;
|
||||
await CustomerInvoiceItemModel.create(itemData, { transaction });
|
||||
|
||||
if (Array.isArray(itemTaxes) && itemTaxes.length > 0) {
|
||||
await CustomerInvoiceItemTaxModel.bulkCreate(itemTaxes, { transaction });
|
||||
}
|
||||
await CustomerInvoiceItemModel.create(item, { transaction });
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,13 +265,6 @@ export class CustomerInvoiceRepository
|
||||
model: CustomerInvoiceItemModel,
|
||||
as: "items",
|
||||
required: false,
|
||||
include: [
|
||||
{
|
||||
model: CustomerInvoiceItemTaxModel,
|
||||
as: "taxes",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
model: CustomerInvoiceTaxModel,
|
||||
@ -371,13 +353,6 @@ export class CustomerInvoiceRepository
|
||||
model: CustomerInvoiceItemModel,
|
||||
as: "items",
|
||||
required: false,
|
||||
include: [
|
||||
{
|
||||
model: CustomerInvoiceItemTaxModel,
|
||||
as: "taxes",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
model: CustomerInvoiceTaxModel,
|
||||
@ -484,7 +459,6 @@ export class CustomerInvoiceRepository
|
||||
as: "taxes",
|
||||
required: false,
|
||||
separate: true, // => query aparte, devuelve siempre array
|
||||
attributes: ["tax_id", "tax_code"],
|
||||
},
|
||||
];
|
||||
|
||||
@ -601,7 +575,6 @@ export class CustomerInvoiceRepository
|
||||
as: "taxes",
|
||||
required: false,
|
||||
separate: true, // => query aparte, devuelve siempre array
|
||||
attributes: ["tax_id", "tax_code"],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import customerInvoiceModelInit from "./models/customer-invoice.model";
|
||||
import customerInvoiceItemModelInit from "./models/customer-invoice-item.model";
|
||||
import customerInvoiceItemTaxesModelInit from "./models/customer-invoice-item-tax.model";
|
||||
import customerInvoiceTaxesModelInit from "./models/customer-invoice-tax.model";
|
||||
import verifactuRecordModelInit from "./models/verifactu-record.model";
|
||||
|
||||
@ -13,7 +12,6 @@ export const models = [
|
||||
customerInvoiceItemModelInit,
|
||||
|
||||
customerInvoiceTaxesModelInit,
|
||||
customerInvoiceItemTaxesModelInit,
|
||||
|
||||
verifactuRecordModelInit,
|
||||
];
|
||||
|
||||
@ -1,125 +0,0 @@
|
||||
import {
|
||||
DataTypes,
|
||||
type InferAttributes,
|
||||
type InferCreationAttributes,
|
||||
Model,
|
||||
type NonAttribute,
|
||||
type Sequelize,
|
||||
} from "sequelize";
|
||||
|
||||
import type { CustomerInvoiceItem } from "../../../domain";
|
||||
|
||||
export type CustomerInvoiceItemTaxCreationAttributes = InferCreationAttributes<
|
||||
CustomerInvoiceItemTaxModel,
|
||||
{ omit: "item" }
|
||||
>;
|
||||
|
||||
export class CustomerInvoiceItemTaxModel extends Model<
|
||||
InferAttributes<CustomerInvoiceItemTaxModel>,
|
||||
InferCreationAttributes<CustomerInvoiceItemTaxModel>
|
||||
> {
|
||||
declare tax_id: string;
|
||||
declare item_id: string;
|
||||
|
||||
declare tax_code: string; //"iva_21"
|
||||
|
||||
// Taxable amount (base imponible) // 100,00 €
|
||||
declare taxable_amount_value: number;
|
||||
declare taxable_amount_scale: number;
|
||||
|
||||
// Total tax amount / taxes total // 21,00 €
|
||||
declare taxes_amount_value: number;
|
||||
declare taxes_amount_scale: number;
|
||||
|
||||
// Relaciones
|
||||
declare item: NonAttribute<CustomerInvoiceItem>;
|
||||
|
||||
static associate(database: Sequelize) {
|
||||
const models = database.models;
|
||||
|
||||
const requiredModels = ["CustomerInvoiceItemModel"];
|
||||
|
||||
// Comprobamos que los modelos existan
|
||||
for (const name of requiredModels) {
|
||||
if (!models[name]) {
|
||||
throw new Error(`[CustomerInvoiceItemTaxModel.associate] Missing model: ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
const { CustomerInvoiceItemModel } = models;
|
||||
|
||||
CustomerInvoiceItemTaxModel.belongsTo(CustomerInvoiceItemModel, {
|
||||
as: "item",
|
||||
targetKey: "item_id",
|
||||
foreignKey: "item_id",
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "CASCADE",
|
||||
});
|
||||
}
|
||||
|
||||
static hooks(_database: Sequelize) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
export default (database: Sequelize) => {
|
||||
CustomerInvoiceItemTaxModel.init(
|
||||
{
|
||||
tax_id: {
|
||||
type: DataTypes.UUID,
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
item_id: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
tax_code: {
|
||||
type: new DataTypes.STRING(),
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
taxable_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
taxable_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
taxes_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
|
||||
taxes_amount_scale: {
|
||||
type: new DataTypes.SMALLINT(),
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize: database,
|
||||
modelName: "CustomerInvoiceItemTaxModel",
|
||||
tableName: "customer_invoice_item_taxes",
|
||||
|
||||
underscored: true,
|
||||
|
||||
indexes: [],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
defaultScope: {},
|
||||
|
||||
scopes: {},
|
||||
}
|
||||
);
|
||||
|
||||
return CustomerInvoiceItemTaxModel;
|
||||
};
|
||||
@ -9,21 +9,15 @@ import {
|
||||
} from "sequelize";
|
||||
|
||||
import type { CustomerInvoiceModel } from "./customer-invoice.model";
|
||||
import type {
|
||||
CustomerInvoiceItemTaxCreationAttributes,
|
||||
CustomerInvoiceItemTaxModel,
|
||||
} from "./customer-invoice-item-tax.model";
|
||||
|
||||
export type CustomerInvoiceItemCreationAttributes = InferCreationAttributes<
|
||||
CustomerInvoiceItemModel,
|
||||
{ omit: "invoice" | "taxes" }
|
||||
> & {
|
||||
taxes?: CustomerInvoiceItemTaxCreationAttributes[];
|
||||
};
|
||||
{ omit: "invoice" }
|
||||
>;
|
||||
|
||||
export class CustomerInvoiceItemModel extends Model<
|
||||
InferAttributes<CustomerInvoiceItemModel>,
|
||||
InferCreationAttributes<CustomerInvoiceItemModel, { omit: "invoice" | "taxes" }>
|
||||
InferCreationAttributes<CustomerInvoiceItemModel, { omit: "invoice" }>
|
||||
> {
|
||||
declare item_id: string;
|
||||
declare invoice_id: string;
|
||||
@ -54,6 +48,39 @@ export class CustomerInvoiceItemModel extends Model<
|
||||
declare taxable_amount_value: CreationOptional<number | null>;
|
||||
declare taxable_amount_scale: number;
|
||||
|
||||
// Código de impuestos
|
||||
|
||||
// IVA percentage
|
||||
declare iva_code: CreationOptional<string | null>;
|
||||
|
||||
declare iva_percentage_value: CreationOptional<number | null>;
|
||||
declare iva_percentage_scale: number;
|
||||
|
||||
// IVA amount
|
||||
|
||||
declare iva_amount_value: CreationOptional<number | null>;
|
||||
declare iva_amount_scale: number;
|
||||
|
||||
// Recargo de equivalencia percentage
|
||||
declare rec_code: CreationOptional<string | null>;
|
||||
|
||||
declare rec_percentage_value: CreationOptional<number | null>;
|
||||
declare rec_percentage_scale: number;
|
||||
|
||||
// Recargo de equivalencia amount
|
||||
declare rec_amount_value: CreationOptional<number | null>;
|
||||
declare rec_amount_scale: number;
|
||||
|
||||
// Retention percentage
|
||||
declare retention_code: CreationOptional<string | null>;
|
||||
|
||||
declare retention_percentage_value: CreationOptional<number | null>;
|
||||
declare retention_percentage_scale: number;
|
||||
|
||||
// Retention amount
|
||||
declare retention_amount_value: CreationOptional<number | null>;
|
||||
declare retention_amount_scale: number;
|
||||
|
||||
// Total taxes amount / taxes total
|
||||
declare taxes_amount_value: CreationOptional<number | null>;
|
||||
declare taxes_amount_scale: number;
|
||||
@ -64,15 +91,10 @@ export class CustomerInvoiceItemModel extends Model<
|
||||
|
||||
// Relaciones
|
||||
declare invoice: NonAttribute<CustomerInvoiceModel>;
|
||||
declare taxes: NonAttribute<CustomerInvoiceItemTaxModel[]>;
|
||||
|
||||
static associate(database: Sequelize) {
|
||||
const models = database.models;
|
||||
const requiredModels = [
|
||||
"CustomerInvoiceModel",
|
||||
"CustomerInvoiceItemModel",
|
||||
"CustomerInvoiceItemTaxModel",
|
||||
];
|
||||
const requiredModels = ["CustomerInvoiceModel", "CustomerInvoiceItemModel"];
|
||||
|
||||
// Comprobamos que los modelos existan
|
||||
for (const name of requiredModels) {
|
||||
@ -81,7 +103,7 @@ export class CustomerInvoiceItemModel extends Model<
|
||||
}
|
||||
}
|
||||
|
||||
const { CustomerInvoiceModel, CustomerInvoiceItemModel, CustomerInvoiceItemTaxModel } = models;
|
||||
const { CustomerInvoiceModel, CustomerInvoiceItemModel } = models;
|
||||
|
||||
CustomerInvoiceItemModel.belongsTo(CustomerInvoiceModel, {
|
||||
as: "invoice",
|
||||
@ -90,15 +112,6 @@ export class CustomerInvoiceItemModel extends Model<
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "CASCADE",
|
||||
});
|
||||
|
||||
CustomerInvoiceItemModel.hasMany(CustomerInvoiceItemTaxModel, {
|
||||
as: "taxes",
|
||||
foreignKey: "item_id",
|
||||
sourceKey: "item_id",
|
||||
constraints: true,
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "CASCADE",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,6 +212,93 @@ export default (database: Sequelize) => {
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
// IVA %
|
||||
|
||||
iva_code: {
|
||||
type: DataTypes.STRING(40),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
iva_percentage_value: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
iva_percentage_scale: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
iva_amount_value: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
iva_amount_scale: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
// REC %
|
||||
rec_code: {
|
||||
type: DataTypes.STRING(40),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
rec_percentage_value: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
rec_percentage_scale: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
rec_amount_value: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
rec_amount_scale: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
// Retención %
|
||||
retention_code: {
|
||||
type: DataTypes.STRING(40),
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
|
||||
retention_percentage_value: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
retention_percentage_scale: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: false,
|
||||
defaultValue: 2,
|
||||
},
|
||||
|
||||
retention_amount_value: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
retention_amount_scale: {
|
||||
type: DataTypes.SMALLINT,
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
},
|
||||
|
||||
taxes_amount_value: {
|
||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||
allowNull: true,
|
||||
@ -230,7 +330,7 @@ export default (database: Sequelize) => {
|
||||
|
||||
underscored: true,
|
||||
|
||||
indexes: [],
|
||||
indexes: [{ fields: ["invoice_id"] }, { fields: ["invoice_id", "position"] }],
|
||||
|
||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||
|
||||
|
||||
@ -49,9 +49,10 @@ export class CustomerInvoiceTaxModel extends Model<
|
||||
|
||||
CustomerInvoiceTaxModel.belongsTo(CustomerInvoiceModel, {
|
||||
as: "invoice",
|
||||
targetKey: "id",
|
||||
foreignKey: "invoice_id",
|
||||
targetKey: "id",
|
||||
onDelete: "CASCADE",
|
||||
onUpdate: "CASCADE",
|
||||
});
|
||||
}
|
||||
|
||||
@ -74,7 +75,7 @@ export default (database: Sequelize) => {
|
||||
},
|
||||
|
||||
tax_code: {
|
||||
type: new DataTypes.STRING(),
|
||||
type: new DataTypes.STRING(40), // Sugerido por IA
|
||||
allowNull: false,
|
||||
},
|
||||
|
||||
@ -113,7 +114,11 @@ export default (database: Sequelize) => {
|
||||
{
|
||||
name: "invoice_id_idx",
|
||||
fields: ["invoice_id"],
|
||||
unique: false,
|
||||
},
|
||||
{
|
||||
name: "invoice_tax_code_unique",
|
||||
fields: ["invoice_id", "tax_code"],
|
||||
unique: true, // cada impuesto aparece como máximo una vez
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
export * from "./customer-invoice.model";
|
||||
export * from "./customer-invoice-item.model";
|
||||
export * from "./customer-invoice-item-tax.model";
|
||||
export * from "./customer-invoice-tax.model";
|
||||
export * from "./verifactu-record.model";
|
||||
|
||||
@ -38,7 +38,13 @@ export const ListIssuedInvoicesResponseSchema = createPaginatedListSchema(
|
||||
country: z.string(),
|
||||
}),
|
||||
|
||||
taxes: z.string(),
|
||||
taxes: z.array(
|
||||
z.object({
|
||||
tax_code: z.string(),
|
||||
taxable_amount: MoneySchema,
|
||||
taxes_amount: MoneySchema,
|
||||
})
|
||||
),
|
||||
|
||||
subtotal_amount: MoneySchema,
|
||||
discount_percentage: PercentageSchema,
|
||||
|
||||
@ -38,7 +38,13 @@ export const ListProformasResponseSchema = createPaginatedListSchema(
|
||||
country: z.string(),
|
||||
}),
|
||||
|
||||
taxes: z.string(),
|
||||
taxes: z.array(
|
||||
z.object({
|
||||
tax_code: z.string(),
|
||||
taxable_amount: MoneySchema,
|
||||
taxes_amount: MoneySchema,
|
||||
})
|
||||
),
|
||||
|
||||
subtotal_amount: MoneySchema,
|
||||
discount_percentage: PercentageSchema,
|
||||
|
||||
@ -11,7 +11,6 @@ import type {
|
||||
*/
|
||||
export const IssuedInvoiceSummaryDtoAdapter = {
|
||||
fromDto(pageDto: IssuedInvoiceSummaryPage, context?: unknown): IssuedInvoiceSummaryPageData {
|
||||
console.log(pageDto);
|
||||
return {
|
||||
...pageDto,
|
||||
items: pageDto.items.map(
|
||||
|
||||
@ -89,7 +89,7 @@ export function useIssuedInvoicesGridColumns(
|
||||
cell: ({ row }) => {
|
||||
const { verifactu } = row.original;
|
||||
const isPending = verifactu.status === "Pendiente";
|
||||
console.log(verifactu.status);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isPending ? (
|
||||
@ -331,12 +331,11 @@ export function useIssuedInvoicesGridColumns(
|
||||
return (
|
||||
<ButtonGroup>
|
||||
{/* Descargar en PDF */}
|
||||
{/* Descargar en PDF */}
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
className="size-8"
|
||||
className={"size-8"}
|
||||
disabled={isPDFLoading || !isCompleted}
|
||||
onClick={(e) => {
|
||||
stop(e);
|
||||
@ -346,9 +345,9 @@ export function useIssuedInvoicesGridColumns(
|
||||
variant="ghost"
|
||||
>
|
||||
{isPDFLoading ? (
|
||||
<Spinner className="size-4" />
|
||||
<Spinner className="size-4 cursor-progress" />
|
||||
) : (
|
||||
<FileDownIcon className="size-4" />
|
||||
<FileDownIcon className="size-4 cursor-pointer" />
|
||||
)}
|
||||
<span className="sr-only">Descargar PDF</span>
|
||||
</Button>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user