Clientes y facturas de cliente

This commit is contained in:
David Arranz 2025-09-19 19:25:41 +02:00
parent 9ef847d54b
commit a57dfe2a02
6 changed files with 9 additions and 267 deletions

View File

@ -1,254 +0,0 @@
<html lang="{{lang_code}}">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css"
referrerpolicy="no-referrer" />
<title>Factura #{{id}}</title>
<style>
:root {
--gray: #6b7280;
--light: #f3f4f6;
--border: #e5e7eb;
}
@page {
margin: 24mm 18mm;
}
body {
font-family: Arial, Helvetica, sans-serif;
color: #000;
font-size: 10.5pt;
line-height: 1.5;
}
.small {
font-size: 9pt;
}
.xsmall {
font-size: 8pt;
}
.muted {
color: var(--gray);
}
.box {
border: 1px solid var(--border);
border-radius: .375rem;
}
.table th {
background: var(--light);
}
.table th,
.table td {
border: 1px solid var(--border);
}
.totals td {
border: 0;
}
.totals tr td:last-child {
text-align: right;
}
#header h1 {
letter-spacing: .03em;
}
#meta td {
padding: .35rem .5rem;
}
@media print {
thead {
display: table-header-group;
}
tfoot {
display: table-footer-group;
}
a[href]:after {
content: "";
}
}
</style>
</head>
<body>
<header id="header" class="mb-4">
<section class="flex items-start justify-between">
<!-- Bloque empresa -->
<aside class="pr-4">
<h1 class="text-2xl font-semibold leading-tight">{{dealer.name}}</h1>
{{#if dealer.logo}}
<img id="dealer-logo" src="{{dealer.logo}}" alt="Logo" class="mt-1 h-10 object-contain" />
{{/if}}
<address id="from" class="not-italic whitespace-pre-line small mt-2">
{{dealer.contact_information}}
</address>
</aside>
<!-- Bloque meta factura -->
<aside class="box p-3 min-w-[260px]">
<table id="meta" class="w-full small">
<tbody>
<tr>
<td class="font-semibold">Factura nº:</td>
<td class="text-right">{{reference}}</td>
</tr>
<tr>
<td class="font-semibold">Fecha:</td>
<td class="text-right">{{date}}</td>
</tr>
<tr>
<td class="font-semibold">Página:</td>
<td class="text-right"><span class="pageNumber"></span> / <span class="totalPages"></span></td>
</tr>
</tbody>
</table>
</aside>
</section>
<!-- Cliente -->
<section class="grid grid-cols-2 gap-4 mt-4 pb-3 border-b border-gray-200">
<aside>
<h3 class="font-semibold mb-1">Cliente</h3>
<address id="to" class="not-italic whitespace-pre-line">{{customer_information}}</address>
</aside>
<aside class="small">
{{#if customer_reference}}
<p><span class="font-semibold">Referencia cliente:</span> {{customer_reference}}</p>
{{/if}}
</aside>
</section>
</header>
<main id="main">
<!-- Detalle líneas -->
<section id="details" class="mt-3">
<table class="table w-full border-collapse">
<thead>
<tr class="text-left">
<th class="px-2 py-2">Concepto</th>
<th class="px-2 py-2 text-right w-24">Cantidad</th>
<th class="px-2 py-2 text-right w-32">Precio unidad</th>
{{#if any_item_has_discount}}
<th class="px-2 py-2 text-right w-24">Dto (%)</th>
{{/if}}
<th class="px-2 py-2 text-right w-36">Importe total</th>
</tr>
</thead>
<tbody>
{{#each items}}
<tr class="align-top">
<td class="px-2 py-2">
<div class="font-medium">{{description}}</div>
{{#if note}}<div class="small muted whitespace-pre-line">{{note}}</div>{{/if}}
</td>
<td class="px-2 py-2 text-right">{{quantity}}</td>
<td class="px-2 py-2 text-right">{{unit_price}}</td>
{{#if ../any_item_has_discount}}
<td class="px-2 py-2 text-right">{{discount}}</td>
{{/if}}
<td class="px-2 py-2 text-right">{{total_price}}</td>
</tr>
{{/each}}
</tbody>
<tfoot>
<tr>
<td colspan="5" class="px-2 py-1 xsmall muted">* Precios en {{currency}}.</td>
</tr>
</tfoot>
</table>
</section>
<!-- Resumen / totales -->
<section id="resume" class="grid grid-cols-2 gap-6 mt-6">
<!-- Notas y forma de pago -->
<aside>
{{#if payment_method}}
<p class="small"><span class="font-semibold">Forma de pago:</span> {{payment_method}}</p>
{{/if}}
{{#if notes}}
<div class="mt-2 small"><span class="font-semibold">Notas:</span> {{notes}}</div>
{{/if}}
{{!-- Bloque especial Domiciliación bancaria --}}
{{#if payment_is_direct_debit}}
<div class="mt-4 box p-3 small">
<div class="font-semibold mb-1 uppercase tracking-wide">DOMICILIACIÓN BANCARIA</div>
<div class="whitespace-pre-line">{{direct_debit_text}}</div>
</div>
{{/if}}
</aside>
<!-- Totals box -->
<aside class="justify-self-end w-full max-w-md">
<table class="w-full totals">
<tbody>
{{#if subtotal_price}}
<tr>
<td class="py-1">Importe neto</td>
<td class="py-1 text-right">{{subtotal_price}}</td>
</tr>
{{/if}}
{{#if discount_price}}
<tr>
<td class="py-1">% Descuento ({{discount.amount}})</td>
<td class="py-1 text-right">-{{discount_price}}</td>
</tr>
{{/if}}
<tr class="border-t border-gray-200">
<td class="py-1 font-medium">Base imponible</td>
<td class="py-1 text-right font-medium">{{before_tax_price}}</td>
</tr>
<tr>
<td class="py-1">IVA {{tax}}</td>
<td class="py-1 text-right">{{tax_price}}</td>
</tr>
<tr class="border-t-2 border-gray-300">
<td class="py-2 text-lg font-semibold">Total factura</td>
<td class="py-2 text-right text-lg font-semibold">{{total_price}}</td>
</tr>
</tbody>
</table>
</aside>
</section>
<!-- Términos legales -->
<section id="legal_terms" class="mt-6">
<p class="xsmall muted whitespace-pre-line">{{quote.default_legal_terms}}</p>
</section>
</main>
<footer id="footer" class="mt-8 pt-3 border-t border-gray-200">
<div class="grid grid-cols-2 gap-4 small">
<div>
<span class="font-semibold">{{dealer.name}}</span>
<div class="whitespace-pre-line">{{dealer.footer_information}}</div>
</div>
<div class="text-right">
{{#if dealer.website}}<div><a href="{{dealer.website}}">{{dealer.website}}</a></div>{{/if}}
{{#if dealer.email}}<div>{{dealer.email}}</div>{{/if}}
{{#if dealer.phone}}<div>{{dealer.phone}}</div>{{/if}}
</div>
</div>
</footer>
{{!-- Helpers opcionales esperados por la plantilla --}}
{{!--
any_item_has_discount: boolean precomputado en tu código
payment_is_direct_debit: boolean si forma de pago es domiciliación
direct_debit_text: texto para el bloque de domiciliación bancaria
currency: ISO o símbolo (EUR, €, etc.)
--}}
</body>
</html>

View File

@ -119,7 +119,6 @@
<div class="p-1 "> <div class="p-1 ">
<p>Factura nº:<strong>&nbsp;{{invoice_number}}</strong></p> <p>Factura nº:<strong>&nbsp;{{invoice_number}}</strong></p>
<p><span>Fecha:<strong>&nbsp;{{invoice_date}}</strong></p> <p><span>Fecha:<strong>&nbsp;{{invoice_date}}</strong></p>
<p><span>Página:</span><span class="pageNumber"></span> / <span class="totalPages"></span></p>
</div> </div>
<div class="p-1 ml-9"> <div class="p-1 ml-9">
<h2 class="font-semibold uppercase mb-1">{{recipient.name}}</h2> <h2 class="font-semibold uppercase mb-1">{{recipient.name}}</h2>

View File

@ -38,7 +38,7 @@ export interface CustomerInvoiceProps {
languageCode: LanguageCode; languageCode: LanguageCode;
currencyCode: CurrencyCode; currencyCode: CurrencyCode;
taxes: Maybe<InvoiceTaxes>; taxes: InvoiceTaxes;
items: CustomerInvoiceItems; items: CustomerInvoiceItems;
discountPercentage: Percentage; discountPercentage: Percentage;
@ -71,10 +71,7 @@ export class CustomerInvoice
currencyCode: props.currencyCode, currencyCode: props.currencyCode,
}); });
this._taxes = props.taxes.match( this._taxes = props.taxes;
(taxes) => taxes,
() => InvoiceTaxes.create({})
);
} }
static create(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> { static create(props: CustomerInvoiceProps, id?: UniqueID): Result<CustomerInvoice, Error> {

View File

@ -115,5 +115,5 @@ export const customerInvoicesRouter = (params: ModuleParams) => {
} }
); );
app.use(`${baseRoutePath}/customer-invoices`, router); app.use(`${baseRoutePath}/proforma-invoices`, router);
}; };

View File

@ -11,7 +11,7 @@ import {
extractOrPushError, extractOrPushError,
maybeFromNullableVO, maybeFromNullableVO,
} from "@repo/rdx-ddd"; } from "@repo/rdx-ddd";
import { Maybe, Result } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils";
import { import {
CustomerInvoice, CustomerInvoice,
CustomerInvoiceItems, CustomerInvoiceItems,
@ -243,7 +243,7 @@ export class CustomerInvoiceDomainMapper
discountPercentage: attributes.discountPercentage!, discountPercentage: attributes.discountPercentage!,
taxes: Maybe.some(taxes), taxes: taxes,
items, items,
}; };

View File

@ -200,8 +200,8 @@ const data2 = {
], ],
}, },
{ {
title: "Facturas de cliente", title: "Proformas de cliente",
url: "/customer-invoices", url: "/customer-proforma",
icon: Bot, icon: Bot,
items: [ items: [
{ {
@ -219,8 +219,8 @@ const data2 = {
], ],
}, },
{ {
title: "Documentation", title: "Facturas de cliente",
url: "#", url: "/customer-invoices",
icon: BookOpen, icon: BookOpen,
items: [ items: [
{ {