04/28 Updated sprint 3
This commit is contained in:
+13
-10
@@ -541,7 +541,6 @@ def attendance_report():
|
||||
except Exception as e:
|
||||
logger_handler.logger.error(f"Error loading attendance report: {e}", exc_info=True)
|
||||
|
||||
import traceback
|
||||
error_traceback = traceback.format_exc()
|
||||
|
||||
|
||||
@@ -857,7 +856,7 @@ def save_manual_attendance():
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger_handler.logger.error(f"Error saving manual attendance record: {e}")
|
||||
logger_handler.logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
logger_handler.logger.error(f"Error saving manual attendance record: {e}", exc_info=True)
|
||||
flash('Error saving attendance record. Please try again.', 'error')
|
||||
return redirect(url_for('attendance.add_manual_attendance'))
|
||||
|
||||
@@ -1358,12 +1357,14 @@ def get_verification_details(record_id):
|
||||
# Prepare record data with safe formatting
|
||||
try:
|
||||
check_in_date_str = record.check_in_date.strftime('%Y-%m-%d') if record.check_in_date else 'N/A'
|
||||
except:
|
||||
except Exception as e:
|
||||
logger_handler.logger.debug(f"check_in_date strftime failed: {e}")
|
||||
check_in_date_str = str(record.check_in_date) if record.check_in_date else 'N/A'
|
||||
|
||||
try:
|
||||
check_in_time_str = record.check_in_time.strftime('%I:%M %p') if record.check_in_time else 'N/A'
|
||||
except:
|
||||
except Exception as e:
|
||||
logger_handler.logger.debug(f"check_in_time strftime failed: {e}")
|
||||
check_in_time_str = str(record.check_in_time) if record.check_in_time else 'N/A'
|
||||
|
||||
record_data = {
|
||||
@@ -1439,12 +1440,14 @@ def verification_review_detail(record_id):
|
||||
# Format date and time for display
|
||||
try:
|
||||
check_in_date = record.check_in_date.strftime('%m/%d/%Y') if record.check_in_date else 'N/A'
|
||||
except:
|
||||
except Exception as e:
|
||||
logger_handler.logger.debug(f"check_in_date strftime failed: {e}")
|
||||
check_in_date = str(record.check_in_date) if record.check_in_date else 'N/A'
|
||||
|
||||
try:
|
||||
check_in_time = record.check_in_time.strftime('%I:%M %p') if record.check_in_time else 'N/A'
|
||||
except:
|
||||
except Exception as e:
|
||||
logger_handler.logger.debug(f"check_in_time strftime failed: {e}")
|
||||
check_in_time = str(record.check_in_time) if record.check_in_time else 'N/A'
|
||||
|
||||
return render_template('verification_review_detail.html',
|
||||
@@ -2012,8 +2015,8 @@ def create_excel_export(selected_columns, column_names, filters):
|
||||
|
||||
if len(cell_value) > max_length:
|
||||
max_length = len(cell_value)
|
||||
except:
|
||||
pass
|
||||
except Exception:
|
||||
pass # Non-string cell value — skip width measurement
|
||||
|
||||
# Set width based on column type with reasonable limits
|
||||
# Define optimal widths for specific column types
|
||||
@@ -2353,8 +2356,8 @@ def create_excel_export_ordered(selected_columns, column_names, filters):
|
||||
|
||||
if len(cell_value) > max_length:
|
||||
max_length = len(cell_value)
|
||||
except:
|
||||
pass
|
||||
except Exception:
|
||||
pass # Non-string cell value — skip width measurement
|
||||
|
||||
# Set width based on column type with reasonable limits
|
||||
# Define optimal widths for specific column types
|
||||
|
||||
+2
-2
@@ -216,8 +216,8 @@ def logout():
|
||||
try:
|
||||
login_time = datetime.fromisoformat(login_time_str)
|
||||
session_duration = (datetime.now() - login_time).total_seconds() / 60 # minutes
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger_handler.logger.debug(f"Could not parse login_time for session duration: {e}")
|
||||
|
||||
# Log user logout
|
||||
if user_id and username:
|
||||
|
||||
+25
-31
@@ -42,24 +42,6 @@ import openpyxl
|
||||
bp = Blueprint('qr_codes', __name__)
|
||||
|
||||
|
||||
def _get_qr_base_url():
|
||||
"""
|
||||
Return the authoritative base URL for QR code destination links.
|
||||
Prefers the explicit QR_BASE_URL config value (set via .env / config.py).
|
||||
Falls back gracefully to request.url_root if not configured.
|
||||
|
||||
Using an explicit QR_BASE_URL is required when the app runs behind a
|
||||
reverse proxy (e.g. nginx) that can cause request.url_root to produce
|
||||
a doubled hostname such as:
|
||||
https://qr.govservicesinc.com,qr.govservicesinc.com/
|
||||
"""
|
||||
configured = current_app.config.get('QR_BASE_URL', '').rstrip('/')
|
||||
if configured:
|
||||
return configured + '/'
|
||||
# Fallback: normalise request.url_root to avoid any trailing-slash issues
|
||||
return request.url_root.rstrip('/') + '/'
|
||||
|
||||
|
||||
# --- ADDED: helper — returns distinct (location, location_address) pairs from
|
||||
# all standard QR codes, used to auto-populate the dynamic QR location list ---
|
||||
def get_unique_qr_locations():
|
||||
@@ -211,7 +193,7 @@ def create_qr_code():
|
||||
qr_url = generate_qr_url(name, new_qr_code.id)
|
||||
|
||||
# Generate QR code data with the destination URL and custom styling
|
||||
qr_data = f"{_get_qr_base_url()}qr/{qr_url}"
|
||||
qr_data = f"{request.url_root}qr/{qr_url}"
|
||||
qr_image = generate_qr_code(
|
||||
data=qr_data,
|
||||
fill_color=fill_color,
|
||||
@@ -339,7 +321,7 @@ def import_bulk_qr_codes():
|
||||
created_by=session['user_id'],
|
||||
generate_qr_code_func=generate_qr_code,
|
||||
generate_qr_url_func=generate_qr_url,
|
||||
request_url_root=_get_qr_base_url(),
|
||||
request_url_root=request.url_root,
|
||||
project_lookup=project_lookup,
|
||||
QRCode=QRCode,
|
||||
Project=Project,
|
||||
@@ -472,11 +454,9 @@ def edit_qr_code(qr_id):
|
||||
'back_color': getattr(qr_code, 'back_color', '#FFFFFF')
|
||||
}
|
||||
|
||||
# QR code name is immutable after creation — always retain the existing value.
|
||||
# The name field in the edit form is rendered read-only; this guard ensures
|
||||
# the constraint is enforced even if the form is submitted programmatically.
|
||||
new_name = qr_code.name # do not accept name changes from the form
|
||||
# qr_code.name is intentionally NOT reassigned here
|
||||
# Update QR code fields
|
||||
new_name = request.form['name']
|
||||
qr_code.name = new_name
|
||||
|
||||
# --- ADDED: for dynamic QR codes, location/address are auto-managed ---
|
||||
new_qr_type = request.form.get('qr_type', 'standard')
|
||||
@@ -561,7 +541,7 @@ def edit_qr_code(qr_id):
|
||||
|
||||
# Regenerate QR code if name or styling changed
|
||||
if name_changed or styling_changed:
|
||||
qr_data = f"{_get_qr_base_url()}qr/{qr_code.qr_url}"
|
||||
qr_data = f"{request.url_root}qr/{qr_code.qr_url}"
|
||||
|
||||
# Use new styling if available, otherwise use defaults
|
||||
styling = get_qr_styling(qr_code)
|
||||
@@ -918,8 +898,22 @@ def qr_checkin(qr_url):
|
||||
|
||||
if verification_photo_data:
|
||||
logger_handler.logger.debug(f"Verification photo provided (size: {len(verification_photo_data)} chars)")
|
||||
|
||||
# Validate photo data (basic validation)
|
||||
|
||||
# Server-side size enforcement — mirrors client-side MAX_SIZE check
|
||||
# but cannot be bypassed by a malicious client.
|
||||
max_photo_bytes = current_app.config.get('VERIFICATION_PHOTO_MAX_SIZE', 5 * 1024 * 1024)
|
||||
if len(verification_photo_data.encode('utf-8')) > max_photo_bytes:
|
||||
logger_handler.logger.warning(
|
||||
f"Verification photo rejected — size {len(verification_photo_data)} chars "
|
||||
f"exceeds limit of {max_photo_bytes} bytes for employee {employee_id}"
|
||||
)
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Photo is too large. Please use a smaller image and try again.',
|
||||
'requires_verification': True
|
||||
}), 413
|
||||
|
||||
# Validate photo data format (basic validation)
|
||||
if verification_photo_data.startswith('data:image/'):
|
||||
attendance.verification_photo = verification_photo_data
|
||||
attendance.verification_required = True
|
||||
@@ -1135,7 +1129,7 @@ def copy_qr_url(qr_id):
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'QR code URL copied to clipboard!',
|
||||
'url': f"{_get_qr_base_url()}qr/{qr_code.qr_url}"
|
||||
'url': f"{request.url_root}qr/{qr_code.qr_url}"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
@@ -1158,7 +1152,7 @@ def open_qr_link(qr_id):
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Opening QR code link...',
|
||||
'url': f"{_get_qr_base_url()}qr/{qr_code.qr_url}"
|
||||
'url': f"{request.url_root}qr/{qr_code.qr_url}"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
@@ -1216,4 +1210,4 @@ def deactivate_qr_code(qr_id):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': 'Error deactivating QR code. Please try again.'
|
||||
}), 500
|
||||
}), 500
|
||||
|
||||
@@ -1073,8 +1073,8 @@ def download_import_template():
|
||||
try:
|
||||
if len(str(cell.value)) > max_length:
|
||||
max_length = len(str(cell.value))
|
||||
except:
|
||||
pass
|
||||
except Exception:
|
||||
pass # Non-string cell value — skip width measurement
|
||||
adjusted_width = min(max_length + 2, 50)
|
||||
ws.column_dimensions[col_letter].width = adjusted_width
|
||||
|
||||
|
||||
@@ -1320,8 +1320,8 @@ def export_time_attendance_excel(records, project_name_for_filename, date_range_
|
||||
try:
|
||||
if cell.value and len(str(cell.value)) > max_length:
|
||||
max_length = len(str(cell.value))
|
||||
except:
|
||||
pass
|
||||
except Exception:
|
||||
pass # Non-string cell value — skip width measurement
|
||||
|
||||
adjusted_width = min(max_length + 2, 50)
|
||||
ws.column_dimensions[column_letter].width = adjusted_width
|
||||
@@ -1453,8 +1453,8 @@ def export_time_attendance_by_building_excel(records, project_name_for_filename,
|
||||
qr_code = QRCode.query.filter_by(location=location_name).first()
|
||||
if qr_code:
|
||||
zone_info = getattr(qr_code, 'zone', '') or ''
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger_handler.logger.debug(f"Could not retrieve zone info for location '{location_name}': {e}")
|
||||
|
||||
# Building header row
|
||||
building_header = f"{location_index}) {location_name} - Zone {zone_info}"
|
||||
@@ -1990,8 +1990,8 @@ def export_time_attendance_by_building_excel(records, project_name_for_filename,
|
||||
try:
|
||||
if cell.value and len(str(cell.value)) > max_length:
|
||||
max_length = len(str(cell.value))
|
||||
except:
|
||||
pass
|
||||
except Exception:
|
||||
pass # Non-string cell value — skip width measurement
|
||||
|
||||
adjusted_width = min(max_length + 2, 50)
|
||||
ws.column_dimensions[column_letter].width = adjusted_width
|
||||
|
||||
Reference in New Issue
Block a user