# HƯỚNG DẪN SYNC ATTENDANCE JOB

## 📋 Tổng quan

Đã tạo Job và Command để đồng bộ dữ liệu chấm công từ máy ZKTeco cho **toàn bộ nhân viên** theo khoảng thời gian chỉ định.

---

## 🔧 CÁC FILE ĐÃ TẠO

### 1. Job: `app/Jobs/SyncAttendanceFromZKTeco.php`
- Xử lý đồng bộ dữ liệu chấm công
- Có thể chạy sync hoặc queue
- Xử lý từng nhân viên một cách thông minh
- Ghi log chi tiết

### 2. Command: `app/Console/Commands/SyncAttendanceCommand.php`
- Chạy job từ CLI
- Hỗ trợ nhiều options
- Có thể chạy manual hoặc cron job

### 3. Controller Method: `PayrollController@syncAttendanceData`
- API endpoint để trigger job
- Tự động detect sync/queue mode
- Trả về response phù hợp

---

## 🎯 TÍNH NĂNG JOB

### ✅ Xử lý đơn giản:
1. **Kết nối máy chấm công** ZKTeco
2. **Lấy tất cả logs** từ máy
3. **Filter theo khoảng thời gian** chỉ định
4. **Xử lý từng nhân viên** (theo employee_code hoặc user_id)
5. **Nhóm logs theo ngày** cho mỗi nhân viên
6. **Lưu check-in/check-out** đơn giản
7. **Cập nhật bảng attendances**

### ✅ Xử lý dữ liệu:
- **Check-in**: Log đầu tiên trong ngày
- **Check-out**: Log cuối cùng trong ngày (nếu có)
- **Status**: Tự động set "Present" nếu có chấm công
- **Logic**: Chỉ lưu thời gian chấm công, không tính toán phức tạp

### ✅ Error handling:
- Log chi tiết từng bước
- Skip nhân viên không tìm thấy
- Continue khi gặp lỗi 1 bản ghi
- Retry mechanism (nếu dùng queue)

---

## 🚀 CÁCH SỬ DỤNG

### 1. Từ Web Interface (Modal)
```
1. Click "Lấy dữ liệu máy chấm công"
2. Chọn khoảng thời gian
3. Click "Bắt đầu đồng bộ"
→ Job sẽ chạy và trả về kết quả
```

### 2. Từ Command Line

#### Sync 7 ngày gần nhất (mặc định):
```bash
php artisan attendance:sync
```

#### Sync khoảng thời gian cụ thể:
```bash
php artisan attendance:sync --start-date=2025-11-01 --end-date=2025-11-30
```

#### Sync 30 ngày gần nhất:
```bash
php artisan attendance:sync --days=30
```

#### Sync cho nhân viên cụ thể:
```bash
php artisan attendance:sync --employee-ids=1,2,3,4,5
```

#### Chạy trong queue (background):
```bash
php artisan attendance:sync --queue
```

#### Kết hợp các options:
```bash
php artisan attendance:sync --start-date=2025-11-01 --end-date=2025-11-30 --employee-ids=1,2,3 --queue
```

### 3. Từ Code (Programmatically)
```php
use App\Jobs\SyncAttendanceFromZKTeco;
use Carbon\Carbon;

// Sync 7 ngày gần nhất cho tất cả nhân viên
$job = new SyncAttendanceFromZKTeco(
    Carbon::now()->subDays(7),
    Carbon::today(),
    null // null = tất cả nhân viên
);

// Chạy sync
$job->handle();

// Hoặc dispatch vào queue
dispatch($job);
```

---

## ⚙️ CẤU HÌNH

### Environment Variables (.env):
```env
# IP máy chấm công ZKTeco
ZKTECO_IP=192.168.1.201

# Queue configuration
QUEUE_CONNECTION=database
# hoặc
QUEUE_CONNECTION=sync
```

### Queue Setup (nếu dùng queue):
```bash
# Tạo bảng jobs
php artisan queue:table
php artisan migrate

# Chạy queue worker
php artisan queue:work
```

---

## 📊 LOGIC XỬ LÝ CHI TIẾT

### 1. Kết nối máy chấm công:
```php
$zk = new ZKTeco(env('ZKTECO_IP', '192.168.1.201'));
if (!$zk->connect()) {
    throw new Exception("Không thể kết nối");
}
```

### 2. Lấy và filter dữ liệu:
```php
$logs = $zk->getAttendance();
$filteredLogs = collect($logs)->filter(function ($log) {
    $logDate = Carbon::parse($log['timestamp']);
    return $logDate->between($startDate, $endDate);
});
```

### 3. Xử lý từng nhân viên:
```php
foreach ($employees as $employee) {
    // Tìm logs của nhân viên này
    $employeeLogs = $filteredLogs->filter(function ($log) use ($employee) {
        $logEmployeeId = $log['id'] ?? $log['userid'];
        return $logEmployeeId == $employee->employee_code || 
               $logEmployeeId == $employee->id;
    });
    
    // Nhóm theo ngày
    $logsByDate = $employeeLogs->groupBy(function ($log) {
        return Carbon::parse($log['timestamp'])->toDateString();
    });
}
```

### 4. Xử lý từng ngày:
```php
foreach ($logsByDate as $date => $dayLogs) {
    $sortedLogs = $dayLogs->sortBy('timestamp');
    
    $checkInTime = Carbon::parse($sortedLogs->first()['timestamp'])->format('H:i:s');
    $checkOutTime = null;
    
    // Nếu có nhiều log, lấy log cuối làm check-out
    if ($sortedLogs->count() > 1) {
        $checkOutTime = Carbon::parse($sortedLogs->last()['timestamp'])->format('H:i:s');
    }
    
    // Kiểm tra đã có bản ghi chưa
    $attendance = Attendance::where('employee_id', $employee->id)
        ->where('date', $date)
        ->first();
    
    if ($attendance) {
        // Cập nhật bản ghi có sẵn
        $attendance->update([
            'check_in_time' => $checkInTime,
            'check_out_time' => $checkOutTime,
        ]);
    } else {
        // Tạo bản ghi mới
        Attendance::create([
            'employee_id' => $employee->id,
            'date' => $date,
            'check_in_time' => $checkInTime,
            'check_out_time' => $checkOutTime,
            'status' => 'Present'
        ]);
    }
}
```

---

## 📝 LOGS VÀ MONITORING

### Log Locations:
```
storage/logs/laravel.log
```

### Log Levels:
- **INFO**: Bắt đầu/kết thúc job, thống kê
- **DEBUG**: Chi tiết từng bản ghi được xử lý
- **WARNING**: Không có dữ liệu, nhân viên không tìm thấy
- **ERROR**: Lỗi kết nối, lỗi xử lý

### Sample Logs:
```
[2025-12-04 10:00:00] INFO: Starting ZKTeco sync job {"start_date":"2025-11-01","end_date":"2025-11-30"}
[2025-12-04 10:00:05] INFO: Filtered attendance logs {"total_logs":1500,"filtered_logs":800}
[2025-12-04 10:00:10] DEBUG: Updated attendance record {"employee_id":5,"date":"2025-11-15","check_in":"08:30:00","check_out":"17:45:00","working_hours":8.25}
[2025-12-04 10:02:00] INFO: ZKTeco sync completed {"processed_records":450,"employees_updated":25}
```

---

## 🔄 CRON JOB (Tự động hóa)

### Thêm vào crontab để chạy hàng ngày:
```bash
# Sync dữ liệu hàng ngày lúc 6:00 AM
0 6 * * * cd /path/to/project && php artisan attendance:sync --days=1 --queue

# Sync dữ liệu tuần lúc Chủ nhật 2:00 AM
0 2 * * 0 cd /path/to/project && php artisan attendance:sync --days=7 --queue
```

### Hoặc dùng Laravel Scheduler (app/Console/Kernel.php):
```php
protected function schedule(Schedule $schedule)
{
    // Sync hàng ngày lúc 6:00 AM
    $schedule->command('attendance:sync --days=1 --queue')
             ->dailyAt('06:00')
             ->withoutOverlapping();
             
    // Sync tuần lúc Chủ nhật 2:00 AM
    $schedule->command('attendance:sync --days=7 --queue')
             ->weeklyOn(0, '02:00')
             ->withoutOverlapping();
}
```

---

## ⚠️ LƯU Ý QUAN TRỌNG

### 1. Performance:
- Job có thể mất **vài phút** với dữ liệu lớn
- Khuyến nghị dùng **queue** cho khoảng thời gian dài
- Giới hạn **tối đa 30 ngày** mỗi lần sync

### 2. Data Integrity:
- Job kiểm tra bản ghi có sẵn → **không tạo duplicate**
- Logs được **sắp xếp theo thời gian** trước khi xử lý
- **Logic đơn giản**: Chỉ lưu check-in/check-out, không tính toán phức tạp

### 3. Error Handling:
- Job **không dừng** khi gặp lỗi 1 nhân viên
- **Log chi tiết** mọi lỗi để debug
- **Retry** tự động nếu dùng queue

### 4. Employee Mapping:
- Tìm nhân viên theo `employee_code` HOẶC `user_id`
- **Skip** nhân viên không tìm thấy
- Chỉ xử lý nhân viên **active** (status != 'inactive')

---

## 🧪 TESTING

### Test Command:
```bash
# Test với 1 ngày
php artisan attendance:sync --start-date=2025-12-04 --end-date=2025-12-04

# Test với 1 nhân viên
php artisan attendance:sync --employee-ids=5 --days=3

# Test kết nối máy chấm công
php artisan attendance:sync --days=0
```

### Test từ Web:
1. Chọn khoảng thời gian ngắn (1-2 ngày)
2. Kiểm tra logs trong `storage/logs/laravel.log`
3. Kiểm tra bảng `attendances` có dữ liệu mới không

---

## ✅ CHECKLIST

- [x] Tạo Job `SyncAttendanceFromZKTeco`
- [x] Tạo Command `SyncAttendanceCommand`
- [x] Cập nhật Controller method
- [x] Xử lý từng nhân viên theo khoảng ngày
- [x] Tính toán check-in/check-out thông minh
- [x] Tính working hours (trừ nghỉ trưa)
- [x] Error handling và logging
- [x] Hỗ trợ sync và queue mode
- [x] CLI command với nhiều options
- [ ] Test với dữ liệu thực
- [ ] Setup cron job tự động
- [ ] Monitor performance với dữ liệu lớn

---

