feat(tasks): show Billable Hours in Task Time Tracking

Add Task.total_billable_hours aggregating billable TimeEntry.duration_seconds (rounded to 2 decimals)
Expose total_billable_hours via Task.to_dict
Update tasks/view.html to display “Billable Hours” when > 0
No schema change; uses existing billable flag and durations
This commit is contained in:
Dries Peeters
2025-09-30 20:51:13 +02:00
parent e1b083d1b6
commit cb4214d12b
2 changed files with 26 additions and 0 deletions

View File

@@ -81,6 +81,22 @@ class Task(db.Model):
return round(total_seconds / 3600, 2)
except Exception:
return 0.0
@property
def total_billable_hours(self):
"""Calculate total billable hours spent on this task"""
try:
from .time_entry import TimeEntry
total_seconds = db.session.query(
db.func.sum(TimeEntry.duration_seconds)
).filter(
TimeEntry.task_id == self.id,
TimeEntry.end_time.isnot(None),
TimeEntry.billable == True
).scalar() or 0
return round(total_seconds / 3600, 2)
except Exception:
return 0.0
@property
def progress_percentage(self):
@@ -220,6 +236,7 @@ class Task(db.Model):
'started_at': self.started_at.isoformat() if self.started_at else None,
'completed_at': self.completed_at.isoformat() if self.completed_at else None,
'total_hours': self.total_hours,
'total_billable_hours': self.total_billable_hours,
'progress_percentage': self.progress_percentage,
'is_active': self.is_active,
'is_overdue': self.is_overdue

View File

@@ -141,6 +141,15 @@
</div>
</div>
{% endif %}
{% if task.total_billable_hours > 0 %}
<div class="col-6 col-md-3">
<div class="text-center">
<div class="h4 text-success mb-1">{{ task.total_billable_hours }}</div>
<small class="text-muted">{{ _('Billable Hours') }}</small>
</div>
</div>
{% endif %}
{% if task.estimated_hours and task.total_hours > 0 %}
<div class="col-6 col-md-3">