
Some checks failed
Deploy to Production, build images and push to Gitea Registry / build_and_push (pull_request) Failing after 1m24s
This commit adds new guidelines for FastAPI and Vue.js development, emphasizing best practices for component structure, API performance, and data handling. It also introduces caching mechanisms using Redis for improved performance and updates the API structure to streamline authentication and user management. Additionally, new endpoints for categories and time entries are implemented, enhancing the overall functionality of the application.
473 lines
24 KiB
Python
473 lines
24 KiB
Python
import enum
|
|
import secrets
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from sqlalchemy import (
|
|
Column,
|
|
Integer,
|
|
String,
|
|
DateTime,
|
|
ForeignKey,
|
|
Boolean,
|
|
Enum as SAEnum,
|
|
UniqueConstraint,
|
|
Index,
|
|
DDL,
|
|
func,
|
|
text as sa_text,
|
|
Text,
|
|
Numeric,
|
|
CheckConstraint,
|
|
Date
|
|
)
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
|
|
from .database import Base
|
|
|
|
# --- Enums ---
|
|
class UserRoleEnum(enum.Enum):
|
|
owner = "owner"
|
|
member = "member"
|
|
|
|
class SplitTypeEnum(enum.Enum):
|
|
EQUAL = "EQUAL" # Split equally among all involved users
|
|
EXACT_AMOUNTS = "EXACT_AMOUNTS" # Specific amounts for each user (defined in ExpenseSplit)
|
|
PERCENTAGE = "PERCENTAGE" # Percentage for each user (defined in ExpenseSplit)
|
|
SHARES = "SHARES" # Proportional to shares/units (defined in ExpenseSplit)
|
|
ITEM_BASED = "ITEM_BASED" # If an expense is derived directly from item prices and who added them
|
|
# Consider renaming to a more generic term like 'DERIVED' or 'ENTITY_DRIVEN'
|
|
# if expenses might be derived from other entities in the future.
|
|
# Add more types as needed, e.g., UNPAID (for tracking debts not part of a formal expense)
|
|
|
|
class ExpenseSplitStatusEnum(enum.Enum):
|
|
unpaid = "unpaid"
|
|
partially_paid = "partially_paid"
|
|
paid = "paid"
|
|
|
|
class ExpenseOverallStatusEnum(enum.Enum):
|
|
unpaid = "unpaid"
|
|
partially_paid = "partially_paid"
|
|
paid = "paid"
|
|
|
|
class RecurrenceTypeEnum(enum.Enum):
|
|
DAILY = "DAILY"
|
|
WEEKLY = "WEEKLY"
|
|
MONTHLY = "MONTHLY"
|
|
YEARLY = "YEARLY"
|
|
# Add more types as needed
|
|
|
|
# Define ChoreFrequencyEnum
|
|
class ChoreFrequencyEnum(enum.Enum):
|
|
one_time = "one_time"
|
|
daily = "daily"
|
|
weekly = "weekly"
|
|
monthly = "monthly"
|
|
custom = "custom"
|
|
|
|
class ChoreTypeEnum(enum.Enum):
|
|
personal = "personal"
|
|
group = "group"
|
|
|
|
class ChoreHistoryEventTypeEnum(str, enum.Enum):
|
|
CREATED = "created"
|
|
UPDATED = "updated"
|
|
DELETED = "deleted"
|
|
COMPLETED = "completed"
|
|
REOPENED = "reopened"
|
|
ASSIGNED = "assigned"
|
|
UNASSIGNED = "unassigned"
|
|
REASSIGNED = "reassigned"
|
|
SCHEDULE_GENERATED = "schedule_generated"
|
|
DUE_DATE_CHANGED = "due_date_changed"
|
|
DETAILS_CHANGED = "details_changed"
|
|
|
|
# --- User Model ---
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
email = Column(String, unique=True, index=True, nullable=False)
|
|
hashed_password = Column(String, nullable=False)
|
|
name = Column(String, index=True, nullable=True)
|
|
is_active = Column(Boolean, default=True, nullable=False)
|
|
is_superuser = Column(Boolean, default=False, nullable=False)
|
|
is_verified = Column(Boolean, default=False, nullable=False)
|
|
is_guest = Column(Boolean, default=False, nullable=False)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
|
|
# --- Relationships ---
|
|
created_groups = relationship("Group", back_populates="creator")
|
|
group_associations = relationship("UserGroup", back_populates="user", cascade="all, delete-orphan")
|
|
created_invites = relationship("Invite", back_populates="creator")
|
|
created_lists = relationship("List", foreign_keys="List.created_by_id", back_populates="creator")
|
|
added_items = relationship("Item", foreign_keys="Item.added_by_id", back_populates="added_by_user")
|
|
completed_items = relationship("Item", foreign_keys="Item.completed_by_id", back_populates="completed_by_user")
|
|
expenses_paid = relationship("Expense", foreign_keys="Expense.paid_by_user_id", back_populates="paid_by_user", cascade="all, delete-orphan")
|
|
expenses_created = relationship("Expense", foreign_keys="Expense.created_by_user_id", back_populates="created_by_user", cascade="all, delete-orphan")
|
|
expense_splits = relationship("ExpenseSplit", foreign_keys="ExpenseSplit.user_id", back_populates="user", cascade="all, delete-orphan")
|
|
settlements_made = relationship("Settlement", foreign_keys="Settlement.paid_by_user_id", back_populates="payer", cascade="all, delete-orphan")
|
|
settlements_received = relationship("Settlement", foreign_keys="Settlement.paid_to_user_id", back_populates="payee", cascade="all, delete-orphan")
|
|
settlements_created = relationship("Settlement", foreign_keys="Settlement.created_by_user_id", back_populates="created_by_user", cascade="all, delete-orphan")
|
|
created_chores = relationship("Chore", foreign_keys="[Chore.created_by_id]", back_populates="creator")
|
|
assigned_chores = relationship("ChoreAssignment", back_populates="assigned_user", cascade="all, delete-orphan")
|
|
chore_history_entries = relationship("ChoreHistory", back_populates="changed_by_user", cascade="all, delete-orphan")
|
|
assignment_history_entries = relationship("ChoreAssignmentHistory", back_populates="changed_by_user", cascade="all, delete-orphan")
|
|
financial_audit_logs = relationship("FinancialAuditLog", back_populates="user")
|
|
time_entries = relationship("TimeEntry", back_populates="user")
|
|
categories = relationship("Category", back_populates="user")
|
|
|
|
class Group(Base):
|
|
__tablename__ = "groups"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
name = Column(String, index=True, nullable=False)
|
|
created_by_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
version = Column(Integer, nullable=False, default=1, server_default='1')
|
|
|
|
creator = relationship("User", back_populates="created_groups")
|
|
member_associations = relationship("UserGroup", back_populates="group", cascade="all, delete-orphan")
|
|
invites = relationship("Invite", back_populates="group", cascade="all, delete-orphan")
|
|
|
|
lists = relationship("List", back_populates="group", cascade="all, delete-orphan")
|
|
expenses = relationship("Expense", foreign_keys="Expense.group_id", back_populates="group", cascade="all, delete-orphan")
|
|
settlements = relationship("Settlement", foreign_keys="Settlement.group_id", back_populates="group", cascade="all, delete-orphan")
|
|
chores = relationship("Chore", back_populates="group", cascade="all, delete-orphan")
|
|
chore_history = relationship("ChoreHistory", back_populates="group", cascade="all, delete-orphan")
|
|
|
|
class UserGroup(Base):
|
|
__tablename__ = "user_groups"
|
|
__table_args__ = (UniqueConstraint('user_id', 'group_id', name='uq_user_group'),)
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
|
|
group_id = Column(Integer, ForeignKey("groups.id", ondelete="CASCADE"), nullable=False)
|
|
role = Column(SAEnum(UserRoleEnum, name="userroleenum", create_type=True), nullable=False, default=UserRoleEnum.member)
|
|
joined_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
|
|
user = relationship("User", back_populates="group_associations")
|
|
group = relationship("Group", back_populates="member_associations")
|
|
|
|
class Invite(Base):
|
|
__tablename__ = "invites"
|
|
__table_args__ = (
|
|
Index('ix_invites_active_code', 'code', unique=True, postgresql_where=sa_text('is_active = true')),
|
|
)
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
code = Column(String, unique=False, index=True, nullable=False, default=lambda: secrets.token_urlsafe(16))
|
|
group_id = Column(Integer, ForeignKey("groups.id", ondelete="CASCADE"), nullable=False)
|
|
created_by_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
expires_at = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc) + timedelta(days=7))
|
|
is_active = Column(Boolean, default=True, nullable=False)
|
|
|
|
group = relationship("Group", back_populates="invites")
|
|
creator = relationship("User", back_populates="created_invites")
|
|
|
|
|
|
class List(Base):
|
|
__tablename__ = "lists"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
name = Column(String, index=True, nullable=False)
|
|
description = Column(Text, nullable=True)
|
|
created_by_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
group_id = Column(Integer, ForeignKey("groups.id"), nullable=True)
|
|
is_complete = Column(Boolean, default=False, nullable=False)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
version = Column(Integer, nullable=False, default=1, server_default='1')
|
|
archived_at = Column(DateTime(timezone=True), nullable=True, index=True)
|
|
|
|
creator = relationship("User", back_populates="created_lists")
|
|
group = relationship("Group", back_populates="lists")
|
|
items = relationship(
|
|
"Item",
|
|
back_populates="list",
|
|
cascade="all, delete-orphan",
|
|
order_by="Item.position.asc(), Item.created_at.asc()"
|
|
)
|
|
|
|
expenses = relationship("Expense", foreign_keys="Expense.list_id", back_populates="list", cascade="all, delete-orphan")
|
|
|
|
class Item(Base):
|
|
__tablename__ = "items"
|
|
__table_args__ = (
|
|
Index('ix_items_list_id_position', 'list_id', 'position'),
|
|
)
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
list_id = Column(Integer, ForeignKey("lists.id", ondelete="CASCADE"), nullable=False)
|
|
name = Column(String, index=True, nullable=False)
|
|
quantity = Column(String, nullable=True)
|
|
is_complete = Column(Boolean, default=False, nullable=False)
|
|
price = Column(Numeric(10, 2), nullable=True)
|
|
position = Column(Integer, nullable=False, server_default='0')
|
|
category_id = Column(Integer, ForeignKey('categories.id'), nullable=True)
|
|
added_by_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
completed_by_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
version = Column(Integer, nullable=False, default=1, server_default='1')
|
|
|
|
# --- Relationships ---
|
|
list = relationship("List", back_populates="items")
|
|
added_by_user = relationship("User", foreign_keys=[added_by_id], back_populates="added_items")
|
|
completed_by_user = relationship("User", foreign_keys=[completed_by_id], back_populates="completed_items")
|
|
expenses = relationship("Expense", back_populates="item")
|
|
category = relationship("Category", back_populates="items")
|
|
|
|
class Expense(Base):
|
|
__tablename__ = "expenses"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
description = Column(String, nullable=False)
|
|
total_amount = Column(Numeric(10, 2), nullable=False)
|
|
currency = Column(String, nullable=False, default="USD")
|
|
expense_date = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
split_type = Column(SAEnum(SplitTypeEnum, name="splittypeenum", create_type=True), nullable=False)
|
|
|
|
list_id = Column(Integer, ForeignKey("lists.id"), nullable=True, index=True)
|
|
group_id = Column(Integer, ForeignKey("groups.id"), nullable=True, index=True)
|
|
item_id = Column(Integer, ForeignKey("items.id"), nullable=True)
|
|
paid_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
created_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
version = Column(Integer, nullable=False, default=1, server_default='1')
|
|
|
|
paid_by_user = relationship("User", foreign_keys=[paid_by_user_id], back_populates="expenses_paid")
|
|
created_by_user = relationship("User", foreign_keys=[created_by_user_id], back_populates="expenses_created")
|
|
list = relationship("List", foreign_keys=[list_id], back_populates="expenses")
|
|
group = relationship("Group", foreign_keys=[group_id], back_populates="expenses")
|
|
item = relationship("Item", foreign_keys=[item_id], back_populates="expenses")
|
|
splits = relationship("ExpenseSplit", back_populates="expense", cascade="all, delete-orphan")
|
|
parent_expense = relationship("Expense", remote_side=[id], back_populates="child_expenses")
|
|
child_expenses = relationship("Expense", back_populates="parent_expense")
|
|
overall_settlement_status = Column(SAEnum(ExpenseOverallStatusEnum, name="expenseoverallstatusenum", create_type=True), nullable=False, server_default=ExpenseOverallStatusEnum.unpaid.value, default=ExpenseOverallStatusEnum.unpaid)
|
|
is_recurring = Column(Boolean, default=False, nullable=False)
|
|
recurrence_pattern_id = Column(Integer, ForeignKey("recurrence_patterns.id"), nullable=True)
|
|
recurrence_pattern = relationship("RecurrencePattern", back_populates="expenses", uselist=False) # One-to-one
|
|
next_occurrence = Column(DateTime(timezone=True), nullable=True) # For recurring expenses
|
|
parent_expense_id = Column(Integer, ForeignKey("expenses.id"), nullable=True)
|
|
last_occurrence = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
__table_args__ = (
|
|
CheckConstraint('group_id IS NOT NULL OR list_id IS NOT NULL', name='ck_expense_group_or_list'),
|
|
)
|
|
|
|
class ExpenseSplit(Base):
|
|
__tablename__ = "expense_splits"
|
|
__table_args__ = (
|
|
UniqueConstraint('expense_id', 'user_id', name='uq_expense_user_split'),
|
|
Index('ix_expense_splits_user_id', 'user_id'), # For looking up user's splits
|
|
)
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
expense_id = Column(Integer, ForeignKey("expenses.id", ondelete="CASCADE"), nullable=False)
|
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
|
|
owed_amount = Column(Numeric(10, 2), nullable=False)
|
|
share_percentage = Column(Numeric(5, 2), nullable=True)
|
|
share_units = Column(Integer, nullable=True)
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
|
|
expense = relationship("Expense", back_populates="splits")
|
|
user = relationship("User", foreign_keys=[user_id], back_populates="expense_splits")
|
|
settlement_activities = relationship("SettlementActivity", back_populates="split", cascade="all, delete-orphan")
|
|
|
|
status = Column(SAEnum(ExpenseSplitStatusEnum, name="expensesplitstatusenum", create_type=True), nullable=False, server_default=ExpenseSplitStatusEnum.unpaid.value, default=ExpenseSplitStatusEnum.unpaid)
|
|
paid_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
class Settlement(Base):
|
|
__tablename__ = "settlements"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
group_id = Column(Integer, ForeignKey("groups.id"), nullable=False, index=True)
|
|
paid_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
paid_to_user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
amount = Column(Numeric(10, 2), nullable=False)
|
|
settlement_date = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
description = Column(Text, nullable=True)
|
|
created_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
version = Column(Integer, nullable=False, default=1, server_default='1')
|
|
|
|
group = relationship("Group", foreign_keys=[group_id], back_populates="settlements")
|
|
payer = relationship("User", foreign_keys=[paid_by_user_id], back_populates="settlements_made")
|
|
payee = relationship("User", foreign_keys=[paid_to_user_id], back_populates="settlements_received")
|
|
created_by_user = relationship("User", foreign_keys=[created_by_user_id], back_populates="settlements_created")
|
|
|
|
__table_args__ = (
|
|
CheckConstraint('paid_by_user_id != paid_to_user_id', name='chk_settlement_different_users'),
|
|
)
|
|
|
|
class SettlementActivity(Base):
|
|
__tablename__ = "settlement_activities"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
expense_split_id = Column(Integer, ForeignKey("expense_splits.id"), nullable=False, index=True)
|
|
paid_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
paid_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
amount_paid = Column(Numeric(10, 2), nullable=False)
|
|
created_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
|
|
split = relationship("ExpenseSplit", back_populates="settlement_activities")
|
|
payer = relationship("User", foreign_keys=[paid_by_user_id], backref="made_settlement_activities")
|
|
creator = relationship("User", foreign_keys=[created_by_user_id], backref="created_settlement_activities")
|
|
|
|
__table_args__ = (
|
|
Index('ix_settlement_activity_expense_split_id', 'expense_split_id'),
|
|
Index('ix_settlement_activity_paid_by_user_id', 'paid_by_user_id'),
|
|
Index('ix_settlement_activity_created_by_user_id', 'created_by_user_id'),
|
|
)
|
|
|
|
|
|
# --- Chore Model ---
|
|
class Chore(Base):
|
|
__tablename__ = "chores"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
type = Column(SAEnum(ChoreTypeEnum, name="choretypeenum", create_type=True), nullable=False)
|
|
group_id = Column(Integer, ForeignKey("groups.id", ondelete="CASCADE"), nullable=True, index=True)
|
|
name = Column(String, nullable=False, index=True)
|
|
description = Column(Text, nullable=True)
|
|
created_by_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
|
parent_chore_id = Column(Integer, ForeignKey('chores.id'), nullable=True, index=True)
|
|
|
|
frequency = Column(SAEnum(ChoreFrequencyEnum, name="chorefrequencyenum", create_type=True), nullable=False)
|
|
custom_interval_days = Column(Integer, nullable=True)
|
|
|
|
next_due_date = Column(Date, nullable=False)
|
|
last_completed_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
|
|
group = relationship("Group", back_populates="chores")
|
|
creator = relationship("User", back_populates="created_chores")
|
|
assignments = relationship("ChoreAssignment", back_populates="chore", cascade="all, delete-orphan")
|
|
history = relationship("ChoreHistory", back_populates="chore", cascade="all, delete-orphan")
|
|
parent_chore = relationship("Chore", remote_side=[id], back_populates="child_chores")
|
|
child_chores = relationship("Chore", back_populates="parent_chore", cascade="all, delete-orphan")
|
|
|
|
|
|
# --- ChoreAssignment Model ---
|
|
class ChoreAssignment(Base):
|
|
__tablename__ = "chore_assignments"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
chore_id = Column(Integer, ForeignKey("chores.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
assigned_to_user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
|
|
due_date = Column(Date, nullable=False)
|
|
is_complete = Column(Boolean, default=False, nullable=False)
|
|
completed_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
|
|
chore = relationship("Chore", back_populates="assignments")
|
|
assigned_user = relationship("User", back_populates="assigned_chores")
|
|
history = relationship("ChoreAssignmentHistory", back_populates="assignment", cascade="all, delete-orphan")
|
|
time_entries = relationship("TimeEntry", back_populates="assignment", cascade="all, delete-orphan")
|
|
|
|
|
|
# === NEW: RecurrencePattern Model ===
|
|
class RecurrencePattern(Base):
|
|
__tablename__ = "recurrence_patterns"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
type = Column(SAEnum(RecurrenceTypeEnum, name="recurrencetypeenum", create_type=True), nullable=False)
|
|
interval = Column(Integer, default=1, nullable=False)
|
|
days_of_week = Column(String, nullable=True)
|
|
end_date = Column(DateTime(timezone=True), nullable=True)
|
|
max_occurrences = Column(Integer, nullable=True)
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
|
|
|
expenses = relationship("Expense", back_populates="recurrence_pattern")
|
|
|
|
|
|
# === END: RecurrencePattern Model ===
|
|
|
|
# === NEW: Chore History Models ===
|
|
|
|
class ChoreHistory(Base):
|
|
__tablename__ = "chore_history"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
chore_id = Column(Integer, ForeignKey("chores.id", ondelete="CASCADE"), nullable=True, index=True)
|
|
group_id = Column(Integer, ForeignKey("groups.id", ondelete="CASCADE"), nullable=True, index=True)
|
|
event_type = Column(SAEnum(ChoreHistoryEventTypeEnum, name="chorehistoryeventtypeenum", create_type=True), nullable=False)
|
|
event_data = Column(JSONB, nullable=True)
|
|
changed_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
timestamp = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
|
|
chore = relationship("Chore", back_populates="history")
|
|
group = relationship("Group", back_populates="chore_history")
|
|
changed_by_user = relationship("User", back_populates="chore_history_entries")
|
|
|
|
class ChoreAssignmentHistory(Base):
|
|
__tablename__ = "chore_assignment_history"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
assignment_id = Column(Integer, ForeignKey("chore_assignments.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
event_type = Column(SAEnum(ChoreHistoryEventTypeEnum, name="chorehistoryeventtypeenum", create_type=True), nullable=False)
|
|
event_data = Column(JSONB, nullable=True)
|
|
changed_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
timestamp = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
|
|
assignment = relationship("ChoreAssignment", back_populates="history")
|
|
changed_by_user = relationship("User", back_populates="assignment_history_entries")
|
|
|
|
# --- New Models from Roadmap ---
|
|
|
|
class FinancialAuditLog(Base):
|
|
__tablename__ = 'financial_audit_log'
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
timestamp = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
user_id = Column(Integer, ForeignKey('users.id'), nullable=True)
|
|
action_type = Column(String, nullable=False, index=True)
|
|
entity_type = Column(String, nullable=False)
|
|
entity_id = Column(Integer, nullable=False)
|
|
details = Column(JSONB, nullable=True)
|
|
|
|
user = relationship("User", back_populates="financial_audit_logs")
|
|
|
|
class Category(Base):
|
|
__tablename__ = 'categories'
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
name = Column(String, nullable=False, index=True)
|
|
user_id = Column(Integer, ForeignKey('users.id'), nullable=True)
|
|
group_id = Column(Integer, ForeignKey('groups.id'), nullable=True)
|
|
|
|
user = relationship("User", back_populates="categories")
|
|
items = relationship("Item", back_populates="category")
|
|
|
|
__table_args__ = (UniqueConstraint('name', 'user_id', 'group_id', name='uq_category_scope'),)
|
|
|
|
class TimeEntry(Base):
|
|
__tablename__ = 'time_entries'
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
chore_assignment_id = Column(Integer, ForeignKey('chore_assignments.id', ondelete="CASCADE"), nullable=False)
|
|
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
|
|
start_time = Column(DateTime(timezone=True), nullable=False)
|
|
end_time = Column(DateTime(timezone=True), nullable=True)
|
|
duration_seconds = Column(Integer, nullable=True)
|
|
|
|
assignment = relationship("ChoreAssignment", back_populates="time_entries")
|
|
user = relationship("User", back_populates="time_entries")
|