Register all active platform services and base features in core DB so admin can manage them, add delivery/hospitality/sports-center backend phases, update apps catalog and production deploy tooling. Co-authored-by: Cursor <cursoragent@cursor.com>
520 lines
19 KiB
Python
520 lines
19 KiB
Python
"""Hospitality foundation aggregates — Phase 12.0.
|
|
|
|
Independent aggregates use UUID references within hospitality_db only.
|
|
No SQLAlchemy relationship graphs between aggregates.
|
|
Venue formats are configurable catalog values — no format-specific engines.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
from sqlalchemy import Boolean, Index, Integer, String, Text, UniqueConstraint
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
from sqlalchemy.types import JSON
|
|
|
|
from app.core.database import Base
|
|
from app.models.base import (
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
SoftDeleteMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
UUIDPrimaryKeyMixin,
|
|
)
|
|
from app.models.types import (
|
|
AuditAction,
|
|
BundleKey,
|
|
BundleStatus,
|
|
GUID,
|
|
LifecycleStatus,
|
|
MenuStatus,
|
|
TableStatus,
|
|
VenueFormat,
|
|
)
|
|
|
|
|
|
class Venue(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
):
|
|
"""Root hospitality venue (cafe / restaurant / bakery / …) for a tenant."""
|
|
|
|
__tablename__ = "venues"
|
|
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[LifecycleStatus] = mapped_column(
|
|
default=LifecycleStatus.DRAFT, nullable=False
|
|
)
|
|
venue_format: Mapped[VenueFormat] = mapped_column(
|
|
default=VenueFormat.RESTAURANT, nullable=False
|
|
)
|
|
legal_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
timezone: Mapped[str] = mapped_column(String(64), default="Asia/Tehran", nullable=False)
|
|
language: Mapped[str] = mapped_column(String(16), default="fa", nullable=False)
|
|
currency_code: Mapped[str] = mapped_column(String(3), default="IRR", nullable=False)
|
|
settings: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint("tenant_id", "code", name="uq_venues_tenant_code"),
|
|
Index("ix_venues_tenant_status", "tenant_id", "status"),
|
|
Index("ix_venues_tenant_deleted", "tenant_id", "is_deleted"),
|
|
Index("ix_venues_tenant_format", "tenant_id", "venue_format"),
|
|
)
|
|
|
|
|
|
class Branch(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
"""Unlimited branches / outlets under a venue."""
|
|
|
|
__tablename__ = "branches"
|
|
|
|
venue_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[LifecycleStatus] = mapped_column(
|
|
default=LifecycleStatus.ACTIVE, nullable=False
|
|
)
|
|
address: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
timezone: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
working_hours: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id", "venue_id", "code", name="uq_branches_tenant_code"
|
|
),
|
|
Index("ix_branches_venue", "tenant_id", "venue_id"),
|
|
Index("ix_branches_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class DiningArea(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
"""Floor / dining area shell for table service."""
|
|
|
|
__tablename__ = "dining_areas"
|
|
|
|
venue_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
branch_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[LifecycleStatus] = mapped_column(
|
|
default=LifecycleStatus.ACTIVE, nullable=False
|
|
)
|
|
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id", "venue_id", "code", name="uq_dining_areas_tenant_code"
|
|
),
|
|
Index("ix_dining_areas_venue", "tenant_id", "venue_id"),
|
|
Index("ix_dining_areas_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class DiningTable(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
"""Table shell — seating / QR table binding engines come in later phases."""
|
|
|
|
__tablename__ = "dining_tables"
|
|
|
|
venue_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
dining_area_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
capacity: Mapped[int] = mapped_column(Integer, default=2, nullable=False)
|
|
status: Mapped[TableStatus] = mapped_column(
|
|
default=TableStatus.AVAILABLE, nullable=False
|
|
)
|
|
qr_token: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id", "venue_id", "code", name="uq_dining_tables_tenant_code"
|
|
),
|
|
Index("ix_dining_tables_venue", "tenant_id", "venue_id"),
|
|
Index("ix_dining_tables_area", "tenant_id", "dining_area_id"),
|
|
Index("ix_dining_tables_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class Menu(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
):
|
|
"""Digital menu shell — catalog depth expands in later phases."""
|
|
|
|
__tablename__ = "menus"
|
|
|
|
venue_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[MenuStatus] = mapped_column(default=MenuStatus.DRAFT, nullable=False)
|
|
currency_code: Mapped[str] = mapped_column(String(3), default="IRR", nullable=False)
|
|
is_default: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint("tenant_id", "venue_id", "code", name="uq_menus_tenant_code"),
|
|
Index("ix_menus_venue", "tenant_id", "venue_id"),
|
|
Index("ix_menus_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class MenuCategory(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
__tablename__ = "menu_categories"
|
|
|
|
venue_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
menu_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[LifecycleStatus] = mapped_column(
|
|
default=LifecycleStatus.ACTIVE, nullable=False
|
|
)
|
|
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id", "menu_id", "code", name="uq_menu_categories_tenant_code"
|
|
),
|
|
Index("ix_menu_categories_menu", "tenant_id", "menu_id"),
|
|
Index("ix_menu_categories_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class MenuItem(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
"""Menu item — catalog depth (modifiers/allergens/media) in Phase 12.1."""
|
|
|
|
__tablename__ = "menu_items"
|
|
|
|
venue_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
menu_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
category_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[LifecycleStatus] = mapped_column(
|
|
default=LifecycleStatus.ACTIVE, nullable=False
|
|
)
|
|
base_price: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
currency_code: Mapped[str] = mapped_column(String(3), default="IRR", nullable=False)
|
|
is_available: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
attributes: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
# Phase 12.1 catalog fields (additive, backward compatible)
|
|
preparation_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
calories: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
spicy_level: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
is_vegetarian: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
is_vegan: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
is_gluten_free: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
tags: Mapped[list | None] = mapped_column(JSON, nullable=True)
|
|
primary_media_ref: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id", "menu_id", "code", name="uq_menu_items_tenant_code"
|
|
),
|
|
Index("ix_menu_items_menu", "tenant_id", "menu_id"),
|
|
Index("ix_menu_items_category", "tenant_id", "category_id"),
|
|
Index("ix_menu_items_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class BundleDefinition(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
"""Catalog of licensable bundles available to a tenant (feature-based licensing)."""
|
|
|
|
__tablename__ = "bundle_definitions"
|
|
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
bundle_key: Mapped[BundleKey] = mapped_column(nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[BundleStatus] = mapped_column(
|
|
default=BundleStatus.AVAILABLE, nullable=False
|
|
)
|
|
feature_keys: Mapped[list | None] = mapped_column(JSON, nullable=True)
|
|
permission_prefixes: Mapped[list | None] = mapped_column(JSON, nullable=True)
|
|
api_prefixes: Mapped[list | None] = mapped_column(JSON, nullable=True)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint("tenant_id", "code", name="uq_bundle_definitions_tenant_code"),
|
|
UniqueConstraint(
|
|
"tenant_id", "bundle_key", name="uq_bundle_definitions_tenant_key"
|
|
),
|
|
Index("ix_bundle_definitions_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class TenantBundle(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
):
|
|
"""Activated bundle for a tenant/venue — hidden features must not expose APIs."""
|
|
|
|
__tablename__ = "tenant_bundles"
|
|
|
|
venue_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
bundle_definition_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
bundle_key: Mapped[BundleKey] = mapped_column(nullable=False)
|
|
status: Mapped[BundleStatus] = mapped_column(
|
|
default=BundleStatus.ACTIVE, nullable=False
|
|
)
|
|
activated_by: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id",
|
|
"venue_id",
|
|
"bundle_key",
|
|
name="uq_tenant_bundles_tenant_venue_key",
|
|
),
|
|
Index("ix_tenant_bundles_status", "tenant_id", "status"),
|
|
Index("ix_tenant_bundles_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class FeatureToggle(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
"""Per-tenant/venue feature toggle for capability discovery."""
|
|
|
|
__tablename__ = "feature_toggles"
|
|
|
|
venue_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
feature_key: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
bundle_key: Mapped[BundleKey | None] = mapped_column(nullable=True)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id",
|
|
"venue_id",
|
|
"feature_key",
|
|
name="uq_feature_toggles_tenant_venue_key",
|
|
),
|
|
Index("ix_feature_toggles_enabled", "tenant_id", "enabled"),
|
|
Index("ix_feature_toggles_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class HospitalityRole(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
__tablename__ = "hospitality_roles"
|
|
|
|
venue_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[LifecycleStatus] = mapped_column(
|
|
default=LifecycleStatus.ACTIVE, nullable=False
|
|
)
|
|
permission_keys: Mapped[list | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id", "code", name="uq_hospitality_roles_tenant_code"
|
|
),
|
|
Index("ix_hospitality_roles_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class HospitalityPermission(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
__tablename__ = "hospitality_permissions"
|
|
|
|
code: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
bundle_key: Mapped[BundleKey | None] = mapped_column(nullable=True)
|
|
status: Mapped[LifecycleStatus] = mapped_column(
|
|
default=LifecycleStatus.ACTIVE, nullable=False
|
|
)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id", "code", name="uq_hospitality_permissions_tenant_code"
|
|
),
|
|
Index("ix_hospitality_permissions_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class HospitalityConfiguration(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
):
|
|
__tablename__ = "hospitality_configurations"
|
|
|
|
venue_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
branch_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
config: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
status: Mapped[LifecycleStatus] = mapped_column(
|
|
default=LifecycleStatus.ACTIVE, nullable=False
|
|
)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id", "code", name="uq_hospitality_configurations_tenant_code"
|
|
),
|
|
Index("ix_hospitality_configurations_venue", "tenant_id", "venue_id"),
|
|
Index(
|
|
"ix_hospitality_configurations_tenant_deleted", "tenant_id", "is_deleted"
|
|
),
|
|
)
|
|
|
|
|
|
class HospitalityEvent(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
"""Domain event shell records (not the outbox bus)."""
|
|
|
|
__tablename__ = "hospitality_events"
|
|
|
|
venue_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=False)
|
|
event_type: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
payload: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
status: Mapped[LifecycleStatus] = mapped_column(
|
|
default=LifecycleStatus.ACTIVE, nullable=False
|
|
)
|
|
|
|
__table_args__ = (
|
|
Index("ix_hospitality_events_venue", "tenant_id", "venue_id"),
|
|
Index("ix_hospitality_events_type", "tenant_id", "event_type"),
|
|
Index("ix_hospitality_events_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class HospitalitySetting(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
):
|
|
__tablename__ = "hospitality_settings"
|
|
|
|
venue_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
key: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
value: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id", "venue_id", "key", name="uq_hospitality_settings_tenant_key"
|
|
),
|
|
Index("ix_hospitality_settings_tenant_deleted", "tenant_id", "is_deleted"),
|
|
)
|
|
|
|
|
|
class HospitalityAuditLog(Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin):
|
|
"""Append-only audit trail — no soft delete."""
|
|
|
|
__tablename__ = "hospitality_audit_logs"
|
|
|
|
entity_type: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
entity_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
action: Mapped[AuditAction] = mapped_column(nullable=False)
|
|
actor_user_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
changes: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
__table_args__ = (
|
|
Index("ix_hospitality_audit_entity", "tenant_id", "entity_type", "entity_id"),
|
|
Index("ix_hospitality_audit_actor", "tenant_id", "actor_user_id"),
|
|
)
|