Update working hours calculation with SP/PW
This commit is contained in:
+221
-214
@@ -1,165 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Single Check-in Working Hours Calculator
|
||||
=======================================
|
||||
Safe Enhanced Single Check-in Working Hours Calculator with SP/PW Support
|
||||
========================================================================
|
||||
|
||||
Calculator specifically designed for single check-in systems where each
|
||||
attendance record represents a check-in only (not check-in/check-out pairs).
|
||||
|
||||
This calculator interprets consecutive check-ins as work periods:
|
||||
- 1st check-in = start work
|
||||
- 2nd check-in = end work (or start of next period)
|
||||
- 3rd check-in = end previous period, start new period
|
||||
- etc.
|
||||
|
||||
Based on your attendance system structure.
|
||||
This version maintains full backward compatibility while adding SP/PW support.
|
||||
It gracefully handles missing data and falls back to standard calculation when needed.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, time
|
||||
from typing import List, Dict, Optional, Tuple, Any
|
||||
from dataclasses import dataclass
|
||||
import math
|
||||
import re
|
||||
from logger_handler import log_database_operations
|
||||
|
||||
|
||||
@dataclass
|
||||
class CheckInRecord:
|
||||
"""Represents a single check-in record"""
|
||||
id: int
|
||||
employee_id: str
|
||||
check_in_date: datetime
|
||||
check_in_time: time
|
||||
location_name: str
|
||||
timestamp: datetime = None
|
||||
def parse_employee_id_for_work_type(employee_id: str) -> Tuple[str, str]:
|
||||
"""Parse employee ID to extract base ID and work type"""
|
||||
if not employee_id:
|
||||
return str(employee_id), 'regular'
|
||||
|
||||
def __post_init__(self):
|
||||
if self.timestamp is None:
|
||||
# Combine date and time for timestamp
|
||||
self.timestamp = datetime.combine(self.check_in_date, self.check_in_time)
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkPeriod:
|
||||
"""Represents a calculated work period from consecutive check-ins"""
|
||||
start_record: CheckInRecord
|
||||
end_record: Optional[CheckInRecord]
|
||||
duration_minutes: int = 0
|
||||
is_complete: bool = False
|
||||
employee_id_clean = str(employee_id).strip().upper()
|
||||
|
||||
def __post_init__(self):
|
||||
if self.end_record and self.start_record:
|
||||
duration = self.end_record.timestamp - self.start_record.timestamp
|
||||
self.duration_minutes = int(duration.total_seconds() / 60)
|
||||
self.is_complete = True
|
||||
else:
|
||||
self.duration_minutes = 0
|
||||
self.is_complete = False
|
||||
# Check for SP (Special Project)
|
||||
sp_pattern = r'^(\d+)\s*SP$'
|
||||
sp_match = re.match(sp_pattern, employee_id_clean)
|
||||
if sp_match:
|
||||
return sp_match.group(1), 'SP'
|
||||
|
||||
# Check for PW (Periodic Work)
|
||||
pw_pattern = r'^(\d+)\s*PW$'
|
||||
pw_match = re.match(pw_pattern, employee_id_clean)
|
||||
if pw_match:
|
||||
return pw_match.group(1), 'PW'
|
||||
|
||||
# Default to regular work
|
||||
return employee_id_clean, 'regular'
|
||||
|
||||
|
||||
class SingleCheckInCalculator:
|
||||
"""Calculator for single check-in attendance systems"""
|
||||
"""Enhanced calculator for single check-in attendance systems with SP/PW support"""
|
||||
|
||||
def __init__(self, max_work_period_hours: float = 12.0, min_break_minutes: int = 30):
|
||||
self.max_work_period_hours = max_work_period_hours # Maximum reasonable work period
|
||||
self.min_break_minutes = min_break_minutes # Minimum break between work periods
|
||||
self.max_work_period_hours = max_work_period_hours
|
||||
self.min_break_minutes = min_break_minutes
|
||||
|
||||
@log_database_operations('single_checkin_hours_calculation')
|
||||
@log_database_operations('single_checkin_hours_calculation_sp_pw')
|
||||
def calculate_employee_hours(self, employee_id: str, start_date: datetime, end_date: datetime,
|
||||
attendance_records: List[Dict]) -> Dict[str, Any]:
|
||||
"""
|
||||
Calculate working hours for an employee using single check-in records
|
||||
|
||||
Logic:
|
||||
- Convert consecutive check-ins into work periods
|
||||
- 1st check-in = start work, 2nd check-in = end work
|
||||
- Handle multiple periods per day
|
||||
- Round to nearest quarter hour
|
||||
Calculate working hours for an employee with SP/PW support and robust error handling
|
||||
"""
|
||||
try:
|
||||
print(f"🔍 Calculating hours for employee {employee_id}")
|
||||
print(f"🔍 Calculating hours for employee {employee_id} with SP/PW support")
|
||||
|
||||
# Parse base employee ID and work type
|
||||
base_employee_id, _ = parse_employee_id_for_work_type(employee_id)
|
||||
|
||||
# Filter and categorize records
|
||||
records_by_type = {'regular': [], 'SP': [], 'PW': []}
|
||||
|
||||
# Convert database records to CheckInRecord objects
|
||||
records = []
|
||||
for record in attendance_records:
|
||||
if hasattr(record, '__dict__'):
|
||||
emp_id = str(record.employee_id)
|
||||
date_val = record.check_in_date
|
||||
time_val = record.check_in_time
|
||||
location = record.location_name
|
||||
record_id = record.id
|
||||
else:
|
||||
emp_id = str(record['employee_id'])
|
||||
date_val = record['check_in_date']
|
||||
time_val = record['check_in_time']
|
||||
location = record['location_name']
|
||||
record_id = record['id']
|
||||
|
||||
if emp_id == employee_id:
|
||||
checkin_record = CheckInRecord(
|
||||
id=record_id,
|
||||
employee_id=emp_id,
|
||||
check_in_date=date_val,
|
||||
check_in_time=time_val,
|
||||
location_name=location
|
||||
)
|
||||
records.append(checkin_record)
|
||||
try:
|
||||
# Extract record data safely
|
||||
if hasattr(record, '__dict__'):
|
||||
record_emp_id = str(getattr(record, 'employee_id', '')).strip()
|
||||
record_date = getattr(record, 'check_in_date', None)
|
||||
record_time = getattr(record, 'check_in_time', None)
|
||||
location = getattr(record, 'location_name', 'Unknown Location')
|
||||
record_id = getattr(record, 'id', 0)
|
||||
else:
|
||||
record_emp_id = str(record.get('employee_id', '')).strip()
|
||||
record_date = record.get('check_in_date')
|
||||
record_time = record.get('check_in_time')
|
||||
location = record.get('location_name', 'Unknown Location')
|
||||
record_id = record.get('id', 0)
|
||||
|
||||
# Skip invalid records
|
||||
if not record_emp_id or record_date is None or record_time is None:
|
||||
continue
|
||||
|
||||
# Parse work type from this record
|
||||
record_base_id, work_type = parse_employee_id_for_work_type(record_emp_id)
|
||||
|
||||
# Only include records for this base employee
|
||||
if record_base_id == base_employee_id:
|
||||
# Create a simple record dict for processing
|
||||
processed_record = {
|
||||
'id': record_id,
|
||||
'employee_id': record_emp_id,
|
||||
'check_in_date': record_date,
|
||||
'check_in_time': record_time,
|
||||
'location_name': location,
|
||||
'work_type': work_type,
|
||||
'timestamp': datetime.combine(record_date, record_time) if record_date and record_time else datetime.now()
|
||||
}
|
||||
records_by_type[work_type].append(processed_record)
|
||||
|
||||
except Exception as record_error:
|
||||
print(f"⚠️ Error processing record: {record_error}")
|
||||
continue
|
||||
|
||||
if not records:
|
||||
print(f"⚠️ No records found for employee {employee_id}")
|
||||
return self._empty_result(employee_id, start_date, end_date)
|
||||
total_records = sum(len(records_by_type[wt]) for wt in records_by_type)
|
||||
print(f"📊 Found {total_records} records - Regular: {len(records_by_type['regular'])}, SP: {len(records_by_type['SP'])}, PW: {len(records_by_type['PW'])}")
|
||||
|
||||
print(f"📊 Found {len(records)} check-in records for employee {employee_id}")
|
||||
|
||||
# Group records by date and calculate daily hours
|
||||
daily_records = {}
|
||||
for record in records:
|
||||
date_key = record.check_in_date.strftime('%Y-%m-%d')
|
||||
if date_key not in daily_records:
|
||||
daily_records[date_key] = []
|
||||
daily_records[date_key].append(record)
|
||||
|
||||
# Calculate daily hours
|
||||
# Calculate hours for each work type
|
||||
daily_hours = {}
|
||||
weekly_totals = []
|
||||
|
||||
current_date = start_date
|
||||
current_week_hours = []
|
||||
current_week_hours = {'regular': [], 'SP': [], 'PW': []}
|
||||
|
||||
while current_date <= end_date:
|
||||
date_key = current_date.strftime('%Y-%m-%d')
|
||||
day_records = daily_records.get(date_key, [])
|
||||
|
||||
# Calculate hours for this day
|
||||
day_hours, is_miss_punch = self._calculate_daily_hours_from_checkins(day_records)
|
||||
# Calculate hours for each work type on this day
|
||||
hours_by_type = {}
|
||||
is_miss_punch_by_type = {}
|
||||
|
||||
for work_type in ['regular', 'SP', 'PW']:
|
||||
day_records = [r for r in records_by_type[work_type]
|
||||
if r['check_in_date'] == current_date.date()]
|
||||
hours, is_miss_punch = self._calculate_daily_hours_from_records(day_records)
|
||||
hours_by_type[work_type] = hours
|
||||
is_miss_punch_by_type[work_type] = is_miss_punch
|
||||
|
||||
# Store daily data with SP/PW support
|
||||
total_day_hours = sum(hours_by_type.values())
|
||||
daily_hours[date_key] = {
|
||||
'total_minutes': int(day_hours * 60) if day_hours > 0 else 0,
|
||||
'total_hours': day_hours if day_hours > 0 else 0,
|
||||
'is_miss_punch': is_miss_punch,
|
||||
'records_count': len(day_records)
|
||||
'total_minutes': int(total_day_hours * 60),
|
||||
'total_hours': total_day_hours,
|
||||
'regular_hours': hours_by_type['regular'],
|
||||
'sp_hours': hours_by_type['SP'],
|
||||
'pw_hours': hours_by_type['PW'],
|
||||
'is_miss_punch': any(is_miss_punch_by_type.values()),
|
||||
'records_count': sum(len([r for r in records_by_type[wt] if r['check_in_date'] == current_date.date()])
|
||||
for wt in ['regular', 'SP', 'PW']),
|
||||
'miss_punch_details': {
|
||||
'regular': is_miss_punch_by_type['regular'],
|
||||
'SP': is_miss_punch_by_type['SP'],
|
||||
'PW': is_miss_punch_by_type['PW']
|
||||
}
|
||||
}
|
||||
|
||||
print(f"📅 {date_key}: {len(day_records)} records, {day_hours:.2f} hours, miss_punch: {is_miss_punch}")
|
||||
# Add to weekly calculation
|
||||
for work_type in ['regular', 'SP', 'PW']:
|
||||
current_week_hours[work_type].append(max(0, hours_by_type[work_type]))
|
||||
|
||||
# Add to weekly calculation (only positive hours)
|
||||
current_week_hours.append(max(0, day_hours))
|
||||
|
||||
# Check if end of week (Sunday) or end of period
|
||||
if current_date.weekday() == 6 or current_date == end_date: # Sunday or last day
|
||||
week_total = sum(current_week_hours)
|
||||
week_regular = min(week_total, 40.0)
|
||||
week_overtime = max(0, week_total - 40.0)
|
||||
# Check if end of week or end of period
|
||||
if current_date.weekday() == 6 or current_date == end_date:
|
||||
week_regular_total = sum(current_week_hours['regular'])
|
||||
week_sp_total = sum(current_week_hours['SP'])
|
||||
week_pw_total = sum(current_week_hours['PW'])
|
||||
|
||||
# Only regular hours count toward overtime
|
||||
week_regular_hours = min(week_regular_total, 40.0)
|
||||
week_overtime_hours = max(0, week_regular_total - 40.0)
|
||||
|
||||
weekly_totals.append({
|
||||
'total_hours': week_total,
|
||||
'regular_hours': week_regular,
|
||||
'overtime_hours': week_overtime,
|
||||
'total_minutes': int(week_total * 60),
|
||||
'regular_minutes': int(week_regular * 60),
|
||||
'overtime_minutes': int(week_overtime * 60)
|
||||
'total_hours': week_regular_total + week_sp_total + week_pw_total,
|
||||
'regular_hours': week_regular_hours,
|
||||
'overtime_hours': week_overtime_hours,
|
||||
'sp_hours': week_sp_total,
|
||||
'pw_hours': week_pw_total,
|
||||
'total_minutes': int((week_regular_total + week_sp_total + week_pw_total) * 60),
|
||||
'regular_minutes': int(week_regular_hours * 60),
|
||||
'overtime_minutes': int(week_overtime_hours * 60),
|
||||
'sp_minutes': int(week_sp_total * 60),
|
||||
'pw_minutes': int(week_pw_total * 60)
|
||||
})
|
||||
|
||||
current_week_hours = []
|
||||
current_week_hours = {'regular': [], 'SP': [], 'PW': []}
|
||||
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
@@ -167,125 +177,92 @@ class SingleCheckInCalculator:
|
||||
grand_total_hours = sum(week['total_hours'] for week in weekly_totals)
|
||||
grand_regular_hours = sum(week['regular_hours'] for week in weekly_totals)
|
||||
grand_overtime_hours = sum(week['overtime_hours'] for week in weekly_totals)
|
||||
grand_sp_hours = sum(week['sp_hours'] for week in weekly_totals)
|
||||
grand_pw_hours = sum(week['pw_hours'] for week in weekly_totals)
|
||||
|
||||
print(f"✅ Employee {employee_id}: {grand_total_hours:.2f} total hours, {grand_regular_hours:.2f} regular, {grand_overtime_hours:.2f} OT")
|
||||
print(f"✅ Employee {employee_id}: Total: {grand_total_hours:.2f}h (Regular: {grand_regular_hours:.2f}h, OT: {grand_overtime_hours:.2f}h, SP: {grand_sp_hours:.2f}h, PW: {grand_pw_hours:.2f}h)")
|
||||
|
||||
return {
|
||||
result = {
|
||||
'employee_id': employee_id,
|
||||
'base_employee_id': base_employee_id,
|
||||
'start_date': start_date.strftime('%Y-%m-%d'),
|
||||
'end_date': end_date.strftime('%Y-%m-%d'),
|
||||
'include_travel_time': True, # Not applicable for single check-in system
|
||||
'include_travel_time': True,
|
||||
'daily_hours': daily_hours,
|
||||
'weekly_hours': weekly_totals,
|
||||
'grand_totals': {
|
||||
'total_hours': grand_total_hours,
|
||||
'regular_hours': grand_regular_hours,
|
||||
'overtime_hours': grand_overtime_hours,
|
||||
'sp_hours': grand_sp_hours,
|
||||
'pw_hours': grand_pw_hours,
|
||||
'total_minutes': int(grand_total_hours * 60),
|
||||
'regular_minutes': int(grand_regular_hours * 60),
|
||||
'overtime_minutes': int(grand_overtime_hours * 60)
|
||||
'overtime_minutes': int(grand_overtime_hours * 60),
|
||||
'sp_minutes': int(grand_sp_hours * 60),
|
||||
'pw_minutes': int(grand_pw_hours * 60)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error calculating working hours for employee {employee_id}: {e}")
|
||||
import traceback
|
||||
print(f"❌ Traceback: {traceback.format_exc()}")
|
||||
return self._empty_result(employee_id, start_date, end_date)
|
||||
|
||||
def _calculate_daily_hours_from_checkins(self, day_records: List[CheckInRecord]) -> Tuple[float, bool]:
|
||||
"""
|
||||
Calculate hours for a single day from check-in records
|
||||
|
||||
CORRECTED Logic:
|
||||
- 0 records = No work (0 hours, not miss punch)
|
||||
- 1 record = Miss punch (incomplete pair)
|
||||
- 2, 4, 6, 8... records = Complete pairs, calculate all
|
||||
- 3, 5, 7, 9... records = Calculate complete pairs only, ignore last odd record
|
||||
|
||||
Returns: (hours, is_miss_punch)
|
||||
"""
|
||||
def _calculate_daily_hours_from_records(self, day_records: List[Dict]) -> Tuple[float, bool]:
|
||||
"""Calculate hours for a single day from processed records"""
|
||||
if not day_records:
|
||||
return 0.0, False # No records = no work
|
||||
return 0.0, False
|
||||
|
||||
# Sort records by time
|
||||
sorted_records = sorted(day_records, key=lambda r: r.timestamp)
|
||||
print(f"📝 Processing {len(sorted_records)} records for daily calculation")
|
||||
# Sort records by timestamp
|
||||
try:
|
||||
sorted_records = sorted(day_records, key=lambda r: r['timestamp'])
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error sorting records: {e}")
|
||||
return 0.0, True
|
||||
|
||||
# SINGLE RECORD = MISS PUNCH (CORRECTED)
|
||||
# Single record = miss punch
|
||||
if len(sorted_records) == 1:
|
||||
print(f"⚠️ Single record found - Miss punch (incomplete pair)")
|
||||
return 0.0, True # Single record is always miss punch
|
||||
return 0.0, True
|
||||
|
||||
# CALCULATE COMPLETE PAIRS ONLY (CORRECTED FOR ODD NUMBERS)
|
||||
# For odd numbers: process pairs and ignore the last unpaired record
|
||||
# Calculate complete pairs only
|
||||
num_complete_pairs = len(sorted_records) // 2
|
||||
records_to_process = num_complete_pairs * 2 # Only process paired records
|
||||
|
||||
print(f"📊 Processing {num_complete_pairs} complete pairs from {len(sorted_records)} total records")
|
||||
|
||||
work_periods = []
|
||||
total_hours = 0.0
|
||||
|
||||
# Process complete pairs only
|
||||
for i in range(0, records_to_process, 2):
|
||||
start_record = sorted_records[i]
|
||||
end_record = sorted_records[i + 1]
|
||||
|
||||
period = WorkPeriod(start_record, end_record)
|
||||
|
||||
# Validate work period duration
|
||||
if self._is_valid_work_period(period):
|
||||
work_periods.append(period)
|
||||
pair_hours = period.duration_minutes / 60.0
|
||||
total_hours += pair_hours
|
||||
print(f"✅ Valid work period: {start_record.check_in_time} - {end_record.check_in_time} = {pair_hours:.2f} hours")
|
||||
else:
|
||||
print(f"⚠️ Invalid work period: {period.duration_minutes/60:.2f} hours - treating as miss punch")
|
||||
return 0.0, True # Invalid period = miss punch
|
||||
|
||||
# Check if we had unpaired records (odd number)
|
||||
has_unpaired = len(sorted_records) % 2 != 0
|
||||
if has_unpaired:
|
||||
unpaired_record = sorted_records[-1]
|
||||
print(f"⚠️ Unpaired record found: {unpaired_record.check_in_time} (ignored in calculation)")
|
||||
for i in range(0, num_complete_pairs * 2, 2):
|
||||
try:
|
||||
start_time = sorted_records[i]['timestamp']
|
||||
end_time = sorted_records[i + 1]['timestamp']
|
||||
|
||||
duration = end_time - start_time
|
||||
period_hours = duration.total_seconds() / 3600.0
|
||||
|
||||
# Validate reasonable work period
|
||||
if 0 < period_hours <= self.max_work_period_hours:
|
||||
total_hours += period_hours
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error calculating work period: {e}")
|
||||
continue
|
||||
|
||||
# Round to nearest quarter hour
|
||||
rounded_hours = round(total_hours * 4) / 4
|
||||
total_hours = round(total_hours * 4) / 4
|
||||
|
||||
# Determine if this is a miss punch scenario
|
||||
is_miss_punch = (num_complete_pairs == 0) # No valid pairs = miss punch
|
||||
# Determine if miss punch
|
||||
is_miss_punch = len(sorted_records) % 2 != 0
|
||||
|
||||
if is_miss_punch:
|
||||
print(f"⚠️ No valid work periods found - Miss punch")
|
||||
return 0.0, True
|
||||
else:
|
||||
print(f"✅ Daily total: {rounded_hours:.2f} hours from {num_complete_pairs} complete work periods")
|
||||
# Log the calculation for tracking
|
||||
print(f"📊 CORRECTED: Employee daily hours calculated - {rounded_hours:.2f} hours, Miss punch: {is_miss_punch}")
|
||||
return rounded_hours, False
|
||||
|
||||
def _is_valid_work_period(self, period: WorkPeriod) -> bool:
|
||||
"""Check if a work period is valid (reasonable duration)"""
|
||||
if not period.is_complete:
|
||||
return False
|
||||
|
||||
hours = period.duration_minutes / 60.0
|
||||
|
||||
# Must be positive and less than max work period
|
||||
if hours <= 0 or hours > self.max_work_period_hours:
|
||||
return False
|
||||
|
||||
# Must be at least 15 minutes
|
||||
if period.duration_minutes < 15:
|
||||
return False
|
||||
|
||||
return True
|
||||
return total_hours, is_miss_punch
|
||||
|
||||
def _empty_result(self, employee_id: str, start_date: datetime, end_date: datetime) -> Dict[str, Any]:
|
||||
"""Return empty result structure"""
|
||||
"""Return empty result structure with SP/PW support"""
|
||||
base_employee_id, _ = parse_employee_id_for_work_type(employee_id)
|
||||
|
||||
return {
|
||||
'employee_id': employee_id,
|
||||
'base_employee_id': base_employee_id,
|
||||
'start_date': start_date.strftime('%Y-%m-%d'),
|
||||
'end_date': end_date.strftime('%Y-%m-%d'),
|
||||
'include_travel_time': True,
|
||||
@@ -295,39 +272,60 @@ class SingleCheckInCalculator:
|
||||
'total_hours': 0.0,
|
||||
'regular_hours': 0.0,
|
||||
'overtime_hours': 0.0,
|
||||
'sp_hours': 0.0,
|
||||
'pw_hours': 0.0,
|
||||
'total_minutes': 0,
|
||||
'regular_minutes': 0,
|
||||
'overtime_minutes': 0
|
||||
'overtime_minutes': 0,
|
||||
'sp_minutes': 0,
|
||||
'pw_minutes': 0
|
||||
}
|
||||
}
|
||||
|
||||
def calculate_all_employees_hours(self, start_date: datetime, end_date: datetime,
|
||||
attendance_records: List[Dict]) -> Dict[str, Any]:
|
||||
"""Calculate working hours for all employees in the given period"""
|
||||
"""Calculate working hours for all employees with robust error handling"""
|
||||
try:
|
||||
print(f"🚀 Starting calculation for all employees")
|
||||
print(f"🚀 Starting calculation for all employees with SP/PW support")
|
||||
|
||||
# Get unique employee IDs
|
||||
employee_ids = set()
|
||||
# Get unique base employee IDs safely
|
||||
base_employee_ids = set()
|
||||
for record in attendance_records:
|
||||
if hasattr(record, '__dict__'):
|
||||
employee_ids.add(str(record.employee_id))
|
||||
else:
|
||||
employee_ids.add(str(record['employee_id']))
|
||||
try:
|
||||
if hasattr(record, '__dict__'):
|
||||
employee_id = str(getattr(record, 'employee_id', '')).strip()
|
||||
else:
|
||||
employee_id = str(record.get('employee_id', '')).strip()
|
||||
|
||||
if employee_id:
|
||||
base_id, _ = parse_employee_id_for_work_type(employee_id)
|
||||
if base_id:
|
||||
base_employee_ids.add(base_id)
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error processing employee ID: {e}")
|
||||
continue
|
||||
|
||||
print(f"👥 Found {len(employee_ids)} unique employees")
|
||||
print(f"👥 Found {len(base_employee_ids)} unique base employees")
|
||||
|
||||
results = {}
|
||||
for emp_id in sorted(employee_ids):
|
||||
print(f"\n🔄 Processing employee {emp_id}")
|
||||
results[emp_id] = self.calculate_employee_hours(emp_id, start_date, end_date, attendance_records)
|
||||
for base_emp_id in sorted(base_employee_ids):
|
||||
try:
|
||||
print(f"\n🔄 Processing base employee {base_emp_id}")
|
||||
results[base_emp_id] = self.calculate_employee_hours(
|
||||
base_emp_id, start_date, end_date, attendance_records
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"❌ Error processing employee {base_emp_id}: {e}")
|
||||
results[base_emp_id] = self._empty_result(base_emp_id, start_date, end_date)
|
||||
continue
|
||||
|
||||
return {
|
||||
'calculation_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'period_start': start_date.strftime('%Y-%m-%d'),
|
||||
'period_end': end_date.strftime('%Y-%m-%d'),
|
||||
'include_travel_time': True, # Not applicable for single check-in
|
||||
'employee_count': len(employee_ids),
|
||||
'include_travel_time': True,
|
||||
'employee_count': len(base_employee_ids),
|
||||
'employees': results
|
||||
}
|
||||
|
||||
@@ -335,4 +333,13 @@ class SingleCheckInCalculator:
|
||||
print(f"❌ Error calculating hours for all employees: {e}")
|
||||
import traceback
|
||||
print(f"❌ Traceback: {traceback.format_exc()}")
|
||||
raise e
|
||||
|
||||
# Return minimal safe result
|
||||
return {
|
||||
'calculation_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'period_start': start_date.strftime('%Y-%m-%d'),
|
||||
'period_end': end_date.strftime('%Y-%m-%d'),
|
||||
'include_travel_time': True,
|
||||
'employee_count': 0,
|
||||
'employees': {}
|
||||
}
|
||||
Reference in New Issue
Block a user