- Migration: services.price_cents, services.availability_text
- Migration: bookings.receipt_number (trigger annuale IANNI-YYYY-NNNN) + receipt_token
- Constraint EXCLUDE bookings_no_overlap (sala unica, status confirmed/completed)
- availability.py: calcolo slot globale (non più per-provider)
- 16 servizi reali da Servizi.xls inseriti, 9 demo archiviati con FK preservata
- provider_services: 3 profili orari (lun-sab 9-19, lun-mar 9-13, lun-gio 9-13)
- Endpoint GET /api/receipts/{token} → PDF WeasyPrint
- Template HTML ricevuta con palette Ianni
- Dockerfile: deps sistema weasyprint (pango/cairo/fonts)
- requirements: +weasyprint>=63.0
- Frontend index.html: prezzo + fascia oraria nelle card servizio, link Scarica ricevuta nella conferma
98 lines
4.9 KiB
HTML
98 lines
4.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="it">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Ricevuta {{ booking.receipt_number }}</title>
|
|
<style>
|
|
@page { size: A4; margin: 18mm 16mm; }
|
|
* { box-sizing: border-box; }
|
|
body { font-family: 'DejaVu Sans', 'Liberation Sans', Arial, sans-serif; font-size: 10.5pt; color: #111; margin: 0; }
|
|
.header { display: flex; justify-content: space-between; align-items: flex-start; border-bottom: 3px solid #002C50; padding-bottom: 12px; margin-bottom: 22px; }
|
|
.brand { color: #002C50; }
|
|
.brand h1 { margin: 0; font-size: 18pt; letter-spacing: 0.5px; }
|
|
.brand .sub { color: #6A971F; font-weight: 600; font-size: 10pt; letter-spacing: 0.3px; }
|
|
.pharm { text-align: right; font-size: 9.5pt; color: #444; line-height: 1.45; }
|
|
.pharm strong { color: #002C50; font-size: 11pt; display:block; margin-bottom: 2px; }
|
|
.receipt-block { background: #f5f7fa; border-left: 4px solid #6A971F; padding: 10px 14px; margin-bottom: 22px; }
|
|
.receipt-block .num { font-size: 13pt; font-weight: 700; color: #002C50; }
|
|
.receipt-block .meta { font-size: 9.5pt; color: #555; margin-top: 3px; }
|
|
h2 { color: #002C50; font-size: 12pt; margin: 18px 0 8px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
table.kv { width: 100%; border-collapse: collapse; margin-bottom: 12px; }
|
|
table.kv td { padding: 6px 0; vertical-align: top; }
|
|
table.kv td.label { color: #666; width: 32%; }
|
|
table.kv td.val { color: #111; font-weight: 500; }
|
|
.service-box { border: 1px solid #d8dde3; border-radius: 4px; padding: 14px; margin-bottom: 16px; }
|
|
.service-box .name { font-weight: 700; color: #002C50; font-size: 12pt; margin-bottom: 4px; }
|
|
.service-box .desc { color: #555; font-size: 9.5pt; margin-bottom: 8px; }
|
|
.price-row { display: flex; justify-content: space-between; align-items: center; border-top: 2px solid #002C50; margin-top: 12px; padding-top: 10px; }
|
|
.price-row .label { font-size: 11pt; color: #002C50; font-weight: 600; }
|
|
.price-row .amount { font-size: 16pt; color: #6A971F; font-weight: 700; }
|
|
.note { font-size: 8.5pt; color: #777; line-height: 1.4; margin-top: 26px; padding-top: 10px; border-top: 1px dashed #d8dde3; }
|
|
.footer { font-size: 8pt; color: #999; text-align: center; margin-top: 30px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="header">
|
|
<div class="brand">
|
|
<h1>Farmacia Ianni</h1>
|
|
<div class="sub">Salute · Servizi · Prevenzione</div>
|
|
</div>
|
|
<div class="pharm">
|
|
<strong>{{ pharmacy.name }}</strong>
|
|
{{ pharmacy.address }}<br>
|
|
Tel. {{ pharmacy.phone }}<br>
|
|
{{ pharmacy.email }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="receipt-block">
|
|
<div class="num">Ricevuta di prenotazione N° {{ booking.receipt_number or '—' }}</div>
|
|
<div class="meta">Emessa il {{ now_str }} · {{ pharmacy.name }}</div>
|
|
</div>
|
|
|
|
<h2>Cliente</h2>
|
|
<table class="kv">
|
|
<tr><td class="label">Nome e cognome</td><td class="val">{{ booking.customer_name }}</td></tr>
|
|
<tr><td class="label">Telefono</td><td class="val">{{ booking.customer_phone }}</td></tr>
|
|
{% if booking.customer_email %}<tr><td class="label">Email</td><td class="val">{{ booking.customer_email }}</td></tr>{% endif %}
|
|
</table>
|
|
|
|
<h2>Appuntamento</h2>
|
|
<table class="kv">
|
|
<tr><td class="label">Data e ora</td><td class="val">{{ start_str }}</td></tr>
|
|
<tr><td class="label">Durata</td><td class="val">{{ service.duration_min }} minuti</td></tr>
|
|
<tr><td class="label">Operatore</td><td class="val">{{ provider.name }}</td></tr>
|
|
<tr><td class="label">Sede</td><td class="val">{{ pharmacy.address }}</td></tr>
|
|
</table>
|
|
|
|
<h2>Servizio prenotato</h2>
|
|
<div class="service-box">
|
|
<div class="name">{{ service.name }}</div>
|
|
{% if service.description %}<div class="desc">{{ service.description }}</div>{% endif %}
|
|
<table class="kv" style="margin-bottom:0">
|
|
<tr><td class="label">Categoria</td><td class="val">{{ service.category|capitalize }}</td></tr>
|
|
{% if service.availability_text %}<tr><td class="label">Fascia di erogazione</td><td class="val">{{ service.availability_text }}</td></tr>{% endif %}
|
|
</table>
|
|
<div class="price-row">
|
|
<div class="label">Importo previsto</div>
|
|
<div class="amount">{{ price_str }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if booking.notes %}
|
|
<h2>Note</h2>
|
|
<div style="background:#fff8e1;border-left:3px solid #f0c040;padding:8px 12px;font-size:10pt;">{{ booking.notes }}</div>
|
|
{% endif %}
|
|
|
|
<div class="note">
|
|
<strong>Avvertenze.</strong> Questa è una ricevuta di prenotazione: documenta l'appuntamento e l'importo previsto, ma NON sostituisce lo scontrino fiscale o la fattura sanitaria che verranno emessi dal personale di farmacia al momento dell'esecuzione del servizio. I servizi sanitari di diagnostica e prevenzione sono esenti IVA ai sensi dell'art. 10 DPR 633/1972. Per disdire l'appuntamento contattare la farmacia con almeno 24 ore di anticipo.
|
|
</div>
|
|
|
|
<div class="footer">
|
|
Token verifica: {{ booking.receipt_token[:8] }}… · Generato automaticamente da bookingservizi.farmaciaianni.it
|
|
</div>
|
|
|
|
</body>
|
|
</html>
|