Spaces:
Sleeping
Sleeping
| """ | |
| Ticket Comment Model - Collaboration & Communication | |
| Enables team collaboration on tickets with threaded discussions. | |
| Supports internal comments (team only) and external comments (visible to client). | |
| """ | |
| from sqlalchemy import Column, String, ForeignKey, Boolean, TIMESTAMP, Text, CheckConstraint | |
| from sqlalchemy.dialects.postgresql import UUID, JSONB, ARRAY | |
| from sqlalchemy.orm import relationship | |
| from datetime import datetime | |
| import uuid | |
| from app.core.database import Base | |
| class TicketComment(Base): | |
| """ | |
| Ticket Comment Model | |
| Threaded comments on tickets. Supports internal (team) and external (client-visible) | |
| comments with mentions and attachments. | |
| Links to: | |
| - tickets (required) - which ticket this comment belongs to | |
| - users (commented by, edited by) - who created/edited comment | |
| - ticket_comments (parent) - for threaded discussions | |
| - documents (attachments) - optional file attachments | |
| """ | |
| __tablename__ = "ticket_comments" | |
| # Primary Key | |
| id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) | |
| # Foreign Keys | |
| ticket_id = Column( | |
| UUID(as_uuid=True), | |
| ForeignKey("tickets.id", ondelete="CASCADE"), | |
| nullable=False, | |
| index=True | |
| ) | |
| user_id = Column( | |
| UUID(as_uuid=True), | |
| ForeignKey("users.id", ondelete="SET NULL"), | |
| nullable=True, # Nullable: user may be deleted | |
| index=True | |
| ) | |
| # Comment Content | |
| comment_text = Column(Text, nullable=False) | |
| # Comment Type & Visibility | |
| is_internal = Column(Boolean, default=True, nullable=False) # TRUE = team only, FALSE = visible to client | |
| comment_type = Column(String(50), default='note', nullable=False) # 'note', 'issue', 'resolution', 'question', 'update' | |
| # Threading (for replies) | |
| parent_comment_id = Column( | |
| UUID(as_uuid=True), | |
| ForeignKey("ticket_comments.id", ondelete="CASCADE"), | |
| nullable=True, | |
| index=True | |
| ) | |
| # Mentions & notifications | |
| mentioned_user_ids = Column(ARRAY(UUID(as_uuid=True)), nullable=True) # Array of user IDs mentioned in comment (e.g., @john) | |
| # Attachments (optional - links to documents) | |
| attachment_document_ids = Column(ARRAY(UUID(as_uuid=True)), nullable=True) # Array of document IDs attached to comment | |
| # Edit Tracking | |
| is_edited = Column(Boolean, default=False, nullable=False) | |
| edited_at = Column(TIMESTAMP(timezone=True), nullable=True) | |
| edited_by_user_id = Column( | |
| UUID(as_uuid=True), | |
| ForeignKey("users.id", ondelete="SET NULL"), | |
| nullable=True | |
| ) | |
| # Metadata | |
| additional_metadata = Column( | |
| JSONB, | |
| nullable=False, | |
| default={}, | |
| server_default="{}" | |
| ) | |
| # Timestamps | |
| created_at = Column( | |
| TIMESTAMP(timezone=True), | |
| nullable=False, | |
| default=datetime.utcnow, | |
| server_default="timezone('utc'::text, now())" | |
| ) | |
| updated_at = Column( | |
| TIMESTAMP(timezone=True), | |
| nullable=False, | |
| default=datetime.utcnow, | |
| onupdate=datetime.utcnow, | |
| server_default="timezone('utc'::text, now())" | |
| ) | |
| deleted_at = Column(TIMESTAMP(timezone=True), nullable=True) | |
| # Relationships | |
| ticket = relationship("Ticket", back_populates="comments") | |
| user = relationship("User", foreign_keys=[user_id]) | |
| edited_by_user = relationship("User", foreign_keys=[edited_by_user_id]) | |
| parent_comment = relationship("TicketComment", remote_side=[id], backref="replies") | |
| # Constraints | |
| __table_args__ = ( | |
| CheckConstraint("LENGTH(TRIM(comment_text)) > 0", name='chk_comment_not_empty'), | |
| ) | |
| def __repr__(self): | |
| return f"<TicketComment(id={self.id}, ticket_id={self.ticket_id}, is_internal={self.is_internal})>" | |
| def to_dict(self): | |
| """Convert comment to dictionary""" | |
| return { | |
| "id": str(self.id), | |
| "ticket_id": str(self.ticket_id), | |
| "user_id": str(self.user_id) if self.user_id else None, | |
| "comment_text": self.comment_text, | |
| "is_internal": self.is_internal, | |
| "comment_type": self.comment_type, | |
| "parent_comment_id": str(self.parent_comment_id) if self.parent_comment_id else None, | |
| "mentioned_user_ids": [str(uid) for uid in self.mentioned_user_ids] if self.mentioned_user_ids else [], | |
| "attachment_document_ids": [str(did) for did in self.attachment_document_ids] if self.attachment_document_ids else [], | |
| "is_edited": self.is_edited, | |
| "edited_at": self.edited_at.isoformat() if self.edited_at else None, | |
| "edited_by_user_id": str(self.edited_by_user_id) if self.edited_by_user_id else None, | |
| "additional_metadata": self.additional_metadata, | |
| "created_at": self.created_at.isoformat() if self.created_at else None, | |
| "updated_at": self.updated_at.isoformat() if self.updated_at else None, | |
| } | |