Update payroll dashboard

This commit is contained in:
2025-08-23 13:05:58 -04:00
parent 3df257c6db
commit 7d841a7c26
2 changed files with 1631 additions and 880 deletions
+155
View File
@@ -4672,6 +4672,161 @@ def calculate_working_hours_api():
'message': 'Internal server error. Please check the server logs.'
}), 500
@app.route('/api/employee/<employee_id>/miss-punch-details', methods=['GET'])
@login_required
@log_database_operations('miss_punch_details_api')
def get_miss_punch_details(employee_id):
"""API endpoint to get detailed miss punch information for an employee"""
try:
# Check permissions
user_role = session.get('role')
if user_role not in ['admin', 'payroll']:
return jsonify({
'success': False,
'message': 'Access denied. Insufficient permissions.'
}), 403
# Get date parameters from query string (from the current payroll filters)
date_from = request.args.get('date_from')
date_to = request.args.get('date_to')
project_filter = request.args.get('project_filter', '')
include_travel_time = request.args.get('include_travel_time', 'true').lower() == 'true'
if not all([date_from, date_to]):
return jsonify({
'success': False,
'message': 'Missing required parameters: date_from, date_to'
}), 400
try:
start_date = datetime.strptime(date_from, '%Y-%m-%d')
end_date = datetime.strptime(date_to, '%Y-%m-%d')
except ValueError:
return jsonify({
'success': False,
'message': 'Invalid date format. Use YYYY-MM-DD.'
}), 400
# Get employee name
try:
employee_query = db.session.execute(text(
"SELECT employee_id, name FROM employees WHERE CAST(employee_id AS TEXT) = :emp_id"
), {'emp_id': str(employee_id)})
employee_row = employee_query.fetchone()
employee_name = employee_row[1] if employee_row and employee_row[1] else f"Employee {employee_id}"
except Exception as e:
print(f"⚠️ Could not load employee name: {e}")
employee_name = f"Employee {employee_id}"
# Get attendance records for the employee within the period
query = db.session.query(AttendanceData).filter(
AttendanceData.employee_id == str(employee_id),
AttendanceData.check_in_date >= start_date.date(),
AttendanceData.check_in_date <= end_date.date()
)
# Apply project filter if provided
if project_filter:
try:
project_id = int(project_filter)
query = query.join(QRCode, AttendanceData.qr_code_id == QRCode.id) \
.filter(QRCode.project_id == project_id)
except ValueError:
pass # Invalid project_id, ignore filter
attendance_records = query.order_by(
AttendanceData.check_in_date,
AttendanceData.check_in_time
).all()
# Convert to the format expected by the calculator
converted_records = []
for record in attendance_records:
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
})()
converted_records.append(converted_record)
# Calculate working hours using the same calculator as the dashboard
from single_checkin_calculator import SingleCheckInCalculator
calculator = SingleCheckInCalculator()
# Calculate hours for this employee
hours_data = calculator.calculate_employee_hours(
str(employee_id), start_date, end_date, converted_records
)
# Extract miss punch details
miss_punch_days = []
if 'daily_hours' in hours_data:
for date_str, day_data in hours_data['daily_hours'].items():
if day_data.get('is_miss_punch', False):
# Get the actual records for this day
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
day_records = [r for r in converted_records if r.check_in_date == date_obj]
# Format the records information with event types
record_details = []
for i, record in enumerate(day_records):
# Determine event type based on position (alternating check-in/check-out)
# First record is always check-in, then alternates
event_type = "Check In" if i % 2 == 0 else "Check Out"
record_details.append({
'time': record.check_in_time.strftime('%H:%M:%S'),
'event_type': event_type,
'location': record.location_name or 'Unknown Location',
'has_gps': record.latitude is not None and record.longitude is not None
})
miss_punch_days.append({
'date': date_str,
'date_formatted': datetime.strptime(date_str, '%Y-%m-%d').strftime('%B %d, %Y (%A)'),
'records_count': day_data.get('records_count', 0),
'records': record_details,
'reason': 'Incomplete punch pairs - missing check-in or check-out' if len(
day_records) % 2 != 0 else 'Invalid work period duration'
})
# Log the API access
logger_handler.logger.info(
f"Miss punch details API accessed by {session.get('username', 'unknown')} for employee {employee_id}")
return jsonify({
'success': True,
'data': {
'employee_id': employee_id,
'employee_name': employee_name,
'period': f"{date_from} to {date_to}",
'miss_punch_count': len(miss_punch_days),
'miss_punch_days': miss_punch_days
}
})
except Exception as e:
print(f"❌ Error in get_miss_punch_details: {e}")
import traceback
print(f"❌ Traceback: {traceback.format_exc()}")
logger_handler.log_flask_error(
error_type="miss_punch_details_api_error",
error_message=str(e),
stack_trace=traceback.format_exc()
)
return jsonify({
'success': False,
'message': 'Internal server error. Please check the server logs.'
}), 500
def get_employee_name(employee_id):
"""Helper function to get employee full name by ID"""
try:
File diff suppressed because it is too large Load Diff