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 { Percentage, ValueObject } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
|
|||||||
@ -42,7 +42,7 @@ export class IssuedInvoiceItemsFullPresenter extends Presenter {
|
|||||||
discount_amount: allAmounts.discountAmount.toObjectString(),
|
discount_amount: allAmounts.discountAmount.toObjectString(),
|
||||||
|
|
||||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||||
tax_codes: invoiceItem.taxes.getCodesToString().split(","),
|
tax_codes: invoiceItem.taxes.getCodesArray(),
|
||||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||||
|
|
||||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export class ProformaItemsFullPresenter extends Presenter {
|
|||||||
discount_amount: allAmounts.discountAmount.toObjectString(),
|
discount_amount: allAmounts.discountAmount.toObjectString(),
|
||||||
|
|
||||||
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
taxable_amount: allAmounts.taxableAmount.toObjectString(),
|
||||||
tax_codes: proformaItem.taxes.getCodesToString().split(","),
|
tax_codes: proformaItem.taxes.getCodesArray(),
|
||||||
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
taxes_amount: allAmounts.taxesAmount.toObjectString(),
|
||||||
|
|
||||||
total_amount: allAmounts.totalAmount.toObjectString(),
|
total_amount: allAmounts.totalAmount.toObjectString(),
|
||||||
|
|||||||
@ -39,7 +39,11 @@ export class IssuedInvoiceListPresenter extends Presenter {
|
|||||||
language_code: invoice.languageCode.code,
|
language_code: invoice.languageCode.code,
|
||||||
currency_code: invoice.currencyCode.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(),
|
subtotal_amount: invoice.subtotalAmount.toObjectString(),
|
||||||
discount_percentage: invoice.discountPercentage.toObjectString(),
|
discount_percentage: invoice.discountPercentage.toObjectString(),
|
||||||
|
|||||||
@ -30,7 +30,11 @@ export class ProformaListPresenter extends Presenter {
|
|||||||
language_code: proforma.languageCode.code,
|
language_code: proforma.languageCode.code,
|
||||||
currency_code: proforma.currencyCode.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(),
|
subtotal_amount: proforma.subtotalAmount.toObjectString(),
|
||||||
discount_percentage: proforma.discountPercentage.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 taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
||||||
|
|
||||||
|
const taxName = taxCatalogItem.match(
|
||||||
|
(item) => item.name,
|
||||||
|
() => taxItem.tax_code // fallback
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tax_code: taxItem.tax_code,
|
tax_code: taxItem.tax_code,
|
||||||
tax_name: taxCatalogItem.unwrap().name,
|
tax_name: taxName,
|
||||||
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
||||||
taxes_amount: MoneyDTOHelper.format(taxItem.taxes_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 taxCatalogItem = this._taxCatalog.findByCode(taxItem.tax_code);
|
||||||
|
|
||||||
|
const taxName = taxCatalogItem.match(
|
||||||
|
(item) => item.name,
|
||||||
|
() => taxItem.tax_code // fallback
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tax_code: taxItem.tax_code,
|
tax_code: taxItem.tax_code,
|
||||||
tax_name: taxCatalogItem.unwrap().name,
|
tax_name: taxName,
|
||||||
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
taxable_amount: MoneyDTOHelper.format(taxItem.taxable_amount, this._locale, moneyOptions),
|
||||||
taxes_amount: MoneyDTOHelper.format(taxItem.taxes_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 invoiceDTO = dtoPresenter.toOutput(invoice);
|
||||||
const prettyDTO = prePresenter.toOutput(invoiceDTO);
|
const prettyDTO = prePresenter.toOutput(invoiceDTO);
|
||||||
|
|
||||||
console.log(prettyDTO.verifactu);
|
|
||||||
|
|
||||||
|
|
||||||
// Obtener y compilar la plantilla HTML
|
// Obtener y compilar la plantilla HTML
|
||||||
const template = this.templateResolver.compileTemplate(
|
const template = this.templateResolver.compileTemplate(
|
||||||
"customer-invoices",
|
"customer-invoices",
|
||||||
|
|||||||
@ -23,8 +23,6 @@ export class IssuedInvoiceReportPDFPresenter extends Presenter<
|
|||||||
format: "HTML",
|
format: "HTML",
|
||||||
}) as IssuedInvoiceReportHTMLPresenter;
|
}) as IssuedInvoiceReportHTMLPresenter;
|
||||||
|
|
||||||
console.log(invoice);
|
|
||||||
|
|
||||||
const htmlData = htmlPresenter.toOutput(invoice, params);
|
const htmlData = htmlPresenter.toOutput(invoice, params);
|
||||||
|
|
||||||
// Generar el PDF con Puppeteer
|
// Generar el PDF con Puppeteer
|
||||||
|
|||||||
@ -236,6 +236,26 @@ export class CustomerInvoice
|
|||||||
return this.paymentMethod.isSome();
|
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(
|
private _getHeaderDiscountAmount(
|
||||||
subtotalAmount: InvoiceAmount,
|
subtotalAmount: InvoiceAmount,
|
||||||
itemsDiscountAmount: InvoiceAmount
|
itemsDiscountAmount: InvoiceAmount
|
||||||
@ -251,79 +271,43 @@ export class CustomerInvoice
|
|||||||
return subtotalAmount.subtract(itemsDiscountAmount).subtract(headerDiscountAmount);
|
return subtotalAmount.subtract(itemsDiscountAmount).subtract(headerDiscountAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getTaxesAmount(taxableAmount: InvoiceAmount): InvoiceAmount {
|
// total impuestos suma(iva + rec + retenciones)
|
||||||
let amount = InvoiceAmount.zero(this.currencyCode.code);
|
private _getTaxesAmount(): InvoiceAmount {
|
||||||
|
const { iva, rec, retention } = this.items.getAggregatedTaxesByType();
|
||||||
const itemTaxes = this.items.getTaxesAmountByTaxes();
|
const total = iva.add(rec).add(retention);
|
||||||
|
return InvoiceAmount.create({
|
||||||
for (const taxItem of itemTaxes) {
|
value: total.convertScale(2).value,
|
||||||
amount = amount.add(
|
currency_code: this.currencyCode.code,
|
||||||
InvoiceAmount.create({
|
}).data;
|
||||||
value: taxItem.taxesAmount.convertScale(2).value,
|
|
||||||
currency_code: this.currencyCode.code,
|
|
||||||
}).data
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return amount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getTotalAmount(taxableAmount: InvoiceAmount, taxesAmount: InvoiceAmount): InvoiceAmount {
|
private _getTotalAmount(taxableAmount: InvoiceAmount, taxesAmount: InvoiceAmount): InvoiceAmount {
|
||||||
return taxableAmount.add(taxesAmount);
|
return taxableAmount.add(taxesAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public _getSubtotalAmount(): InvoiceAmount {
|
/** Totales expuestos */
|
||||||
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);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
public getTaxes(): InvoiceTaxTotal[] {
|
public getTaxes(): InvoiceTaxTotal[] {
|
||||||
const itemTaxes = this.items.getTaxesAmountByTaxes();
|
const map = this.items.getAggregatedTaxesByCode();
|
||||||
|
const currency = this.currencyCode.code;
|
||||||
|
|
||||||
return itemTaxes.map((item) => {
|
const result: InvoiceTaxTotal[] = [];
|
||||||
return {
|
|
||||||
tax: item.tax,
|
for (const [tax_code, entry] of map.entries()) {
|
||||||
|
result.push({
|
||||||
|
tax: entry.tax,
|
||||||
taxableAmount: InvoiceAmount.create({
|
taxableAmount: InvoiceAmount.create({
|
||||||
value: item.taxableAmount.convertScale(2).value,
|
value: entry.taxable.convertScale(2).value,
|
||||||
currency_code: this.currencyCode.code,
|
currency_code: currency,
|
||||||
}).data,
|
}).data,
|
||||||
taxesAmount: InvoiceAmount.create({
|
taxesAmount: InvoiceAmount.create({
|
||||||
value: item.taxesAmount.convertScale(2).value,
|
value: entry.total.convertScale(2).value,
|
||||||
currency_code: this.currencyCode.code,
|
currency_code: currency,
|
||||||
}).data,
|
}).data,
|
||||||
};
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllAmounts() {
|
public getAllAmounts() {
|
||||||
@ -337,7 +321,7 @@ export class CustomerInvoice
|
|||||||
headerDiscountAmount
|
headerDiscountAmount
|
||||||
); //
|
); //
|
||||||
|
|
||||||
const taxesAmount = this._getTaxesAmount(taxableAmount);
|
const taxesAmount = this._getTaxesAmount();
|
||||||
const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount);
|
const totalAmount = this._getTotalAmount(taxableAmount, taxesAmount);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import {
|
import { type CurrencyCode, DomainEntity, type LanguageCode, type UniqueID } from "@repo/rdx-ddd";
|
||||||
type CurrencyCode,
|
|
||||||
DomainEntity,
|
|
||||||
type LanguageCode,
|
|
||||||
type Percentage,
|
|
||||||
type UniqueID,
|
|
||||||
} from "@repo/rdx-ddd";
|
|
||||||
import { type Maybe, Result } from "@repo/rdx-utils";
|
import { type Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -13,7 +7,7 @@ import {
|
|||||||
ItemDiscount,
|
ItemDiscount,
|
||||||
ItemQuantity,
|
ItemQuantity,
|
||||||
} from "../../value-objects";
|
} from "../../value-objects";
|
||||||
import type { ItemTaxTotal, ItemTaxes } from "../item-taxes";
|
import type { ItemTaxGroup } from "../../value-objects/item-tax-group";
|
||||||
|
|
||||||
export interface CustomerInvoiceItemProps {
|
export interface CustomerInvoiceItemProps {
|
||||||
description: Maybe<CustomerInvoiceItemDescription>;
|
description: Maybe<CustomerInvoiceItemDescription>;
|
||||||
@ -22,7 +16,7 @@ export interface CustomerInvoiceItemProps {
|
|||||||
|
|
||||||
discountPercentage: Maybe<ItemDiscount>; // % descuento
|
discountPercentage: Maybe<ItemDiscount>; // % descuento
|
||||||
|
|
||||||
taxes: ItemTaxes;
|
taxes: ItemTaxGroup;
|
||||||
|
|
||||||
languageCode: LanguageCode;
|
languageCode: LanguageCode;
|
||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
@ -38,7 +32,7 @@ export interface ICustomerInvoiceItem {
|
|||||||
|
|
||||||
discountPercentage: Maybe<ItemDiscount>; // % descuento
|
discountPercentage: Maybe<ItemDiscount>; // % descuento
|
||||||
|
|
||||||
taxes: ItemTaxes;
|
taxes: ItemTaxGroup;
|
||||||
|
|
||||||
languageCode: LanguageCode;
|
languageCode: LanguageCode;
|
||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
@ -80,31 +74,25 @@ export class CustomerInvoiceItem
|
|||||||
return this._isValued;
|
return this._isValued;
|
||||||
}
|
}
|
||||||
|
|
||||||
get description(): Maybe<CustomerInvoiceItemDescription> {
|
get description() {
|
||||||
return this.props.description;
|
return this.props.description;
|
||||||
}
|
}
|
||||||
|
get quantity() {
|
||||||
get quantity(): Maybe<ItemQuantity> {
|
|
||||||
return this.props.quantity;
|
return this.props.quantity;
|
||||||
}
|
}
|
||||||
|
get unitAmount() {
|
||||||
get unitAmount(): Maybe<ItemAmount> {
|
|
||||||
return this.props.unitAmount;
|
return this.props.unitAmount;
|
||||||
}
|
}
|
||||||
|
get discountPercentage() {
|
||||||
get discountPercentage(): Maybe<Percentage> {
|
|
||||||
return this.props.discountPercentage;
|
return this.props.discountPercentage;
|
||||||
}
|
}
|
||||||
|
get languageCode() {
|
||||||
get languageCode(): LanguageCode {
|
|
||||||
return this.props.languageCode;
|
return this.props.languageCode;
|
||||||
}
|
}
|
||||||
|
get currencyCode() {
|
||||||
get currencyCode(): CurrencyCode {
|
|
||||||
return this.props.currencyCode;
|
return this.props.currencyCode;
|
||||||
}
|
}
|
||||||
|
get taxes() {
|
||||||
get taxes(): ItemTaxes {
|
|
||||||
return this.props.taxes;
|
return this.props.taxes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +112,7 @@ export class CustomerInvoiceItem
|
|||||||
*/
|
*/
|
||||||
private _getDiscountAmount(subtotalAmount: ItemAmount): ItemAmount {
|
private _getDiscountAmount(subtotalAmount: ItemAmount): ItemAmount {
|
||||||
const discount = this.discountPercentage.match(
|
const discount = this.discountPercentage.match(
|
||||||
(percentage) => percentage,
|
(discount) => discount,
|
||||||
() => ItemDiscount.zero()
|
() => ItemDiscount.zero()
|
||||||
);
|
);
|
||||||
return subtotalAmount.percentage(discount);
|
return subtotalAmount.percentage(discount);
|
||||||
@ -141,6 +129,11 @@ export class CustomerInvoiceItem
|
|||||||
return subtotalAmount.subtract(discountAmount);
|
return subtotalAmount.subtract(discountAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* importes individuales: iva / rec / ret */
|
||||||
|
private _getIndividualTaxAmounts(taxableAmount: ItemAmount) {
|
||||||
|
return this.props.taxes.calculateAmounts(taxableAmount);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @summary Calcula el importe total de impuestos sobre la base imponible.
|
* @summary Calcula el importe total de impuestos sobre la base imponible.
|
||||||
@ -148,7 +141,8 @@ export class CustomerInvoiceItem
|
|||||||
* @returns El importe de impuestos calculado.
|
* @returns El importe de impuestos calculado.
|
||||||
*/
|
*/
|
||||||
private _getTaxesAmount(taxableAmount: ItemAmount): ItemAmount {
|
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.
|
* Si la cantidad o el importe unitario no están definidos, se asumen valores cero.
|
||||||
*/
|
*/
|
||||||
public getSubtotalAmount(): ItemAmount {
|
public getSubtotalAmount(): ItemAmount {
|
||||||
const curCode = this.currencyCode.code;
|
const qty = this.quantity.match(
|
||||||
const quantity = this.quantity.match(
|
|
||||||
(quantity) => quantity,
|
(quantity) => quantity,
|
||||||
() => ItemQuantity.zero()
|
() => ItemQuantity.zero()
|
||||||
);
|
);
|
||||||
const unitAmount = this.unitAmount.match(
|
const unit = this.unitAmount.match(
|
||||||
(unitAmount) => unitAmount,
|
(unitAmount) => unitAmount,
|
||||||
() => ItemAmount.zero(curCode)
|
() => ItemAmount.zero(this.currencyCode.code)
|
||||||
);
|
);
|
||||||
|
return unit.multiply(qty);
|
||||||
return unitAmount.multiply(quantity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -198,6 +190,11 @@ export class CustomerInvoiceItem
|
|||||||
return this._getTaxableAmount(this.getSubtotalAmount(), this.getDiscountAmount());
|
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.
|
* @summary Calcula el importe total de impuestos aplicados al ítem.
|
||||||
* @returns Un `ItemAmount` con el total de impuestos.
|
* @returns Un `ItemAmount` con el total de impuestos.
|
||||||
@ -217,14 +214,6 @@ export class CustomerInvoiceItem
|
|||||||
return this._getTotalAmount(taxableAmount, taxesAmount);
|
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.
|
* @summary Devuelve todos los importes calculados del ítem en un único objeto.
|
||||||
* @returns Un objeto con las propiedades:
|
* @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 { Collection } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { ItemAmount } from "../../value-objects";
|
import { ItemAmount } from "../../value-objects";
|
||||||
import { ItemTaxes } from "../item-taxes";
|
|
||||||
|
|
||||||
import type { CustomerInvoiceItem } from "./customer-invoice-item";
|
import type { CustomerInvoiceItem } from "./customer-invoice-item";
|
||||||
|
|
||||||
@ -18,10 +17,9 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
|||||||
private _currencyCode!: CurrencyCode;
|
private _currencyCode!: CurrencyCode;
|
||||||
|
|
||||||
constructor(props: CustomerInvoiceItemsProps) {
|
constructor(props: CustomerInvoiceItemsProps) {
|
||||||
const { items = [], languageCode, currencyCode } = props;
|
super(props.items ?? []);
|
||||||
super(items);
|
this._languageCode = props.languageCode;
|
||||||
this._languageCode = languageCode;
|
this._currencyCode = props.currencyCode;
|
||||||
this._currencyCode = currencyCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static create(props: CustomerInvoiceItemsProps): CustomerInvoiceItems {
|
public static create(props: CustomerInvoiceItemsProps): CustomerInvoiceItems {
|
||||||
@ -105,65 +103,83 @@ export class CustomerInvoiceItems extends Collection<CustomerInvoiceItem> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* totales de iva/rec/ret a nivel factura */
|
||||||
* @summary Agrupa los importes imponibles e impuestos por tipo de impuesto.
|
public getAggregatedTaxesByType() {
|
||||||
* @returns Un array con objetos que contienen:
|
let iva = ItemAmount.zero(this._currencyCode.code);
|
||||||
* - `tax`: El tipo de impuesto.
|
let rec = ItemAmount.zero(this._currencyCode.code);
|
||||||
* - `taxableAmount`: El total de base imponible asociada a ese impuesto.
|
let retention = ItemAmount.zero(this._currencyCode.code);
|
||||||
* - `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 }
|
|
||||||
>();
|
|
||||||
|
|
||||||
for (const item of this.getAll()) {
|
for (const item of this.getAll()) {
|
||||||
for (const { taxableAmount, tax, taxesAmount } of item.getTaxesAmountByTaxes()) {
|
const { ivaAmount, recAmount, retentionAmount } = item.getIndividualTaxAmounts();
|
||||||
const key = getTaxCode(tax);
|
|
||||||
const current = resultMap.get(key) ?? {
|
|
||||||
tax,
|
|
||||||
taxableAmount: ItemAmount.zero(currencyCode),
|
|
||||||
taxesAmount: ItemAmount.zero(currencyCode),
|
|
||||||
};
|
|
||||||
|
|
||||||
resultMap.set(key, {
|
iva = iva.add(ivaAmount);
|
||||||
tax: current.tax,
|
rec = rec.add(recAmount);
|
||||||
taxableAmount: current.taxableAmount.add(taxableAmount),
|
retention = retention.add(retentionAmount);
|
||||||
taxesAmount: current.taxesAmount.add(taxesAmount),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(resultMap.values());
|
return { iva, rec, retention };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/* agrupación por código fiscal → usado para customer_invoice_taxes */
|
||||||
* @summary Obtiene la lista de impuestos únicos aplicados en todos los ítems.
|
public getAggregatedTaxesByCode() {
|
||||||
* @returns Un objeto `ItemTaxes` que contiene todos los impuestos distintos.
|
const map = new Map<string, { tax: Tax; taxable: ItemAmount; total: ItemAmount }>();
|
||||||
* @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>());
|
|
||||||
|
|
||||||
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 "./customer-invoice-items";
|
||||||
export * from "./invoice-payment-method";
|
export * from "./invoice-payment-method";
|
||||||
export * from "./invoice-taxes";
|
export * from "./invoice-taxes";
|
||||||
export * from "./item-taxes";
|
|
||||||
export * from "./verifactu-record";
|
export * from "./verifactu-record";
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { Tax } from "@erp/core/api";
|
import type { Tax } from "@erp/core/api";
|
||||||
import { DomainEntity, UniqueID } from "@repo/rdx-ddd";
|
import { DomainEntity, type UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
import { InvoiceAmount } from "../../value-objects/invoice-amount";
|
|
||||||
|
import type { InvoiceAmount } from "../../value-objects/invoice-amount";
|
||||||
|
|
||||||
export interface InvoiceTaxProps {
|
export interface InvoiceTaxProps {
|
||||||
tax: Tax;
|
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";
|
import { InvoiceAmount } from "../../value-objects";
|
||||||
|
|
||||||
export type InvoiceTaxTotal = {
|
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 type { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import type { Maybe, Result } from "@repo/rdx-utils";
|
||||||
import { CustomerInvoiceNumber, CustomerInvoiceSerie } from "../value-objects";
|
|
||||||
|
import type { CustomerInvoiceNumber, CustomerInvoiceSerie } from "../value-objects";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Servicio de dominio que define cómo se genera el siguiente número de factura.
|
* 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 { CompositeSpecification } from "@repo/rdx-ddd";
|
||||||
import { CustomerInvoice } from "../aggregates";
|
|
||||||
|
import type { CustomerInvoice } from "../aggregates";
|
||||||
import { INVOICE_STATUS } from "../value-objects";
|
import { INVOICE_STATUS } from "../value-objects";
|
||||||
|
|
||||||
export class ProformaCanTranstionToIssuedSpecification extends CompositeSpecification<CustomerInvoice> {
|
export class ProformaCanTranstionToIssuedSpecification extends CompositeSpecification<CustomerInvoice> {
|
||||||
|
|||||||
@ -8,4 +8,5 @@ export * from "./invoice-recipient";
|
|||||||
export * from "./item-amount";
|
export * from "./item-amount";
|
||||||
export * from "./item-discount";
|
export * from "./item-discount";
|
||||||
export * from "./item-quantity";
|
export * from "./item-quantity";
|
||||||
|
export * from "./item-tax-group";
|
||||||
export * from "./verifactu-status";
|
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";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
type InvoiceAmountProps = Pick<MoneyValueProps, "value" | "currency_code">;
|
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";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
type ItemAmountProps = Pick<MoneyValueProps, "value" | "currency_code">;
|
type ItemAmountProps = Pick<MoneyValueProps, "value" | "currency_code">;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Percentage, PercentageProps } from "@repo/rdx-ddd";
|
import { Percentage, type PercentageProps } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import type { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
type ItemDiscountProps = Pick<PercentageProps, "value">;
|
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">;
|
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 {
|
import {
|
||||||
type ISequelizeDomainMapper,
|
type ISequelizeDomainMapper,
|
||||||
type MapperParamsType,
|
type MapperParamsType,
|
||||||
SequelizeDomainMapper,
|
SequelizeDomainMapper,
|
||||||
|
Tax,
|
||||||
} from "@erp/core/api";
|
} from "@erp/core/api";
|
||||||
import {
|
import {
|
||||||
UniqueID,
|
UniqueID,
|
||||||
@ -22,15 +24,13 @@ import {
|
|||||||
ItemAmount,
|
ItemAmount,
|
||||||
ItemDiscount,
|
ItemDiscount,
|
||||||
ItemQuantity,
|
ItemQuantity,
|
||||||
ItemTaxes,
|
ItemTaxGroup,
|
||||||
} from "../../../domain";
|
} from "../../../domain";
|
||||||
import type {
|
import type {
|
||||||
CustomerInvoiceItemCreationAttributes,
|
CustomerInvoiceItemCreationAttributes,
|
||||||
CustomerInvoiceItemModel,
|
CustomerInvoiceItemModel,
|
||||||
} from "../../sequelize";
|
} from "../../sequelize";
|
||||||
|
|
||||||
import { ItemTaxesDomainMapper } from "./item-taxes.mapper";
|
|
||||||
|
|
||||||
export interface ICustomerInvoiceItemDomainMapper
|
export interface ICustomerInvoiceItemDomainMapper
|
||||||
extends ISequelizeDomainMapper<
|
extends ISequelizeDomainMapper<
|
||||||
CustomerInvoiceItemModel,
|
CustomerInvoiceItemModel,
|
||||||
@ -46,11 +46,19 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
>
|
>
|
||||||
implements ICustomerInvoiceItemDomainMapper
|
implements ICustomerInvoiceItemDomainMapper
|
||||||
{
|
{
|
||||||
private _taxesMapper: ItemTaxesDomainMapper;
|
private _taxCatalog!: JsonTaxCatalogProvider;
|
||||||
|
|
||||||
constructor(params: MapperParamsType) {
|
constructor(params: MapperParamsType) {
|
||||||
super();
|
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(
|
private mapAttributesToDomain(
|
||||||
@ -97,6 +105,26 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
errors
|
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 {
|
return {
|
||||||
itemId,
|
itemId,
|
||||||
|
|
||||||
@ -106,6 +134,12 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
quantity,
|
quantity,
|
||||||
unitAmount,
|
unitAmount,
|
||||||
discountPercentage,
|
discountPercentage,
|
||||||
|
|
||||||
|
taxes: ItemTaxGroup.create({
|
||||||
|
iva: iva!,
|
||||||
|
rec: rec!,
|
||||||
|
retention: retention!,
|
||||||
|
}).data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,23 +156,6 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
// 1) Valores escalares (atributos generales)
|
// 1) Valores escalares (atributos generales)
|
||||||
const attributes = this.mapAttributesToDomain(source, params);
|
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
|
// Si hubo errores de mapeo, devolvemos colección de validación
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
@ -146,9 +163,7 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const taxes = ItemTaxes.create(taxesResults.data.getAll());
|
// 2) Construcción del elemento de dominio
|
||||||
|
|
||||||
// 3) Construcción del elemento de dominio
|
|
||||||
const createResult = CustomerInvoiceItem.create(
|
const createResult = CustomerInvoiceItem.create(
|
||||||
{
|
{
|
||||||
languageCode: attributes.languageCode!,
|
languageCode: attributes.languageCode!,
|
||||||
@ -157,7 +172,7 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
quantity: attributes.quantity!,
|
quantity: attributes.quantity!,
|
||||||
unitAmount: attributes.unitAmount!,
|
unitAmount: attributes.unitAmount!,
|
||||||
discountPercentage: attributes.discountPercentage!,
|
discountPercentage: attributes.discountPercentage!,
|
||||||
taxes,
|
taxes: attributes.taxes!,
|
||||||
},
|
},
|
||||||
attributes.itemId
|
attributes.itemId
|
||||||
);
|
);
|
||||||
@ -183,18 +198,6 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
errors: ValidationErrorDetail[];
|
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();
|
const allAmounts = source.getAllAmounts();
|
||||||
|
|
||||||
return Result.ok({
|
return Result.ok({
|
||||||
@ -235,7 +238,9 @@ export class CustomerInvoiceItemDomainMapper
|
|||||||
total_amount_value: allAmounts.totalAmount.value,
|
total_amount_value: allAmounts.totalAmount.value,
|
||||||
total_amount_scale: allAmounts.totalAmount.scale,
|
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,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
/*if (recipientResult.isFailure) {
|
|
||||||
errors.push({
|
|
||||||
path: "recipient",
|
|
||||||
|
|
||||||
message: recipientResult.error.message,
|
|
||||||
});
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// 3) Verifactu (snapshot en la factura o include)
|
// 3) Verifactu (snapshot en la factura o include)
|
||||||
const verifactuResult = this._verifactuMapper.mapToDomain(source.verifactu, {
|
const verifactuResult = this._verifactuMapper.mapToDomain(source.verifactu, {
|
||||||
errors,
|
errors,
|
||||||
@ -235,14 +227,6 @@ export class CustomerInvoiceDomainMapper
|
|||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
/*if (verifactuResult.isFailure) {
|
|
||||||
errors.push({
|
|
||||||
path: "verifactu",
|
|
||||||
|
|
||||||
message: verifactuResult.error.message,
|
|
||||||
});
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// 4) Items (colección)
|
// 4) Items (colección)
|
||||||
const itemsResults = this._itemsMapper.mapToDomainCollection(
|
const itemsResults = this._itemsMapper.mapToDomainCollection(
|
||||||
source.items,
|
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
|
// 5) Si hubo errores de mapeo, devolvemos colección de validación
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
@ -273,9 +247,6 @@ export class CustomerInvoiceDomainMapper
|
|||||||
|
|
||||||
// 6) Construcción del agregado (Dominio)
|
// 6) Construcción del agregado (Dominio)
|
||||||
|
|
||||||
const verifactu = verifactuResult.data;
|
|
||||||
const recipient = recipientResult.data;
|
|
||||||
|
|
||||||
const items = CustomerInvoiceItems.create({
|
const items = CustomerInvoiceItems.create({
|
||||||
languageCode: attributes.languageCode!,
|
languageCode: attributes.languageCode!,
|
||||||
currencyCode: attributes.currencyCode!,
|
currencyCode: attributes.currencyCode!,
|
||||||
@ -294,7 +265,7 @@ export class CustomerInvoiceDomainMapper
|
|||||||
operationDate: attributes.operationDate!,
|
operationDate: attributes.operationDate!,
|
||||||
|
|
||||||
customerId: attributes.customerId!,
|
customerId: attributes.customerId!,
|
||||||
recipient: recipient,
|
recipient: recipientResult.data,
|
||||||
|
|
||||||
reference: attributes.reference!,
|
reference: attributes.reference!,
|
||||||
description: attributes.description!,
|
description: attributes.description!,
|
||||||
@ -308,7 +279,7 @@ export class CustomerInvoiceDomainMapper
|
|||||||
paymentMethod: attributes.paymentMethod!,
|
paymentMethod: attributes.paymentMethod!,
|
||||||
|
|
||||||
items,
|
items,
|
||||||
verifactu,
|
verifactu: verifactuResult.data,
|
||||||
};
|
};
|
||||||
|
|
||||||
const createResult = CustomerInvoice.create(invoiceProps, attributes.invoiceId);
|
const createResult = CustomerInvoice.create(invoiceProps, attributes.invoiceId);
|
||||||
|
|||||||
@ -1,11 +1,26 @@
|
|||||||
import { JsonTaxCatalogProvider } from "@erp/core";
|
import type { JsonTaxCatalogProvider } from "@erp/core";
|
||||||
import { MapperParamsType, SequelizeDomainMapper, Tax } from "@erp/core/api";
|
import { type MapperParamsType, SequelizeDomainMapper, Tax } from "@erp/core/api";
|
||||||
import { UniqueID, ValidationErrorDetail } from "@repo/rdx-ddd";
|
import { UniqueID, type ValidationErrorDetail } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
import { Result } from "@repo/rdx-utils";
|
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<
|
export class TaxesDomainMapper extends SequelizeDomainMapper<
|
||||||
CustomerInvoiceTaxModel,
|
CustomerInvoiceTaxModel,
|
||||||
CustomerInvoiceTaxCreationAttributes,
|
CustomerInvoiceTaxCreationAttributes,
|
||||||
@ -41,15 +56,17 @@ export class TaxesDomainMapper extends SequelizeDomainMapper<
|
|||||||
attributes: Partial<CustomerInvoiceItemProps>;
|
attributes: Partial<CustomerInvoiceItemProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const currency_code = attributes.currencyCode!.code;
|
||||||
|
|
||||||
return Result.ok({
|
return Result.ok({
|
||||||
taxableAmount: ItemAmount.create({
|
taxableAmount: ItemAmount.create({
|
||||||
value: source.taxable_amount_value,
|
value: source.taxable_amount_value,
|
||||||
currency_code: attributes.currencyCode!.code,
|
currency_code,
|
||||||
}).data,
|
}).data,
|
||||||
tax: Tax.createFromCode(source.tax_code, this._taxCatalog).data,
|
tax: Tax.createFromCode(source.tax_code, this._taxCatalog).data,
|
||||||
taxesAmount: ItemAmount.create({
|
taxesAmount: ItemAmount.create({
|
||||||
value: source.taxes_amount_value,
|
value: source.taxes_amount_value,
|
||||||
currency_code: attributes.currencyCode!.code,
|
currency_code,
|
||||||
}).data,
|
}).data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -67,8 +84,6 @@ export class TaxesDomainMapper extends SequelizeDomainMapper<
|
|||||||
errors: ValidationErrorDetail[];
|
errors: ValidationErrorDetail[];
|
||||||
};
|
};
|
||||||
|
|
||||||
source;
|
|
||||||
|
|
||||||
return Result.ok({
|
return Result.ok({
|
||||||
tax_id: UniqueID.generateNewID().toPrimitive(),
|
tax_id: UniqueID.generateNewID().toPrimitive(),
|
||||||
invoice_id: parent.id.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,
|
CustomerInvoiceStatus,
|
||||||
InvoiceAmount,
|
InvoiceAmount,
|
||||||
type InvoiceRecipient,
|
type InvoiceRecipient,
|
||||||
|
ItemAmount,
|
||||||
type VerifactuRecord,
|
type VerifactuRecord,
|
||||||
} from "../../../domain";
|
} from "../../../domain";
|
||||||
import type { CustomerInvoiceModel } from "../../sequelize";
|
import type { CustomerInvoiceModel } from "../../sequelize";
|
||||||
@ -50,7 +51,11 @@ export type CustomerInvoiceListDTO = {
|
|||||||
languageCode: LanguageCode;
|
languageCode: LanguageCode;
|
||||||
currencyCode: CurrencyCode;
|
currencyCode: CurrencyCode;
|
||||||
|
|
||||||
taxes: string;
|
taxes: {
|
||||||
|
tax_code: string;
|
||||||
|
taxable_amount: InvoiceAmount;
|
||||||
|
taxes_amount: InvoiceAmount;
|
||||||
|
}[];
|
||||||
|
|
||||||
discountPercentage: Percentage;
|
discountPercentage: Percentage;
|
||||||
|
|
||||||
@ -103,7 +108,23 @@ export class CustomerInvoiceListMapper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3) Taxes
|
// 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
|
// 4) Verifactu record
|
||||||
let verifactu: Maybe<VerifactuRecord> = Maybe.none();
|
let verifactu: Maybe<VerifactuRecord> = Maybe.none();
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import type {
|
|||||||
|
|
||||||
import { CustomerInvoiceModel } from "./models/customer-invoice.model";
|
import { CustomerInvoiceModel } from "./models/customer-invoice.model";
|
||||||
import { CustomerInvoiceItemModel } from "./models/customer-invoice-item.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 { CustomerInvoiceTaxModel } from "./models/customer-invoice-tax.model";
|
||||||
import { VerifactuRecordModel } from "./models/verifactu-record.model";
|
import { VerifactuRecordModel } from "./models/verifactu-record.model";
|
||||||
|
|
||||||
@ -102,12 +101,7 @@ export class CustomerInvoiceRepository
|
|||||||
// 3. Inserta items + sus taxes
|
// 3. Inserta items + sus taxes
|
||||||
if (Array.isArray(items) && items.length > 0) {
|
if (Array.isArray(items) && items.length > 0) {
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const { taxes: itemTaxes, ...itemData } = item;
|
await CustomerInvoiceItemModel.create(item, { transaction });
|
||||||
await CustomerInvoiceItemModel.create(itemData, { transaction });
|
|
||||||
|
|
||||||
if (Array.isArray(itemTaxes) && itemTaxes.length > 0) {
|
|
||||||
await CustomerInvoiceItemTaxModel.bulkCreate(itemTaxes, { transaction });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,12 +165,7 @@ export class CustomerInvoiceRepository
|
|||||||
// 4. Inserta items + sus taxes
|
// 4. Inserta items + sus taxes
|
||||||
if (Array.isArray(items) && items.length > 0) {
|
if (Array.isArray(items) && items.length > 0) {
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const { taxes: itemTaxes, ...itemData } = item;
|
await CustomerInvoiceItemModel.create(item, { transaction });
|
||||||
await CustomerInvoiceItemModel.create(itemData, { transaction });
|
|
||||||
|
|
||||||
if (Array.isArray(itemTaxes) && itemTaxes.length > 0) {
|
|
||||||
await CustomerInvoiceItemTaxModel.bulkCreate(itemTaxes, { transaction });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,13 +265,6 @@ export class CustomerInvoiceRepository
|
|||||||
model: CustomerInvoiceItemModel,
|
model: CustomerInvoiceItemModel,
|
||||||
as: "items",
|
as: "items",
|
||||||
required: false,
|
required: false,
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: CustomerInvoiceItemTaxModel,
|
|
||||||
as: "taxes",
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: CustomerInvoiceTaxModel,
|
model: CustomerInvoiceTaxModel,
|
||||||
@ -371,13 +353,6 @@ export class CustomerInvoiceRepository
|
|||||||
model: CustomerInvoiceItemModel,
|
model: CustomerInvoiceItemModel,
|
||||||
as: "items",
|
as: "items",
|
||||||
required: false,
|
required: false,
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: CustomerInvoiceItemTaxModel,
|
|
||||||
as: "taxes",
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
model: CustomerInvoiceTaxModel,
|
model: CustomerInvoiceTaxModel,
|
||||||
@ -484,7 +459,6 @@ export class CustomerInvoiceRepository
|
|||||||
as: "taxes",
|
as: "taxes",
|
||||||
required: false,
|
required: false,
|
||||||
separate: true, // => query aparte, devuelve siempre array
|
separate: true, // => query aparte, devuelve siempre array
|
||||||
attributes: ["tax_id", "tax_code"],
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -601,7 +575,6 @@ export class CustomerInvoiceRepository
|
|||||||
as: "taxes",
|
as: "taxes",
|
||||||
required: false,
|
required: false,
|
||||||
separate: true, // => query aparte, devuelve siempre array
|
separate: true, // => query aparte, devuelve siempre array
|
||||||
attributes: ["tax_id", "tax_code"],
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import customerInvoiceModelInit from "./models/customer-invoice.model";
|
import customerInvoiceModelInit from "./models/customer-invoice.model";
|
||||||
import customerInvoiceItemModelInit from "./models/customer-invoice-item.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 customerInvoiceTaxesModelInit from "./models/customer-invoice-tax.model";
|
||||||
import verifactuRecordModelInit from "./models/verifactu-record.model";
|
import verifactuRecordModelInit from "./models/verifactu-record.model";
|
||||||
|
|
||||||
@ -13,7 +12,6 @@ export const models = [
|
|||||||
customerInvoiceItemModelInit,
|
customerInvoiceItemModelInit,
|
||||||
|
|
||||||
customerInvoiceTaxesModelInit,
|
customerInvoiceTaxesModelInit,
|
||||||
customerInvoiceItemTaxesModelInit,
|
|
||||||
|
|
||||||
verifactuRecordModelInit,
|
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";
|
} from "sequelize";
|
||||||
|
|
||||||
import type { CustomerInvoiceModel } from "./customer-invoice.model";
|
import type { CustomerInvoiceModel } from "./customer-invoice.model";
|
||||||
import type {
|
|
||||||
CustomerInvoiceItemTaxCreationAttributes,
|
|
||||||
CustomerInvoiceItemTaxModel,
|
|
||||||
} from "./customer-invoice-item-tax.model";
|
|
||||||
|
|
||||||
export type CustomerInvoiceItemCreationAttributes = InferCreationAttributes<
|
export type CustomerInvoiceItemCreationAttributes = InferCreationAttributes<
|
||||||
CustomerInvoiceItemModel,
|
CustomerInvoiceItemModel,
|
||||||
{ omit: "invoice" | "taxes" }
|
{ omit: "invoice" }
|
||||||
> & {
|
>;
|
||||||
taxes?: CustomerInvoiceItemTaxCreationAttributes[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export class CustomerInvoiceItemModel extends Model<
|
export class CustomerInvoiceItemModel extends Model<
|
||||||
InferAttributes<CustomerInvoiceItemModel>,
|
InferAttributes<CustomerInvoiceItemModel>,
|
||||||
InferCreationAttributes<CustomerInvoiceItemModel, { omit: "invoice" | "taxes" }>
|
InferCreationAttributes<CustomerInvoiceItemModel, { omit: "invoice" }>
|
||||||
> {
|
> {
|
||||||
declare item_id: string;
|
declare item_id: string;
|
||||||
declare invoice_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_value: CreationOptional<number | null>;
|
||||||
declare taxable_amount_scale: number;
|
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
|
// Total taxes amount / taxes total
|
||||||
declare taxes_amount_value: CreationOptional<number | null>;
|
declare taxes_amount_value: CreationOptional<number | null>;
|
||||||
declare taxes_amount_scale: number;
|
declare taxes_amount_scale: number;
|
||||||
@ -64,15 +91,10 @@ export class CustomerInvoiceItemModel extends Model<
|
|||||||
|
|
||||||
// Relaciones
|
// Relaciones
|
||||||
declare invoice: NonAttribute<CustomerInvoiceModel>;
|
declare invoice: NonAttribute<CustomerInvoiceModel>;
|
||||||
declare taxes: NonAttribute<CustomerInvoiceItemTaxModel[]>;
|
|
||||||
|
|
||||||
static associate(database: Sequelize) {
|
static associate(database: Sequelize) {
|
||||||
const models = database.models;
|
const models = database.models;
|
||||||
const requiredModels = [
|
const requiredModels = ["CustomerInvoiceModel", "CustomerInvoiceItemModel"];
|
||||||
"CustomerInvoiceModel",
|
|
||||||
"CustomerInvoiceItemModel",
|
|
||||||
"CustomerInvoiceItemTaxModel",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Comprobamos que los modelos existan
|
// Comprobamos que los modelos existan
|
||||||
for (const name of requiredModels) {
|
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, {
|
CustomerInvoiceItemModel.belongsTo(CustomerInvoiceModel, {
|
||||||
as: "invoice",
|
as: "invoice",
|
||||||
@ -90,15 +112,6 @@ export class CustomerInvoiceItemModel extends Model<
|
|||||||
onDelete: "CASCADE",
|
onDelete: "CASCADE",
|
||||||
onUpdate: "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,
|
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: {
|
taxes_amount_value: {
|
||||||
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
type: new DataTypes.BIGINT(), // importante: evita problemas de precisión con valores grandes
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -230,7 +330,7 @@ export default (database: Sequelize) => {
|
|||||||
|
|
||||||
underscored: true,
|
underscored: true,
|
||||||
|
|
||||||
indexes: [],
|
indexes: [{ fields: ["invoice_id"] }, { fields: ["invoice_id", "position"] }],
|
||||||
|
|
||||||
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
whereMergeStrategy: "and", // <- cómo tratar el merge de un scope
|
||||||
|
|
||||||
|
|||||||
@ -49,9 +49,10 @@ export class CustomerInvoiceTaxModel extends Model<
|
|||||||
|
|
||||||
CustomerInvoiceTaxModel.belongsTo(CustomerInvoiceModel, {
|
CustomerInvoiceTaxModel.belongsTo(CustomerInvoiceModel, {
|
||||||
as: "invoice",
|
as: "invoice",
|
||||||
targetKey: "id",
|
|
||||||
foreignKey: "invoice_id",
|
foreignKey: "invoice_id",
|
||||||
|
targetKey: "id",
|
||||||
onDelete: "CASCADE",
|
onDelete: "CASCADE",
|
||||||
|
onUpdate: "CASCADE",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +75,7 @@ export default (database: Sequelize) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
tax_code: {
|
tax_code: {
|
||||||
type: new DataTypes.STRING(),
|
type: new DataTypes.STRING(40), // Sugerido por IA
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -113,7 +114,11 @@ export default (database: Sequelize) => {
|
|||||||
{
|
{
|
||||||
name: "invoice_id_idx",
|
name: "invoice_id_idx",
|
||||||
fields: ["invoice_id"],
|
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.model";
|
||||||
export * from "./customer-invoice-item.model";
|
export * from "./customer-invoice-item.model";
|
||||||
export * from "./customer-invoice-item-tax.model";
|
|
||||||
export * from "./customer-invoice-tax.model";
|
export * from "./customer-invoice-tax.model";
|
||||||
export * from "./verifactu-record.model";
|
export * from "./verifactu-record.model";
|
||||||
|
|||||||
@ -38,7 +38,13 @@ export const ListIssuedInvoicesResponseSchema = createPaginatedListSchema(
|
|||||||
country: z.string(),
|
country: z.string(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
taxes: z.string(),
|
taxes: z.array(
|
||||||
|
z.object({
|
||||||
|
tax_code: z.string(),
|
||||||
|
taxable_amount: MoneySchema,
|
||||||
|
taxes_amount: MoneySchema,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
discount_percentage: PercentageSchema,
|
discount_percentage: PercentageSchema,
|
||||||
|
|||||||
@ -38,7 +38,13 @@ export const ListProformasResponseSchema = createPaginatedListSchema(
|
|||||||
country: z.string(),
|
country: z.string(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
taxes: z.string(),
|
taxes: z.array(
|
||||||
|
z.object({
|
||||||
|
tax_code: z.string(),
|
||||||
|
taxable_amount: MoneySchema,
|
||||||
|
taxes_amount: MoneySchema,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
subtotal_amount: MoneySchema,
|
subtotal_amount: MoneySchema,
|
||||||
discount_percentage: PercentageSchema,
|
discount_percentage: PercentageSchema,
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import type {
|
|||||||
*/
|
*/
|
||||||
export const IssuedInvoiceSummaryDtoAdapter = {
|
export const IssuedInvoiceSummaryDtoAdapter = {
|
||||||
fromDto(pageDto: IssuedInvoiceSummaryPage, context?: unknown): IssuedInvoiceSummaryPageData {
|
fromDto(pageDto: IssuedInvoiceSummaryPage, context?: unknown): IssuedInvoiceSummaryPageData {
|
||||||
console.log(pageDto);
|
|
||||||
return {
|
return {
|
||||||
...pageDto,
|
...pageDto,
|
||||||
items: pageDto.items.map(
|
items: pageDto.items.map(
|
||||||
|
|||||||
@ -89,7 +89,7 @@ export function useIssuedInvoicesGridColumns(
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const { verifactu } = row.original;
|
const { verifactu } = row.original;
|
||||||
const isPending = verifactu.status === "Pendiente";
|
const isPending = verifactu.status === "Pendiente";
|
||||||
console.log(verifactu.status);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
@ -331,12 +331,11 @@ export function useIssuedInvoicesGridColumns(
|
|||||||
return (
|
return (
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
{/* Descargar en PDF */}
|
{/* Descargar en PDF */}
|
||||||
{/* Descargar en PDF */}
|
|
||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="size-8"
|
className={"size-8"}
|
||||||
disabled={isPDFLoading || !isCompleted}
|
disabled={isPDFLoading || !isCompleted}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
stop(e);
|
stop(e);
|
||||||
@ -346,9 +345,9 @@ export function useIssuedInvoicesGridColumns(
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{isPDFLoading ? (
|
{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>
|
<span className="sr-only">Descargar PDF</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user