145 lines
6.0 KiB
Python
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()
|
|
} |