fix: refactor ZKTeco API receiver to group by day and use correct AttendanceRecord table, and limit agent to last 15 days

This commit is contained in:
2026-05-19 14:17:48 +08:00
parent cfb183f8f7
commit 5696c98bb5
4 changed files with 94 additions and 59 deletions

View File

@@ -46,60 +46,84 @@ class AttendanceSyncController extends Controller
return response()->json(['error' => 'Branch not found'], 404);
}
// 3. Process the logs (same logic as BiometricAttendanceController)
// 3. Process the logs (match BiometricAttendanceController logic)
// Ensure we handle arrays with 'recordTime' or 'timestamp'
$formattedAttendances = [];
foreach ($attendances as $record) {
$empCode = $record['deviceUserId'] ?? $record['id'] ?? null;
$punchTime = $record['recordTime'] ?? $record['timestamp'] ?? null;
$uid = $record['userSn'] ?? $record['uid'] ?? null;
if ($empCode && $punchTime) {
$formattedAttendances[] = [
'id' => $uid,
'emp_code' => (string) $empCode,
'punch_time' => $punchTime,
'branch_id' => $branch->id
];
}
}
$groupedAttendances = collect($formattedAttendances)->groupBy(function ($item) {
return $item['emp_code'] . '_' . date('Y-m-d', strtotime($item['punch_time']));
});
$processed = 0;
$inserted = 0;
foreach ($attendances as $record) {
foreach ($groupedAttendances as $key => $dayEntries) {
$processed++;
// Typical payload from zklib-js: { userSn: 1, deviceUserId: '1', recordTime: '2023-10-14 08:30:00' }
$empCode = $record['deviceUserId'] ?? $record['id'] ?? null;
$punchTime = $record['recordTime'] ?? $record['timestamp'] ?? null;
if (!$empCode || !$punchTime) {
continue;
}
// Find employee by emp_code inside this branch's company
$employee = \App\Models\Employee::where('emp_code', $empCode)
$sorted = $dayEntries->sortBy('punch_time');
$firstEntry = $sorted->first();
$lastEntry = $sorted->last();
// Note: Since this is an API, there is no Auth::user(). We use the Branch's creator.
// BiometricAttendanceController uses whereIn('created_by', getCompanyAndUsersId())
// We just use where('created_by', $branch->created_by) or similar.
$employee = \App\Models\Employee::with('user')
->where('created_by', $branch->created_by)
->where('biometric_emp_id', $firstEntry['emp_code'])
->first();
if (!$employee) {
continue;
}
// Parse date and time
$ts = strtotime($punchTime);
$date = date('Y-m-d', $ts);
$time = date('H:i:s', $ts);
// Check if record already exists to prevent duplicates
$exists = AttendanceEmployee::where('employee_id', $employee->id)
->where('date', $date)
->where('clock_in', $time)
->exists();
if (!$exists) {
AttendanceEmployee::create([
'employee_id' => $employee->id,
'date' => $date,
'status' => 'Present',
'clock_in' => $time,
'clock_out' => '00:00:00',
'late' => '00:00:00',
'early_leaving' => '00:00:00',
'overtime' => '00:00:00',
'total_rest' => '00:00:00',
'created_by' => $branch->created_by,
]);
$inserted++;
if ($employee && $sorted->count() > 1) {
$attedanceDate = date('Y-m-d', strtotime($firstEntry['punch_time']));
$clockInTime = date('H:i:s', strtotime($firstEntry['punch_time']));
$clockOutTime = date('H:i:s', strtotime($lastEntry['punch_time']));
$exists = \App\Models\AttendanceRecord::where('employee_id', $employee->user_id)
->where('date', $attedanceDate)
->exists();
if (!$exists) {
$shift = \App\Models\Shift::where('id', $employee->shift_id)->where('status', 'active')->first()
?? \App\Models\Shift::where('created_by', $branch->created_by)->where('status', 'active')->first();
$policy = \App\Models\AttendancePolicy::where('id', $employee->attendance_policy_id)->where('status', 'active')->first()
?? \App\Models\AttendancePolicy::where('created_by', $branch->created_by)->where('status', 'active')->first();
$attendance = new \App\Models\AttendanceRecord();
$attendance->employee_id = $employee->user_id;
$attendance->biometric_id = $firstEntry['id'];
$attendance->shift_id = $shift?->id;
$attendance->attendance_policy_id = $policy?->id;
$attendance->date = $attedanceDate;
$attendance->clock_in = $clockInTime;
$attendance->clock_out = $clockOutTime;
$attendance->created_by = $branch->created_by;
$attendance->save();
$attendance->fresh();
$attendance->processAttendance();
$inserted++;
}
}
}
return response()->json([
'success' => true,
'message' => "Processed {$processed} records, Inserted {$inserted} new attendances.",
'message' => "Processed {$processed} unique daily records, Inserted {$inserted} new attendances.",
]);
}
}