Files
HRM-System/app/Http/Controllers/Api/AttendanceSyncController.php

109 lines
4.0 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Branch;
use App\Models\AttendanceEmployee;
use Illuminate\Support\Facades\Log;
class AttendanceSyncController extends Controller
{
public function sync(Request $request)
{
// 1. Verify API Key
$apiKey = $request->header('X-API-Key') ?? $request->input('api_key');
// You should store a master API key in your .env like ZKTECO_SYNC_KEY=xxx
$validKey = env('ZKTECO_SYNC_KEY', 'default_secure_sync_key_123');
if ($apiKey !== $validKey) {
Log::warning('Unauthorized ZKTeco sync attempt', ['ip' => $request->ip()]);
return response()->json(['error' => 'Unauthorized'], 401);
}
$branchId = $request->input('branch_id');
$zktecoIp = $request->input('zkteco_ip');
$attendances = $request->input('attendances', []);
if (empty($attendances)) {
return response()->json(['message' => 'No attendance records provided'], 400);
}
// 2. Find the branch
$branchQuery = Branch::query();
if ($branchId) {
$branchQuery->where('id', $branchId);
} elseif ($zktecoIp) {
$branchQuery->where('zkteco_ip', $zktecoIp);
} else {
return response()->json(['error' => 'Must provide branch_id or zkteco_ip'], 400);
}
$branch = $branchQuery->first();
if (!$branch) {
return response()->json(['error' => 'Branch not found'], 404);
}
// 3. Save directly to biometric_attendances table
$processed = 0;
foreach ($attendances as $record) {
$empCode = $record['deviceUserId'] ?? $record['id'] ?? null;
$rawPunchTime = $record['recordTime'] ?? $record['timestamp'] ?? null;
$punchTime = $this->parseDateSafe($rawPunchTime);
$uid = $record['userSn'] ?? $record['uid'] ?? null;
$type = $record['state'] ?? $record['type'] ?? 0;
if ($empCode && $punchTime) {
// Insert or ignore based on biometric_emp_id and punch_time to prevent duplicates
$exists = \App\Models\BiometricAttendance::where('biometric_emp_id', $empCode)
->where('punch_time', $punchTime->toDateTimeString())
->exists();
if (!$exists) {
\App\Models\BiometricAttendance::create([
'branch_id' => $branch->id,
'biometric_emp_id' => $empCode,
'punch_time' => $punchTime->toDateTimeString(),
'punch_type' => $type,
'terminal_alias' => $branch->name . ' (Agent)',
'sync_status' => 'pending',
]);
$processed++;
}
}
}
return response()->json([
'success' => true,
'message' => "Successfully pushed {$processed} new records to the Biometric Attendance database for Branch {$branch->id}.",
]);
}
private function parseDateSafe($dateStr)
{
if (!$dateStr) {
return null;
}
// Handle numeric timestamps (Unix seconds or milliseconds)
if (is_numeric($dateStr)) {
if (strlen((string)$dateStr) > 10) {
// It's likely in milliseconds
return \Carbon\Carbon::createFromTimestampMs($dateStr)->setTimezone('Asia/Manila');
}
return \Carbon\Carbon::createFromTimestamp($dateStr)->setTimezone('Asia/Manila');
}
// Remove JS-specific timezone names like "(China Standard Time)" to prevent Carbon parse errors
$cleanDateStr = trim(explode('(', $dateStr)[0]);
try {
return \Carbon\Carbon::parse($cleanDateStr, 'Asia/Manila')->setTimezone('Asia/Manila');
} catch (\Exception $e) {
return null;
}
}
}