nexus-5/core/models/report.py
2026-01-26 11:09:40 -05:00

145 lines
6.0 KiB
Python

from django.conf import settings
from django.db import models
from django.db.models import Q
from decimal import Decimal
from core.models.base import BaseModel
from core.models.profile import TeamProfile
from core.models.service import Service
from core.models.project import Project
class Report(BaseModel):
"""Report records"""
date = models.DateField()
team_member = models.ForeignKey(TeamProfile, on_delete=models.PROTECT, related_name='reports')
services = models.ManyToManyField(Service, related_name='reports', blank=True)
projects = models.ManyToManyField(Project, related_name='reports', blank=True)
class Meta:
ordering = ['-date']
indexes = [
models.Index(fields=['team_member', 'date']),
]
verbose_name = "Report"
verbose_name_plural = "Reports"
def __str__(self):
return f"Report for {self.team_member.full_name} on {self.date}"
def get_service_labor_share(self, service):
"""Get this team member's share of labor for a service (excluding Dispatch)"""
if not service.account_address:
return Decimal('0.00')
# Get the labor rate for the service's account address
labor = service.account_address.labors.filter(
Q(start_date__lte=self.date) &
(Q(end_date__isnull=True) | Q(end_date__gte=self.date))
).first()
if not labor:
return Decimal('0.00')
# Count team members assigned to this service, excluding Dispatch
team_members = service.team_members.exclude(id=settings.DISPATCH_TEAM_PROFILE_ID)
team_member_count = team_members.count()
if team_member_count == 0:
return Decimal('0.00')
# Only include this team member's share if they're assigned to the service (and not Dispatch)
if not team_members.filter(id=self.team_member.id).exists():
return Decimal('0.00')
# Divide labor rate by number of team members (excluding Dispatch)
return labor.amount / team_member_count
def get_project_labor_share(self, project):
"""Get this team member's share of labor for a project (excluding Dispatch)"""
# Count team members assigned to this project, excluding Dispatch
team_members = project.team_members.exclude(id=settings.DISPATCH_TEAM_PROFILE_ID)
team_member_count = team_members.count()
if team_member_count == 0:
return Decimal('0.00')
# Only include this team member's share if they're assigned to the project (and not Dispatch)
if not team_members.filter(id=self.team_member.id).exists():
return Decimal('0.00')
# Divide project labor by number of team members (excluding Dispatch)
return project.labor / team_member_count
def get_services_labor_total(self):
"""Calculate total labor share for all services in this report"""
total = Decimal('0.00')
for service in self.services.all():
total += self.get_service_labor_share(service)
return total
def get_projects_labor_total(self):
"""Calculate total labor share for all projects in this report"""
total = Decimal('0.00')
for project in self.projects.all():
total += self.get_project_labor_share(project)
return total
def get_total_labor_value(self):
"""Calculate total labor share for both services and projects"""
return self.get_services_labor_total() + self.get_projects_labor_total()
def get_labor_breakdown(self):
"""Get a detailed breakdown of labor shares for this specific team member"""
services_data = []
for service in self.services.all():
# Count team members excluding Dispatch
team_members = service.team_members.exclude(id=settings.DISPATCH_TEAM_PROFILE_ID)
team_member_count = team_members.count()
is_assigned = team_members.filter(id=self.team_member.id).exists()
labor_rate = Decimal('0.00')
if service.account_address:
labor = service.account_address.labors.filter(
Q(start_date__lte=self.date) &
(Q(end_date__isnull=True) | Q(end_date__gte=self.date))
).first()
labor_rate = labor.amount if labor else Decimal('0.00')
share = self.get_service_labor_share(service)
services_data.append({
'service_id': service.id,
'account_name': service.account_address.account.name if service.account_address else None,
'address': service.account_address.name if service.account_address else None,
'total_labor_rate': labor_rate,
'team_member_count': team_member_count,
'is_team_member_assigned': is_assigned,
'labor_share': share
})
projects_data = []
for project in self.projects.all():
# Count team members excluding Dispatch
team_members = project.team_members.exclude(id=settings.DISPATCH_TEAM_PROFILE_ID)
team_member_count = team_members.count()
is_assigned = team_members.filter(id=self.team_member.id).exists()
share = self.get_project_labor_share(project)
projects_data.append({
'project_id': project.id,
'project_name': project.name,
'total_labor_amount': project.labor,
'team_member_count': team_member_count,
'is_team_member_assigned': is_assigned,
'labor_share': share
})
return {
'team_member_id': self.team_member.id,
'team_member_name': self.team_member.full_name,
'services': services_data,
'projects': projects_data,
'services_total': self.get_services_labor_total(),
'projects_total': self.get_projects_labor_total(),
'grand_total': self.get_total_labor_value()
}