|
@@ -400,12 +400,15 @@ class HrEfficiency(models.Model):
|
|
|
"""
|
|
"""
|
|
|
Calculate planned hours from planning module
|
|
Calculate planned hours from planning module
|
|
|
"""
|
|
"""
|
|
|
- # Get planning slots for the employee in the date range
|
|
|
|
|
- # Use flexible date range to capture slots that cross month boundaries
|
|
|
|
|
|
|
+ # Get planning slots for the employee that overlap with the date range
|
|
|
|
|
+ # This is the same logic as Odoo Planning's Gantt chart
|
|
|
|
|
+ start_datetime = datetime.combine(start_date, datetime.min.time())
|
|
|
|
|
+ end_datetime = datetime.combine(end_date, datetime.max.time())
|
|
|
|
|
+
|
|
|
planning_slots = self.env['planning.slot'].search([
|
|
planning_slots = self.env['planning.slot'].search([
|
|
|
('employee_id', '=', employee.id),
|
|
('employee_id', '=', employee.id),
|
|
|
- ('start_datetime', '<', datetime.combine(end_date + relativedelta(days=1), datetime.min.time())),
|
|
|
|
|
- ('end_datetime', '>', datetime.combine(start_date - relativedelta(days=1), datetime.max.time())),
|
|
|
|
|
|
|
+ ('start_datetime', '<=', end_datetime),
|
|
|
|
|
+ ('end_datetime', '>=', start_datetime),
|
|
|
# Removed state restriction to include all planning slots regardless of state
|
|
# Removed state restriction to include all planning slots regardless of state
|
|
|
])
|
|
])
|
|
|
|
|
|
|
@@ -413,25 +416,27 @@ class HrEfficiency(models.Model):
|
|
|
total_billable = 0.0
|
|
total_billable = 0.0
|
|
|
total_non_billable = 0.0
|
|
total_non_billable = 0.0
|
|
|
|
|
|
|
|
|
|
+ # Get working intervals for the resource and company calendar
|
|
|
|
|
+ # This is the same approach as Odoo Planning's Gantt chart
|
|
|
|
|
+ start_utc = pytz.utc.localize(start_datetime)
|
|
|
|
|
+ end_utc = pytz.utc.localize(end_datetime)
|
|
|
|
|
+
|
|
|
|
|
+ if employee.resource_id:
|
|
|
|
|
+ resource_work_intervals, calendar_work_intervals = employee.resource_id._get_valid_work_intervals(
|
|
|
|
|
+ start_utc, end_utc, calendars=employee.company_id.resource_calendar_id
|
|
|
|
|
+ )
|
|
|
|
|
+ else:
|
|
|
|
|
+ # Fallback to company calendar if no resource
|
|
|
|
|
+ calendar_work_intervals = {employee.company_id.resource_calendar_id.id: []}
|
|
|
|
|
+ resource_work_intervals = {}
|
|
|
|
|
+
|
|
|
for slot in planning_slots:
|
|
for slot in planning_slots:
|
|
|
- # Use the same logic as Odoo Planning to calculate allocated hours
|
|
|
|
|
- if slot.start_datetime and slot.end_datetime and slot.resource_id:
|
|
|
|
|
- # Calculate the intersection between slot and working intervals
|
|
|
|
|
- start_utc = pytz.utc.localize(slot.start_datetime)
|
|
|
|
|
- end_utc = pytz.utc.localize(slot.end_datetime)
|
|
|
|
|
-
|
|
|
|
|
- # Get working intervals for the resource
|
|
|
|
|
- resource_work_intervals, calendar_work_intervals = slot.resource_id._get_valid_work_intervals(
|
|
|
|
|
- start_utc, end_utc, calendars=slot.company_id.resource_calendar_id
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # Calculate duration over working periods
|
|
|
|
|
- hours = slot._get_duration_over_period(
|
|
|
|
|
- start_utc, end_utc,
|
|
|
|
|
- resource_work_intervals, calendar_work_intervals, has_allocated_hours=False
|
|
|
|
|
- )
|
|
|
|
|
- else:
|
|
|
|
|
- hours = slot.allocated_hours or 0.0
|
|
|
|
|
|
|
+ # Use the same logic as Odoo Planning's Gantt chart
|
|
|
|
|
+ # Calculate duration only within the specified period
|
|
|
|
|
+ hours = slot._get_duration_over_period(
|
|
|
|
|
+ start_utc, end_utc,
|
|
|
|
|
+ resource_work_intervals, calendar_work_intervals, has_allocated_hours=False
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
# Check if the slot is linked to a billable project
|
|
# Check if the slot is linked to a billable project
|
|
|
if slot.project_id and slot.project_id.allow_billable:
|
|
if slot.project_id and slot.project_id.allow_billable:
|