Files
roaauto/backend/app/invoices/service.py
Marius Mutu 3bdafad22a 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>
2026-03-13 17:34:36 +02:00

50 lines
1.3 KiB
Python

from datetime import UTC, datetime
from sqlalchemy import select, text
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.base import uuid7
from app.db.models.invoice import Invoice
from app.db.models.order import Order
async def create_invoice(
db: AsyncSession, tenant_id: str, order_id: str
) -> Invoice:
r = await db.execute(
select(Order).where(Order.id == order_id, Order.tenant_id == tenant_id)
)
order = r.scalar_one_or_none()
if not order:
raise ValueError("Order not found")
if order.status != "VALIDAT":
raise ValueError("Order must be VALIDAT to create invoice")
# Generate next invoice number for this tenant
r = await db.execute(
text(
"SELECT COUNT(*) as cnt FROM invoices WHERE tenant_id = :tid"
),
{"tid": tenant_id},
)
count = r.scalar() + 1
now = datetime.now(UTC)
nr_factura = f"F-{now.year}-{count:04d}"
invoice = Invoice(
id=uuid7(),
tenant_id=tenant_id,
order_id=order_id,
nr_factura=nr_factura,
data_factura=now.isoformat().split("T")[0],
total=order.total_general,
)
db.add(invoice)
# Update order status to FACTURAT
order.status = "FACTURAT"
order.updated_at = now.isoformat()
await db.commit()
await db.refresh(invoice)
return invoice