From a7d350457a7dbec5063be546f0298a2603c1814f Mon Sep 17 00:00:00 2001 From: NguyenND Date: Fri, 17 Oct 2025 17:23:37 -0400 Subject: [PATCH] Updated time attendance exporting function --- app.py | 37 ++++++++++++++++++++++++++---------- single_checkin_calculator.py | 35 +++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/app.py b/app.py index d904cb9..19cff97 100644 --- a/app.py +++ b/app.py @@ -7432,7 +7432,7 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_ # Setup styles header_font = Font(name='Arial', size=11, bold=True, color='FFFFFF') - header_fill = PatternFill(start_color='366092', end_color='366092', fill_type='solid') + header_fill = PatternFill(start_color='000000', end_color='000000', fill_type='solid') data_font = Font(name='Arial', size=10) bold_font = Font(name='Arial', size=10, bold=True) border = Border( @@ -7631,19 +7631,36 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_ all_records_for_day = [] for loc_data in date_locations.values(): all_records_for_day.extend(loc_data['records']) - + if len(all_records_for_day) >= 2: sorted_all_records = sorted(all_records_for_day, key=lambda x: x.check_in_time) - first_time = sorted_all_records[0].check_in_time - last_time = sorted_all_records[-1].check_in_time - first_datetime = datetime.combine(date_obj, first_time) - last_datetime = datetime.combine(date_obj, last_time) - calculated_hours = (last_datetime - first_datetime).total_seconds() / 3600.0 - calculated_hours = round(calculated_hours, 2) + # NEW: Check if all records are IN or all OUT + record_types = [] + for record in sorted_all_records: + action_desc = record.action_description.lower() if record.action_description else '' + if 'out' in action_desc or 'checkout' in action_desc: + record_types.append('OUT') + else: + record_types.append('IN') - weekly_total_hours += calculated_hours - total_hours = calculated_hours + # If all same type (all IN or all OUT), hours = 0 + if len(set(record_types)) == 1: + print(f"⚠️ {date_str}: All {len(sorted_all_records)} records are {record_types[0]} - 0 working hours") + calculated_hours = 0.0 + total_hours = 0.0 + else: + # Mixed IN/OUT - calculate from first to last + first_time = sorted_all_records[0].check_in_time + last_time = sorted_all_records[-1].check_in_time + + first_datetime = datetime.combine(date_obj, first_time) + last_datetime = datetime.combine(date_obj, last_time) + calculated_hours = (last_datetime - first_datetime).total_seconds() / 3600.0 + calculated_hours = round(calculated_hours, 2) + + weekly_total_hours += calculated_hours + total_hours = calculated_hours # Daily total display (only shown on last location's last row) daily_total_display = round(total_hours, 2) if total_hours > 0 else '' diff --git a/single_checkin_calculator.py b/single_checkin_calculator.py index 6fc763b..4c48af3 100644 --- a/single_checkin_calculator.py +++ b/single_checkin_calculator.py @@ -93,6 +93,7 @@ class SingleCheckInCalculator: 'check_in_time': record_time, 'location_name': location, 'work_type': work_type, + 'action_description': getattr(record, 'action_description', '') if hasattr(record, '__dict__') else record.get('action_description', ''), 'timestamp': datetime.combine(record_date, record_time) if record_date and record_time else datetime.now() } records_by_type[work_type].append(processed_record) @@ -228,6 +229,21 @@ class SingleCheckInCalculator: if len(sorted_records) == 1: return 0.0, True + # NEW: Check if all records are IN or all OUT using action_description + if len(sorted_records) >= 2: + record_types = [] + for record in sorted_records: + action_desc = record.get('action_description', '').lower() + if 'out' in action_desc or 'checkout' in action_desc: + record_types.append('OUT') + else: + record_types.append('IN') + + # If all are IN or all are OUT, return 0 hours with miss punch + if len(set(record_types)) == 1: + print(f"⚠️ All {len(sorted_records)} records are {record_types[0]} - 0 working hours") + return 0.0, True + # Calculate complete pairs only num_complete_pairs = len(sorted_records) // 2 total_hours = 0.0 @@ -238,21 +254,14 @@ class SingleCheckInCalculator: 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 - + hours = duration.total_seconds() / 3600.0 + total_hours += hours except Exception as e: - print(f"⚠️ Error calculating work period: {e}") - continue + print(f"⚠️ Error calculating pair hours: {e}") + return 0.0, True - # Round to nearest quarter hour - total_hours = round(total_hours * 4) / 4 - - # Determine if miss punch - is_miss_punch = len(sorted_records) % 2 != 0 + # Odd number of records = miss punch + is_miss_punch = (len(sorted_records) % 2 != 0) return total_hours, is_miss_punch