Updating the TA exporting function
This commit is contained in:
@@ -7,6 +7,7 @@ from datetime import datetime, date, time, timedelta
|
||||
from sqlalchemy import text
|
||||
from user_agents import parse
|
||||
import io, os, base64, re, uuid, requests, json, qrcode, math, traceback, googlemaps
|
||||
import openpyxl.cell.cell
|
||||
from PIL import Image, ImageDraw
|
||||
from math import radians, sin, cos, asin, sqrt
|
||||
from dotenv import load_dotenv
|
||||
@@ -5750,8 +5751,7 @@ def get_miss_punch_details(employee_id):
|
||||
converted_records.append(converted_record)
|
||||
|
||||
# Calculate working hours using the same calculator as the dashboard
|
||||
# from single_checkin_calculator import SingleCheckInCalculator
|
||||
calculator = SingleCheckInCalculator()
|
||||
#calculator = SingleCheckInCalculator()
|
||||
|
||||
# Calculate hours for this employee
|
||||
hours_data = calculator.calculate_employee_hours(
|
||||
@@ -7338,69 +7338,506 @@ def export_time_attendance_csv(records, project_name_for_filename, date_range_st
|
||||
)
|
||||
|
||||
def export_time_attendance_excel(records, project_name_for_filename, date_range_str, filter_str):
|
||||
"""Generate Excel export of time attendance records"""
|
||||
import io
|
||||
"""Generate Excel export with template format matching the provided template"""
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
import io
|
||||
|
||||
# Create workbook
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Time Attendance Records"
|
||||
ws.title = "Sheet0"
|
||||
|
||||
# Define styles
|
||||
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
|
||||
header_font = Font(bold=True, color="FFFFFF", size=11)
|
||||
# Get date range for calculations
|
||||
if records:
|
||||
start_date = min(r.attendance_date for r in records)
|
||||
end_date = max(r.attendance_date for r in records)
|
||||
else:
|
||||
return None
|
||||
|
||||
# Convert TimeAttendance records to format expected by calculator
|
||||
converted_records = []
|
||||
for record in records:
|
||||
converted_record = type('Record', (), {
|
||||
'id': record.id,
|
||||
'employee_id': str(record.employee_id),
|
||||
'check_in_date': record.attendance_date,
|
||||
'check_in_time': record.attendance_time,
|
||||
'location_name': record.location_name,
|
||||
'latitude': None,
|
||||
'longitude': None,
|
||||
'qr_code': type('QRCode', (), {
|
||||
'location': record.location_name,
|
||||
'location_address': record.recorded_address or '',
|
||||
'project': None
|
||||
})()
|
||||
})()
|
||||
converted_records.append(converted_record)
|
||||
|
||||
# Calculate working hours using SingleCheckInCalculator
|
||||
calculator = SingleCheckInCalculator()
|
||||
hours_data = calculator.calculate_all_employees_hours(
|
||||
datetime.combine(start_date, datetime.min.time()),
|
||||
datetime.combine(end_date, datetime.max.time()),
|
||||
converted_records
|
||||
)
|
||||
|
||||
# Get employee names
|
||||
employee_names = {}
|
||||
for record in records:
|
||||
if record.employee_id not in employee_names:
|
||||
employee_names[record.employee_id] = record.employee_name
|
||||
|
||||
# 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')
|
||||
data_font = Font(name='Arial', size=10)
|
||||
bold_font = Font(name='Arial', size=10, bold=True)
|
||||
border = Border(
|
||||
left=Side(style='thin'),
|
||||
right=Side(style='thin'),
|
||||
top=Side(style='thin'),
|
||||
bottom=Side(style='thin')
|
||||
)
|
||||
# Orange background for Missed Punch
|
||||
missed_punch_fill = PatternFill(start_color='FFC000', end_color='FFC000', fill_type='solid')
|
||||
|
||||
# Header row
|
||||
headers = ['Employee ID', 'Employee Name', 'Platform', 'Date', 'Time',
|
||||
'Location Name', 'Action Description', 'Event Description', 'Recorded Address']
|
||||
# Write main headers
|
||||
current_row = 1
|
||||
|
||||
for col_num, header in enumerate(headers, 1):
|
||||
cell = ws.cell(row=1, column=col_num, value=header)
|
||||
cell.fill = header_fill
|
||||
cell.font = header_font
|
||||
cell.alignment = Alignment(horizontal='center', vertical='center')
|
||||
cell.border = border
|
||||
# Row 1: Company name
|
||||
ws.merge_cells(f'A{current_row}:N{current_row}')
|
||||
title_cell = ws.cell(row=current_row, column=1, value='LT Services Inc.')
|
||||
title_cell.font = Font(name='Arial', size=14, bold=True)
|
||||
title_cell.alignment = Alignment(horizontal='left')
|
||||
current_row += 1
|
||||
|
||||
# Data rows
|
||||
for row_num, record in enumerate(records, 2):
|
||||
ws.cell(row=row_num, column=1, value=record.employee_id).border = border
|
||||
ws.cell(row=row_num, column=2, value=record.employee_name).border = border
|
||||
ws.cell(row=row_num, column=3, value=record.platform or '').border = border
|
||||
ws.cell(row=row_num, column=4, value=record.attendance_date.strftime('%Y-%m-%d') if record.attendance_date else '').border = border
|
||||
ws.cell(row=row_num, column=5, value=record.attendance_time.strftime('%H:%M:%S') if record.attendance_time else '').border = border
|
||||
ws.cell(row=row_num, column=6, value=record.location_name).border = border
|
||||
ws.cell(row=row_num, column=7, value=record.action_description).border = border
|
||||
ws.cell(row=row_num, column=8, value=record.event_description or '').border = border
|
||||
ws.cell(row=row_num, column=9, value=record.recorded_address or '').border = border
|
||||
# Row 2: Summary title
|
||||
ws.merge_cells(f'A{current_row}:N{current_row}')
|
||||
summary_cell = ws.cell(row=current_row, column=1, value='Summary report of Hours worked')
|
||||
summary_cell.font = Font(name='Arial', size=12, bold=True)
|
||||
summary_cell.alignment = Alignment(horizontal='left')
|
||||
current_row += 1
|
||||
|
||||
# Auto-size columns
|
||||
for col in ws.columns:
|
||||
# Row 3: Project name
|
||||
project_display = project_name_for_filename.replace('_', ' ').strip() if project_name_for_filename else "[Project Name]"
|
||||
project_cell = ws.cell(row=current_row, column=1, value=project_display)
|
||||
project_cell.font = Font(name='Arial', size=11, bold=True)
|
||||
project_cell.alignment = Alignment(horizontal='left')
|
||||
current_row += 1
|
||||
|
||||
# Row 4: Date range
|
||||
date_range_text = f"Date range: {start_date.strftime('%m/%d/%Y')} to {end_date.strftime('%m/%d/%Y')}"
|
||||
ws.merge_cells(f'A{current_row}:N{current_row}')
|
||||
date_cell = ws.cell(row=current_row, column=1, value=date_range_text)
|
||||
date_cell.font = Font(name='Arial', size=11)
|
||||
date_cell.alignment = Alignment(horizontal='left')
|
||||
current_row += 1
|
||||
|
||||
# Row 5: Empty row
|
||||
current_row += 1
|
||||
|
||||
# Empty row before first employee
|
||||
current_row += 1
|
||||
|
||||
# Write data for each employee
|
||||
for employee_id, emp_data in hours_data['employees'].items():
|
||||
employee_name = employee_names.get(employee_id, f'Employee {employee_id}')
|
||||
|
||||
# Employee header row (merged A to O)
|
||||
ws.merge_cells(f'A{current_row}:O{current_row}')
|
||||
emp_header = ws.cell(row=current_row, column=1,
|
||||
value=f'Employee ID {employee_id}: {employee_name}')
|
||||
emp_header.font = Font(name='Arial', size=11, bold=True)
|
||||
emp_header.alignment = Alignment(horizontal='left')
|
||||
current_row += 1
|
||||
|
||||
# Column headers
|
||||
headers = ['Day', 'Date', 'In', 'Out', 'Location', 'Zone', 'Hours/Building',
|
||||
'Daily Total', 'Regular Hours', 'OT Hours', 'Building Address',
|
||||
'Recorded Location', 'Distance (Mile)', 'Possible Violation']
|
||||
|
||||
for col, header in enumerate(headers, 1):
|
||||
cell = ws.cell(row=current_row, column=col, value=header)
|
||||
cell.font = header_font
|
||||
cell.fill = header_fill
|
||||
cell.border = border
|
||||
cell.alignment = Alignment(horizontal='center', vertical='center')
|
||||
current_row += 1
|
||||
|
||||
# Group records by date
|
||||
daily_location_data = {}
|
||||
employee_records = [r for r in converted_records if str(r.employee_id) == employee_id]
|
||||
|
||||
for record in employee_records:
|
||||
date_key = record.check_in_date.strftime('%Y-%m-%d')
|
||||
if date_key not in daily_location_data:
|
||||
daily_location_data[date_key] = {
|
||||
'records': [],
|
||||
'locations': set()
|
||||
}
|
||||
daily_location_data[date_key]['records'].append(record)
|
||||
daily_location_data[date_key]['locations'].add(record.location_name)
|
||||
|
||||
# Track weekly hours for overtime calculation
|
||||
weekly_total_hours = 0
|
||||
current_week_start = None
|
||||
grand_regular_hours = 0
|
||||
grand_ot_hours = 0
|
||||
|
||||
# Get all dates that have records (not all weekdays)
|
||||
dates_with_records = sorted([
|
||||
date_str for date_str, day_data in emp_data['daily_hours'].items()
|
||||
if day_data.get('records_count', 0) > 0
|
||||
])
|
||||
|
||||
# Write daily data (ONLY DAYS WITH RECORDS)
|
||||
for date_str in dates_with_records:
|
||||
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
|
||||
day_data = emp_data['daily_hours'][date_str]
|
||||
|
||||
# Check for week boundary (week starts on Monday)
|
||||
week_start = date_obj - timedelta(days=date_obj.weekday())
|
||||
if current_week_start is not None and week_start != current_week_start:
|
||||
# Write weekly total row
|
||||
week_regular = min(weekly_total_hours, 40.0)
|
||||
week_overtime = max(0, weekly_total_hours - 40.0)
|
||||
|
||||
ws.cell(row=current_row, column=7, value='Weekly Total: ').font = bold_font
|
||||
ws.cell(row=current_row, column=8, value=round(weekly_total_hours, 2)).font = bold_font
|
||||
ws.cell(row=current_row, column=9, value=round(week_regular, 2)).font = bold_font
|
||||
ws.cell(row=current_row, column=10, value=round(week_overtime, 2)).font = bold_font
|
||||
|
||||
grand_regular_hours += week_regular
|
||||
grand_ot_hours += week_overtime
|
||||
current_row += 1
|
||||
|
||||
weekly_total_hours = 0
|
||||
|
||||
current_week_start = week_start
|
||||
|
||||
# Get day records
|
||||
day_records = daily_location_data.get(date_str, {}).get('records', [])
|
||||
sorted_records = sorted(day_records, key=lambda x: x.check_in_time)
|
||||
|
||||
total_hours = day_data['total_hours']
|
||||
is_miss_punch = day_data.get('is_miss_punch', False)
|
||||
|
||||
# Check if different locations (login and logout at different places)
|
||||
locations = daily_location_data.get(date_str, {}).get('locations', set())
|
||||
has_different_locations = len(locations) > 1
|
||||
|
||||
# Calculate total hours for the day (even if miss punch)
|
||||
if not is_miss_punch and total_hours > 0:
|
||||
weekly_total_hours += total_hours
|
||||
elif is_miss_punch and len(sorted_records) >= 2:
|
||||
# Calculate hours even for miss punch (different locations)
|
||||
first_time = sorted_records[0].check_in_time
|
||||
last_time = sorted_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 * 4) / 4 # Round to quarter hour
|
||||
weekly_total_hours += calculated_hours
|
||||
total_hours = calculated_hours
|
||||
|
||||
# Calculate precise hours for Hours/Building column
|
||||
if len(sorted_records) >= 2 and not is_miss_punch:
|
||||
first_time = sorted_records[0].check_in_time
|
||||
last_time = sorted_records[-1].check_in_time
|
||||
first_datetime = datetime.combine(date_obj, first_time)
|
||||
last_datetime = datetime.combine(date_obj, last_time)
|
||||
duration = (last_datetime - first_datetime).total_seconds() / 3600.0
|
||||
precise_hours = round(duration, 2)
|
||||
else:
|
||||
precise_hours = round(total_hours, 2) if total_hours > 0 else 0
|
||||
|
||||
# Round daily total for display
|
||||
daily_total_display = round(total_hours * 2) / 2
|
||||
if daily_total_display == int(daily_total_display):
|
||||
daily_total_display = int(daily_total_display)
|
||||
|
||||
# HANDLE MISSED PUNCH CASES
|
||||
if is_miss_punch:
|
||||
if len(sorted_records) == 1:
|
||||
# Single record - show as Missed Punch in both columns with ORANGE background
|
||||
record = sorted_records[0]
|
||||
row_data = [
|
||||
date_obj.strftime('%A').upper(),
|
||||
date_obj.strftime('%m/%d/%Y'),
|
||||
record.check_in_time.strftime('%I:%M:%S %p'),
|
||||
'', # No check out
|
||||
record.location_name,
|
||||
'',
|
||||
'Missed Punch',
|
||||
'Missed Punch',
|
||||
'',
|
||||
'',
|
||||
getattr(record.qr_code, 'location_address', '') or '',
|
||||
record.location_name,
|
||||
'',
|
||||
'No'
|
||||
]
|
||||
|
||||
for col, value in enumerate(row_data, 1):
|
||||
cell = ws.cell(row=current_row, column=col, value=value)
|
||||
cell.font = data_font
|
||||
cell.border = border
|
||||
# Apply orange background to Missed Punch cells (columns G and H)
|
||||
if col in [7, 8]: # Hours/Building and Daily Total columns
|
||||
cell.fill = missed_punch_fill
|
||||
current_row += 1
|
||||
|
||||
elif has_different_locations:
|
||||
# Different locations - show TWO rows with ORANGE background
|
||||
# First row: Check-in with Missed Punch
|
||||
first_record = sorted_records[0]
|
||||
row_data = [
|
||||
date_obj.strftime('%A').upper(),
|
||||
date_obj.strftime('%m/%d/%Y'),
|
||||
first_record.check_in_time.strftime('%I:%M:%S %p'),
|
||||
'', # Empty Out
|
||||
first_record.location_name,
|
||||
'',
|
||||
'Missed Punch',
|
||||
'', # Empty Daily Total on first row
|
||||
'',
|
||||
'',
|
||||
getattr(first_record.qr_code, 'location_address', '') or '',
|
||||
first_record.location_name,
|
||||
'',
|
||||
'No'
|
||||
]
|
||||
|
||||
for col, value in enumerate(row_data, 1):
|
||||
cell = ws.cell(row=current_row, column=col, value=value)
|
||||
cell.font = data_font
|
||||
cell.border = border
|
||||
# Apply orange background to Missed Punch cell (column G)
|
||||
if col == 7:
|
||||
cell.fill = missed_punch_fill
|
||||
current_row += 1
|
||||
|
||||
# Second row: Check-out with Missed Punch and Daily Total
|
||||
last_record = sorted_records[-1]
|
||||
row_data = [
|
||||
date_obj.strftime('%A').upper(),
|
||||
date_obj.strftime('%m/%d/%Y'),
|
||||
'', # Empty In
|
||||
last_record.check_in_time.strftime('%I:%M:%S %p'),
|
||||
last_record.location_name,
|
||||
'',
|
||||
'Missed Punch',
|
||||
daily_total_display, # Show calculated Daily Total
|
||||
'',
|
||||
'',
|
||||
getattr(last_record.qr_code, 'location_address', '') or '',
|
||||
last_record.location_name,
|
||||
'',
|
||||
'No'
|
||||
]
|
||||
|
||||
for col, value in enumerate(row_data, 1):
|
||||
cell = ws.cell(row=current_row, column=col, value=value)
|
||||
cell.font = data_font
|
||||
cell.border = border
|
||||
# Apply orange background to Missed Punch cell (column G)
|
||||
if col == 7:
|
||||
cell.fill = missed_punch_fill
|
||||
current_row += 1
|
||||
else:
|
||||
# Multiple records same location - show pairs with Missed Punch and ORANGE background
|
||||
# Show pairs in same row (In and Out), last unpaired record gets its own row
|
||||
num_complete_pairs = len(sorted_records) // 2
|
||||
|
||||
# Write complete pairs (2 records per row)
|
||||
for i in range(0, num_complete_pairs * 2, 2):
|
||||
check_in_record = sorted_records[i]
|
||||
check_out_record = sorted_records[i + 1]
|
||||
|
||||
row_data = [
|
||||
date_obj.strftime('%A').upper(),
|
||||
date_obj.strftime('%m/%d/%Y'),
|
||||
check_in_record.check_in_time.strftime('%I:%M:%S %p'), # In
|
||||
check_out_record.check_in_time.strftime('%I:%M:%S %p'), # Out
|
||||
check_in_record.location_name,
|
||||
'',
|
||||
'Missed Punch',
|
||||
'', # Daily Total shown only on last row
|
||||
'',
|
||||
'',
|
||||
getattr(check_in_record.qr_code, 'location_address', '') or '',
|
||||
check_in_record.location_name,
|
||||
'',
|
||||
'No'
|
||||
]
|
||||
|
||||
for col, value in enumerate(row_data, 1):
|
||||
cell = ws.cell(row=current_row, column=col, value=value)
|
||||
cell.font = data_font
|
||||
cell.border = border
|
||||
# Apply orange background to Missed Punch cell (column G)
|
||||
if col == 7:
|
||||
cell.fill = missed_punch_fill
|
||||
current_row += 1
|
||||
|
||||
# Write the last unpaired record if odd number of records
|
||||
if len(sorted_records) % 2 == 1:
|
||||
last_record = sorted_records[-1]
|
||||
|
||||
# Get the original TimeAttendance record to check action_description
|
||||
original_record = None
|
||||
for rec in records:
|
||||
if (rec.employee_id == last_record.employee_id and
|
||||
rec.attendance_date == last_record.check_in_date and
|
||||
rec.attendance_time == last_record.check_in_time):
|
||||
original_record = rec
|
||||
break
|
||||
|
||||
# Determine if this is a check-in or check-out based on action_description
|
||||
is_check_out = False
|
||||
if original_record and original_record.action_description:
|
||||
action_lower = original_record.action_description.lower()
|
||||
is_check_out = 'out' in action_lower or 'checkout' in action_lower
|
||||
|
||||
# Place time in correct column based on action
|
||||
if is_check_out:
|
||||
# Check-out: empty In, time in Out
|
||||
in_time = ''
|
||||
out_time = last_record.check_in_time.strftime('%I:%M:%S %p')
|
||||
else:
|
||||
# Check-in: time in In, empty Out
|
||||
in_time = last_record.check_in_time.strftime('%I:%M:%S %p')
|
||||
out_time = ''
|
||||
|
||||
row_data = [
|
||||
date_obj.strftime('%A').upper(),
|
||||
date_obj.strftime('%m/%d/%Y'),
|
||||
in_time,
|
||||
out_time,
|
||||
last_record.location_name,
|
||||
'',
|
||||
'Missed Punch',
|
||||
daily_total_display, # Show Daily Total on last row
|
||||
'',
|
||||
'',
|
||||
getattr(last_record.qr_code, 'location_address', '') or '',
|
||||
last_record.location_name,
|
||||
'',
|
||||
'No'
|
||||
]
|
||||
|
||||
for col, value in enumerate(row_data, 1):
|
||||
cell = ws.cell(row=current_row, column=col, value=value)
|
||||
cell.font = data_font
|
||||
cell.border = border
|
||||
# Apply orange background to Missed Punch cells (columns G and H)
|
||||
if col in [7, 8]:
|
||||
cell.fill = missed_punch_fill
|
||||
current_row += 1
|
||||
else:
|
||||
# NORMAL RECORDS (no miss punch)
|
||||
if len(sorted_records) >= 2:
|
||||
for i in range(0, len(sorted_records) - 1, 2):
|
||||
start_record = sorted_records[i]
|
||||
end_record = sorted_records[i + 1]
|
||||
|
||||
row_data = [
|
||||
date_obj.strftime('%A').upper(),
|
||||
date_obj.strftime('%m/%d/%Y'),
|
||||
start_record.check_in_time.strftime('%I:%M:%S %p'),
|
||||
end_record.check_in_time.strftime('%I:%M:%S %p'),
|
||||
start_record.location_name,
|
||||
'',
|
||||
precise_hours if i == 0 else '',
|
||||
daily_total_display if i == 0 else '',
|
||||
'',
|
||||
'',
|
||||
getattr(start_record.qr_code, 'location_address', '') or '',
|
||||
start_record.location_name,
|
||||
'',
|
||||
'No'
|
||||
]
|
||||
|
||||
for col, value in enumerate(row_data, 1):
|
||||
cell = ws.cell(row=current_row, column=col, value=value)
|
||||
cell.font = data_font
|
||||
cell.border = border
|
||||
current_row += 1
|
||||
else:
|
||||
# Single record (rare case)
|
||||
start_record = sorted_records[0] if sorted_records else None
|
||||
row_data = [
|
||||
date_obj.strftime('%A').upper(),
|
||||
date_obj.strftime('%m/%d/%Y'),
|
||||
start_record.check_in_time.strftime('%I:%M:%S %p') if start_record else '',
|
||||
'',
|
||||
start_record.location_name if start_record else '',
|
||||
'',
|
||||
precise_hours,
|
||||
daily_total_display,
|
||||
'',
|
||||
'',
|
||||
getattr(start_record.qr_code, 'location_address', '') if start_record else '',
|
||||
start_record.location_name if start_record else '',
|
||||
'',
|
||||
'No'
|
||||
]
|
||||
|
||||
for col, value in enumerate(row_data, 1):
|
||||
cell = ws.cell(row=current_row, column=col, value=value)
|
||||
cell.font = data_font
|
||||
cell.border = border
|
||||
current_row += 1
|
||||
|
||||
# Write final weekly total for this employee
|
||||
if weekly_total_hours > 0:
|
||||
week_regular = min(weekly_total_hours, 40.0)
|
||||
week_overtime = max(0, weekly_total_hours - 40.0)
|
||||
|
||||
ws.cell(row=current_row, column=7, value='Weekly Total: ').font = bold_font
|
||||
ws.cell(row=current_row, column=8, value=round(weekly_total_hours, 2)).font = bold_font
|
||||
ws.cell(row=current_row, column=9, value=round(week_regular, 2)).font = bold_font
|
||||
ws.cell(row=current_row, column=10, value=round(week_overtime, 2)).font = bold_font
|
||||
|
||||
grand_regular_hours += week_regular
|
||||
grand_ot_hours += week_overtime
|
||||
current_row += 1
|
||||
|
||||
# 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=9, value=round(grand_regular_hours, 2)).font = Font(name='Arial', size=10, bold=True)
|
||||
ws.cell(row=current_row, column=10, value=round(grand_ot_hours, 2)).font = Font(name='Arial', size=10, bold=True)
|
||||
current_row += 1
|
||||
|
||||
# Empty row after each employee
|
||||
current_row += 1
|
||||
|
||||
# Auto-size columns - handle merged cells properly
|
||||
for col_idx in range(1, 15):
|
||||
max_length = 0
|
||||
column = col[0].column_letter
|
||||
for cell in col:
|
||||
try:
|
||||
if len(str(cell.value)) > max_length:
|
||||
max_length = len(str(cell.value))
|
||||
except:
|
||||
pass
|
||||
column_letter = get_column_letter(col_idx)
|
||||
|
||||
for row in ws.iter_rows(min_col=col_idx, max_col=col_idx):
|
||||
for cell in row:
|
||||
if isinstance(cell, openpyxl.cell.cell.MergedCell):
|
||||
continue
|
||||
try:
|
||||
if cell.value and len(str(cell.value)) > max_length:
|
||||
max_length = len(str(cell.value))
|
||||
except:
|
||||
pass
|
||||
|
||||
adjusted_width = min(max_length + 2, 50)
|
||||
ws.column_dimensions[column].width = adjusted_width
|
||||
ws.column_dimensions[column_letter].width = adjusted_width
|
||||
|
||||
# Save to BytesIO
|
||||
output = io.BytesIO()
|
||||
wb.save(output)
|
||||
output.seek(0)
|
||||
|
||||
# New filename format
|
||||
# Filename
|
||||
if date_range_str:
|
||||
filename = f'{project_name_for_filename}time_attendance_{date_range_str}.xlsx'
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user