This commit is contained in:
Iliyan Angelov
2025-11-21 01:20:51 +02:00
parent a38ab4fa82
commit 6f85b8cf17
242 changed files with 7154 additions and 14492 deletions

View File

@@ -20,36 +20,5 @@ from .cookie_integration_config import CookieIntegrationConfig
from .system_settings import SystemSettings
from .invoice import Invoice, InvoiceItem
from .page_content import PageContent, PageType
__all__ = [
"Role",
"User",
"RefreshToken",
"PasswordResetToken",
"RoomType",
"Room",
"Booking",
"Payment",
"Service",
"ServiceUsage",
"ServiceBooking",
"ServiceBookingItem",
"ServicePayment",
"ServiceBookingStatus",
"ServicePaymentStatus",
"ServicePaymentMethod",
"Promotion",
"CheckInCheckOut",
"Banner",
"Review",
"Favorite",
"AuditLog",
"CookiePolicy",
"CookieIntegrationConfig",
"SystemSettings",
"Invoice",
"InvoiceItem",
"PageContent",
"PageType",
]
from .chat import Chat, ChatMessage, ChatStatus
__all__ = ['Role', 'User', 'RefreshToken', 'PasswordResetToken', 'RoomType', 'Room', 'Booking', 'Payment', 'Service', 'ServiceUsage', 'ServiceBooking', 'ServiceBookingItem', 'ServicePayment', 'ServiceBookingStatus', 'ServicePaymentStatus', 'ServicePaymentMethod', 'Promotion', 'CheckInCheckOut', 'Banner', 'Review', 'Favorite', 'AuditLog', 'CookiePolicy', 'CookieIntegrationConfig', 'SystemSettings', 'Invoice', 'InvoiceItem', 'PageContent', 'PageType', 'Chat', 'ChatMessage', 'ChatStatus']

Binary file not shown.

View File

@@ -1,28 +1,20 @@
"""
Audit log model for tracking important actions
"""
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, JSON
from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class AuditLog(Base):
__tablename__ = "audit_logs"
__tablename__ = 'audit_logs'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True)
action = Column(String(100), nullable=False, index=True) # e.g., "user.created", "booking.cancelled"
resource_type = Column(String(50), nullable=False, index=True) # e.g., "user", "booking"
user_id = Column(Integer, ForeignKey('users.id'), nullable=True, index=True)
action = Column(String(100), nullable=False, index=True)
resource_type = Column(String(50), nullable=False, index=True)
resource_id = Column(Integer, nullable=True, index=True)
ip_address = Column(String(45), nullable=True) # IPv6 compatible
ip_address = Column(String(45), nullable=True)
user_agent = Column(String(255), nullable=True)
request_id = Column(String(36), nullable=True, index=True) # UUID
details = Column(JSON, nullable=True) # Additional context
status = Column(String(20), nullable=False, default="success") # success, failed, error
request_id = Column(String(36), nullable=True, index=True)
details = Column(JSON, nullable=True)
status = Column(String(20), nullable=False, default='success')
error_message = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
# Relationships
user = relationship("User", foreign_keys=[user_id])
user = relationship('User', foreign_keys=[user_id])

View File

@@ -3,16 +3,14 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class Banner(Base):
__tablename__ = "banners"
__tablename__ = 'banners'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
title = Column(String(100), nullable=False)
description = Column(Text, nullable=True)
image_url = Column(String(255), nullable=False)
link_url = Column(String(255), nullable=True)
position = Column(String(50), nullable=False, default="home")
position = Column(String(50), nullable=False, default='home')
display_order = Column(Integer, nullable=False, default=0)
is_active = Column(Boolean, nullable=False, default=True)
start_date = Column(DateTime, nullable=True)
@@ -27,5 +25,4 @@ class Banner(Base):
return False
if not self.start_date or not self.end_date:
return self.is_active
return self.start_date <= now <= self.end_date
return self.start_date <= now <= self.end_date

View File

@@ -4,41 +4,35 @@ from datetime import datetime
import enum
from ..config.database import Base
class BookingStatus(str, enum.Enum):
pending = "pending"
confirmed = "confirmed"
checked_in = "checked_in"
checked_out = "checked_out"
cancelled = "cancelled"
pending = 'pending'
confirmed = 'confirmed'
checked_in = 'checked_in'
checked_out = 'checked_out'
cancelled = 'cancelled'
class Booking(Base):
__tablename__ = "bookings"
__tablename__ = 'bookings'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
booking_number = Column(String(50), unique=True, nullable=False, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
room_id = Column(Integer, ForeignKey("rooms.id"), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
room_id = Column(Integer, ForeignKey('rooms.id'), nullable=False)
check_in_date = Column(DateTime, nullable=False)
check_out_date = Column(DateTime, nullable=False)
num_guests = Column(Integer, nullable=False, default=1)
total_price = Column(Numeric(10, 2), nullable=False)
original_price = Column(Numeric(10, 2), nullable=True) # Price before discount
discount_amount = Column(Numeric(10, 2), nullable=True, default=0) # Discount amount applied
promotion_code = Column(String(50), nullable=True) # Promotion code used
original_price = Column(Numeric(10, 2), nullable=True)
discount_amount = Column(Numeric(10, 2), nullable=True, default=0)
promotion_code = Column(String(50), nullable=True)
status = Column(Enum(BookingStatus), nullable=False, default=BookingStatus.pending)
deposit_paid = Column(Boolean, nullable=False, default=False)
requires_deposit = Column(Boolean, nullable=False, default=False)
special_requests = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
user = relationship("User", back_populates="bookings")
room = relationship("Room", back_populates="bookings")
payments = relationship("Payment", back_populates="booking", cascade="all, delete-orphan")
invoices = relationship("Invoice", back_populates="booking", cascade="all, delete-orphan")
service_usages = relationship("ServiceUsage", back_populates="booking", cascade="all, delete-orphan")
checkin_checkout = relationship("CheckInCheckOut", back_populates="booking", uselist=False)
user = relationship('User', back_populates='bookings')
room = relationship('Room', back_populates='bookings')
payments = relationship('Payment', back_populates='booking', cascade='all, delete-orphan')
invoices = relationship('Invoice', back_populates='booking', cascade='all, delete-orphan')
service_usages = relationship('ServiceUsage', back_populates='booking', cascade='all, delete-orphan')
checkin_checkout = relationship('CheckInCheckOut', back_populates='booking', uselist=False)

View File

@@ -0,0 +1,37 @@
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, Enum, Boolean
from sqlalchemy.orm import relationship
from datetime import datetime
import enum
from ..config.database import Base
class ChatStatus(str, enum.Enum):
pending = 'pending'
active = 'active'
closed = 'closed'
class Chat(Base):
__tablename__ = 'chats'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
visitor_id = Column(Integer, ForeignKey('users.id'), nullable=True)
visitor_name = Column(String(100), nullable=True)
visitor_email = Column(String(100), nullable=True)
staff_id = Column(Integer, ForeignKey('users.id'), nullable=True)
status = Column(Enum(ChatStatus), nullable=False, default=ChatStatus.pending)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
closed_at = Column(DateTime, nullable=True)
visitor = relationship('User', foreign_keys=[visitor_id], back_populates='visitor_chats')
staff = relationship('User', foreign_keys=[staff_id], back_populates='staff_chats')
messages = relationship('ChatMessage', back_populates='chat', cascade='all, delete-orphan', order_by='ChatMessage.created_at')
class ChatMessage(Base):
__tablename__ = 'chat_messages'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
chat_id = Column(Integer, ForeignKey('chats.id'), nullable=False)
sender_id = Column(Integer, ForeignKey('users.id'), nullable=True)
sender_type = Column(String(20), nullable=False)
message = Column(Text, nullable=False)
is_read = Column(Boolean, nullable=False, default=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
chat = relationship('Chat', back_populates='messages')
sender = relationship('User', foreign_keys=[sender_id])

View File

@@ -3,25 +3,20 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class CheckInCheckOut(Base):
__tablename__ = "checkin_checkout"
__tablename__ = 'checkin_checkout'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
booking_id = Column(Integer, ForeignKey("bookings.id"), nullable=False, unique=True)
booking_id = Column(Integer, ForeignKey('bookings.id'), nullable=False, unique=True)
checkin_time = Column(DateTime, nullable=True)
checkout_time = Column(DateTime, nullable=True)
checkin_by = Column(Integer, ForeignKey("users.id"), nullable=True)
checkout_by = Column(Integer, ForeignKey("users.id"), nullable=True)
checkin_by = Column(Integer, ForeignKey('users.id'), nullable=True)
checkout_by = Column(Integer, ForeignKey('users.id'), nullable=True)
room_condition_checkin = Column(Text, nullable=True)
room_condition_checkout = Column(Text, nullable=True)
additional_charges = Column(Numeric(10, 2), nullable=False, default=0.0)
notes = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
booking = relationship("Booking", back_populates="checkin_checkout")
checked_in_by = relationship("User", foreign_keys=[checkin_by], back_populates="checkins_processed")
checked_out_by = relationship("User", foreign_keys=[checkout_by], back_populates="checkouts_processed")
booking = relationship('Booking', back_populates='checkin_checkout')
checked_in_by = relationship('User', foreign_keys=[checkin_by], back_populates='checkins_processed')
checked_out_by = relationship('User', foreign_keys=[checkout_by], back_populates='checkouts_processed')

View File

@@ -1,30 +1,14 @@
from datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
from ..config.database import Base
class CookieIntegrationConfig(Base):
"""
Stores IDs for well-known integrations (e.g., Google Analytics, Meta Pixel).
Does NOT allow arbitrary script injection from the dashboard.
"""
__tablename__ = "cookie_integration_configs"
__tablename__ = 'cookie_integration_configs'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
ga_measurement_id = Column(String(64), nullable=True) # e.g. G-XXXXXXXXXX
fb_pixel_id = Column(String(64), nullable=True) # e.g. 1234567890
ga_measurement_id = Column(String(64), nullable=True)
fb_pixel_id = Column(String(64), nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
)
updated_by_id = Column(Integer, ForeignKey("users.id"), nullable=True)
updated_by = relationship("User", lazy="joined")
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
updated_by_id = Column(Integer, ForeignKey('users.id'), nullable=True)
updated_by = relationship('User', lazy='joined')

View File

@@ -1,31 +1,15 @@
from datetime import datetime
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer
from sqlalchemy.orm import relationship
from ..config.database import Base
class CookiePolicy(Base):
"""
Global cookie policy controlled by administrators.
This does NOT store per-user consent; it controls which cookie categories
are available to be requested from users (e.g., disable analytics entirely).
"""
__tablename__ = "cookie_policies"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
analytics_enabled = Column(Boolean, default=True, nullable=False)
marketing_enabled = Column(Boolean, default=True, nullable=False)
preferences_enabled = Column(Boolean, default=True, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
updated_by_id = Column(Integer, ForeignKey("users.id"), nullable=True)
updated_by = relationship("User", lazy="joined")
__tablename__ = 'cookie_policies'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
analytics_enabled = Column(Boolean, default=True, nullable=False)
marketing_enabled = Column(Boolean, default=True, nullable=False)
preferences_enabled = Column(Boolean, default=True, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
updated_by_id = Column(Integer, ForeignKey('users.id'), nullable=True)
updated_by = relationship('User', lazy='joined')

View File

@@ -3,17 +3,12 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class Favorite(Base):
__tablename__ = "favorites"
__tablename__ = 'favorites'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
room_id = Column(Integer, ForeignKey("rooms.id"), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
room_id = Column(Integer, ForeignKey('rooms.id'), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
user = relationship("User", back_populates="favorites")
room = relationship("Room", back_populates="favorites")
user = relationship('User', back_populates='favorites')
room = relationship('Room', back_populates='favorites')

View File

@@ -4,98 +4,68 @@ from datetime import datetime
import enum
from ..config.database import Base
class InvoiceStatus(str, enum.Enum):
draft = "draft"
sent = "sent"
paid = "paid"
overdue = "overdue"
cancelled = "cancelled"
draft = 'draft'
sent = 'sent'
paid = 'paid'
overdue = 'overdue'
cancelled = 'cancelled'
class Invoice(Base):
__tablename__ = "invoices"
__tablename__ = 'invoices'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
invoice_number = Column(String(50), unique=True, nullable=False, index=True)
booking_id = Column(Integer, ForeignKey("bookings.id"), nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# Invoice details
booking_id = Column(Integer, ForeignKey('bookings.id'), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
issue_date = Column(DateTime, default=datetime.utcnow, nullable=False)
due_date = Column(DateTime, nullable=False)
paid_date = Column(DateTime, nullable=True)
# Amounts
subtotal = Column(Numeric(10, 2), nullable=False, default=0.00)
tax_rate = Column(Numeric(5, 2), nullable=False, default=0.00) # Tax percentage
tax_amount = Column(Numeric(10, 2), nullable=False, default=0.00)
discount_amount = Column(Numeric(10, 2), nullable=False, default=0.00)
subtotal = Column(Numeric(10, 2), nullable=False, default=0.0)
tax_rate = Column(Numeric(5, 2), nullable=False, default=0.0)
tax_amount = Column(Numeric(10, 2), nullable=False, default=0.0)
discount_amount = Column(Numeric(10, 2), nullable=False, default=0.0)
total_amount = Column(Numeric(10, 2), nullable=False)
amount_paid = Column(Numeric(10, 2), nullable=False, default=0.00)
amount_paid = Column(Numeric(10, 2), nullable=False, default=0.0)
balance_due = Column(Numeric(10, 2), nullable=False)
# Status
status = Column(Enum(InvoiceStatus), nullable=False, default=InvoiceStatus.draft)
is_proforma = Column(Boolean, nullable=False, default=False) # True for proforma invoices
# Company/Organization information (for admin to manage)
is_proforma = Column(Boolean, nullable=False, default=False)
company_name = Column(String(200), nullable=True)
company_address = Column(Text, nullable=True)
company_phone = Column(String(50), nullable=True)
company_email = Column(String(100), nullable=True)
company_tax_id = Column(String(100), nullable=True)
company_logo_url = Column(String(500), nullable=True)
# Customer information (snapshot at invoice creation)
customer_name = Column(String(200), nullable=False)
customer_email = Column(String(100), nullable=False)
customer_address = Column(Text, nullable=True)
customer_phone = Column(String(50), nullable=True)
customer_tax_id = Column(String(100), nullable=True)
# Additional information
notes = Column(Text, nullable=True)
terms_and_conditions = Column(Text, nullable=True)
payment_instructions = Column(Text, nullable=True)
# Metadata
created_by_id = Column(Integer, ForeignKey("users.id"), nullable=True)
updated_by_id = Column(Integer, ForeignKey("users.id"), nullable=True)
created_by_id = Column(Integer, ForeignKey('users.id'), nullable=True)
updated_by_id = Column(Integer, ForeignKey('users.id'), nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
booking = relationship("Booking", back_populates="invoices")
user = relationship("User", foreign_keys=[user_id], backref="invoices")
created_by = relationship("User", foreign_keys=[created_by_id])
updated_by = relationship("User", foreign_keys=[updated_by_id])
items = relationship("InvoiceItem", back_populates="invoice", cascade="all, delete-orphan")
booking = relationship('Booking', back_populates='invoices')
user = relationship('User', foreign_keys=[user_id], backref='invoices')
created_by = relationship('User', foreign_keys=[created_by_id])
updated_by = relationship('User', foreign_keys=[updated_by_id])
items = relationship('InvoiceItem', back_populates='invoice', cascade='all, delete-orphan')
class InvoiceItem(Base):
__tablename__ = "invoice_items"
__tablename__ = 'invoice_items'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
invoice_id = Column(Integer, ForeignKey("invoices.id"), nullable=False)
# Item details
invoice_id = Column(Integer, ForeignKey('invoices.id'), nullable=False)
description = Column(String(500), nullable=False)
quantity = Column(Numeric(10, 2), nullable=False, default=1.00)
quantity = Column(Numeric(10, 2), nullable=False, default=1.0)
unit_price = Column(Numeric(10, 2), nullable=False)
tax_rate = Column(Numeric(5, 2), nullable=False, default=0.00)
discount_amount = Column(Numeric(10, 2), nullable=False, default=0.00)
tax_rate = Column(Numeric(5, 2), nullable=False, default=0.0)
discount_amount = Column(Numeric(10, 2), nullable=False, default=0.0)
line_total = Column(Numeric(10, 2), nullable=False)
# Optional reference to booking items
room_id = Column(Integer, ForeignKey("rooms.id"), nullable=True)
service_id = Column(Integer, ForeignKey("services.id"), nullable=True)
# Metadata
room_id = Column(Integer, ForeignKey('rooms.id'), nullable=True)
service_id = Column(Integer, ForeignKey('services.id'), nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Relationships
invoice = relationship("Invoice", back_populates="items")
room = relationship("Room")
service = relationship("Service")
invoice = relationship('Invoice', back_populates='items')
room = relationship('Room')
service = relationship('Service')

View File

@@ -2,31 +2,23 @@ from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, Enum as
from sqlalchemy.orm import relationship
from datetime import datetime
import enum
from ..config.database import Base
class PageType(str, enum.Enum):
HOME = "home"
CONTACT = "contact"
ABOUT = "about"
FOOTER = "footer"
SEO = "seo"
HOME = 'home'
CONTACT = 'contact'
ABOUT = 'about'
FOOTER = 'footer'
SEO = 'seo'
class PageContent(Base):
__tablename__ = "page_contents"
__tablename__ = 'page_contents'
id = Column(Integer, primary_key=True, index=True)
page_type = Column(SQLEnum(PageType), nullable=False, unique=True, index=True)
# General content fields
title = Column(String(500), nullable=True)
subtitle = Column(String(1000), nullable=True)
description = Column(Text, nullable=True)
content = Column(Text, nullable=True) # Rich text content
# SEO fields
content = Column(Text, nullable=True)
meta_title = Column(String(500), nullable=True)
meta_description = Column(Text, nullable=True)
meta_keywords = Column(String(1000), nullable=True)
@@ -34,67 +26,57 @@ class PageContent(Base):
og_description = Column(Text, nullable=True)
og_image = Column(String(1000), nullable=True)
canonical_url = Column(String(1000), nullable=True)
# Contact/Footer specific fields (stored as JSON strings)
contact_info = Column(Text, nullable=True) # JSON: phone, email, address
map_url = Column(String(1000), nullable=True) # Google Maps embed URL
social_links = Column(Text, nullable=True) # JSON: facebook, twitter, instagram, etc.
footer_links = Column(Text, nullable=True) # JSON: quick links, support links
badges = Column(Text, nullable=True) # JSON: array of badges with text and icon
copyright_text = Column(Text, nullable=True) # Copyright text with {YEAR} placeholder for automatic year
# Home page specific
contact_info = Column(Text, nullable=True)
map_url = Column(String(1000), nullable=True)
social_links = Column(Text, nullable=True)
footer_links = Column(Text, nullable=True)
badges = Column(Text, nullable=True)
copyright_text = Column(Text, nullable=True)
hero_title = Column(String(500), nullable=True)
hero_subtitle = Column(String(1000), nullable=True)
hero_image = Column(String(1000), nullable=True)
# About page specific
story_content = Column(Text, nullable=True)
values = Column(Text, nullable=True) # JSON array of values
features = Column(Text, nullable=True) # JSON array of features
about_hero_image = Column(Text, nullable=True) # Hero image for about page
mission = Column(Text, nullable=True) # Mission statement
vision = Column(Text, nullable=True) # Vision statement
team = Column(Text, nullable=True) # JSON array of team members with name, role, image, bio, social_links
timeline = Column(Text, nullable=True) # JSON array of timeline events with year, title, description, image
achievements = Column(Text, nullable=True) # JSON array of achievements with icon, title, description, year, image
# Home page luxury sections
values = Column(Text, nullable=True)
features = Column(Text, nullable=True)
about_hero_image = Column(Text, nullable=True)
mission = Column(Text, nullable=True)
vision = Column(Text, nullable=True)
team = Column(Text, nullable=True)
timeline = Column(Text, nullable=True)
achievements = Column(Text, nullable=True)
luxury_section_title = Column(Text, nullable=True)
luxury_section_subtitle = Column(Text, nullable=True)
luxury_section_image = Column(Text, nullable=True)
luxury_features = Column(Text, nullable=True) # JSON array of features with icon, title, description
luxury_features = Column(Text, nullable=True)
luxury_gallery_section_title = Column(Text, nullable=True)
luxury_gallery_section_subtitle = Column(Text, nullable=True)
luxury_gallery = Column(Text, nullable=True) # JSON array of image URLs
luxury_gallery = Column(Text, nullable=True)
luxury_testimonials_section_title = Column(Text, nullable=True)
luxury_testimonials_section_subtitle = Column(Text, nullable=True)
luxury_testimonials = Column(Text, nullable=True) # JSON array of testimonials
luxury_testimonials = Column(Text, nullable=True)
amenities_section_title = Column(String(500), nullable=True)
amenities_section_subtitle = Column(String(1000), nullable=True)
amenities = Column(Text, nullable=True) # JSON array of amenities with icon, title, description, image
amenities = Column(Text, nullable=True)
testimonials_section_title = Column(String(500), nullable=True)
testimonials_section_subtitle = Column(String(1000), nullable=True)
testimonials = Column(Text, nullable=True) # JSON array of testimonials with name, role, image, rating, comment
testimonials = Column(Text, nullable=True)
gallery_section_title = Column(String(500), nullable=True)
gallery_section_subtitle = Column(String(1000), nullable=True)
gallery_images = Column(Text, nullable=True) # JSON array of image URLs
gallery_images = Column(Text, nullable=True)
about_preview_title = Column(String(500), nullable=True)
about_preview_subtitle = Column(String(1000), nullable=True)
about_preview_content = Column(Text, nullable=True)
about_preview_image = Column(String(1000), nullable=True)
stats = Column(Text, nullable=True) # JSON array of stats with number, label, icon
# Additional luxury sections
stats = Column(Text, nullable=True)
luxury_services_section_title = Column(Text, nullable=True)
luxury_services_section_subtitle = Column(Text, nullable=True)
luxury_services = Column(Text, nullable=True) # JSON array of services with icon, title, description, image
luxury_services = Column(Text, nullable=True)
luxury_experiences_section_title = Column(Text, nullable=True)
luxury_experiences_section_subtitle = Column(Text, nullable=True)
luxury_experiences = Column(Text, nullable=True) # JSON array of experiences with icon, title, description, image
luxury_experiences = Column(Text, nullable=True)
awards_section_title = Column(Text, nullable=True)
awards_section_subtitle = Column(Text, nullable=True)
awards = Column(Text, nullable=True) # JSON array of awards with icon, title, description, image, year
awards = Column(Text, nullable=True)
cta_title = Column(Text, nullable=True)
cta_subtitle = Column(Text, nullable=True)
cta_button_text = Column(Text, nullable=True)
@@ -102,12 +84,7 @@ class PageContent(Base):
cta_image = Column(Text, nullable=True)
partners_section_title = Column(Text, nullable=True)
partners_section_subtitle = Column(Text, nullable=True)
partners = Column(Text, nullable=True) # JSON array of partners with name, logo, link
# Status
partners = Column(Text, nullable=True)
is_active = Column(Boolean, default=True, nullable=False)
# Timestamps
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)

View File

@@ -3,18 +3,13 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class PasswordResetToken(Base):
__tablename__ = "password_reset_tokens"
__tablename__ = 'password_reset_tokens'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
token = Column(String(255), unique=True, nullable=False, index=True)
expires_at = Column(DateTime, nullable=False)
used = Column(Boolean, default=False, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
user = relationship("User")
user = relationship('User')

View File

@@ -4,48 +4,40 @@ from datetime import datetime
import enum
from ..config.database import Base
class PaymentMethod(str, enum.Enum):
cash = "cash"
credit_card = "credit_card"
debit_card = "debit_card"
bank_transfer = "bank_transfer"
e_wallet = "e_wallet"
stripe = "stripe"
paypal = "paypal"
cash = 'cash'
credit_card = 'credit_card'
debit_card = 'debit_card'
bank_transfer = 'bank_transfer'
e_wallet = 'e_wallet'
stripe = 'stripe'
paypal = 'paypal'
class PaymentType(str, enum.Enum):
full = "full"
deposit = "deposit"
remaining = "remaining"
full = 'full'
deposit = 'deposit'
remaining = 'remaining'
class PaymentStatus(str, enum.Enum):
pending = "pending"
completed = "completed"
failed = "failed"
refunded = "refunded"
pending = 'pending'
completed = 'completed'
failed = 'failed'
refunded = 'refunded'
class Payment(Base):
__tablename__ = "payments"
__tablename__ = 'payments'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
booking_id = Column(Integer, ForeignKey("bookings.id"), nullable=False)
booking_id = Column(Integer, ForeignKey('bookings.id'), nullable=False)
amount = Column(Numeric(10, 2), nullable=False)
payment_method = Column(Enum(PaymentMethod), nullable=False)
payment_type = Column(Enum(PaymentType), nullable=False, default=PaymentType.full)
deposit_percentage = Column(Integer, nullable=True)
related_payment_id = Column(Integer, ForeignKey("payments.id"), nullable=True)
related_payment_id = Column(Integer, ForeignKey('payments.id'), nullable=True)
payment_status = Column(Enum(PaymentStatus), nullable=False, default=PaymentStatus.pending)
transaction_id = Column(String(100), nullable=True)
payment_date = Column(DateTime, nullable=True)
notes = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
booking = relationship("Booking", back_populates="payments")
related_payment = relationship("Payment", remote_side=[id], backref="related_payments")
booking = relationship('Booking', back_populates='payments')
related_payment = relationship('Payment', remote_side=[id], backref='related_payments')

View File

@@ -4,15 +4,12 @@ from datetime import datetime
import enum
from ..config.database import Base
class DiscountType(str, enum.Enum):
percentage = "percentage"
fixed_amount = "fixed_amount"
percentage = 'percentage'
fixed_amount = 'fixed_amount'
class Promotion(Base):
__tablename__ = "promotions"
__tablename__ = 'promotions'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
code = Column(String(50), unique=True, nullable=False, index=True)
name = Column(String(100), nullable=False)
@@ -43,18 +40,13 @@ class Promotion(Base):
def calculate_discount(self, booking_amount):
if not self.is_valid():
return 0.0
if self.min_booking_amount and booking_amount < float(self.min_booking_amount):
return 0.0
discount = 0.0
if self.discount_type == DiscountType.percentage:
discount = float(booking_amount) * float(self.discount_value) / 100.0
elif self.discount_type == DiscountType.fixed_amount:
discount = float(self.discount_value)
if self.max_discount_amount and discount > float(self.max_discount_amount):
discount = float(self.max_discount_amount)
return discount
return discount

View File

@@ -3,16 +3,11 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class RefreshToken(Base):
__tablename__ = "refresh_tokens"
__tablename__ = 'refresh_tokens'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
token = Column(String(500), unique=True, nullable=False, index=True)
expires_at = Column(DateTime, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Relationships
user = relationship("User", back_populates="refresh_tokens")
user = relationship('User', back_populates='refresh_tokens')

View File

@@ -4,26 +4,20 @@ from datetime import datetime
import enum
from ..config.database import Base
class ReviewStatus(str, enum.Enum):
pending = "pending"
approved = "approved"
rejected = "rejected"
pending = 'pending'
approved = 'approved'
rejected = 'rejected'
class Review(Base):
__tablename__ = "reviews"
__tablename__ = 'reviews'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
room_id = Column(Integer, ForeignKey("rooms.id"), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
room_id = Column(Integer, ForeignKey('rooms.id'), nullable=False)
rating = Column(Integer, nullable=False)
comment = Column(Text, nullable=False)
status = Column(Enum(ReviewStatus), nullable=False, default=ReviewStatus.pending)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
user = relationship("User", back_populates="reviews")
room = relationship("Room", back_populates="reviews")
user = relationship('User', back_populates='reviews')
room = relationship('Room', back_populates='reviews')

View File

@@ -3,16 +3,11 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class Role(Base):
__tablename__ = "roles"
__tablename__ = 'roles'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String(50), unique=True, nullable=False, index=True)
description = Column(String(255), nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
users = relationship("User", back_populates="role")
users = relationship('User', back_populates='role')

View File

@@ -4,36 +4,30 @@ from datetime import datetime
import enum
from ..config.database import Base
class RoomStatus(str, enum.Enum):
available = "available"
occupied = "occupied"
maintenance = "maintenance"
cleaning = "cleaning"
available = 'available'
occupied = 'occupied'
maintenance = 'maintenance'
cleaning = 'cleaning'
class Room(Base):
__tablename__ = "rooms"
__tablename__ = 'rooms'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
room_type_id = Column(Integer, ForeignKey("room_types.id"), nullable=False)
room_type_id = Column(Integer, ForeignKey('room_types.id'), nullable=False)
room_number = Column(String(20), unique=True, nullable=False, index=True)
floor = Column(Integer, nullable=False)
status = Column(Enum(RoomStatus), nullable=False, default=RoomStatus.available)
price = Column(Numeric(10, 2), nullable=False)
featured = Column(Boolean, nullable=False, default=False)
capacity = Column(Integer, nullable=True) # Room-specific capacity, overrides room_type capacity
room_size = Column(String(50), nullable=True) # e.g., "1 Room", "2 Rooms", "50 sqm"
view = Column(String(100), nullable=True) # e.g., "City View", "Ocean View", etc.
capacity = Column(Integer, nullable=True)
room_size = Column(String(50), nullable=True)
view = Column(String(100), nullable=True)
images = Column(JSON, nullable=True)
amenities = Column(JSON, nullable=True)
description = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
room_type = relationship("RoomType", back_populates="rooms")
bookings = relationship("Booking", back_populates="room")
reviews = relationship("Review", back_populates="room")
favorites = relationship("Favorite", back_populates="room", cascade="all, delete-orphan")
room_type = relationship('RoomType', back_populates='rooms')
bookings = relationship('Booking', back_populates='room')
reviews = relationship('Review', back_populates='room')
favorites = relationship('Favorite', back_populates='room', cascade='all, delete-orphan')

View File

@@ -3,10 +3,8 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class RoomType(Base):
__tablename__ = "room_types"
__tablename__ = 'room_types'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String(100), unique=True, nullable=False)
description = Column(Text, nullable=True)
@@ -15,7 +13,4 @@ class RoomType(Base):
amenities = Column(JSON, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
rooms = relationship("Room", back_populates="room_type")
rooms = relationship('Room', back_populates='room_type')

View File

@@ -3,10 +3,8 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class Service(Base):
__tablename__ = "services"
__tablename__ = 'services'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String(100), nullable=False)
description = Column(Text, nullable=True)
@@ -15,7 +13,4 @@ class Service(Base):
is_active = Column(Boolean, nullable=False, default=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
service_usages = relationship("ServiceUsage", back_populates="service")
service_usages = relationship('ServiceUsage', back_populates='service')

View File

@@ -4,66 +4,53 @@ from datetime import datetime
import enum
from ..config.database import Base
class ServiceBookingStatus(str, enum.Enum):
pending = "pending"
confirmed = "confirmed"
completed = "completed"
cancelled = "cancelled"
pending = 'pending'
confirmed = 'confirmed'
completed = 'completed'
cancelled = 'cancelled'
class ServiceBooking(Base):
__tablename__ = "service_bookings"
__tablename__ = 'service_bookings'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
booking_number = Column(String(50), unique=True, nullable=False, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
total_amount = Column(Numeric(10, 2), nullable=False)
status = Column(Enum(ServiceBookingStatus), nullable=False, default=ServiceBookingStatus.pending)
notes = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
user = relationship("User", back_populates="service_bookings")
service_items = relationship("ServiceBookingItem", back_populates="service_booking", cascade="all, delete-orphan")
payments = relationship("ServicePayment", back_populates="service_booking", cascade="all, delete-orphan")
user = relationship('User', back_populates='service_bookings')
service_items = relationship('ServiceBookingItem', back_populates='service_booking', cascade='all, delete-orphan')
payments = relationship('ServicePayment', back_populates='service_booking', cascade='all, delete-orphan')
class ServiceBookingItem(Base):
__tablename__ = "service_booking_items"
__tablename__ = 'service_booking_items'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
service_booking_id = Column(Integer, ForeignKey("service_bookings.id"), nullable=False)
service_id = Column(Integer, ForeignKey("services.id"), nullable=False)
service_booking_id = Column(Integer, ForeignKey('service_bookings.id'), nullable=False)
service_id = Column(Integer, ForeignKey('services.id'), nullable=False)
quantity = Column(Integer, nullable=False, default=1)
unit_price = Column(Numeric(10, 2), nullable=False)
total_price = Column(Numeric(10, 2), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Relationships
service_booking = relationship("ServiceBooking", back_populates="service_items")
service = relationship("Service")
service_booking = relationship('ServiceBooking', back_populates='service_items')
service = relationship('Service')
class ServicePaymentStatus(str, enum.Enum):
pending = "pending"
completed = "completed"
failed = "failed"
refunded = "refunded"
pending = 'pending'
completed = 'completed'
failed = 'failed'
refunded = 'refunded'
class ServicePaymentMethod(str, enum.Enum):
cash = "cash"
stripe = "stripe"
bank_transfer = "bank_transfer"
cash = 'cash'
stripe = 'stripe'
bank_transfer = 'bank_transfer'
class ServicePayment(Base):
__tablename__ = "service_payments"
__tablename__ = 'service_payments'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
service_booking_id = Column(Integer, ForeignKey("service_bookings.id"), nullable=False)
service_booking_id = Column(Integer, ForeignKey('service_bookings.id'), nullable=False)
amount = Column(Numeric(10, 2), nullable=False)
payment_method = Column(Enum(ServicePaymentMethod), nullable=False)
payment_status = Column(Enum(ServicePaymentStatus), nullable=False, default=ServicePaymentStatus.pending)
@@ -72,7 +59,4 @@ class ServicePayment(Base):
notes = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
service_booking = relationship("ServiceBooking", back_populates="payments")
service_booking = relationship('ServiceBooking', back_populates='payments')

View File

@@ -3,13 +3,11 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class ServiceUsage(Base):
__tablename__ = "service_usages"
__tablename__ = 'service_usages'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
booking_id = Column(Integer, ForeignKey("bookings.id"), nullable=False)
service_id = Column(Integer, ForeignKey("services.id"), nullable=False)
booking_id = Column(Integer, ForeignKey('bookings.id'), nullable=False)
service_id = Column(Integer, ForeignKey('services.id'), nullable=False)
quantity = Column(Integer, nullable=False, default=1)
unit_price = Column(Numeric(10, 2), nullable=False)
total_price = Column(Numeric(10, 2), nullable=False)
@@ -17,8 +15,5 @@ class ServiceUsage(Base):
notes = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
booking = relationship("Booking", back_populates="service_usages")
service = relationship("Service", back_populates="service_usages")
booking = relationship('Booking', back_populates='service_usages')
service = relationship('Service', back_populates='service_usages')

View File

@@ -3,19 +3,12 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class SystemSettings(Base):
"""
System-wide settings controlled by administrators.
Stores key-value pairs for platform configuration like currency, etc.
"""
__tablename__ = "system_settings"
__tablename__ = 'system_settings'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
key = Column(String(100), unique=True, nullable=False, index=True)
value = Column(Text, nullable=False)
description = Column(Text, nullable=True)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
updated_by_id = Column(Integer, ForeignKey("users.id"), nullable=True)
updated_by = relationship("User", lazy="joined")
updated_by_id = Column(Integer, ForeignKey('users.id'), nullable=True)
updated_by = relationship('User', lazy='joined')

View File

@@ -3,33 +3,30 @@ from sqlalchemy.orm import relationship
from datetime import datetime
from ..config.database import Base
class User(Base):
__tablename__ = "users"
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
role_id = Column(Integer, ForeignKey("roles.id"), nullable=False)
role_id = Column(Integer, ForeignKey('roles.id'), nullable=False)
email = Column(String(100), unique=True, nullable=False, index=True)
password = Column(String(255), nullable=False)
full_name = Column(String(100), nullable=False)
phone = Column(String(20), nullable=True)
address = Column(Text, nullable=True)
avatar = Column(String(255), nullable=True)
currency = Column(String(3), nullable=False, default='VND') # ISO 4217 currency code
currency = Column(String(3), nullable=False, default='VND')
is_active = Column(Boolean, nullable=False, default=True)
mfa_enabled = Column(Boolean, nullable=False, default=False)
mfa_secret = Column(String(255), nullable=True) # TOTP secret key (encrypted in production)
mfa_backup_codes = Column(Text, nullable=True) # JSON array of backup codes (hashed)
mfa_secret = Column(String(255), nullable=True)
mfa_backup_codes = Column(Text, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
# Relationships
role = relationship("Role", back_populates="users")
bookings = relationship("Booking", back_populates="user")
refresh_tokens = relationship("RefreshToken", back_populates="user", cascade="all, delete-orphan")
checkins_processed = relationship("CheckInCheckOut", foreign_keys="CheckInCheckOut.checkin_by", back_populates="checked_in_by")
checkouts_processed = relationship("CheckInCheckOut", foreign_keys="CheckInCheckOut.checkout_by", back_populates="checked_out_by")
reviews = relationship("Review", back_populates="user")
favorites = relationship("Favorite", back_populates="user", cascade="all, delete-orphan")
service_bookings = relationship("ServiceBooking", back_populates="user")
role = relationship('Role', back_populates='users')
bookings = relationship('Booking', back_populates='user')
refresh_tokens = relationship('RefreshToken', back_populates='user', cascade='all, delete-orphan')
checkins_processed = relationship('CheckInCheckOut', foreign_keys='CheckInCheckOut.checkin_by', back_populates='checked_in_by')
checkouts_processed = relationship('CheckInCheckOut', foreign_keys='CheckInCheckOut.checkout_by', back_populates='checked_out_by')
reviews = relationship('Review', back_populates='user')
favorites = relationship('Favorite', back_populates='user', cascade='all, delete-orphan')
service_bookings = relationship('ServiceBooking', back_populates='user')
visitor_chats = relationship('Chat', foreign_keys='Chat.visitor_id', back_populates='visitor')
staff_chats = relationship('Chat', foreign_keys='Chat.staff_id', back_populates='staff')