feat: add fix_production_attendance.php script
This commit is contained in:
128
fix_production_attendance.php
Normal file
128
fix_production_attendance.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
$app = require_once __DIR__.'/../bootstrap/app.php';
|
||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||
$kernel->bootstrap();
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\Employee;
|
||||
use App\Models\BiometricAttendance;
|
||||
use App\Models\AttendanceRecord;
|
||||
use App\Models\Shift;
|
||||
use App\Models\AttendancePolicy;
|
||||
use App\Http\Controllers\BiometricAttendanceController;
|
||||
use Carbon\Carbon;
|
||||
|
||||
DB::transaction(function() {
|
||||
echo "=== 1. Running Timezone Correction Migration logic ===\n";
|
||||
// Check if migration has run. We can check if we need to shift.
|
||||
// If we have punch times before May 29 that are in early morning (UTC), we shift them.
|
||||
// To prevent double-shifting, we can check a flag or just run the update on records
|
||||
// that have punch_time hours that don't match local time, or we can check if the migration ran.
|
||||
$migrationRun = DB::table('migrations')->where('migration', '2026_05_30_030000_fix_biometric_timezone_offset')->exists();
|
||||
|
||||
if (!$migrationRun) {
|
||||
echo "Migration has not run yet. Shifting raw biometric punches by +8 hours...\n";
|
||||
DB::statement("UPDATE biometric_attendances SET punch_time = DATE_ADD(punch_time, INTERVAL 8 HOUR)");
|
||||
// Insert migration record so Laravel knows it ran
|
||||
DB::table('migrations')->insert([
|
||||
'migration' => '2026_05_30_030000_fix_biometric_timezone_offset',
|
||||
'batch' => (DB::table('migrations')->max('batch') ?? 0) + 1
|
||||
]);
|
||||
echo "Raw punches shifted successfully.\n";
|
||||
} else {
|
||||
echo "Migration already ran. Raw punches are already in local time.\n";
|
||||
}
|
||||
|
||||
echo "\n=== 2. Re-syncing and Repairing Attendance Records ===\n";
|
||||
|
||||
// Fetch all biometric attendances
|
||||
$allBioRecords = BiometricAttendance::all();
|
||||
|
||||
// Eager-load employees with shifts
|
||||
$empIds = $allBioRecords->pluck('biometric_emp_id')->unique();
|
||||
$employees = Employee::with(['user', 'shift'])
|
||||
->whereIn('biometric_emp_id', $empIds)
|
||||
->get()
|
||||
->keyBy('biometric_emp_id');
|
||||
|
||||
$grouped = $allBioRecords->groupBy(function ($item) use ($employees) {
|
||||
$employee = $employees->get($item->biometric_emp_id);
|
||||
$workDate = BiometricAttendanceController::getWorkDateForPunch($item->punch_time, $employee);
|
||||
return $item->biometric_emp_id . '_' . $workDate;
|
||||
});
|
||||
|
||||
$updatedCount = 0;
|
||||
$createdCount = 0;
|
||||
|
||||
foreach ($grouped as $key => $dayEntries) {
|
||||
$sorted = $dayEntries->sortBy('punch_time');
|
||||
$firstEntry = $sorted->first();
|
||||
$lastEntry = $sorted->last();
|
||||
|
||||
$employee = $employees->get($firstEntry->biometric_emp_id);
|
||||
if (!$employee) continue;
|
||||
|
||||
$workDate = BiometricAttendanceController::getWorkDateForPunch($firstEntry->punch_time, $employee);
|
||||
$clockInTime = $firstEntry->punch_time->format('H:i:s');
|
||||
$clockOutTime = $sorted->count() > 1 ? $lastEntry->punch_time->format('H:i:s') : null;
|
||||
|
||||
// Find existing attendance record
|
||||
$attendance = AttendanceRecord::where('employee_id', $employee->user_id)
|
||||
->where('date', $workDate)
|
||||
->first();
|
||||
|
||||
if ($attendance) {
|
||||
// Keep existing shift/policy if set
|
||||
if (empty($attendance->shift_id)) {
|
||||
$shift = Shift::where('id', $employee->shift_id)->where('status', 'active')->first();
|
||||
$attendance->shift_id = $shift?->id;
|
||||
}
|
||||
if (empty($attendance->attendance_policy_id)) {
|
||||
$policy = AttendancePolicy::where('id', $employee->attendance_policy_id)->where('status', 'active')->first();
|
||||
$attendance->attendance_policy_id = $policy?->id;
|
||||
}
|
||||
|
||||
$attendance->biometric_id = $firstEntry->id;
|
||||
$attendance->branch_id = $firstEntry->branch_id;
|
||||
$attendance->clock_in = $clockInTime;
|
||||
$attendance->clock_out = $clockOutTime;
|
||||
$attendance->save();
|
||||
|
||||
$attendance->fresh();
|
||||
$attendance->processAttendance();
|
||||
|
||||
echo "Updated record for Employee User ID {$employee->user_id} ({$employee->user->name}) on Date {$workDate}: In {$clockInTime} | Out " . ($clockOutTime ?? 'NULL') . "\n";
|
||||
$updatedCount++;
|
||||
} else {
|
||||
$shift = Shift::where('id', $employee->shift_id)->where('status', 'active')->first();
|
||||
$policy = AttendancePolicy::where('id', $employee->attendance_policy_id)->where('status', 'active')->first();
|
||||
|
||||
$attendance = new AttendanceRecord();
|
||||
$attendance->employee_id = $employee->user_id;
|
||||
$attendance->branch_id = $firstEntry->branch_id;
|
||||
$attendance->biometric_id = $firstEntry->id;
|
||||
$attendance->shift_id = $shift?->id;
|
||||
$attendance->attendance_policy_id = $policy?->id;
|
||||
$attendance->date = $workDate;
|
||||
$attendance->clock_in = $clockInTime;
|
||||
$attendance->clock_out = $clockOutTime;
|
||||
$attendance->created_by = $employee->user_id; // fallback creator
|
||||
$attendance->save();
|
||||
|
||||
$attendance->fresh();
|
||||
$attendance->processAttendance();
|
||||
|
||||
echo "Created record for Employee User ID {$employee->user_id} ({$employee->user->name}) on Date {$workDate}: In {$clockInTime} | Out " . ($clockOutTime ?? 'NULL') . "\n";
|
||||
$createdCount++;
|
||||
}
|
||||
|
||||
// Mark raw entries as synced
|
||||
foreach ($dayEntries as $entry) {
|
||||
$entry->update(['sync_status' => 'synced']);
|
||||
}
|
||||
}
|
||||
|
||||
echo "\nSync Complete! Created: {$createdCount} | Updated: {$updatedCount}\n";
|
||||
});
|
||||
Reference in New Issue
Block a user