Facturas de cliente
This commit is contained in:
parent
75a0294cb1
commit
5f08cfaa15
@ -1,5 +1,5 @@
|
|||||||
import { IPresenterRegistry } from "./presenter-registry.interface";
|
import type { IPresenter, IPresenterOutputParams } from "./presenter.interface";
|
||||||
import { IPresenter, IPresenterOutputParams } from "./presenter.interface";
|
import type { IPresenterRegistry } from "./presenter-registry.interface";
|
||||||
|
|
||||||
export abstract class Presenter<TSource = unknown, TOutput = unknown>
|
export abstract class Presenter<TSource = unknown, TOutput = unknown>
|
||||||
implements IPresenter<TSource, TOutput>
|
implements IPresenter<TSource, TOutput>
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
// --- Adaptador que carga el catálogo JSON en memoria e indexa por code ---
|
// --- Adaptador que carga el catálogo JSON en memoria e indexa por code ---
|
||||||
|
|
||||||
import { Maybe } from "@repo/rdx-utils";
|
import { Maybe } from "@repo/rdx-utils";
|
||||||
import { TaxCatalogProvider } from "./tax-catalog.provider";
|
|
||||||
import { TaxCatalogType, TaxItemType, TaxLookupItems } from "./tax-catalog-types";
|
import type { TaxCatalogProvider } from "./tax-catalog.provider";
|
||||||
|
import type { TaxCatalogType, TaxItemType, TaxLookupItems } from "./tax-catalog-types";
|
||||||
|
|
||||||
export class JsonTaxCatalogProvider implements TaxCatalogProvider {
|
export class JsonTaxCatalogProvider implements TaxCatalogProvider {
|
||||||
// Índice por código normalizado
|
// Índice por código normalizado
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
// --- Puerto (interfaz) para resolver tasas desde un catálogo ---
|
import type { Maybe } from "@repo/rdx-utils"; // Usa tu implementación real de Maybe
|
||||||
|
|
||||||
import { Maybe } from "@repo/rdx-utils"; // Usa tu implementación real de Maybe
|
import type { TaxCatalogType, TaxItemType, TaxLookupItems } from "./tax-catalog-types";
|
||||||
import { TaxCatalogType, TaxItemType, TaxLookupItems } from "./tax-catalog-types";
|
|
||||||
|
|
||||||
export interface TaxCatalogProvider {
|
export interface TaxCatalogProvider {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -3,11 +3,14 @@ import type { UniqueID } from "@repo/rdx-ddd";
|
|||||||
import { type Collection, Maybe, Result } from "@repo/rdx-utils";
|
import { type Collection, Maybe, Result } from "@repo/rdx-utils";
|
||||||
import type { Transaction } from "sequelize";
|
import type { Transaction } from "sequelize";
|
||||||
|
|
||||||
import type {
|
import {
|
||||||
CustomerInvoiceNumber,
|
CustomerInvoiceIsProformaSpecification,
|
||||||
CustomerInvoiceSerie,
|
type CustomerInvoiceNumber,
|
||||||
CustomerInvoiceStatus,
|
type CustomerInvoiceSerie,
|
||||||
ICustomerInvoiceNumberGenerator,
|
type CustomerInvoiceStatus,
|
||||||
|
type ICustomerInvoiceNumberGenerator,
|
||||||
|
ProformaCannotBeDeletedError,
|
||||||
|
StatusInvoiceIsDraftSpecification,
|
||||||
} from "../../domain";
|
} from "../../domain";
|
||||||
import {
|
import {
|
||||||
CustomerInvoice,
|
CustomerInvoice,
|
||||||
@ -69,6 +72,27 @@ export class CustomerInvoiceApplicationService {
|
|||||||
return CustomerInvoice.create({ ...props, companyId }, proformaId);
|
return CustomerInvoice.create({ ...props, companyId }, proformaId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guarda una nueva factura y devuelve la factura guardada.
|
||||||
|
*
|
||||||
|
* @param companyId - Identificador de la empresa a la que pertenece la factura.
|
||||||
|
* @param proforma - La factura a guardar.
|
||||||
|
* @param transaction - Transacción activa para la operación.
|
||||||
|
* @returns Result<CustomerInvoice, Error> - La factura guardada o un error si falla la operación.
|
||||||
|
*/
|
||||||
|
async createIssueInvoiceInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
invoice: CustomerInvoice,
|
||||||
|
transaction: Transaction
|
||||||
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
|
const result = await this.repository.create(invoice, transaction);
|
||||||
|
if (result.isFailure) {
|
||||||
|
return Result.fail(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getIssueInvoiceByIdInCompany(companyId, invoice.id, transaction);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guarda una nueva proforma y devuelve la proforma guardada.
|
* Guarda una nueva proforma y devuelve la proforma guardada.
|
||||||
*
|
*
|
||||||
@ -77,7 +101,7 @@ export class CustomerInvoiceApplicationService {
|
|||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<CustomerInvoice, Error> - La proforma guardada o un error si falla la operación.
|
* @returns Result<CustomerInvoice, Error> - La proforma guardada o un error si falla la operación.
|
||||||
*/
|
*/
|
||||||
async createInvoiceInCompany(
|
async createProformaInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
proforma: CustomerInvoice,
|
proforma: CustomerInvoice,
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
@ -113,7 +137,27 @@ export class CustomerInvoiceApplicationService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Comprueba si existe o no en persistencia una factura con el ID proporcionado
|
* Comprueba si existe o no en persistencia una proforma con el ID proporcionado
|
||||||
|
*
|
||||||
|
* @param companyId - Identificador de la empresa a la que pertenece la factura.
|
||||||
|
* @param proformaId - Identificador UUID de la factura.
|
||||||
|
* @param transaction - Transacción activa para la operación.
|
||||||
|
* @returns Result<Boolean, Error> - Existe la factura o no.
|
||||||
|
*/
|
||||||
|
|
||||||
|
async existsProformaByIdInCompany(
|
||||||
|
companyId: UniqueID,
|
||||||
|
proformaId: UniqueID,
|
||||||
|
transaction?: Transaction
|
||||||
|
): Promise<Result<boolean, Error>> {
|
||||||
|
return this.repository.existsByIdInCompany(companyId, proformaId, transaction, {
|
||||||
|
is_proforma: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Comprueba si existe o no en persistencia una proforma con el ID proporcionado
|
||||||
*
|
*
|
||||||
* @param companyId - Identificador de la empresa a la que pertenece la factura.
|
* @param companyId - Identificador de la empresa a la que pertenece la factura.
|
||||||
* @param invoiceId - Identificador UUID de la factura.
|
* @param invoiceId - Identificador UUID de la factura.
|
||||||
@ -121,12 +165,14 @@ export class CustomerInvoiceApplicationService {
|
|||||||
* @returns Result<Boolean, Error> - Existe la factura o no.
|
* @returns Result<Boolean, Error> - Existe la factura o no.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async existsByIdInCompany(
|
async existsIssueInvoiceByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
invoiceId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: Transaction
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
return this.repository.existsByIdInCompany(companyId, invoiceId, transaction);
|
return this.repository.existsByIdInCompany(companyId, invoiceId, transaction, {
|
||||||
|
is_proforma: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -199,13 +245,13 @@ export class CustomerInvoiceApplicationService {
|
|||||||
changes: CustomerInvoicePatchProps,
|
changes: CustomerInvoicePatchProps,
|
||||||
transaction?: Transaction
|
transaction?: Transaction
|
||||||
): Promise<Result<CustomerInvoice, Error>> {
|
): Promise<Result<CustomerInvoice, Error>> {
|
||||||
const invoiceResult = await this.getProformaByIdInCompany(companyId, proformaId, transaction);
|
const proformaResult = await this.getProformaByIdInCompany(companyId, proformaId, transaction);
|
||||||
|
|
||||||
if (invoiceResult.isFailure) {
|
if (proformaResult.isFailure) {
|
||||||
return Result.fail(invoiceResult.error);
|
return Result.fail(proformaResult.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = invoiceResult.data.update(changes);
|
const updated = proformaResult.data.update(changes);
|
||||||
|
|
||||||
if (updated.isFailure) {
|
if (updated.isFailure) {
|
||||||
return Result.fail(updated.error);
|
return Result.fail(updated.error);
|
||||||
@ -215,27 +261,48 @@ export class CustomerInvoiceApplicationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Elimina (o marca como eliminada) una factura según su ID.
|
* Elimina (o marca como eliminada) una proforma según su ID.
|
||||||
*
|
*
|
||||||
* @param companyId - Identificador de la empresa a la que pertenece la factura.
|
* @param companyId - Identificador de la empresa a la que pertenece la proforma.
|
||||||
* @param invoiceId - Identificador UUID de la factura.
|
* @param proformaId - Identificador UUID de la proforma.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<boolean, Error> - Resultado de la operación.
|
* @returns Result<boolean, Error> - Resultado de la operación.
|
||||||
*/
|
*/
|
||||||
async deleteInvoiceByIdInCompany(
|
async deleteProformaByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
invoiceId: UniqueID,
|
proformaId: UniqueID,
|
||||||
transaction?: Transaction
|
transaction?: Transaction
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
return this.repository.deleteByIdInCompany(companyId, invoiceId, transaction);
|
// 1) Buscar la proforma
|
||||||
|
const proformaResult = await this.getProformaByIdInCompany(companyId, proformaId, transaction);
|
||||||
|
|
||||||
|
if (proformaResult.isFailure) return Result.fail(proformaResult.error);
|
||||||
|
const proforma = proformaResult.data;
|
||||||
|
|
||||||
|
// 2) Validar: es proforma
|
||||||
|
const isProforma = new CustomerInvoiceIsProformaSpecification();
|
||||||
|
if (!(await isProforma.isSatisfiedBy(proforma))) {
|
||||||
|
return Result.fail(new ProformaCannotBeDeletedError(proformaId.toString(), "not a proforma"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Validar: estado draft
|
||||||
|
const isDraft = new StatusInvoiceIsDraftSpecification();
|
||||||
|
if (!(await isDraft.isSatisfiedBy(proforma))) {
|
||||||
|
return Result.fail(
|
||||||
|
new ProformaCannotBeDeletedError(proformaId.toString(), "status is not 'draft'")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Borrar la proforma (baja lógica)
|
||||||
|
return this.repository.deleteProformaByIdInCompany(companyId, proformaId, transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Actualiza el "status" de una proforma
|
* Actualiza el "status" de una proforma
|
||||||
*
|
*
|
||||||
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
* @param companyId - Identificador UUID de la empresa a la que pertenece la proforma.
|
||||||
* @param proformaId - UUID de la factura a eliminar.
|
* @param proformaId - UUID de la proforma a actualizar.
|
||||||
* @param newStatus - nuevo estado
|
* @param newStatus - nuevo estado
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<boolean, Error>
|
* @returns Result<boolean, Error>
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export class ChangeStatusProformaUseCase {
|
|||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
/** 1. Recuperamos la proforma */
|
/** 1. Recuperamos la proforma */
|
||||||
const proformaResult = await this.service.getInvoiceByIdInCompany(
|
const proformaResult = await this.service.getProformaByIdInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
proformaId,
|
proformaId,
|
||||||
transaction
|
transaction
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { EntityNotFoundError, type ITransactionManager } from "@erp/core/api";
|
import type { ITransactionManager } from "@erp/core/api";
|
||||||
import { UniqueID } from "@repo/rdx-ddd";
|
import { UniqueID } from "@repo/rdx-ddd";
|
||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
@ -19,32 +19,15 @@ export class DeleteProformaUseCase {
|
|||||||
const { proforma_id, companyId } = params;
|
const { proforma_id, companyId } = params;
|
||||||
|
|
||||||
const idOrError = UniqueID.create(proforma_id);
|
const idOrError = UniqueID.create(proforma_id);
|
||||||
|
|
||||||
if (idOrError.isFailure) {
|
if (idOrError.isFailure) {
|
||||||
return Result.fail(idOrError.error);
|
return Result.fail(idOrError.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const invoiceId = idOrError.data;
|
const proformaId = idOrError.data;
|
||||||
|
|
||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
const existsCheck = await this.service.existsByIdInCompany(
|
return await this.service.deleteProformaByIdInCompany(companyId, proformaId, transaction);
|
||||||
companyId,
|
|
||||||
invoiceId,
|
|
||||||
transaction
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existsCheck.isFailure) {
|
|
||||||
return Result.fail(existsCheck.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
const invoiceExists = existsCheck.data;
|
|
||||||
|
|
||||||
if (!invoiceExists) {
|
|
||||||
return Result.fail(new EntityNotFoundError("Proforma", "id", invoiceId.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.service.deleteInvoiceByIdInCompany(companyId, invoiceId, transaction);
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
return Result.fail(error as Error);
|
return Result.fail(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export class IssueProformaInvoiceUseCase {
|
|||||||
return this.transactionManager.complete(async (transaction) => {
|
return this.transactionManager.complete(async (transaction) => {
|
||||||
try {
|
try {
|
||||||
/** 1. Recuperamos la proforma */
|
/** 1. Recuperamos la proforma */
|
||||||
const proformaResult = await this.service.getInvoiceByIdInCompany(
|
const proformaResult = await this.service.getProformaByIdInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
proformaId,
|
proformaId,
|
||||||
transaction
|
transaction
|
||||||
@ -74,7 +74,7 @@ export class IssueProformaInvoiceUseCase {
|
|||||||
if (issuedInvoiceOrError.isFailure) return Result.fail(issuedInvoiceOrError.error);
|
if (issuedInvoiceOrError.isFailure) return Result.fail(issuedInvoiceOrError.error);
|
||||||
|
|
||||||
/** 5. Guardar la nueva factura */
|
/** 5. Guardar la nueva factura */
|
||||||
const saveInvoiceResult = await this.service.createInvoiceInCompany(
|
const saveInvoiceResult = await this.service.createIssueInvoiceInCompany(
|
||||||
companyId,
|
companyId,
|
||||||
issuedInvoiceOrError.data,
|
issuedInvoiceOrError.data,
|
||||||
transaction
|
transaction
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
@ -8,7 +8,7 @@
|
|||||||
<title>Factura F26200</title>
|
<title>Factura F26200</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Tahoma, sans-serif;
|
||||||
margin: 40px;
|
margin: 40px;
|
||||||
color: #333;
|
color: #333;
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
@ -55,7 +55,9 @@
|
|||||||
|
|
||||||
table th,
|
table th,
|
||||||
table td {
|
table td {
|
||||||
border: 0px solid;
|
border-top: 1px solid;
|
||||||
|
border-left: 1px solid;
|
||||||
|
border-bottom: 0px solid;
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
@ -114,13 +116,24 @@
|
|||||||
<aside class="flex items-start mb-4 w-full">
|
<aside class="flex items-start mb-4 w-full">
|
||||||
<!-- Bloque IZQUIERDO: imagen arriba + texto abajo, alineado a la izquierda -->
|
<!-- Bloque IZQUIERDO: imagen arriba + texto abajo, alineado a la izquierda -->
|
||||||
<div class="w-[70%] flex flex-col items-start text-left">
|
<div class="w-[70%] flex flex-col items-start text-left">
|
||||||
<img src="https://rodax-software.com/images/logo1.jpg" alt="Logo Rodax" class="block h-14 w-auto mb-1" />
|
<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> - <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="flex w-full">
|
||||||
<div class="p-1 ">
|
<div class="p-3 ">
|
||||||
<p>Factura nº:<strong> {{series}}{{invoice_number}}</strong></p>
|
<p>Factura nº:<strong> {{series}}{{invoice_number}}</strong></p>
|
||||||
<p><span>Fecha:<strong> {{invoice_date}}</strong></p>
|
<p><span>Fecha:<strong> {{invoice_date}}</strong></p>
|
||||||
|
<p>Página <span class="pageNumber"></span> de <span class="totalPages"></span></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-1 ml-9">
|
<div class="p-3 ml-9">
|
||||||
<h2 class="font-semibold uppercase mb-1">{{recipient.name}}</h2>
|
<h2 class="font-semibold uppercase mb-1">{{recipient.name}}</h2>
|
||||||
<p>{{recipient.tin}}</p>
|
<p>{{recipient.tin}}</p>
|
||||||
<p>{{recipient.street}}</p>
|
<p>{{recipient.street}}</p>
|
||||||
@ -131,14 +144,8 @@
|
|||||||
|
|
||||||
<!-- Bloque DERECHO: logo2 arriba y texto DEBAJO -->
|
<!-- Bloque DERECHO: logo2 arriba y texto DEBAJO -->
|
||||||
<div class="ml-auto flex flex-col items-end text-right">
|
<div class="ml-auto flex flex-col items-end text-right">
|
||||||
<img src="https://rodax-software.com/images/logo2.jpg" alt="Logo secundario"
|
<img src="https://rodax-software.com/images/factura_acana.jpg" alt="Factura"
|
||||||
class="block h-5 w-auto md:h-8 mb-1" />
|
class="block h-14 w-auto md:h-8 mb-1" />
|
||||||
<div class="not-italic text-xs leading-tight">
|
|
||||||
<p>Telf: 91 785 02 47 / 686 62 10 59</p>
|
|
||||||
<p><a href="mailto:info@rodax-software.com" class="hover:underline">info@rodax-software.com</a></p>
|
|
||||||
<p><a href="https://www.rodax-software.com" target="_blank" rel="noopener"
|
|
||||||
class="hover:underline">www.rodax-software.com</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</header>
|
</header>
|
||||||
@ -146,25 +153,15 @@
|
|||||||
<main id="main">
|
<main id="main">
|
||||||
<section id="details" class="border-b border-black ">
|
<section id="details" class="border-b border-black ">
|
||||||
|
|
||||||
<div class="relative pt-0 border-b border-black">
|
|
||||||
<!-- Badge TOTAL decorado con imagen -->
|
|
||||||
<div class="absolute -top-9 right-0">
|
|
||||||
|
|
||||||
<div class="relative text-sm font-semibold text-black pr-2 pl-10 py-2 justify-center bg-red-900"
|
|
||||||
style="background-image: url('https://rodax-software.com/images/img-total2.jpg'); background-size: cover; background-position: left;">
|
|
||||||
<!-- Texto del total -->
|
|
||||||
<span>TOTAL: {{total_amount}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tu tabla -->
|
<!-- Tu tabla -->
|
||||||
<table class="table-header">
|
<table class="table-header">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-left">
|
<tr class="text-left bg-gray-200 text-red-500">
|
||||||
<th class="py-2">Concepto</th>
|
<th class="py-2">Concepto</th>
|
||||||
<th class="py-2">Cantidad</th>
|
<th class="py-2">Cantidad</th>
|
||||||
<th class="py-2">Precio unidad</th>
|
<th class="py-2">Precio unidad</th>
|
||||||
|
<th class="py-2">Dto</th>
|
||||||
<th class="py-2">Importe total</th>
|
<th class="py-2">Importe total</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -225,20 +222,11 @@
|
|||||||
<td class="w-5"> </td>
|
<td class="w-5"> </td>
|
||||||
<td class="px-4 text-right">{{taxable_amount}}</td>
|
<td class="px-4 text-right">{{taxable_amount}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{#if taxes_amount }}
|
|
||||||
<tr>
|
|
||||||
<td class="px-4 text-right">IVA 21%</td>
|
|
||||||
<td class="w-5"> </td>
|
|
||||||
<td class="px-4 text-right">{{taxes_amount}}</td>
|
|
||||||
</tr>
|
|
||||||
{{else}}
|
|
||||||
<!-- iva 0-->
|
|
||||||
{{/if}}
|
|
||||||
{{#each taxes}}
|
{{#each taxes}}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 text-right">IVA {{tax_code}}%</td>
|
<td class="px-4 text-right">{{tax_name}}</td>
|
||||||
<td class="w-5"> </td>
|
<td class="w-5"> </td>
|
||||||
<td class="px-4 text-right">{{taxes_amount.value}}</td>
|
<td class="px-4 text-right">{{taxes_amount}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
<tr class="">
|
<tr class="">
|
||||||
|
|||||||
@ -0,0 +1,260 @@
|
|||||||
|
<!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: Arial, sans-serif;
|
||||||
|
margin: 40px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 11pt;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
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: 0px solid;
|
||||||
|
padding: 3px 10px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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_rodax.jpg" alt="Logo Rodax" class="block h-14 w-auto mb-1" />
|
||||||
|
<div class="flex w-full">
|
||||||
|
<div class="p-1 ">
|
||||||
|
<p>Factura nº:<strong> {{series}}{{invoice_number}}</strong></p>
|
||||||
|
<p><span>Fecha:<strong> {{invoice_date}}</strong></p>
|
||||||
|
</div>
|
||||||
|
<div class="p-1 ml-9">
|
||||||
|
<h2 class="font-semibold uppercase mb-1">{{recipient.name}}</h2>
|
||||||
|
<p>{{recipient.tin}}</p>
|
||||||
|
<p>{{recipient.street}}</p>
|
||||||
|
<p>{{recipient.postal_code}} {{recipient.city}} {{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/logo2.jpg" alt="Logo secundario"
|
||||||
|
class="block h-5 w-auto md:h-8 mb-1" />
|
||||||
|
<div class="not-italic text-xs leading-tight">
|
||||||
|
<p>Telf: 91 785 02 47 / 686 62 10 59</p>
|
||||||
|
<p><a href="mailto:info@rodax-software.com" class="hover:underline">info@rodax-software.com</a></p>
|
||||||
|
<p><a href="https://www.rodax-software.com" target="_blank" rel="noopener"
|
||||||
|
class="hover:underline">www.rodax-software.com</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<section id="details" class="border-b border-black ">
|
||||||
|
|
||||||
|
<div class="relative pt-0 border-b border-black">
|
||||||
|
<!-- Badge TOTAL decorado con imagen -->
|
||||||
|
<div class="absolute -top-9 right-0">
|
||||||
|
|
||||||
|
<div class="relative text-sm font-semibold text-black pr-2 pl-10 py-2 justify-center bg-red-900"
|
||||||
|
style="background-image: url('https://rodax-software.com/images/img-total2.jpg'); background-size: cover; background-position: left;">
|
||||||
|
<!-- Texto del total -->
|
||||||
|
<span>TOTAL: {{total_amount}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tu tabla -->
|
||||||
|
<table class="table-header">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-left">
|
||||||
|
<th class="py-2">Concepto</th>
|
||||||
|
<th class="py-2">Cantidad</th>
|
||||||
|
<th class="py-2">Precio unidad</th>
|
||||||
|
<th class="py-2">Importe total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{{#each items}}
|
||||||
|
<tr>
|
||||||
|
<td>{{description}}</td>
|
||||||
|
<td class="text-right">{{#if quantity}}{{quantity}}{{else}} {{/if}}</td>
|
||||||
|
<td class="text-right">{{#if unit_amount}}{{unit_amount}}{{else}} {{/if}}</td>
|
||||||
|
<td class="text-right">{{#if taxable_amount}}{{taxable_amount}}{{else}} {{/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 class="px-4 text-right">Importe neto</td>
|
||||||
|
<td class="w-5"> </td>
|
||||||
|
<td class="px-4 text-right">{{subtotal_amount}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 text-right">Descuento {{discount_percentage}}</td>
|
||||||
|
<td class="w-5"> </td>
|
||||||
|
<td class="px-4 text-right">{{discount_amount.value}}</td>
|
||||||
|
</tr>
|
||||||
|
{{else}}
|
||||||
|
<!-- dto 0-->
|
||||||
|
{{/if}}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 text-right">Base imponible</td>
|
||||||
|
<td class="w-5"> </td>
|
||||||
|
<td class="px-4 text-right">{{taxable_amount}}</td>
|
||||||
|
</tr>
|
||||||
|
{{#each taxes}}
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 text-right">{{tax_name}}</td>
|
||||||
|
<td class="w-5"> </td>
|
||||||
|
<td class="px-4 text-right">{{taxes_amount}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
<tr class="">
|
||||||
|
<td class="px-4 text-right accent-color">
|
||||||
|
Total factura
|
||||||
|
</td>
|
||||||
|
<td class="w-5"> </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 20.073, Libro 0, Folio 141, Sección 8, Hoja M-354212
|
||||||
|
| CIF: B83999441 -
|
||||||
|
Rodax Software S.L.</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@ -1 +1 @@
|
|||||||
export * from "./update-customer-invoice.use-case";
|
export * from "./update-proforma.use-case";
|
||||||
|
|||||||
@ -10,20 +10,20 @@ import type { CustomerInvoiceApplicationService } from "../../../services/custom
|
|||||||
|
|
||||||
import { mapDTOToUpdateCustomerInvoicePatchProps } from "./map-dto-to-update-customer-invoice-props";
|
import { mapDTOToUpdateCustomerInvoicePatchProps } from "./map-dto-to-update-customer-invoice-props";
|
||||||
|
|
||||||
type UpdateCustomerInvoiceUseCaseInput = {
|
type UpdateProformaUseCaseInput = {
|
||||||
companyId: UniqueID;
|
companyId: UniqueID;
|
||||||
proforma_id: string;
|
proforma_id: string;
|
||||||
dto: UpdateProformaByIdRequestDTO;
|
dto: UpdateProformaByIdRequestDTO;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class UpdateCustomerInvoiceUseCase {
|
export class UpdateProformaUseCase {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly service: CustomerInvoiceApplicationService,
|
private readonly service: CustomerInvoiceApplicationService,
|
||||||
private readonly transactionManager: ITransactionManager,
|
private readonly transactionManager: ITransactionManager,
|
||||||
private readonly presenterRegistry: IPresenterRegistry
|
private readonly presenterRegistry: IPresenterRegistry
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public execute(params: UpdateCustomerInvoiceUseCaseInput) {
|
public execute(params: UpdateProformaUseCaseInput) {
|
||||||
const { companyId, proforma_id, dto } = params;
|
const { companyId, proforma_id, dto } = params;
|
||||||
|
|
||||||
const idOrError = UniqueID.create(proforma_id);
|
const idOrError = UniqueID.create(proforma_id);
|
||||||
@ -2,3 +2,4 @@ export * from "./customer-invoice-id-already-exits-error";
|
|||||||
export * from "./entity-is-not-proforma-error";
|
export * from "./entity-is-not-proforma-error";
|
||||||
export * from "./invalid-proforma-transition-error";
|
export * from "./invalid-proforma-transition-error";
|
||||||
export * from "./proforma-cannot-be-converted-to-invoice-error";
|
export * from "./proforma-cannot-be-converted-to-invoice-error";
|
||||||
|
export * from "./proforma-cannot-be-deleted-error";
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
// domain/errors/proforma-cannot-be-deleted.error.ts
|
||||||
|
import { DomainError } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
/** Proforma solo se puede borrar si está en 'draft' */
|
||||||
|
export class ProformaCannotBeDeletedError extends DomainError {
|
||||||
|
constructor(id: string, reason?: string, options?: ErrorOptions) {
|
||||||
|
super(`Proforma '${id}' cannot be deleted${reason ? `: ${reason}` : ""}.`, options);
|
||||||
|
this.name = "ProformaCannotBeDeletedError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const isProformaCannotBeDeletedError = (e: unknown): e is ProformaCannotBeDeletedError =>
|
||||||
|
e instanceof ProformaCannotBeDeletedError;
|
||||||
@ -36,7 +36,8 @@ export interface ICustomerInvoiceRepository {
|
|||||||
existsByIdInCompany(
|
existsByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
transaction?: unknown
|
transaction: unknown,
|
||||||
|
options: unknown
|
||||||
): Promise<Result<boolean, Error>>;
|
): Promise<Result<boolean, Error>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,14 +72,14 @@ export interface ICustomerInvoiceRepository {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Elimina o marca como eliminada una factura dentro de una empresa.
|
* Elimina o marca como eliminada una proforma dentro de una empresa.
|
||||||
*
|
*
|
||||||
* @param companyId - ID de la empresa.
|
* @param companyId - ID de la empresa.
|
||||||
* @param id - UUID de la factura a eliminar.
|
* @param id - UUID de la proforma a eliminar.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<void, Error>
|
* @returns Result<void, Error>
|
||||||
*/
|
*/
|
||||||
deleteByIdInCompany(
|
deleteProformaByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
transaction: unknown
|
transaction: unknown
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { UtcDate } from "@repo/rdx-ddd";
|
import type { UtcDate } from "@repo/rdx-ddd";
|
||||||
import { Maybe, Result } from "@repo/rdx-utils";
|
import { Maybe, Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { CustomerInvoice } from "../aggregates";
|
import { CustomerInvoice } from "../aggregates";
|
||||||
import { EntityIsNotProformaError, ProformaCannotBeConvertedToInvoiceError } from "../errors";
|
import { EntityIsNotProformaError, ProformaCannotBeConvertedToInvoiceError } from "../errors";
|
||||||
import {
|
import {
|
||||||
CustomerInvoiceIsProformaSpecification,
|
CustomerInvoiceIsProformaSpecification,
|
||||||
ProformaCanTranstionToIssuedSpecification,
|
ProformaCanTranstionToIssuedSpecification,
|
||||||
} from "../specs";
|
} from "../specs";
|
||||||
import { CustomerInvoiceNumber, CustomerInvoiceStatus } from "../value-objects";
|
import { type CustomerInvoiceNumber, CustomerInvoiceStatus } from "../value-objects";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Servicio de dominio que encapsula la lógica de emisión de factura definitiva desde una proforma.
|
* Servicio de dominio que encapsula la lógica de emisión de factura definitiva desde una proforma.
|
||||||
@ -15,11 +16,6 @@ export class IssueCustomerInvoiceDomainService {
|
|||||||
private readonly isProformaSpec = new CustomerInvoiceIsProformaSpecification();
|
private readonly isProformaSpec = new CustomerInvoiceIsProformaSpecification();
|
||||||
private readonly isApprovedSpec = new ProformaCanTranstionToIssuedSpecification();
|
private readonly isApprovedSpec = new ProformaCanTranstionToIssuedSpecification();
|
||||||
|
|
||||||
public linkWithProforma(
|
|
||||||
invoice: CustomerInvoice,
|
|
||||||
proforma: CustomerInvoice
|
|
||||||
): Result<CustomerInvoice, Error> {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convierte una proforma en factura definitiva.
|
* Convierte una proforma en factura definitiva.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Result } from "@repo/rdx-utils";
|
import { Result } from "@repo/rdx-utils";
|
||||||
|
|
||||||
import { CustomerInvoice } from "../aggregates";
|
import { CustomerInvoice } from "../aggregates";
|
||||||
import { EntityIsNotProformaError, InvalidProformaTransitionError } from "../errors";
|
import { EntityIsNotProformaError, InvalidProformaTransitionError } from "../errors";
|
||||||
import { CustomerInvoiceIsProformaSpecification } from "../specs";
|
import { CustomerInvoiceIsProformaSpecification } from "../specs";
|
||||||
|
|||||||
@ -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";
|
||||||
|
|
||||||
export class CustomerInvoiceIsProformaSpecification extends CompositeSpecification<CustomerInvoice> {
|
export class CustomerInvoiceIsProformaSpecification extends CompositeSpecification<CustomerInvoice> {
|
||||||
public async isSatisfiedBy(proforma: CustomerInvoice): Promise<boolean> {
|
public async isSatisfiedBy(proforma: CustomerInvoice): Promise<boolean> {
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export * from "./customer-invoice-is-proforma.specification";
|
export * from "./customer-invoice-is-proforma.specification";
|
||||||
export * from "./proforma-can-transtion-to-issued.specification";
|
export * from "./proforma-can-transtion-to-issued.specification";
|
||||||
|
export * from "./status-invoice-is-draft.specification";
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
// domain/specifications/status-invoice-is-draft.specification.ts
|
||||||
|
import { CompositeSpecification } from "@repo/rdx-ddd";
|
||||||
|
|
||||||
|
import type { CustomerInvoice } from "../aggregates";
|
||||||
|
|
||||||
|
/** Verifica que el estado es borrador */
|
||||||
|
export class StatusInvoiceIsDraftSpecification extends CompositeSpecification<CustomerInvoice> {
|
||||||
|
public async isSatisfiedBy(invoice: CustomerInvoice): Promise<boolean> {
|
||||||
|
return invoice.status.isDraft();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,13 +19,14 @@ import {
|
|||||||
CustomerInvoiceReportPDFPresenter,
|
CustomerInvoiceReportPDFPresenter,
|
||||||
CustomerInvoiceReportPresenter,
|
CustomerInvoiceReportPresenter,
|
||||||
CustomerInvoiceTaxesReportPresenter,
|
CustomerInvoiceTaxesReportPresenter,
|
||||||
|
DeleteProformaUseCase,
|
||||||
GetProformaUseCase,
|
GetProformaUseCase,
|
||||||
IssueProformaInvoiceUseCase,
|
IssueProformaInvoiceUseCase,
|
||||||
ListCustomerInvoicesPresenter,
|
ListCustomerInvoicesPresenter,
|
||||||
ListProformasUseCase,
|
ListProformasUseCase,
|
||||||
RecipientInvoiceFullPresenter,
|
RecipientInvoiceFullPresenter,
|
||||||
ReportProformaUseCase,
|
ReportProformaUseCase,
|
||||||
UpdateCustomerInvoiceUseCase,
|
UpdateProformaUseCase,
|
||||||
} from "../application";
|
} from "../application";
|
||||||
|
|
||||||
import { CustomerInvoiceDomainMapper, CustomerInvoiceListMapper } from "./mappers";
|
import { CustomerInvoiceDomainMapper, CustomerInvoiceListMapper } from "./mappers";
|
||||||
@ -42,14 +43,14 @@ export type CustomerInvoiceDeps = {
|
|||||||
taxes: JsonTaxCatalogProvider;
|
taxes: JsonTaxCatalogProvider;
|
||||||
};
|
};
|
||||||
useCases: {
|
useCases: {
|
||||||
list: () => ListProformasUseCase;
|
list_proformas: () => ListProformasUseCase;
|
||||||
get: () => GetProformaUseCase;
|
get_proforma: () => GetProformaUseCase;
|
||||||
create: () => CreateProformaUseCase;
|
create_proforma: () => CreateProformaUseCase;
|
||||||
update: () => UpdateCustomerInvoiceUseCase;
|
update_proforma: () => UpdateProformaUseCase;
|
||||||
//delete: () => DeleteCustomerInvoiceUseCase;
|
delete_proforma: () => DeleteProformaUseCase;
|
||||||
report: () => ReportProformaUseCase;
|
report_proforma: () => ReportProformaUseCase;
|
||||||
issue: () => IssueProformaInvoiceUseCase;
|
issue_proforma: () => IssueProformaInvoiceUseCase;
|
||||||
changeStatus: () => ChangeStatusProformaUseCase;
|
changeStatus_proforma: () => ChangeStatusProformaUseCase;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -123,16 +124,20 @@ export function buildCustomerInvoiceDependencies(params: ModuleParams): Customer
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const useCases = {
|
const useCases: CustomerInvoiceDeps["useCases"] = {
|
||||||
list: () => new ListProformasUseCase(appService, transactionManager, presenterRegistry),
|
list_proformas: () =>
|
||||||
get: () => new GetProformaUseCase(appService, transactionManager, presenterRegistry),
|
new ListProformasUseCase(appService, transactionManager, presenterRegistry),
|
||||||
create: () =>
|
get_proforma: () => new GetProformaUseCase(appService, transactionManager, presenterRegistry),
|
||||||
|
create_proforma: () =>
|
||||||
new CreateProformaUseCase(appService, transactionManager, presenterRegistry, catalogs.taxes),
|
new CreateProformaUseCase(appService, transactionManager, presenterRegistry, catalogs.taxes),
|
||||||
update: () =>
|
update_proforma: () =>
|
||||||
new UpdateCustomerInvoiceUseCase(appService, transactionManager, presenterRegistry),
|
new UpdateProformaUseCase(appService, transactionManager, presenterRegistry),
|
||||||
report: () => new ReportProformaUseCase(appService, transactionManager, presenterRegistry),
|
delete_proforma: () => new DeleteProformaUseCase(appService, transactionManager),
|
||||||
issue: () => new IssueProformaInvoiceUseCase(appService, transactionManager, presenterRegistry),
|
report_proforma: () =>
|
||||||
changeStatus: () => new ChangeStatusProformaUseCase(appService, transactionManager),
|
new ReportProformaUseCase(appService, transactionManager, presenterRegistry),
|
||||||
|
issue_proforma: () =>
|
||||||
|
new IssueProformaInvoiceUseCase(appService, transactionManager, presenterRegistry),
|
||||||
|
changeStatus_proforma: () => new ChangeStatusProformaUseCase(appService, transactionManager),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -4,10 +4,7 @@ import type { DeleteProformaUseCase } from "../../../../application";
|
|||||||
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
||||||
|
|
||||||
export class DeleteProformaController extends ExpressController {
|
export class DeleteProformaController extends ExpressController {
|
||||||
public constructor(
|
public constructor(private readonly useCase: DeleteProformaUseCase) {
|
||||||
private readonly useCase: DeleteProformaUseCase
|
|
||||||
/* private readonly presenter: any */
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
this.errorMapper = customerInvoicesApiErrorMapper;
|
this.errorMapper = customerInvoicesApiErrorMapper;
|
||||||
|
|
||||||
@ -22,6 +19,9 @@ export class DeleteProformaController extends ExpressController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { proforma_id } = this.req.params;
|
const { proforma_id } = this.req.params;
|
||||||
|
if (!proforma_id) {
|
||||||
|
return this.invalidInputError("Proforma ID missing");
|
||||||
|
}
|
||||||
|
|
||||||
const result = await this.useCase.execute({ proforma_id, companyId });
|
const result = await this.useCase.execute({ proforma_id, companyId });
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
import { ExpressController, authGuard, forbidQueryFieldGuard, tenantGuard } from "@erp/core/api";
|
||||||
|
|
||||||
import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto";
|
import type { UpdateProformaByIdRequestDTO } from "../../../../../common/dto";
|
||||||
import type { UpdateCustomerInvoiceUseCase } from "../../../../application";
|
import type { UpdateProformaUseCase } from "../../../../application";
|
||||||
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
import { customerInvoicesApiErrorMapper } from "../../customer-invoices-api-error-mapper";
|
||||||
|
|
||||||
export class UpdateProformaController extends ExpressController {
|
export class UpdateProformaController extends ExpressController {
|
||||||
public constructor(private readonly useCase: UpdateCustomerInvoiceUseCase) {
|
public constructor(private readonly useCase: UpdateProformaUseCase) {
|
||||||
super();
|
super();
|
||||||
this.errorMapper = customerInvoicesApiErrorMapper;
|
this.errorMapper = customerInvoicesApiErrorMapper;
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
isCustomerInvoiceIdAlreadyExistsError,
|
isCustomerInvoiceIdAlreadyExistsError,
|
||||||
isEntityIsNotProformaError,
|
isEntityIsNotProformaError,
|
||||||
isProformaCannotBeConvertedToInvoiceError,
|
isProformaCannotBeConvertedToInvoiceError,
|
||||||
|
isProformaCannotBeDeletedError,
|
||||||
} from "../../domain";
|
} from "../../domain";
|
||||||
|
|
||||||
// Crea una regla específica (prioridad alta para sobreescribir mensajes)
|
// Crea una regla específica (prioridad alta para sobreescribir mensajes)
|
||||||
@ -47,8 +48,18 @@ const proformaConversionRule: ErrorToApiRule = {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const proformaCannotBeDeletedRule: ErrorToApiRule = {
|
||||||
|
priority: 120,
|
||||||
|
matches: (e) => isProformaCannotBeDeletedError(e),
|
||||||
|
build: (e) =>
|
||||||
|
new ValidationApiError(
|
||||||
|
(e as ProformaCannotBeConvertedToInvoiceError).message || "Proforma cannot be deleted."
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
// Cómo aplicarla: crea una nueva instancia del mapper con la regla extra
|
// Cómo aplicarla: crea una nueva instancia del mapper con la regla extra
|
||||||
export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
export const customerInvoicesApiErrorMapper: ApiErrorMapper = ApiErrorMapper.default()
|
||||||
.register(invoiceDuplicateRule)
|
.register(invoiceDuplicateRule)
|
||||||
.register(entityIsNotProformaError)
|
.register(entityIsNotProformaError)
|
||||||
.register(proformaConversionRule);
|
.register(proformaConversionRule)
|
||||||
|
.register(proformaCannotBeDeletedRule);
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import {
|
|||||||
ChangeStatusProformaByIdParamsRequestSchema,
|
ChangeStatusProformaByIdParamsRequestSchema,
|
||||||
ChangeStatusProformaByIdRequestSchema,
|
ChangeStatusProformaByIdRequestSchema,
|
||||||
CreateProformaRequestSchema,
|
CreateProformaRequestSchema,
|
||||||
|
DeleteProformaByIdParamsRequestSchema,
|
||||||
GetProformaByIdRequestSchema,
|
GetProformaByIdRequestSchema,
|
||||||
|
IssueProformaByIdParamsRequestSchema,
|
||||||
ListProformasRequestSchema,
|
ListProformasRequestSchema,
|
||||||
ReportProformaByIdRequestSchema,
|
ReportProformaByIdRequestSchema,
|
||||||
UpdateProformaByIdParamsRequestSchema,
|
UpdateProformaByIdParamsRequestSchema,
|
||||||
@ -19,12 +21,13 @@ import { buildCustomerInvoiceDependencies } from "../dependencies";
|
|||||||
import {
|
import {
|
||||||
ChangeStatusProformaController,
|
ChangeStatusProformaController,
|
||||||
CreateProformaController,
|
CreateProformaController,
|
||||||
|
DeleteProformaController,
|
||||||
GetProformaController,
|
GetProformaController,
|
||||||
|
IssueProformaController,
|
||||||
ListProformasController,
|
ListProformasController,
|
||||||
ReportProformaController,
|
ReportProformaController,
|
||||||
UpdateProformaController,
|
UpdateProformaController,
|
||||||
} from "./controllers/proformas";
|
} from "./controllers/proformas";
|
||||||
import { IssueProformaController } from "./controllers/proformas/issue-proforma.controller";
|
|
||||||
|
|
||||||
export const proformasRouter = (params: ModuleParams) => {
|
export const proformasRouter = (params: ModuleParams) => {
|
||||||
const { app, baseRoutePath, logger } = params as {
|
const { app, baseRoutePath, logger } = params as {
|
||||||
@ -60,7 +63,7 @@ export const proformasRouter = (params: ModuleParams) => {
|
|||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(ListProformasRequestSchema, "params"),
|
validateRequest(ListProformasRequestSchema, "params"),
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.list();
|
const useCase = deps.useCases.list_proformas();
|
||||||
const controller = new ListProformasController(useCase /*, deps.presenters.list */);
|
const controller = new ListProformasController(useCase /*, deps.presenters.list */);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
@ -71,7 +74,7 @@ export const proformasRouter = (params: ModuleParams) => {
|
|||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(GetProformaByIdRequestSchema, "params"),
|
validateRequest(GetProformaByIdRequestSchema, "params"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.get();
|
const useCase = deps.useCases.get_proforma();
|
||||||
const controller = new GetProformaController(useCase);
|
const controller = new GetProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
@ -83,7 +86,7 @@ export const proformasRouter = (params: ModuleParams) => {
|
|||||||
|
|
||||||
validateRequest(CreateProformaRequestSchema, "body"),
|
validateRequest(CreateProformaRequestSchema, "body"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.create();
|
const useCase = deps.useCases.create_proforma();
|
||||||
const controller = new CreateProformaController(useCase);
|
const controller = new CreateProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
@ -96,30 +99,30 @@ export const proformasRouter = (params: ModuleParams) => {
|
|||||||
validateRequest(UpdateProformaByIdParamsRequestSchema, "params"),
|
validateRequest(UpdateProformaByIdParamsRequestSchema, "params"),
|
||||||
validateRequest(UpdateProformaByIdRequestSchema, "body"),
|
validateRequest(UpdateProformaByIdRequestSchema, "body"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.update();
|
const useCase = deps.useCases.update_proforma();
|
||||||
const controller = new UpdateProformaController(useCase);
|
const controller = new UpdateProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/*router.delete(
|
router.delete(
|
||||||
"/:proforma_id",
|
"/:proforma_id",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
|
|
||||||
validateRequest(DeleteCustomerInvoiceByIdRequestSchema, "params"),
|
validateRequest(DeleteProformaByIdParamsRequestSchema, "params"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.delete();
|
const useCase = deps.useCases.delete_proforma();
|
||||||
const controller = new DeleteCustomerInvoiceController(useCase);
|
const controller = new DeleteProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
);*/
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:proforma_id/report",
|
"/:proforma_id/report",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
validateRequest(ReportProformaByIdRequestSchema, "params"),
|
validateRequest(ReportProformaByIdRequestSchema, "params"),
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.report();
|
const useCase = deps.useCases.report_proforma();
|
||||||
const controller = new ReportProformaController(useCase);
|
const controller = new ReportProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
@ -133,7 +136,7 @@ export const proformasRouter = (params: ModuleParams) => {
|
|||||||
validateRequest(ChangeStatusProformaByIdRequestSchema, "body"),
|
validateRequest(ChangeStatusProformaByIdRequestSchema, "body"),
|
||||||
|
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.changeStatus();
|
const useCase = deps.useCases.changeStatus_proforma();
|
||||||
const controller = new ChangeStatusProformaController(useCase);
|
const controller = new ChangeStatusProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
@ -143,11 +146,10 @@ export const proformasRouter = (params: ModuleParams) => {
|
|||||||
"/:proforma_id/issue",
|
"/:proforma_id/issue",
|
||||||
//checkTabContext,
|
//checkTabContext,
|
||||||
|
|
||||||
/*validateRequest(XXX, "params"),
|
validateRequest(IssueProformaByIdParamsRequestSchema, "params"),
|
||||||
validateRequest(XXX, "body"),*/
|
|
||||||
|
|
||||||
(req: Request, res: Response, next: NextFunction) => {
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
const useCase = deps.useCases.issue();
|
const useCase = deps.useCases.issue_proforma();
|
||||||
const controller = new IssueProformaController(useCase);
|
const controller = new IssueProformaController(useCase);
|
||||||
return controller.execute(req, res, next);
|
return controller.execute(req, res, next);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -166,16 +166,23 @@ export class CustomerInvoiceRepository
|
|||||||
* @param companyId - Identificador UUID de la empresa a la que pertenece la factura.
|
* @param companyId - Identificador UUID de la empresa a la que pertenece la factura.
|
||||||
* @param id - Identificador UUID de la factura.
|
* @param id - Identificador UUID de la factura.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
|
* @param options - Opciones adicionales para la consulta (Sequelize FindOptions)
|
||||||
* @returns Result<boolean, Error>
|
* @returns Result<boolean, Error>
|
||||||
*/
|
*/
|
||||||
async existsByIdInCompany(
|
async existsByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
transaction?: Transaction
|
transaction: Transaction,
|
||||||
|
options: FindOptions<InferAttributes<CustomerInvoiceModel>> = {}
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
try {
|
try {
|
||||||
const count = await CustomerInvoiceModel.count({
|
const count = await CustomerInvoiceModel.count({
|
||||||
where: { id: id.toString(), company_id: companyId.toString() },
|
...options,
|
||||||
|
where: {
|
||||||
|
id: id.toString(),
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
...(options.where ?? {}),
|
||||||
|
},
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
return Result.ok(Boolean(count > 0));
|
return Result.ok(Boolean(count > 0));
|
||||||
@ -191,7 +198,7 @@ export class CustomerInvoiceRepository
|
|||||||
* @param companyId - Identificador UUID de la empresa a la que pertenece la factura.
|
* @param companyId - Identificador UUID de la empresa a la que pertenece la factura.
|
||||||
* @param id - UUID de la factura.
|
* @param id - UUID de la factura.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @params options - Opciones adicionales para la consulta (Sequelize FindOptions)
|
* @param options - Opciones adicionales para la consulta (Sequelize FindOptions)
|
||||||
* @returns Result<CustomerInvoice, Error>
|
* @returns Result<CustomerInvoice, Error>
|
||||||
*/
|
*/
|
||||||
async getByIdInCompany(
|
async getByIdInCompany(
|
||||||
@ -367,21 +374,25 @@ export class CustomerInvoiceRepository
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Elimina o marca como eliminada una factura.
|
* Elimina o marca como eliminada una proforma dentro de una empresa.
|
||||||
*
|
*
|
||||||
* @param companyId - Identificador UUID de la empresa a la que pertenece el cliente.
|
* @param companyId - ID de la empresa.
|
||||||
* @param id - UUID de la factura a eliminar.
|
* @param id - UUID de la proforma a eliminar.
|
||||||
* @param transaction - Transacción activa para la operación.
|
* @param transaction - Transacción activa para la operación.
|
||||||
* @returns Result<boolean, Error>
|
* @returns Result<void, Error>
|
||||||
*/
|
*/
|
||||||
async deleteByIdInCompany(
|
async deleteProformaByIdInCompany(
|
||||||
companyId: UniqueID,
|
companyId: UniqueID,
|
||||||
id: UniqueID,
|
id: UniqueID,
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
): Promise<Result<boolean, Error>> {
|
): Promise<Result<boolean, Error>> {
|
||||||
try {
|
try {
|
||||||
const deleted = await CustomerInvoiceModel.destroy({
|
const deleted = await CustomerInvoiceModel.destroy({
|
||||||
where: { id: id.toString(), company_id: companyId.toString() },
|
where: {
|
||||||
|
id: id.toString(),
|
||||||
|
company_id: companyId.toString(),
|
||||||
|
is_proforma: true,
|
||||||
|
},
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -424,8 +435,6 @@ export class CustomerInvoiceRepository
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(affected);
|
|
||||||
|
|
||||||
if (affected === 0) {
|
if (affected === 0) {
|
||||||
return Result.fail(
|
return Result.fail(
|
||||||
new InfrastructureRepositoryError(
|
new InfrastructureRepositoryError(
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import { z } from "zod/v4";
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const DeleteProformaByIdRequestSchema = z.object({
|
export const DeleteProformaByIdParamsRequestSchema = z.object({
|
||||||
id: z.string(),
|
proforma_id: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type DeleteProformaByIdRequestDTO = z.infer<typeof DeleteProformaByIdRequestSchema>;
|
export type DeleteProformaByIdRequestDTO = z.infer<typeof DeleteProformaByIdParamsRequestSchema>;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ export * from "./change-status-proforma-by-id.request.dto";
|
|||||||
export * from "./create-proforma.request.dto";
|
export * from "./create-proforma.request.dto";
|
||||||
export * from "./delete-proforma-by-id.request.dto";
|
export * from "./delete-proforma-by-id.request.dto";
|
||||||
export * from "./get-proforma-by-id.request.dto";
|
export * from "./get-proforma-by-id.request.dto";
|
||||||
|
export * from "./issue-proforma-by-id.request.dto";
|
||||||
export * from "./list-proformas.request.dto";
|
export * from "./list-proformas.request.dto";
|
||||||
export * from "./report-proforma-by-id.request.dto";
|
export * from "./report-proforma-by-id.request.dto";
|
||||||
export * from "./update-proforma-by-id.request.dto";
|
export * from "./update-proforma-by-id.request.dto";
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const IssueProformaByIdParamsRequestSchema = z.object({
|
||||||
|
proforma_id: z.string(),
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user