feat(backend): PDF deviz + portal client + SMS + invoice service
- PDF generation with WeasyPrint: deviz and factura templates (A4, branding)
- GET /orders/{id}/pdf/deviz returns PDF with order lines and totals
- Client portal (public, no auth): GET /p/{token}, POST /p/{token}/accept|reject
- SMS service (SMSAPI.ro) - skips in dev when no token configured
- Invoice service: create from validated order, auto-number (F-YYYY-NNNN)
- GET /invoices/{id}/pdf returns factura PDF
- Order status_client field for client accept/reject tracking
- Alembic migration for status_client
- 19 passing tests (auth + sync + orders + pdf + portal + invoices)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
65
backend/app/pdf/templates/factura.html
Normal file
65
backend/app/pdf/templates/factura.html
Normal file
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ro"><head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
@page { size: A4; margin: 2cm; }
|
||||
body { font-family: DejaVu Sans, sans-serif; font-size: 11pt; color: #111; }
|
||||
.header { display: flex; justify-content: space-between; margin-bottom: 24px; }
|
||||
h2 { margin: 0; font-size: 16pt; }
|
||||
h3 { font-size: 11pt; margin: 16px 0 6px; color: #374151; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th { background: #f3f4f6; padding: 6px 8px; text-align: left; font-size: 10pt; }
|
||||
td { padding: 5px 8px; border-bottom: 1px solid #e5e7eb; font-size: 10pt; }
|
||||
.totals { margin-top: 20px; text-align: right; }
|
||||
.totals div { margin-bottom: 4px; }
|
||||
.total-final { font-weight: bold; font-size: 13pt; border-top: 2px solid #111; padding-top: 6px; }
|
||||
.info-box { border: 1px solid #d1d5db; padding: 12px; margin-bottom: 16px; }
|
||||
</style>
|
||||
</head><body>
|
||||
<div class="header">
|
||||
<div>
|
||||
<strong>FURNIZOR</strong><br>
|
||||
<strong>{{ tenant.nume }}</strong><br>
|
||||
{% if tenant.cui %}CUI: {{ tenant.cui }}<br>{% endif %}
|
||||
{% if tenant.reg_com %}Reg. Com.: {{ tenant.reg_com }}<br>{% endif %}
|
||||
{% if tenant.adresa %}{{ tenant.adresa }}<br>{% endif %}
|
||||
{% if tenant.iban %}IBAN: {{ tenant.iban }}<br>{% endif %}
|
||||
{% if tenant.banca %}Banca: {{ tenant.banca }}{% endif %}
|
||||
</div>
|
||||
<div style="text-align:right">
|
||||
<h2>FACTURA Nr. {{ invoice.nr_factura }}</h2>
|
||||
<div>Data: {{ invoice.data_factura }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>CLIENT</strong><br>
|
||||
{{ order.client_nume or 'N/A' }}<br>
|
||||
{% if order.client_cui %}CUI: {{ order.client_cui }}<br>{% endif %}
|
||||
{% if order.client_adresa %}{{ order.client_adresa }}{% endif %}
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 8px;">
|
||||
Auto: <strong>{{ order.nr_auto }}</strong> | Deviz: {{ order.id[:8]|upper }}
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr><th>#</th><th>Descriere</th><th>UM</th><th>Cant.</th><th>Pret unit. (RON)</th><th>Total (RON)</th></tr>
|
||||
{% for l in lines %}
|
||||
<tr>
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ l.descriere }}</td>
|
||||
<td>{{ l.um or ('ore' if l.tip == 'manopera' else 'buc') }}</td>
|
||||
<td>{{ l.ore if l.tip == 'manopera' else l.cantitate }}</td>
|
||||
<td>{{ "%.2f"|format(l.pret_ora if l.tip == 'manopera' else l.pret_unitar) }}</td>
|
||||
<td>{{ "%.2f"|format(l.total or 0) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<div class="totals">
|
||||
<div>Manopera: {{ "%.2f"|format(order.total_manopera or 0) }} RON</div>
|
||||
<div>Materiale: {{ "%.2f"|format(order.total_materiale or 0) }} RON</div>
|
||||
<div class="total-final">TOTAL: {{ "%.2f"|format(invoice.total or 0) }} RON</div>
|
||||
</div>
|
||||
</body></html>
|
||||
Reference in New Issue
Block a user