Verifactu

This commit is contained in:
David Arranz 2025-11-17 19:12:52 +01:00
parent 89e22de2bd
commit 4c4afe2b3a
14 changed files with 583 additions and 49 deletions

View File

@ -1,7 +1,9 @@
import { Collection, Result, ResultCollection } from "@repo/rdx-utils";
import { Model } from "sequelize";
import { MapperParamsType } from "../../../domain";
import { ISequelizeDomainMapper } from "./sequelize-mapper.interface";
import type { Model } from "sequelize";
import type { MapperParamsType } from "../../../domain";
import type { ISequelizeDomainMapper } from "./sequelize-mapper.interface";
export abstract class SequelizeDomainMapper<TModel extends Model, TModelAttributes, TEntity>
implements ISequelizeDomainMapper<TModel, TModelAttributes, TEntity>

View File

@ -1,4 +1,4 @@
import { DomainMapperWithBulk, IQueryMapperWithBulk } from "../../../domain";
import type { DomainMapperWithBulk, IQueryMapperWithBulk } from "../../../domain";
export interface ISequelizeDomainMapper<TModel, TModelAttributes, TEntity>
extends DomainMapperWithBulk<TModel | TModelAttributes, TEntity> {}

View File

@ -55,6 +55,7 @@ export class IssueProformaUseCase {
proformaId,
transaction
);
if (proformaResult.isFailure) return Result.fail(proformaResult.error);
const proforma = proformaResult.data;
@ -64,6 +65,7 @@ export class IssueProformaUseCase {
proforma.series,
transaction
);
if (nextNumberResult.isFailure) return Result.fail(nextNumberResult.error);
/** 4. Crear factura definitiva (dominio) */
@ -71,6 +73,7 @@ export class IssueProformaUseCase {
issueNumber: nextNumberResult.data,
issueDate: UtcDate.today(),
});
if (issuedInvoiceOrError.isFailure) return Result.fail(issuedInvoiceOrError.error);
/** 5. Guardar la nueva factura */

View File

@ -0,0 +1,284 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.css"
referrerpolicy="no-referrer" />
<title>Factura F26200</title>
<style>
body {
font-family: Tahoma, sans-serif;
margin: 40px;
color: #333;
font-size: 11pt;
line-height: 1.6;
}
header {
font-family: Tahoma, sans-serif;
display: flex;
justify-content: space-between;
margin-bottom: 20px;
margin-bottom: 0;
padding-bottom: 0;
}
.accent-color {
background-color: #F08119;
}
.company-info,
.invoice-meta {
width: 48%;
}
.invoice-meta {
text-align: right;
}
h1 {
font-size: 20px;
margin-bottom: 5px;
}
.contact {
font-size: 14px;
margin-top: 10px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 0px;
margin-bottom: 15px;
}
table th,
table td {
border-top: 0px solid;
border-left: 1px solid #000;
border-right: 1px solid #000;
border-bottom: 0px solid;
padding: 3px 10px;
text-align: left;
vertical-align: top;
}
table th {
margin-bottom: 10px;
border-top: 1px solid #000;
border-bottom: 1px solid #000;
text-align: center;
background-color: #e7e0df;
color: #ff0014;
}
.totals {
margin-top: 20px;
width: 100%;
}
.totals td {
padding: 5px 10px;
}
.totals td.label {
text-align: right;
font-weight: bold;
}
footer {
margin-top: 40px;
font-size: 10px;
}
.highlight {
background-color: #eef;
}
.accent-color {
background-color: #F08119;
}
.info-box {
border: 2px solid black;
border-radius: 12px;
padding: 3px 3px;
display: inline-block;
}
@media print {
* {
-webkit-print-color-adjust: exact;
}
thead {
display: table-header-group;
}
tfoot {
display: table-footer-group;
}
}
</style>
</head>
<body>
<header>
<aside class="flex items-start mb-4 w-full">
<!-- Bloque IZQUIERDO: imagen arriba + texto abajo, alineado a la izquierda -->
<div class="w-[70%] flex flex-col items-start text-left">
<img src="https://rodax-software.com/images/logo_acana.jpg" alt="Logo Acana" class="block h-24 w-auto mb-1" />
<div class="p-3 not-italic text-xs leading-tight" style="font-size: 8pt;">
<p>Aliso Design S.L. B86913910</p>
<p>C/ La Fundición, 27. Pol. Santa Ana</p>
<p>Rivas Vaciamadrid 28522 Madrid</p>
<p>Telf: 91 301 65 57 / 91 301 65 58</p>
<p><a href="mailto:info@acanainteriorismo.com"
class="hover:underline">info@acanainteriorismo.com</a>&nbsp;-&nbsp;<a
href="https://www.acanainteriorismo.com" target="_blank" rel="noopener"
class="hover:underline">www.acanainteriorismo.com</a></p>
</div>
<div class="flex w-full">
<div class="info-box" style="border: 2px solid black; border-radius: 12px; padding: 10px 20px;">
<p>Factura nº:<strong>&nbsp;{{series}}{{invoice_number}}</strong></p>
<p><span>Fecha:<strong>&nbsp;{{invoice_date}}</strong></p>
<p>Página <span class="pageNumber"></span> de <span class="totalPages"></span></p>
</div>
<div class="p-3 ml-9">
<h2 class="font-semibold uppercase mb-1">{{recipient.name}}</h2>
<p>{{recipient.tin}}</p>
<p>{{recipient.street}}</p>
<p>{{recipient.postal_code}}&nbsp;&nbsp;{{recipient.city}}&nbsp;&nbsp;{{recipient.province}}</p>
</div>
</div>
</div>
<!-- Bloque DERECHO: logo2 arriba y texto DEBAJO -->
<div class="ml-auto flex flex-col items-end text-right">
<img src="https://rodax-software.com/images/factura_acana.jpg" alt="Factura"
class="block h-14 w-auto md:h-8 mb-1" />
</div>
</aside>
</header>
<main id="main">
<section id="details" class="border-b border-black ">
<!-- Tu tabla -->
<table class="table-header">
<thead>
<tr>
<th class="py-2">Concepto</th>
<th class="py-2">Ud.</th>
<th class="py-2">Imp.</th>
<th class="py-2">&nbsp;</th>
<th class="py-2">Imp.&nbsp;total</th>
</tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{description}}</td>
<td class="text-right">{{#if quantity}}{{quantity}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if unit_amount}}{{unit_amount}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if discount_percentage}}{{discount_percentage}}{{else}}&nbsp;{{/if}}</td>
<td class="text-right">{{#if taxable_amount}}{{taxable_amount}}{{else}}&nbsp;{{/if}}</td>
</td>
</tr>
{{/each}}
</tbody>
</table>
</section>
<section id="resume" class="flex items-center justify-between pb-4 mb-4">
<div class="grow relative pt-10 self-start">
{{#if payment_method}}
<div class="">
<p class=" text-sm"><strong>Forma de pago:</strong> {{payment_method}}</p>
</div>
{{else}}
<!-- Empty payment method-->
{{/if}}
{{#if notes}}
<div class="pt-4">
<p class="text-sm"><strong>Notas:</strong> {{notes}} </p>
</div>
{{else}}
<!-- Empty notes-->
{{/if}}
</div>
<div class="relative pt-10 grow">
<table class=" table-header min-w-full bg-transparent">
<tbody>
{{#if discount_percentage}}
<tr>
<td></td>
<td class="px-4 text-right">Importe&nbsp;neto</td>
<td class="w-5">&nbsp;</td>
<td class="px-4 text-right">{{subtotal_amount}}</td>
</tr>
<tr>
<td></td>
<td class="px-4 text-right">Descuento&nbsp;{{discount_percentage}}</td>
<td class="w-5">&nbsp;</td>
<td class="px-4 text-right">{{discount_amount.value}}</td>
</tr>
{{else}}
<!-- dto 0-->
{{/if}}
<tr>
<td></td>
<td class="px-4 text-right">Base&nbsp;imponible</td>
<td class="w-5">&nbsp;</td>
<td class="px-4 text-right">{{taxable_amount}}</td>
</tr>
{{#each taxes}}
<tr>
<td></td>
<td class="px-4 text-right">{{tax_name}}</td>
<td class="w-5">&nbsp;</td>
<td class="px-4 text-right">{{taxes_amount}}</td>
</tr>
{{/each}}
<tr class="">
<td></td>
<td class="px-4 text-right accent-color">
Total&nbsp;factura
</td>
<td class="w-5">&nbsp;</td>
<td class="px-4 text-right accent-color">
{{total_amount}}</td>
</tr>
</tbody>
</table>
</div>
</section>
</main>
<footer id="footer" class="mt-4">
<aside>
<p class="text-center">Insc. en el Reg. Merc. de Madrid, Tomo 31.839, Libro 0, Folio 191, Sección 8, Hoja M-572991
CIF: B86913910</p>
<p class="text-left" style="font-size: 6pt;">Información en protección de datos<br />De conformidad con lo
dispuesto en el RGPD y LOPDGDD,
informamos que los datos personales serán tratados por
ALISO DESIGN S.L para cumplir con la obligación tributaria de emitir facturas. Podrá solicitar más información,
y ejercer sus derechos escribiendo a info@acanainteriorismo.com o mediante correo postal a la dirección CALLE LA
FUNDICION 27 POL. IND. SANTA ANA (28522) RIVAS-VACIAMADRID, MADRID. Para el ejercicio de sus derechos, en caso
de que sea necesario, se le solicitará documento que acredite su identidad. Si siente vulnerados sus derechos
puede presentar una reclamación ante la AEPD, en su web: www.aepd.es.</p>
</aside>
</footer>
</body>
</html>

View File

@ -7,6 +7,8 @@ export interface VerifactuRecordProps {
estado: VerifactuRecordEstado;
url: Maybe<URLAddress>;
qrCode: Maybe<string>;
uuid: Maybe<string>;
operacion: Maybe<string>;
}
export class VerifactuRecord extends DomainEntity<VerifactuRecordProps> {
@ -32,6 +34,14 @@ export class VerifactuRecord extends DomainEntity<VerifactuRecordProps> {
return this.props.qrCode;
}
get uuid(): Maybe<string> {
return this.props.uuid;
}
get operacion(): Maybe<string> {
return this.props.operacion;
}
getProps(): VerifactuRecordProps {
return this.props;
}
@ -45,6 +55,8 @@ export class VerifactuRecord extends DomainEntity<VerifactuRecordProps> {
status: this.estado.toString(),
url: toEmptyString(this.url, (value) => value.toString()),
qr_code: toEmptyString(this.qrCode, (value) => value.toString()),
uuid: toEmptyString(this.uuid, (value) => value.toString()),
operacion: toEmptyString(this.operacion, (value) => value.toString()),
};
}
}

View File

@ -1,13 +1,18 @@
import type { UtcDate } from "@repo/rdx-ddd";
import { UniqueID, type UtcDate } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import { CustomerInvoice } from "../aggregates";
import { VerifactuRecord } from "../entities";
import { EntityIsNotProformaError, ProformaCannotBeConvertedToInvoiceError } from "../errors";
import {
CustomerInvoiceIsProformaSpecification,
ProformaCanTranstionToIssuedSpecification,
} from "../specs";
import { type CustomerInvoiceNumber, CustomerInvoiceStatus } from "../value-objects";
import {
type CustomerInvoiceNumber,
CustomerInvoiceStatus,
VerifactuRecordEstado,
} from "../value-objects";
/**
* Servicio de dominio que encapsula la lógica de emisión de factura definitiva desde una proforma.
@ -43,6 +48,23 @@ export class IssueCustomerInvoiceDomainService {
return Result.fail(new ProformaCannotBeConvertedToInvoiceError(proforma.id.toString()));
}
const verifactuRecordOrError = VerifactuRecord.create(
{
estado: VerifactuRecordEstado.createPendiente(),
qrCode: Maybe.none(),
url: Maybe.none(),
uuid: Maybe.none(),
operacion: Maybe.none(),
},
UniqueID.generateNewID()
);
if (verifactuRecordOrError.isFailure) {
return Result.fail(new ProformaCannotBeConvertedToInvoiceError(proforma.id.toString()));
}
const verifactuRecord = verifactuRecordOrError.data;
/** 3. Generar la nueva factura definitiva (inmutable) */
const proformaProps = proforma.getProps();
const newInvoiceOrError = CustomerInvoice.create({
@ -53,6 +75,7 @@ export class IssueCustomerInvoiceDomainService {
invoiceNumber: issueNumber,
invoiceDate: issueDate,
description: proformaProps.description.isNone() ? Maybe.some(".") : proformaProps.description,
verifactu: Maybe.some(verifactuRecord),
});
if (newInvoiceOrError.isFailure) {

View File

@ -1,25 +1,34 @@
import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import {
type ISequelizeDomainMapper,
type MapperParamsType,
SequelizeDomainMapper,
} from "@erp/core/api";
import {
UniqueID,
ValidationErrorCollection,
type ValidationErrorDetail,
extractOrPushError,
maybeFromNullableVO,
toNullable,
UniqueID,
ValidationErrorCollection,
ValidationErrorDetail,
} from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import {
CustomerInvoice,
type CustomerInvoice,
CustomerInvoiceItem,
CustomerInvoiceItemDescription,
CustomerInvoiceItemProps,
CustomerInvoiceProps,
type CustomerInvoiceItemProps,
type CustomerInvoiceProps,
ItemAmount,
ItemDiscount,
ItemQuantity,
ItemTaxes,
} from "../../../domain";
import { CustomerInvoiceItemCreationAttributes, CustomerInvoiceItemModel } from "../../sequelize";
import type {
CustomerInvoiceItemCreationAttributes,
CustomerInvoiceItemModel,
} from "../../sequelize";
import { ItemTaxesDomainMapper } from "./item-taxes.mapper";
export interface ICustomerInvoiceItemDomainMapper

View File

@ -1,31 +1,38 @@
import { ISequelizeDomainMapper, MapperParamsType, SequelizeDomainMapper } from "@erp/core/api";
import {
type ISequelizeDomainMapper,
type MapperParamsType,
SequelizeDomainMapper,
} from "@erp/core/api";
import {
CurrencyCode,
extractOrPushError,
LanguageCode,
maybeFromNullableVO,
Percentage,
TextValue,
toNullable,
UniqueID,
UtcDate,
ValidationErrorCollection,
ValidationErrorDetail,
type ValidationErrorDetail,
extractOrPushError,
maybeFromNullableVO,
toNullable,
} from "@repo/rdx-ddd";
import { Collection, isNullishOrEmpty, Maybe, Result } from "@repo/rdx-utils";
import { Collection, Maybe, Result, isNullishOrEmpty } from "@repo/rdx-utils";
import {
CustomerInvoice,
CustomerInvoiceItems,
CustomerInvoiceNumber,
CustomerInvoiceProps,
type CustomerInvoiceProps,
CustomerInvoiceSerie,
CustomerInvoiceStatus,
InvoicePaymentMethod,
} from "../../../domain";
import { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
import type { CustomerInvoiceCreationAttributes, CustomerInvoiceModel } from "../../sequelize";
import { CustomerInvoiceItemDomainMapper } from "./customer-invoice-item.mapper";
import { InvoiceRecipientDomainMapper } from "./invoice-recipient.mapper";
import { TaxesDomainMapper } from "./invoice-taxes.mapper";
import { CustomerInvoiceVerifactuDomainMapper } from "./invoice-verifactu.mapper";
export interface ICustomerInvoiceDomainMapper
extends ISequelizeDomainMapper<
@ -45,6 +52,7 @@ export class CustomerInvoiceDomainMapper
private _itemsMapper: CustomerInvoiceItemDomainMapper;
private _recipientMapper: InvoiceRecipientDomainMapper;
private _taxesMapper: TaxesDomainMapper;
private _verifactuMapper: CustomerInvoiceVerifactuDomainMapper;
constructor(params: MapperParamsType) {
super();
@ -52,6 +60,7 @@ export class CustomerInvoiceDomainMapper
this._itemsMapper = new CustomerInvoiceItemDomainMapper(params); // Instanciar el mapper de items
this._recipientMapper = new InvoiceRecipientDomainMapper();
this._taxesMapper = new TaxesDomainMapper(params);
this._verifactuMapper = new CustomerInvoiceVerifactuDomainMapper();
}
private _mapAttributesToDomain(source: CustomerInvoiceModel, params?: MapperParamsType) {
@ -219,7 +228,22 @@ export class CustomerInvoiceDomainMapper
});
}*/
// 3) Items (colección)
// 3) Verifactu (snapshot en la factura o include)
const verifactuResult = this._verifactuMapper.mapToDomain(source.verifactu, {
errors,
attributes,
...params,
});
/*if (verifactuResult.isFailure) {
errors.push({
path: "verifactu",
message: verifactuResult.error.message,
});
}*/
// 4) Items (colección)
const itemsResults = this._itemsMapper.mapToDomainCollection(
source.items,
source.items.length,
@ -249,6 +273,7 @@ export class CustomerInvoiceDomainMapper
// 6) Construcción del agregado (Dominio)
const verifactu = verifactuResult.data;
const recipient = recipientResult.data;
const items = CustomerInvoiceItems.create({
@ -283,6 +308,7 @@ export class CustomerInvoiceDomainMapper
paymentMethod: attributes.paymentMethod!,
items,
verifactu,
};
const createResult = CustomerInvoice.create(invoiceProps, attributes.invoiceId);
@ -342,7 +368,14 @@ export class CustomerInvoiceDomainMapper
...params,
});
// 4) Si hubo errores de mapeo, devolvemos colección de validación
// 4) Verifactu
const verifactuResult = this._verifactuMapper.mapToPersistence(source.verifactu, {
errors,
parent: source,
...params,
});
// 5) Si hubo errores de mapeo, devolvemos colección de validación
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Customer invoice mapping to persistence failed", errors)
@ -351,6 +384,7 @@ export class CustomerInvoiceDomainMapper
const items = itemsResult.data;
const taxes = taxesResult.data;
const verifactu = verifactuResult.data;
const allAmounts = source.getAllAmounts(); // Da los totales ya calculados
@ -404,6 +438,7 @@ export class CustomerInvoiceDomainMapper
taxes,
items,
verifactu,
};
return Result.ok<CustomerInvoiceCreationAttributes>(

View File

@ -1,21 +1,22 @@
import { MapperParamsType } from "@erp/core/api";
import type { MapperParamsType } from "@erp/core/api";
import {
City,
Country,
extractOrPushError,
maybeFromNullableVO,
Name,
PostalCode,
Province,
Street,
TINNumber,
toNullable,
ValidationErrorCollection,
ValidationErrorDetail,
type ValidationErrorDetail,
extractOrPushError,
maybeFromNullableVO,
toNullable,
} from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import { CustomerInvoice, CustomerInvoiceProps, InvoiceRecipient } from "../../../domain";
import { CustomerInvoiceModel } from "../../sequelize";
import { type CustomerInvoice, type CustomerInvoiceProps, InvoiceRecipient } from "../../../domain";
import type { CustomerInvoiceModel } from "../../sequelize";
export class InvoiceRecipientDomainMapper {
public mapToDomain(
@ -133,7 +134,7 @@ export class InvoiceRecipientDomainMapper {
const { isProforma, hasRecipient } = parent;
// Validación: facturas emitidas deben tener destinatario.
if (!isProforma && !hasRecipient) {
if (!(isProforma || hasRecipient)) {
errors.push({
path: "recipient",
message: "[CustomerInvoiceDomainMapper] Issued customer invoice without recipient data",

View File

@ -0,0 +1,142 @@
import type { MapperParamsType } from "@erp/core/api";
import { type ISequelizeDomainMapper, SequelizeDomainMapper } from "@erp/core/api";
import {
URLAddress,
UniqueID,
ValidationErrorCollection,
type ValidationErrorDetail,
extractOrPushError,
maybeFromNullableVO,
toNullable,
} from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils";
import {
type CustomerInvoice,
type CustomerInvoiceProps,
VerifactuRecord,
VerifactuRecordEstado,
} from "../../../domain";
import type { VerifactuRecordCreationAttributes, VerifactuRecordModel } from "../../sequelize";
export interface ICustomerInvoiceVerifactuDomainMapper
extends ISequelizeDomainMapper<
VerifactuRecordModel,
VerifactuRecordCreationAttributes,
Maybe<VerifactuRecord>
> {}
export class CustomerInvoiceVerifactuDomainMapper
extends SequelizeDomainMapper<
VerifactuRecordModel,
VerifactuRecordCreationAttributes,
Maybe<VerifactuRecord>
>
implements ICustomerInvoiceVerifactuDomainMapper
{
public mapToDomain(
source: VerifactuRecordModel,
params?: MapperParamsType
): Result<Maybe<VerifactuRecord>, Error> {
const { errors, attributes } = params as {
errors: ValidationErrorDetail[];
attributes: Partial<CustomerInvoiceProps>;
};
if (!source) {
return Result.ok(Maybe.none());
}
const recordId = extractOrPushError(UniqueID.create(source.id), "id", errors);
const estado = extractOrPushError(
VerifactuRecordEstado.create(source.estado),
"estado",
errors
);
const qr = extractOrPushError(
maybeFromNullableVO(source.qr, (value) => Result.ok(String(value))),
"qr",
errors
);
const url = extractOrPushError(
maybeFromNullableVO(source.url, (value) => URLAddress.create(value)),
"url",
errors
);
const uuid = extractOrPushError(
maybeFromNullableVO(source.uuid, (value) => Result.ok(String(value))),
"uuid",
errors
);
const operacion = extractOrPushError(
maybeFromNullableVO(source.operacion, (value) => Result.ok(String(value))),
"operacion",
errors
);
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Verifactu record mapping failed [mapToDTO]", errors)
);
}
const createResult = VerifactuRecord.create(
{
estado: estado!,
qrCode: qr!,
url: url!,
uuid: uuid!,
operacion: operacion!,
},
recordId!
);
if (createResult.isFailure) {
return Result.fail(
new ValidationErrorCollection("Invoice verifactu entity creation failed", [
{ path: "verifactu", message: createResult.error.message },
])
);
}
return Result.ok(Maybe.some(createResult.data));
}
mapToPersistence(
source: Maybe<VerifactuRecord>,
params?: MapperParamsType
): Result<VerifactuRecordCreationAttributes, Error> {
const { errors, parent } = params as {
parent: CustomerInvoice;
errors: ValidationErrorDetail[];
};
if (source.isNone()) {
return Result.ok({
id: UniqueID.generateNewID().toPrimitive(),
invoice_id: parent.id.toPrimitive(),
estado: VerifactuRecordEstado.createPendiente().toPrimitive(),
qr: null,
url: null,
uuid: null,
operacion: null,
});
}
const verifactu = source.unwrap();
return Result.ok({
id: verifactu.id.toPrimitive(),
invoice_id: parent.id.toPrimitive(),
estado: verifactu.estado.toPrimitive(),
qr: toNullable(verifactu.qrCode, (v) => v),
url: toNullable(verifactu.url, (v) => v.toPrimitive()),
uuid: toNullable(verifactu.uuid, (v) => v),
operacion: toNullable(verifactu.operacion, (v) => v),
});
}
}

View File

@ -46,6 +46,18 @@ export class VerifactuRecordListMapper
errors
);
const uuid = extractOrPushError(
maybeFromNullableVO(raw.uuid, (value) => Result.ok(String(value))),
"uuid",
errors
);
const operacion = extractOrPushError(
maybeFromNullableVO(raw.operacion, (value) => Result.ok(String(value))),
"operacion",
errors
);
if (errors.length > 0) {
return Result.fail(
new ValidationErrorCollection("Verifactu record mapping failed [mapToDTO]", errors)
@ -57,6 +69,8 @@ export class VerifactuRecordListMapper
estado: estado!,
qrCode: qr!,
url: url!,
uuid: uuid!,
operacion: operacion!,
},
recordId!
);

View File

@ -17,14 +17,18 @@ import type {
CustomerInvoiceTaxCreationAttributes,
CustomerInvoiceTaxModel,
} from "./customer-invoice-tax.model";
import type { VerifactuRecordModel } from "./verifactu-record.model";
import type {
VerifactuRecordCreationAttributes,
VerifactuRecordModel,
} from "./verifactu-record.model";
export type CustomerInvoiceCreationAttributes = InferCreationAttributes<
CustomerInvoiceModel,
{ omit: "items" | "taxes" | "current_customer" }
{ omit: "items" | "taxes" | "current_customer" | "verifactu" }
> & {
items?: CustomerInvoiceItemCreationAttributes[];
taxes?: CustomerInvoiceTaxCreationAttributes[];
verifactu?: VerifactuRecordCreationAttributes;
};
export class CustomerInvoiceModel extends Model<

View File

@ -1,4 +1,5 @@
import {
type CreationOptional,
DataTypes,
type InferAttributes,
type InferCreationAttributes,
@ -6,7 +7,10 @@ import {
type Sequelize,
} from "sequelize";
export type VerifactuRecordCreationAttributes = InferCreationAttributes<VerifactuRecordModel>;
export type VerifactuRecordCreationAttributes = InferCreationAttributes<
VerifactuRecordModel,
{ omit: "url" | "qr" | "uuid" | "operacion" }
>;
export class VerifactuRecordModel extends Model<
InferAttributes<VerifactuRecordModel>,
@ -14,13 +18,12 @@ export class VerifactuRecordModel extends Model<
> {
declare id: string;
declare invoice_id: string;
declare estado: string;
declare url: string;
declare qr: Blob;
declare uuid: string;
declare operacion: string;
declare url: CreationOptional<string>;
declare qr: CreationOptional<Blob>;
declare uuid: CreationOptional<string>;
declare operacion: CreationOptional<string>;
static associate(database: Sequelize) {
const models = database.models;
@ -64,29 +67,31 @@ export default (database: Sequelize) => {
estado: {
type: new DataTypes.TEXT(),
allowNull: true,
defaultValue: null,
allowNull: false,
},
url: {
type: new DataTypes.TEXT(),
allowNull: false,
defaultValue: "",
},
qr: {
type: new DataTypes.BLOB(),
allowNull: false,
defaultValue: "",
},
uuid: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: "",
},
operacion: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
allowNull: false,
defaultValue: "",
},
},
{

View File

@ -28,11 +28,11 @@
"proformas": {
"status": {
"all": "Todas",
"draft": "Borradores",
"sent": "Enviadas",
"approved": "Aprovadas",
"rejected": "Rechazadas",
"issued": "Emitidas"
"draft": "Borrador",
"sent": "Enviada",
"approved": "Aprobada",
"rejected": "Rechazada",
"issued": "Emitida"
}
},
"issued_invoices": {