Fixing distance value in export file
This commit is contained in:
@@ -5737,16 +5737,24 @@ def get_miss_punch_details(employee_id):
|
||||
|
||||
# Convert to the format expected by the calculator
|
||||
converted_records = []
|
||||
for record in attendance_records:
|
||||
for record in records:
|
||||
# Get distance from the TimeAttendance record
|
||||
distance_value = getattr(record, 'distance', None)
|
||||
|
||||
converted_record = type('Record', (), {
|
||||
'id': record.id,
|
||||
'employee_id': str(record.employee_id),
|
||||
'check_in_date': record.check_in_date,
|
||||
'check_in_time': record.check_in_time,
|
||||
'location_name': record.location_name or 'Unknown Location',
|
||||
'latitude': record.latitude,
|
||||
'longitude': record.longitude,
|
||||
'qr_code': record.qr_code
|
||||
'check_in_date': record.attendance_date,
|
||||
'check_in_time': record.attendance_time,
|
||||
'location_name': record.location_name,
|
||||
'latitude': None,
|
||||
'longitude': None,
|
||||
'distance': distance_value, # ADD THIS LINE
|
||||
'qr_code': type('QRCode', (), {
|
||||
'location': record.location_name,
|
||||
'location_address': record.recorded_address or '',
|
||||
'project': None
|
||||
})()
|
||||
})()
|
||||
converted_records.append(converted_record)
|
||||
|
||||
@@ -7067,7 +7075,7 @@ def download_import_template():
|
||||
|
||||
# Define headers
|
||||
headers = ['ID', 'Name', 'Platform', 'Date', 'Time', 'Location Name',
|
||||
'Action Description', 'Event Description', 'Recorded Address']
|
||||
'Action Description', 'Event Description', 'Recorded Address', 'Distance']
|
||||
|
||||
# Style headers
|
||||
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
|
||||
@@ -7083,11 +7091,9 @@ def download_import_template():
|
||||
# Add sample data rows
|
||||
sample_data = [
|
||||
['12345', 'John Doe', 'iPhone - iOS', '2025-10-06', '09:00:00',
|
||||
'HQ Suite 210', 'Check In', 'Morning Entry', '123 Main Street'],
|
||||
['12345', 'John Doe', 'iPhone - iOS', '2025-10-06', '17:30:00',
|
||||
'HQ Suite 210', 'Check Out', 'Evening Exit', '123 Main Street'],
|
||||
'HQ Suite 210', 'Check In', 'Main Office', '123 Main St', '0.125'],
|
||||
['67890', 'Jane Smith', 'Android', '2025-10-06', '08:45:00',
|
||||
'Branch Office', 'Check In', 'Morning Entry', '456 Oak Avenue'],
|
||||
'Branch Office', 'Check In', 'Morning Entry', '456 Oak Avenue', '0.250'],
|
||||
]
|
||||
|
||||
for row_num, row_data in enumerate(sample_data, 2):
|
||||
@@ -7124,6 +7130,7 @@ def download_import_template():
|
||||
["- Platform: Device platform (e.g., iPhone - iOS, Android)"],
|
||||
["- Event Description: Additional event details"],
|
||||
["- Recorded Address: Physical address where attendance was recorded"],
|
||||
["- Distance: Distance in miles between Building and Recorded Address (optional)"],
|
||||
[""],
|
||||
["Important Notes:"],
|
||||
["- Do not modify the header row"],
|
||||
@@ -7575,8 +7582,8 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_
|
||||
'',
|
||||
'',
|
||||
getattr(record.qr_code, 'location_address', '') or '',
|
||||
record.location_name,
|
||||
'',
|
||||
getattr(record.qr_code, 'location_address', '') or '',
|
||||
getattr(record, 'distance', None) or '',
|
||||
'No'
|
||||
]
|
||||
|
||||
@@ -7605,8 +7612,8 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_
|
||||
'',
|
||||
'',
|
||||
getattr(first_record.qr_code, 'location_address', '') or '',
|
||||
first_record.location_name,
|
||||
'',
|
||||
getattr(first_record.qr_code, 'location_address', '') or '',
|
||||
getattr(first_record, 'distance', None) or '',
|
||||
'No'
|
||||
]
|
||||
|
||||
@@ -7633,8 +7640,8 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_
|
||||
'',
|
||||
'',
|
||||
getattr(last_record.qr_code, 'location_address', '') or '',
|
||||
last_record.location_name,
|
||||
'',
|
||||
getattr(last_record.qr_code, 'location_address', '') or '',
|
||||
getattr(last_record, 'distance', None) or '',
|
||||
'No'
|
||||
]
|
||||
|
||||
@@ -7674,8 +7681,8 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_
|
||||
'',
|
||||
'',
|
||||
getattr(check_in_record.qr_code, 'location_address', '') or '',
|
||||
check_in_record.location_name,
|
||||
'',
|
||||
getattr(check_in_record.qr_code, 'location_address', '') or '',
|
||||
getattr(check_in_record, 'distance', None) or '',
|
||||
'No'
|
||||
]
|
||||
|
||||
@@ -7722,13 +7729,13 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_
|
||||
out_time,
|
||||
last_record.location_name,
|
||||
'',
|
||||
'Missed Punch', # Only THIS unpaired record shows Missed Punch
|
||||
'Missed Punch',
|
||||
daily_total_display, # Show Daily Total on last row
|
||||
'',
|
||||
'',
|
||||
getattr(last_record.qr_code, 'location_address', '') or '',
|
||||
last_record.location_name,
|
||||
'',
|
||||
getattr(last_record.qr_code, 'location_address', '') or '',
|
||||
getattr(last_record, 'distance', None) or '',
|
||||
'No'
|
||||
]
|
||||
|
||||
@@ -7759,8 +7766,8 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_
|
||||
'',
|
||||
'',
|
||||
getattr(start_record.qr_code, 'location_address', '') or '',
|
||||
start_record.location_name,
|
||||
'',
|
||||
getattr(start_record.qr_code, 'location_address', '') or '',
|
||||
getattr(start_record, 'distance', None) or '',
|
||||
'No'
|
||||
]
|
||||
|
||||
@@ -7784,8 +7791,8 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_
|
||||
'',
|
||||
'',
|
||||
getattr(start_record.qr_code, 'location_address', '') if start_record else '',
|
||||
start_record.location_name if start_record else '',
|
||||
'',
|
||||
getattr(start_record.qr_code, 'location_address', '') if start_record else '',
|
||||
getattr(start_record, 'distance', None) if start_record else '',
|
||||
'No'
|
||||
]
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ class TimeAttendance(base.db.Model):
|
||||
action_description = base.db.Column(base.db.String(100), nullable=False)
|
||||
event_description = base.db.Column(base.db.Text, nullable=True)
|
||||
recorded_address = base.db.Column(base.db.Text, nullable=True)
|
||||
# Distance/Location accuracy field (in miles)
|
||||
distance = base.db.Column(base.db.Float, nullable=True)
|
||||
|
||||
# Import tracking
|
||||
import_batch_id = base.db.Column(base.db.String(36), nullable=True, index=True)
|
||||
|
||||
@@ -313,6 +313,7 @@
|
||||
<th>Action Description</th>
|
||||
<th>Event Description</th>
|
||||
<th>Recorded Address</th>
|
||||
<th>Distance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -326,6 +327,7 @@
|
||||
<td>Check In</td>
|
||||
<td>Main Office</td>
|
||||
<td>123 Main St</td>
|
||||
<td>0.125</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>67890</td>
|
||||
@@ -337,6 +339,7 @@
|
||||
<td>Check Out</td>
|
||||
<td>Main Office</td>
|
||||
<td>123 Main St</td>
|
||||
<td>0.125</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -157,6 +157,63 @@ class TimeAttendanceImportService:
|
||||
# Not a hyperlink formula, return as-is
|
||||
return cell_value if cell_value else None
|
||||
|
||||
def _parse_distance_field(self, row) -> Optional[float]:
|
||||
"""
|
||||
Parse Distance field from Excel (optional column)
|
||||
|
||||
Args:
|
||||
row: DataFrame row containing the 'Distance' column
|
||||
|
||||
Returns:
|
||||
Distance value as float in miles, or None if not present/invalid
|
||||
"""
|
||||
# Check if Distance column exists
|
||||
if 'Distance' not in row.index:
|
||||
return None
|
||||
|
||||
distance_value = row.get('Distance')
|
||||
|
||||
# Check if value exists and is not NaN
|
||||
if pd.isna(distance_value):
|
||||
return None
|
||||
|
||||
# Try to parse as float
|
||||
try:
|
||||
# Handle string values
|
||||
if isinstance(distance_value, str):
|
||||
distance_str = distance_value.strip()
|
||||
|
||||
# Skip empty strings
|
||||
if not distance_str or distance_str.lower() in ['', 'n/a', 'na', 'none']:
|
||||
return None
|
||||
|
||||
# Remove any text like "miles", "mi", "m"
|
||||
distance_str = distance_str.lower()
|
||||
distance_str = distance_str.replace('miles', '').replace('mile', '').replace('mi', '').replace('m', '').strip()
|
||||
|
||||
# Parse the number
|
||||
distance_float = float(distance_str)
|
||||
else:
|
||||
# Already a number
|
||||
distance_float = float(distance_value)
|
||||
|
||||
# Validate reasonable range (0 to 100 miles)
|
||||
if distance_float < 0:
|
||||
if self.logger:
|
||||
self.logger.logger.warning(f"Negative distance value {distance_float} converted to positive")
|
||||
distance_float = abs(distance_float)
|
||||
|
||||
if distance_float > 100:
|
||||
if self.logger:
|
||||
self.logger.logger.warning(f"Distance value {distance_float} exceeds 100 miles, may be invalid")
|
||||
|
||||
return round(distance_float, 4) # Round to 4 decimal places
|
||||
|
||||
except (ValueError, TypeError) as e:
|
||||
if self.logger:
|
||||
self.logger.logger.debug(f"Could not parse distance value '{distance_value}': {e}")
|
||||
return None
|
||||
|
||||
def _process_recorded_address(self, row):
|
||||
"""
|
||||
Process recorded address field, handling Excel HYPERLINK formulas
|
||||
@@ -574,6 +631,7 @@ class TimeAttendanceImportService:
|
||||
'action_description': str(row['Action Description']).strip(),
|
||||
'event_description': str(row.get('Event Description', '')).strip() if pd.notna(row.get('Event Description')) else None,
|
||||
'recorded_address': self._process_recorded_address(row),
|
||||
'distance': self._parse_distance_field(row),
|
||||
}
|
||||
|
||||
# Check for duplicates
|
||||
@@ -805,6 +863,7 @@ class TimeAttendanceImportService:
|
||||
'action_description': record.action_description,
|
||||
'event_description': record.event_description,
|
||||
'recorded_address': record.recorded_address,
|
||||
'distance': getattr(record, 'distance', None),
|
||||
'import_batch_id': record.import_batch_id,
|
||||
'import_date': record.import_date,
|
||||
'import_source': record.import_source
|
||||
|
||||
Reference in New Issue
Block a user