- 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
76 lines
2.9 KiB
Python
76 lines
2.9 KiB
Python
from sqlalchemy import Column, Integer, String, Boolean, Text, DateTime, ForeignKey, UniqueConstraint
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy.sql import func
|
|
from app.database import Base
|
|
|
|
|
|
class Service(Base):
|
|
__tablename__ = "services"
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
name = Column(Text, nullable=False)
|
|
slug = Column(Text, nullable=False, unique=True)
|
|
duration_min = Column(Integer, nullable=False, default=15)
|
|
description = Column(Text)
|
|
category = Column(Text, default="generale")
|
|
price_cents = Column(Integer)
|
|
availability_text = Column(Text)
|
|
active = Column(Boolean, default=True)
|
|
sort_order = Column(Integer, default=0)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
provider_services = relationship("ProviderService", back_populates="service")
|
|
bookings = relationship("Booking", back_populates="service")
|
|
|
|
|
|
class Provider(Base):
|
|
__tablename__ = "providers"
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
name = Column(Text, nullable=False)
|
|
email = Column(Text)
|
|
phone = Column(Text)
|
|
google_calendar_id = Column(Text)
|
|
active = Column(Boolean, default=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
provider_services = relationship("ProviderService", back_populates="provider")
|
|
bookings = relationship("Booking", back_populates="provider")
|
|
|
|
|
|
class ProviderService(Base):
|
|
__tablename__ = "provider_services"
|
|
__table_args__ = (UniqueConstraint("provider_id", "service_id"),)
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
provider_id = Column(Integer, ForeignKey("providers.id", ondelete="CASCADE"))
|
|
service_id = Column(Integer, ForeignKey("services.id", ondelete="CASCADE"))
|
|
availability_rules = Column(JSONB, nullable=False, default=[])
|
|
|
|
provider = relationship("Provider", back_populates="provider_services")
|
|
service = relationship("Service", back_populates="provider_services")
|
|
|
|
|
|
class Booking(Base):
|
|
__tablename__ = "bookings"
|
|
|
|
id = Column(Integer, primary_key=True)
|
|
service_id = Column(Integer, ForeignKey("services.id"))
|
|
provider_id = Column(Integer, ForeignKey("providers.id"))
|
|
customer_name = Column(Text, nullable=False)
|
|
customer_phone = Column(Text, nullable=False)
|
|
customer_email = Column(Text)
|
|
start_at = Column(DateTime(timezone=True), nullable=False)
|
|
end_at = Column(DateTime(timezone=True), nullable=False)
|
|
status = Column(Text, default="confirmed")
|
|
google_event_id = Column(Text)
|
|
receipt_number = Column(Text)
|
|
receipt_token = Column(Text)
|
|
notes = Column(Text)
|
|
reminder_sent = Column(Boolean, default=False)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
service = relationship("Service", back_populates="bookings")
|
|
provider = relationship("Provider", back_populates="bookings")
|