Updated export with special working hours (SP, PW, PT)
This commit is contained in:
@@ -8988,6 +8988,37 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_
|
|||||||
grand_ot_hours += week_overtime
|
grand_ot_hours += week_overtime
|
||||||
current_row += 1
|
current_row += 1
|
||||||
|
|
||||||
|
# Write extra working hours rows (SP/PW/PT) if employee has any
|
||||||
|
# Get extra hours from emp_data grand_totals
|
||||||
|
grand_totals = emp_data.get('grand_totals', {})
|
||||||
|
sp_hours = grand_totals.get('sp_hours', 0.0)
|
||||||
|
pw_hours = grand_totals.get('pw_hours', 0.0)
|
||||||
|
pt_hours = grand_totals.get('pt_hours', 0.0)
|
||||||
|
|
||||||
|
# Write SP row if hours > 0
|
||||||
|
if sp_hours > 0:
|
||||||
|
ws.cell(row=current_row, column=7, value='Special Project (SP): ').font = Font(name='Arial', size=10, bold=True, italic=True)
|
||||||
|
ws.cell(row=current_row, column=9, value=round(sp_hours, 2)).font = Font(name='Arial', size=10, bold=True, italic=True)
|
||||||
|
# Log SP hours export
|
||||||
|
logger_handler.logger.info(f"Export: Employee {employee_id} SP hours: {sp_hours:.2f}")
|
||||||
|
current_row += 1
|
||||||
|
|
||||||
|
# Write PW row if hours > 0
|
||||||
|
if pw_hours > 0:
|
||||||
|
ws.cell(row=current_row, column=7, value='Periodic Work (PW): ').font = Font(name='Arial', size=10, bold=True, italic=True)
|
||||||
|
ws.cell(row=current_row, column=9, value=round(pw_hours, 2)).font = Font(name='Arial', size=10, bold=True, italic=True)
|
||||||
|
# Log PW hours export
|
||||||
|
logger_handler.logger.info(f"Export: Employee {employee_id} PW hours: {pw_hours:.2f}")
|
||||||
|
current_row += 1
|
||||||
|
|
||||||
|
# Write PT row if hours > 0
|
||||||
|
if pt_hours > 0:
|
||||||
|
ws.cell(row=current_row, column=7, value='Part-Time (PT): ').font = Font(name='Arial', size=10, bold=True, italic=True)
|
||||||
|
ws.cell(row=current_row, column=9, value=round(pt_hours, 2)).font = Font(name='Arial', size=10, bold=True, italic=True)
|
||||||
|
# Log PT hours export
|
||||||
|
logger_handler.logger.info(f"Export: Employee {employee_id} PT hours: {pt_hours:.2f}")
|
||||||
|
current_row += 1
|
||||||
|
|
||||||
# Write GRAND TOTAL row
|
# Write GRAND TOTAL row
|
||||||
ws.cell(row=current_row, column=7, value='GRAND TOTAL: ').font = Font(name='Arial', size=10, bold=True)
|
ws.cell(row=current_row, column=7, value='GRAND TOTAL: ').font = Font(name='Arial', size=10, bold=True)
|
||||||
ws.cell(row=current_row, column=9, value=round(grand_regular_hours, 2)).font = Font(name='Arial', size=10, bold=True)
|
ws.cell(row=current_row, column=9, value=round(grand_regular_hours, 2)).font = Font(name='Arial', size=10, bold=True)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from logger_handler import log_database_operations
|
|||||||
|
|
||||||
|
|
||||||
def parse_employee_id_for_work_type(employee_id: str) -> Tuple[str, str]:
|
def parse_employee_id_for_work_type(employee_id: str) -> Tuple[str, str]:
|
||||||
"""Parse employee ID to extract base ID and work type"""
|
"""Parse employee ID to extract base ID and work type (supports SP, PW, PT)"""
|
||||||
if not employee_id:
|
if not employee_id:
|
||||||
return str(employee_id), 'regular'
|
return str(employee_id), 'regular'
|
||||||
|
|
||||||
@@ -34,6 +34,12 @@ def parse_employee_id_for_work_type(employee_id: str) -> Tuple[str, str]:
|
|||||||
if pw_match:
|
if pw_match:
|
||||||
return pw_match.group(1), 'PW'
|
return pw_match.group(1), 'PW'
|
||||||
|
|
||||||
|
# Check for PT (Part-Time)
|
||||||
|
pt_pattern = r'^(\d+)\s*PT$'
|
||||||
|
pt_match = re.match(pt_pattern, employee_id_clean)
|
||||||
|
if pt_match:
|
||||||
|
return pt_match.group(1), 'PT'
|
||||||
|
|
||||||
# Default to regular work
|
# Default to regular work
|
||||||
return employee_id_clean, 'regular'
|
return employee_id_clean, 'regular'
|
||||||
|
|
||||||
@@ -135,7 +141,7 @@ class SingleCheckInCalculator:
|
|||||||
base_employee_id, _ = parse_employee_id_for_work_type(employee_id)
|
base_employee_id, _ = parse_employee_id_for_work_type(employee_id)
|
||||||
|
|
||||||
# Filter and categorize records
|
# Filter and categorize records
|
||||||
records_by_type = {'regular': [], 'SP': [], 'PW': []}
|
records_by_type = {'regular': [], 'SP': [], 'PW': [], 'PT': []}
|
||||||
|
|
||||||
for record in attendance_records:
|
for record in attendance_records:
|
||||||
try:
|
try:
|
||||||
@@ -195,14 +201,14 @@ class SingleCheckInCalculator:
|
|||||||
hours_by_type = {}
|
hours_by_type = {}
|
||||||
is_miss_punch_by_type = {}
|
is_miss_punch_by_type = {}
|
||||||
|
|
||||||
for work_type in ['regular', 'SP', 'PW']:
|
for work_type in ['regular', 'SP', 'PW', 'PT']:
|
||||||
day_records = [r for r in records_by_type[work_type]
|
day_records = [r for r in records_by_type[work_type]
|
||||||
if r['check_in_date'] == current_date.date()]
|
if r['check_in_date'] == current_date.date()]
|
||||||
hours, is_miss_punch = self._calculate_daily_hours_from_records(day_records)
|
hours, is_miss_punch = self._calculate_daily_hours_from_records(day_records)
|
||||||
hours_by_type[work_type] = hours
|
hours_by_type[work_type] = hours
|
||||||
is_miss_punch_by_type[work_type] = is_miss_punch
|
is_miss_punch_by_type[work_type] = is_miss_punch
|
||||||
|
|
||||||
# Store daily data with SP/PW support
|
# Store daily data with SP/PW/PT support
|
||||||
total_day_hours = sum(hours_by_type.values())
|
total_day_hours = sum(hours_by_type.values())
|
||||||
daily_hours[date_key] = {
|
daily_hours[date_key] = {
|
||||||
'total_minutes': int(total_day_hours * 60),
|
'total_minutes': int(total_day_hours * 60),
|
||||||
@@ -210,18 +216,20 @@ class SingleCheckInCalculator:
|
|||||||
'regular_hours': hours_by_type['regular'],
|
'regular_hours': hours_by_type['regular'],
|
||||||
'sp_hours': hours_by_type['SP'],
|
'sp_hours': hours_by_type['SP'],
|
||||||
'pw_hours': hours_by_type['PW'],
|
'pw_hours': hours_by_type['PW'],
|
||||||
|
'pt_hours': hours_by_type['PT'],
|
||||||
'is_miss_punch': any(is_miss_punch_by_type.values()),
|
'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()])
|
'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']),
|
for wt in ['regular', 'SP', 'PW', 'PT']),
|
||||||
'miss_punch_details': {
|
'miss_punch_details': {
|
||||||
'regular': is_miss_punch_by_type['regular'],
|
'regular': is_miss_punch_by_type['regular'],
|
||||||
'SP': is_miss_punch_by_type['SP'],
|
'SP': is_miss_punch_by_type['SP'],
|
||||||
'PW': is_miss_punch_by_type['PW']
|
'PW': is_miss_punch_by_type['PW'],
|
||||||
|
'PT': is_miss_punch_by_type['PT']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add to weekly calculation
|
# Add to weekly calculation
|
||||||
for work_type in ['regular', 'SP', 'PW']:
|
for work_type in ['regular', 'SP', 'PW', 'PT']:
|
||||||
current_week_hours[work_type].append(max(0, hours_by_type[work_type]))
|
current_week_hours[work_type].append(max(0, hours_by_type[work_type]))
|
||||||
|
|
||||||
# Check if end of week or end of period
|
# Check if end of week or end of period
|
||||||
@@ -229,35 +237,39 @@ class SingleCheckInCalculator:
|
|||||||
week_regular_total = sum(current_week_hours['regular'])
|
week_regular_total = sum(current_week_hours['regular'])
|
||||||
week_sp_total = sum(current_week_hours['SP'])
|
week_sp_total = sum(current_week_hours['SP'])
|
||||||
week_pw_total = sum(current_week_hours['PW'])
|
week_pw_total = sum(current_week_hours['PW'])
|
||||||
|
week_pt_total = sum(current_week_hours['PT'])
|
||||||
|
|
||||||
# NEW: Round the weekly total
|
# Round the weekly total
|
||||||
week_total_raw = week_regular_total + week_sp_total + week_pw_total
|
week_total_raw = week_regular_total + week_sp_total + week_pw_total + week_pt_total
|
||||||
week_total_rounded = round_base100_hours(week_total_raw)
|
week_total_rounded = round_base100_hours(week_total_raw)
|
||||||
|
|
||||||
# Only regular hours count toward overtime
|
# Only regular hours count toward overtime
|
||||||
week_regular_hours = min(week_regular_total, 40.0)
|
week_regular_hours = min(week_regular_total, 40.0)
|
||||||
week_overtime_hours = max(0, week_regular_total - 40.0)
|
week_overtime_hours = max(0, week_regular_total - 40.0)
|
||||||
|
|
||||||
# NEW: Round regular and overtime hours
|
# Round regular, overtime, and special hours
|
||||||
week_regular_hours_rounded = round_base100_hours(week_regular_hours)
|
week_regular_hours_rounded = round_base100_hours(week_regular_hours)
|
||||||
week_overtime_hours_rounded = round_base100_hours(week_overtime_hours)
|
week_overtime_hours_rounded = round_base100_hours(week_overtime_hours)
|
||||||
week_sp_hours_rounded = round_base100_hours(week_sp_total)
|
week_sp_hours_rounded = round_base100_hours(week_sp_total)
|
||||||
week_pw_hours_rounded = round_base100_hours(week_pw_total)
|
week_pw_hours_rounded = round_base100_hours(week_pw_total)
|
||||||
|
week_pt_hours_rounded = round_base100_hours(week_pt_total)
|
||||||
|
|
||||||
weekly_totals.append({
|
weekly_totals.append({
|
||||||
'total_hours': week_total_rounded, # CHANGED: Use rounded value
|
'total_hours': week_total_rounded,
|
||||||
'regular_hours': week_regular_hours_rounded, # CHANGED: Use rounded value
|
'regular_hours': week_regular_hours_rounded,
|
||||||
'overtime_hours': week_overtime_hours_rounded, # CHANGED: Use rounded value
|
'overtime_hours': week_overtime_hours_rounded,
|
||||||
'sp_hours': week_sp_hours_rounded, # CHANGED: Use rounded value
|
'sp_hours': week_sp_hours_rounded,
|
||||||
'pw_hours': week_pw_hours_rounded, # CHANGED: Use rounded value
|
'pw_hours': week_pw_hours_rounded,
|
||||||
|
'pt_hours': week_pt_hours_rounded,
|
||||||
'total_minutes': int(week_total_rounded * 60),
|
'total_minutes': int(week_total_rounded * 60),
|
||||||
'regular_minutes': int(week_regular_hours_rounded * 60),
|
'regular_minutes': int(week_regular_hours_rounded * 60),
|
||||||
'overtime_minutes': int(week_overtime_hours_rounded * 60),
|
'overtime_minutes': int(week_overtime_hours_rounded * 60),
|
||||||
'sp_minutes': int(week_sp_hours_rounded * 60),
|
'sp_minutes': int(week_sp_hours_rounded * 60),
|
||||||
'pw_minutes': int(week_pw_hours_rounded * 60)
|
'pw_minutes': int(week_pw_hours_rounded * 60),
|
||||||
|
'pt_minutes': int(week_pt_hours_rounded * 60)
|
||||||
})
|
})
|
||||||
|
|
||||||
current_week_hours = {'regular': [], 'SP': [], 'PW': []}
|
current_week_hours = {'regular': [], 'SP': [], 'PW': [], 'PT': []}
|
||||||
|
|
||||||
current_date += timedelta(days=1)
|
current_date += timedelta(days=1)
|
||||||
|
|
||||||
@@ -267,6 +279,7 @@ class SingleCheckInCalculator:
|
|||||||
grand_overtime_raw = sum(week['overtime_hours'] for week in weekly_totals)
|
grand_overtime_raw = sum(week['overtime_hours'] for week in weekly_totals)
|
||||||
grand_sp_raw = sum(week['sp_hours'] for week in weekly_totals)
|
grand_sp_raw = sum(week['sp_hours'] for week in weekly_totals)
|
||||||
grand_pw_raw = sum(week['pw_hours'] for week in weekly_totals)
|
grand_pw_raw = sum(week['pw_hours'] for week in weekly_totals)
|
||||||
|
grand_pt_raw = sum(week.get('pt_hours', 0) for week in weekly_totals)
|
||||||
|
|
||||||
# Round grand totals
|
# Round grand totals
|
||||||
grand_total_hours = round_base100_hours(grand_total_raw)
|
grand_total_hours = round_base100_hours(grand_total_raw)
|
||||||
@@ -274,8 +287,9 @@ class SingleCheckInCalculator:
|
|||||||
grand_overtime_hours = round_base100_hours(grand_overtime_raw)
|
grand_overtime_hours = round_base100_hours(grand_overtime_raw)
|
||||||
grand_sp_hours = round_base100_hours(grand_sp_raw)
|
grand_sp_hours = round_base100_hours(grand_sp_raw)
|
||||||
grand_pw_hours = round_base100_hours(grand_pw_raw)
|
grand_pw_hours = round_base100_hours(grand_pw_raw)
|
||||||
|
grand_pt_hours = round_base100_hours(grand_pt_raw)
|
||||||
|
|
||||||
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)")
|
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, PT: {grand_pt_hours:.2f}h)")
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
'employee_id': employee_id,
|
'employee_id': employee_id,
|
||||||
@@ -291,11 +305,13 @@ class SingleCheckInCalculator:
|
|||||||
'overtime_hours': grand_overtime_hours,
|
'overtime_hours': grand_overtime_hours,
|
||||||
'sp_hours': grand_sp_hours,
|
'sp_hours': grand_sp_hours,
|
||||||
'pw_hours': grand_pw_hours,
|
'pw_hours': grand_pw_hours,
|
||||||
|
'pt_hours': grand_pt_hours,
|
||||||
'total_minutes': int(grand_total_hours * 60),
|
'total_minutes': int(grand_total_hours * 60),
|
||||||
'regular_minutes': int(grand_regular_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),
|
'sp_minutes': int(grand_sp_hours * 60),
|
||||||
'pw_minutes': int(grand_pw_hours * 60)
|
'pw_minutes': int(grand_pw_hours * 60),
|
||||||
|
'pt_minutes': int(grand_pt_hours * 60)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +389,7 @@ class SingleCheckInCalculator:
|
|||||||
return rounded_total_hours, is_miss_punch
|
return rounded_total_hours, is_miss_punch
|
||||||
|
|
||||||
def _empty_result(self, employee_id: str, start_date: datetime, end_date: datetime) -> Dict[str, Any]:
|
def _empty_result(self, employee_id: str, start_date: datetime, end_date: datetime) -> Dict[str, Any]:
|
||||||
"""Return empty result structure with SP/PW support"""
|
"""Return empty result structure with SP/PW/PT support"""
|
||||||
base_employee_id, _ = parse_employee_id_for_work_type(employee_id)
|
base_employee_id, _ = parse_employee_id_for_work_type(employee_id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -390,11 +406,13 @@ class SingleCheckInCalculator:
|
|||||||
'overtime_hours': 0.0,
|
'overtime_hours': 0.0,
|
||||||
'sp_hours': 0.0,
|
'sp_hours': 0.0,
|
||||||
'pw_hours': 0.0,
|
'pw_hours': 0.0,
|
||||||
|
'pt_hours': 0.0,
|
||||||
'total_minutes': 0,
|
'total_minutes': 0,
|
||||||
'regular_minutes': 0,
|
'regular_minutes': 0,
|
||||||
'overtime_minutes': 0,
|
'overtime_minutes': 0,
|
||||||
'sp_minutes': 0,
|
'sp_minutes': 0,
|
||||||
'pw_minutes': 0
|
'pw_minutes': 0,
|
||||||
|
'pt_minutes': 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user