File size: 5,225 Bytes
dad7dc2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""
Ticket Progress Report Model - Progress tracking for task tickets

Supervisors document work progress with narrative descriptions and photo evidence.
No subjective percentage - focus on what's done, what's left, and blockers.
"""

from sqlalchemy import Column, String, ForeignKey, Boolean, DECIMAL, TIMESTAMP, Integer, Text, Date, CheckConstraint
from sqlalchemy.dialects.postgresql import UUID, ARRAY
from sqlalchemy.orm import relationship
from datetime import datetime
import uuid

from app.core.database import Base


class TicketProgressReport(Base):
    """
    Ticket Progress Report Model
    
    Tracks work progress on task tickets with:
    - Narrative description of completed work
    - Issues encountered and resolved
    - Team size and hours worked
    - Location verification
    - Photo evidence (via ticket_images with polymorphic linking)
    
    Links to:
    - tickets (required) - which ticket this report is for
    - users (reported_by) - supervisor who created report
    - ticket_images (via polymorphic link) - progress photos
    """
    
    __tablename__ = "ticket_progress_reports"
    
    # 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
    )
    reported_by_user_id = Column(
        UUID(as_uuid=True),
        ForeignKey("users.id", ondelete="RESTRICT"),
        nullable=False,
        index=True
    )
    
    # Progress Narrative
    work_completed_description = Column(Text, nullable=False)  # What was done (required)
    work_remaining_description = Column(Text, nullable=True)  # What's left
    issues_encountered = Column(Text, nullable=True)  # Blockers/problems
    issues_resolved = Column(Text, nullable=True)  # What we fixed
    next_steps = Column(Text, nullable=True)  # What needs to happen next
    estimated_completion_date = Column(Date, nullable=True)  # When will this be done?
    
    # Team and Effort Tracking
    team_size_on_site = Column(Integer, nullable=True)  # Number of workers present
    hours_worked = Column(DECIMAL(5, 2), nullable=True)  # Total man-hours
    
    # Location Verification
    report_latitude = Column(DECIMAL(10, 7), nullable=True)
    report_longitude = Column(DECIMAL(10, 7), nullable=True)
    location_verified = Column(Boolean, nullable=False, default=False)
    
    # Environmental Context
    weather_conditions = Column(Text, nullable=True)  # Weather affecting work
    notes = Column(Text, nullable=True)  # Additional notes
    
    # 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="progress_reports")
    reported_by_user = relationship("User", foreign_keys=[reported_by_user_id])
    
    # Images linked via polymorphic relationship
    # Query: SELECT * FROM ticket_images WHERE linked_entity_type = 'progress_report' AND linked_entity_id = self.id
    
    # Constraints
    __table_args__ = (
        CheckConstraint('team_size_on_site IS NULL OR team_size_on_site > 0', name='chk_progress_positive_team_size'),
        CheckConstraint('hours_worked IS NULL OR hours_worked >= 0', name='chk_progress_positive_hours'),
    )
    
    def __repr__(self):
        return f"<TicketProgressReport(id={self.id}, ticket_id={self.ticket_id}, created_at={self.created_at})>"
    
    def to_dict(self):
        """Convert progress report to dictionary"""
        return {
            "id": str(self.id),
            "ticket_id": str(self.ticket_id),
            "reported_by_user_id": str(self.reported_by_user_id),
            "work_completed_description": self.work_completed_description,
            "work_remaining_description": self.work_remaining_description,
            "issues_encountered": self.issues_encountered,
            "issues_resolved": self.issues_resolved,
            "next_steps": self.next_steps,
            "estimated_completion_date": self.estimated_completion_date.isoformat() if self.estimated_completion_date else None,
            "team_size_on_site": self.team_size_on_site,
            "hours_worked": float(self.hours_worked) if self.hours_worked else None,
            "report_latitude": float(self.report_latitude) if self.report_latitude else None,
            "report_longitude": float(self.report_longitude) if self.report_longitude else None,
            "location_verified": self.location_verified,
            "weather_conditions": self.weather_conditions,
            "notes": self.notes,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
        }