Update payroll dashboard
This commit is contained in:
@@ -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:
|
||||
|
||||
+793
-197
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user